From 34ce7468695dac0469f8f9ece4bd9e03d2ce8b9b Mon Sep 17 00:00:00 2001 From: Jason Wen Date: Mon, 22 Dec 2025 09:29:01 -0500 Subject: [PATCH 1/3] ui: `DualButtonActionSP` and `dual_button_item_sp` helpers (#1608) --- system/ui/sunnypilot/widgets/list_view.py | 50 +++++++++++++++++++---- 1 file changed, 42 insertions(+), 8 deletions(-) diff --git a/system/ui/sunnypilot/widgets/list_view.py b/system/ui/sunnypilot/widgets/list_view.py index 4a9c5fead..c71d6e226 100644 --- a/system/ui/sunnypilot/widgets/list_view.py +++ b/system/ui/sunnypilot/widgets/list_view.py @@ -14,7 +14,7 @@ from openpilot.system.ui.sunnypilot.widgets.toggle import ToggleSP from openpilot.system.ui.widgets.button import Button, ButtonStyle from openpilot.system.ui.widgets.label import gui_label from openpilot.system.ui.widgets.list_view import ListItem, ToggleAction, ItemAction, MultipleButtonAction, ButtonAction, \ - _resolve_value, BUTTON_WIDTH, BUTTON_HEIGHT, TEXT_PADDING + _resolve_value, BUTTON_WIDTH, BUTTON_HEIGHT, TEXT_PADDING, DualButtonAction from openpilot.system.ui.widgets.scroller_tici import LineSeparator, LINE_COLOR, LINE_PADDING from openpilot.system.ui.sunnypilot.lib.styles import style from openpilot.system.ui.sunnypilot.widgets.option_control import OptionControlSP, LABEL_WIDTH @@ -84,6 +84,33 @@ class ButtonActionSP(ButtonAction): return pressed +class DualButtonActionSP(DualButtonAction): + def __init__(self, left_text: str | Callable[[], str], right_text: str | Callable[[], str], left_callback: Callable = None, + right_callback: Callable = None, enabled: bool | Callable[[], bool] = True, border_radius: int = 15): + DualButtonAction.__init__(self, left_text, right_text, left_callback, right_callback, enabled) + self.left_button._border_radius = self.right_button._border_radius = border_radius + + def _render(self, rect: rl.Rectangle): + button_spacing = 20 + button_height = 150 + button_width = (rect.width - button_spacing) / 2 + button_y = rect.y + (rect.height - button_height) / 2 + + left_rect = rl.Rectangle(rect.x, button_y, button_width, button_height) + right_rect = rl.Rectangle(rect.x + button_width + button_spacing, button_y, button_width, button_height) + + # expand one to full width if other is not visible + if not self.left_button.is_visible: + right_rect.x = rect.x + right_rect.width = rect.width + elif not self.right_button.is_visible: + left_rect.width = rect.width + + # Render buttons + self.left_button.render(left_rect) + self.right_button.render(right_rect) + + class MultipleButtonActionSP(MultipleButtonAction): def __init__(self, buttons: list[str | Callable[[], str]], button_width: int, selected_index: int = 0, callback: Callable = None, param: str | None = None): @@ -251,13 +278,13 @@ class ListItemSP(ListItem): item_y = self._rect.y + (style.ITEM_BASE_HEIGHT - self._text_size.y) // 2 if self.inline else self._rect.y + style.ITEM_PADDING * 1.5 rl.draw_text_ex(self._font, self.title, rl.Vector2(text_x, item_y), style.ITEM_TEXT_FONT_SIZE, 0, self.title_color) - # Draw right item if present - if self.action_item: - right_rect = self.get_right_item_rect(self._rect) - if self.action_item.render(right_rect) and self.action_item.enabled: - # Right item was clicked/activated - if self.callback: - self.callback() + # Draw right item if present + if self.action_item: + right_rect = self.get_right_item_rect(self._rect) + if self.action_item.render(right_rect) and self.action_item.enabled: + # Right item was clicked/activated + if self.callback: + self.callback() # Draw description if visible if self.description_visible: @@ -310,6 +337,13 @@ def button_item_sp(title: str | Callable[[], str], button_text: str | Callable[[ return ListItemSP(title=title, description=description, action_item=action, callback=callback) +def dual_button_item_sp(left_text: str | Callable[[], str], right_text: str | Callable[[], str], left_callback: Callable = None, + right_callback: Callable = None, description: str | Callable[[], str] | None = None, + enabled: bool | Callable[[], bool] = True, border_radius: int = 15) -> ListItemSP: + action = DualButtonActionSP(left_text, right_text, left_callback, right_callback, enabled, border_radius) + return ListItemSP(title="", description=description, action_item=action) + + class LineSeparatorSP(LineSeparator): def __init__(self, height: int = 1): super().__init__() From 373894a81f573d76e667ce7fcdc24ec1ad2d29c4 Mon Sep 17 00:00:00 2001 From: Matt Purnell <65473602+mpurnell1@users.noreply.github.com> Date: Mon, 22 Dec 2025 23:42:24 -0600 Subject: [PATCH 2/3] docs: Update branch installation instructions in README (#1610) * Update branch installation instructions * Add another link * Add limited support link * Incorporate suggested link * Tweak wording --- README.md | 60 ++----------------------------------------------------- 1 file changed, 2 insertions(+), 58 deletions(-) diff --git a/README.md b/README.md index 598e1273a..71fbb00e4 100644 --- a/README.md +++ b/README.md @@ -11,66 +11,10 @@ Join the official sunnypilot community forum to stay up to date with all the lat https://docs.sunnypilot.ai/ is your one stop shop for everything from features to installation to FAQ about the sunnypilot ## 🚘 Running on a dedicated device in a car -* A supported device to run this software - * a [comma three](https://comma.ai/shop/products/three) or a [C3X](https://comma.ai/shop/comma-3x) -* This software -* One of [the 325+ supported cars](https://github.com/sunnypilot/sunnypilot/blob/master/docs/CARS.md). We support Honda, Toyota, Hyundai, Nissan, Kia, Chrysler, Lexus, Acura, Audi, VW, Ford, and more. If your car is not supported but has adaptive cruise control and lane-keeping assist, it's likely able to run sunnypilot. -* A [car harness](https://comma.ai/shop/products/car-harness) to connect to your car - -Detailed instructions for [how to mount the device in a car](https://comma.ai/setup). +First, check out this list of items you'll need to [get started](https://community.sunnypilot.ai/t/getting-started-using-sunnypilot-in-your-supported-car/251). ## Installation -Please refer to [Recommended Branches](#recommended-branches) to find your preferred/supported branch. This guide will assume you want to install the latest `staging` branch. - -### If you want to use our newest branches (our rewrite) -> [!TIP] ->You can see the rewrite state on our [rewrite project board](https://github.com/orgs/sunnypilot/projects/2), and to install the new branches, you can use the following links - -* sunnypilot not installed or you installed a version before 0.8.17? - 1. [Factory reset/uninstall](https://github.com/commaai/openpilot/wiki/FAQ#how-can-i-reset-the-device) the previous software if you have another software/fork installed. - 2. After factory reset/uninstall and upon reboot, select `Custom Software` when given the option. - 3. Input the installation URL per [Recommended Branches](#recommended-branches). Example: ```https://staging.sunnypilot.ai```. - 4. Complete the rest of the installation following the onscreen instructions. - -* sunnypilot already installed and you installed a version after 0.8.17? - 1. On the comma three/3X, go to `Settings` ▶️ `Software`. - 2. At the `Download` option, press `CHECK`. This will fetch the list of latest branches from sunnypilot. - 3. At the `Target Branch` option, press `SELECT` to open the Target Branch selector. - 4. Scroll to select the desired branch per Recommended Branches (see below). Example: `staging` - -### Recommended Branches -| Branch | Installation URL | -|:---------------:|:---------------------------------------------:| -| `release` | `https://release.sunnypilot.ai` | -| `staging` | `https://staging.sunnypilot.ai` | -| `dev` | `https://dev.sunnypilot.ai` | -| `custom-branch` | `https://install.sunnypilot.ai/{branch_name}` | - -> [!TIP] -> You can use sunnypilot/targetbranch as an install URL. Example: 'sunnypilot/staging'. - -> [!NOTE] -> Do you require further assistance with software installation? Join the [sunnypilot community forum](https://community.sunnypilot.ai/new-topic?category=general/qa) and create a topic in the General/Q&A Category channel. - - -
- -Older legacy branches - -### If you want to use our older legacy branches (*not recommended*) - -> [**IMPORTANT**] -> It is recommended to [re-flash AGNOS](https://flash.comma.ai/) if you intend to downgrade from the new branches. -> You can still restore the latest sunnylink backup made on the old branches. - -| Branch | Installation URL | -|:------------:|:--------------------------------:| -| `release-c3` | https://release-c3.sunnypilot.ai | -| `staging-c3` | https://staging-c3.sunnypilot.ai | -| `dev-c3` | https://dev-c3.sunnypilot.ai | - -
- +Next, refer to the sunnypilot community forum for [installation instructions](https://community.sunnypilot.ai/t/read-before-installing-sunnypilot/254), as well as a complete list of [Recommended Branch Installations](https://community.sunnypilot.ai/t/recommended-branch-installations/235). ## 🎆 Pull Requests We welcome both pull requests and issues on GitHub. Bug fixes are encouraged. From a04a5b4284066f3f05feede209f7ec544a79d6f7 Mon Sep 17 00:00:00 2001 From: Nayan Date: Tue, 23 Dec 2025 00:51:35 -0500 Subject: [PATCH 3/3] ui: expand `DeviceLayoutSP` (#1560) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * commaai/openpilot:d05cb31e2e916fba41ba8167030945f427fd811b * bump opendbc * bump opendbc * bump opendbc * bump opendbc * bump opendbc * sunnypilot: remove Qt * cabana: revert to stock Qt * commaai/openpilot:5198b1b079c37742c1050f02ce0aa6dd42b038b9 * commaai/openpilot:954b567b9ba0f3d1ae57d6aa7797fa86dd92ec6e * commaai/openpilot:7534b2a160faa683412c04c1254440e338931c5e * sum more * bump opendbc * not yet * should've been symlink'ed * raylib says wut * quiet mode back * more fixes * no more * too extra red diff on the side * need to bring this back * too extra * let's update docs here * Revert "let's update docs here" This reverts commit 51fe03cd5121e6fdf14657b2c33852c34922b851. * param to control stock vs sp ui * init styles * SP Toggles * Lint * optimizations * multi-button * Lint * param to control stock vs sp ui * init styles * SP Toggles * Lint * optimizations * sp raylib preview * fix callback * fix ui preview * better padding * this * support for next line multi-button * uhh * disabled colors * listitem -> listitemsp * listitem -> listitemsp * add show_description method * remove padding from line separator. like, WHY? 😩😩 * ui: `GuiApplicationExt` * simple button * simple button * add to readme * use gui_app.sunnypilot_ui() * i've got something to confessa * init * more init * power buttons always visible * uh, nope * add reset to offroad only * support wake up offroad * flippity floppity * dual button item sp * use dual button item sp * lint * keep @devtekve from going blind * more round * some * revert * slight diff * should've been inline * cleanup power btns and offroad transitions * bruh * 1st row red diff * 2nd row red diff * 3rd row red diff * slight diff * move around * more diff * only when onroad we move to the top, not the toggle * nah * sort --------- Co-authored-by: Jason Wen Co-authored-by: DevTekVE Co-authored-by: James Vecellio-Grant <159560811+Discountchubbs@users.noreply.github.com> Co-authored-by: discountchubbs --- .../ui/sunnypilot/layouts/settings/device.py | 208 ++++++++++++++++++ .../sunnypilot/layouts/settings/settings.py | 28 +-- selfdrive/ui/sunnypilot/ui_state.py | 9 + selfdrive/ui/ui_state.py | 6 +- system/ui/sunnypilot/widgets/list_view.py | 14 ++ 5 files changed, 249 insertions(+), 16 deletions(-) diff --git a/selfdrive/ui/sunnypilot/layouts/settings/device.py b/selfdrive/ui/sunnypilot/layouts/settings/device.py index 081969cf1..36c5fdb34 100644 --- a/selfdrive/ui/sunnypilot/layouts/settings/device.py +++ b/selfdrive/ui/sunnypilot/layouts/settings/device.py @@ -5,8 +5,216 @@ This file is part of sunnypilot and is licensed under the MIT License. See the LICENSE.md file in the root directory for more details. """ from openpilot.selfdrive.ui.layouts.settings.device import DeviceLayout +from openpilot.selfdrive.ui.ui_state import ui_state +from openpilot.system.hardware import HARDWARE +from openpilot.system.ui.lib.application import gui_app +from openpilot.system.ui.lib.multilang import tr +from openpilot.system.ui.sunnypilot.widgets.list_view import option_item_sp, multiple_button_item_sp, button_item_sp, \ + dual_button_item_sp, Spacer +from openpilot.system.ui.widgets import DialogResult +from openpilot.system.ui.widgets.button import ButtonStyle +from openpilot.system.ui.widgets.confirm_dialog import alert_dialog, ConfirmDialog +from openpilot.system.ui.widgets.list_view import text_item +from openpilot.system.ui.widgets.scroller_tici import LineSeparator + +offroad_time_options = { + 0: 0, + 1: 5, + 2: 10, + 3: 15, + 4: 30, + 5: 60, + 6: 120, + 7: 180, + 8: 300, + 9: 600, + 10: 1440, + 11: 1800, +} class DeviceLayoutSP(DeviceLayout): def __init__(self): DeviceLayout.__init__(self) + self._scroller._line_separator = None + + def _initialize_items(self): + DeviceLayout._initialize_items(self) + + # Using dual button with no right button for better alignment + self._always_offroad_btn = dual_button_item_sp( + left_text=lambda: tr("Enable Always Offroad"), + left_callback=self._handle_always_offroad, + right_text="", + right_callback=None, + ) + self._always_offroad_btn.action_item.right_button.set_visible(False) + + self._max_time_offroad = option_item_sp( + title=lambda: tr("Max Time Offroad"), + description=lambda: tr("Device will automatically shutdown after set time once the engine is turned off.\n(30h is the default)"), + param="MaxTimeOffroad", + min_value=0, + max_value=11, + value_change_step=1, + on_value_changed=None, + enabled=True, + icon="", + value_map=offroad_time_options, + label_width=360, + use_float_scaling=False, + inline=True, + label_callback=self._update_max_time_offroad_label + ) + + self._device_wake_mode = multiple_button_item_sp( + title=lambda: tr("Wake Up Behavior"), + description=self.wake_mode_description, + param="DeviceBootMode", + buttons=[lambda: tr("Default"), lambda: tr("Offroad")], + button_width=364, + callback=None, + inline=True, + ) + + self._quiet_mode_and_dcam = dual_button_item_sp( + left_text=lambda: tr("Quiet Mode"), + right_text=lambda: tr("Driver Camera Preview"), + left_callback=lambda: ui_state.params.put_bool("QuietMode", not ui_state.params.get_bool("QuietMode")), + right_callback=self._show_driver_camera + ) + self._quiet_mode_and_dcam.action_item.right_button.set_button_style(ButtonStyle.NORMAL) + + self._reg_and_training = dual_button_item_sp( + left_text=lambda: tr("Regulatory"), + left_callback=self._on_regulatory, + right_text=lambda: tr("Training Guide"), + right_callback=self._on_review_training_guide + ) + self._reg_and_training.action_item.right_button.set_button_style(ButtonStyle.NORMAL) + + self._onroad_uploads_and_reset_settings = dual_button_item_sp( + left_text=lambda: tr("Onroad Uploads"), + left_callback=lambda: ui_state.params.put_bool("OnroadUploads", not ui_state.params.get_bool("OnroadUploads")), + right_text=lambda: tr("Reset Settings"), + right_callback=self._reset_settings + ) + + self._power_buttons = dual_button_item_sp( + left_text=lambda: tr("Reboot"), + right_text=lambda: tr("Power Off"), + left_callback=self._reboot_prompt, + right_callback=self._power_off_prompt + ) + + items = [ + text_item(lambda: tr("Dongle ID"), self._params.get("DongleId") or (lambda: tr("N/A"))), + LineSeparator(), + text_item(lambda: tr("Serial"), self._params.get("HardwareSerial") or (lambda: tr("N/A"))), + LineSeparator(), + self._pair_device_btn, + LineSeparator(), + self._reset_calib_btn, + LineSeparator(), + button_item_sp(lambda: tr("Change Language"), lambda: tr("CHANGE"), callback=self._show_language_dialog), + LineSeparator(), + self._device_wake_mode, + LineSeparator(), + self._max_time_offroad, + LineSeparator(height=10), + self._quiet_mode_and_dcam, + self._reg_and_training, + self._onroad_uploads_and_reset_settings, + Spacer(10), + LineSeparator(height=10), + self._power_buttons, + ] + + return items + + def _offroad_transition(self): + self._power_buttons.action_item.right_button.set_visible(ui_state.is_offroad()) + + @staticmethod + def wake_mode_description() -> str: + def_str = tr("Default: Device will boot/wake-up normally & will be ready to engage.") + offrd_str = tr("Offroad: Device will be in Always Offroad mode after boot/wake-up.") + header = tr("Controls state of the device after boot/sleep.") + + return f"{header}\n\n{def_str}\n{offrd_str}" + + @staticmethod + def _reset_settings(): + def _do_reset(result: int): + if result == DialogResult.CONFIRM: + for _key in ui_state.params.all_keys(): + ui_state.params.remove(_key) + HARDWARE.reboot() + + def _second_confirm(result: int): + if result == DialogResult.CONFIRM: + gui_app.set_modal_overlay(ConfirmDialog( + text=tr("The reset cannot be undone. You have been warned."), + confirm_text=tr("Confirm") + ), callback=_do_reset) + + gui_app.set_modal_overlay(ConfirmDialog( + text=tr("Are you sure you want to reset all sunnypilot settings to default? Once the settings are reset, there is no going back."), + confirm_text=tr("Reset") + ), callback=_second_confirm) + + @staticmethod + def _handle_always_offroad(): + if ui_state.engaged: + gui_app.set_modal_overlay(alert_dialog(tr("Disengage to Enter Always Offroad Mode"))) + return + + _offroad_mode_state = ui_state.params.get_bool("OffroadMode") + _offroad_mode_str = tr("Are you sure you want to exit Always Offroad mode?") if _offroad_mode_state else \ + tr("Are you sure you want to enter Always Offroad mode?") + + def _set_always_offroad(result: int): + if result == DialogResult.CONFIRM and not ui_state.engaged: + ui_state.params.put_bool("OffroadMode", not _offroad_mode_state) + + gui_app.set_modal_overlay(ConfirmDialog(_offroad_mode_str, tr("Confirm")), callback=lambda result: _set_always_offroad(result)) + + @staticmethod + def _update_max_time_offroad_label(value: int) -> str: + label = tr("Always On") if value == 0 else f"{value}" + tr("m") if value < 60 else f"{value // 60}" + tr("h") + label += tr(" (Default)") if value == 1800 else "" + return label + + def _update_state(self): + super()._update_state() + + # Handle Always Offroad button + always_offroad = ui_state.params.get_bool("OffroadMode") + + # Text & Color + offroad_mode_btn_text = tr("Exit Always Offroad") if always_offroad else tr("Enable Always Offroad") + offroad_mode_btn_style = ButtonStyle.NORMAL if always_offroad else ButtonStyle.DANGER + self._always_offroad_btn.action_item.left_button.set_text(offroad_mode_btn_text) + self._always_offroad_btn.action_item.left_button.set_button_style(offroad_mode_btn_style) + + # Position + if self._scroller._items.__contains__(self._always_offroad_btn): + self._scroller._items.remove(self._always_offroad_btn) + if ui_state.is_offroad() and not always_offroad: + self._scroller._items.insert(len(self._scroller._items) - 1, self._always_offroad_btn) + elif not ui_state.is_offroad(): + self._scroller._items.insert(0, self._always_offroad_btn) + + # Quiet Mode button + self._quiet_mode_and_dcam.action_item.left_button.set_button_style(ButtonStyle.PRIMARY if ui_state.params.get_bool("QuietMode") else ButtonStyle.NORMAL) + + # Onroad Uploads + self._onroad_uploads_and_reset_settings.action_item.left_button.set_button_style( + ButtonStyle.PRIMARY if ui_state.params.get_bool("OnroadUploads") else ButtonStyle.NORMAL + ) + + # Offroad only buttons + self._quiet_mode_and_dcam.action_item.right_button.set_enabled(ui_state.is_offroad()) + self._reg_and_training.action_item.left_button.set_enabled(ui_state.is_offroad()) + self._reg_and_training.action_item.right_button.set_enabled(ui_state.is_offroad()) + self._onroad_uploads_and_reset_settings.action_item.right_button.set_enabled(ui_state.is_offroad()) diff --git a/selfdrive/ui/sunnypilot/layouts/settings/settings.py b/selfdrive/ui/sunnypilot/layouts/settings/settings.py index 103dd99ef..bc83c82f8 100644 --- a/selfdrive/ui/sunnypilot/layouts/settings/settings.py +++ b/selfdrive/ui/sunnypilot/layouts/settings/settings.py @@ -9,28 +9,28 @@ from enum import IntEnum import pyray as rl from openpilot.selfdrive.ui.layouts.settings import settings as OP -from openpilot.selfdrive.ui.sunnypilot.layouts.settings.device import DeviceLayoutSP from openpilot.selfdrive.ui.layouts.settings.firehose import FirehoseLayout -from openpilot.selfdrive.ui.sunnypilot.layouts.settings.software import SoftwareLayoutSP from openpilot.selfdrive.ui.layouts.settings.toggles import TogglesLayout -from openpilot.system.ui.lib.application import gui_app, MousePos -from openpilot.system.ui.lib.multilang import tr_noop -from openpilot.system.ui.sunnypilot.lib.styles import style -from openpilot.system.ui.widgets.scroller_tici import Scroller -from openpilot.system.ui.lib.text_measure import measure_text_cached -from openpilot.system.ui.lib.wifi_manager import WifiManager -from openpilot.system.ui.widgets import Widget +from openpilot.selfdrive.ui.sunnypilot.layouts.settings.cruise import CruiseLayout +from openpilot.selfdrive.ui.sunnypilot.layouts.settings.developer import DeveloperLayoutSP +from openpilot.selfdrive.ui.sunnypilot.layouts.settings.device import DeviceLayoutSP +from openpilot.selfdrive.ui.sunnypilot.layouts.settings.display import DisplayLayout from openpilot.selfdrive.ui.sunnypilot.layouts.settings.models import ModelsLayout from openpilot.selfdrive.ui.sunnypilot.layouts.settings.network import NetworkUISP -from openpilot.selfdrive.ui.sunnypilot.layouts.settings.sunnylink import SunnylinkLayout from openpilot.selfdrive.ui.sunnypilot.layouts.settings.osm import OSMLayout +from openpilot.selfdrive.ui.sunnypilot.layouts.settings.software import SoftwareLayoutSP +from openpilot.selfdrive.ui.sunnypilot.layouts.settings.steering import SteeringLayout +from openpilot.selfdrive.ui.sunnypilot.layouts.settings.sunnylink import SunnylinkLayout from openpilot.selfdrive.ui.sunnypilot.layouts.settings.trips import TripsLayout from openpilot.selfdrive.ui.sunnypilot.layouts.settings.vehicle import VehicleLayout -from openpilot.selfdrive.ui.sunnypilot.layouts.settings.steering import SteeringLayout -from openpilot.selfdrive.ui.sunnypilot.layouts.settings.cruise import CruiseLayout from openpilot.selfdrive.ui.sunnypilot.layouts.settings.visuals import VisualsLayout -from openpilot.selfdrive.ui.sunnypilot.layouts.settings.display import DisplayLayout -from openpilot.selfdrive.ui.sunnypilot.layouts.settings.developer import DeveloperLayoutSP +from openpilot.system.ui.lib.application import gui_app, MousePos +from openpilot.system.ui.lib.multilang import tr_noop +from openpilot.system.ui.lib.text_measure import measure_text_cached +from openpilot.system.ui.lib.wifi_manager import WifiManager +from openpilot.system.ui.sunnypilot.lib.styles import style +from openpilot.system.ui.widgets import Widget +from openpilot.system.ui.widgets.scroller_tici import Scroller # from openpilot.selfdrive.ui.sunnypilot.layouts.settings.navigation import NavigationLayout diff --git a/selfdrive/ui/sunnypilot/ui_state.py b/selfdrive/ui/sunnypilot/ui_state.py index ac08538df..ca8125512 100644 --- a/selfdrive/ui/sunnypilot/ui_state.py +++ b/selfdrive/ui/sunnypilot/ui_state.py @@ -73,3 +73,12 @@ class UIStateSP: self.developer_ui = self.params.get("DevUIInfo") self.rainbow_path = self.params.get_bool("RainbowMode") self.chevron_metrics = self.params.get("ChevronInfo") + + +class DeviceSP: + def __init__(self): + self._params = Params() + + def _set_awake(self, on: bool): + if on and self._params.get("DeviceBootMode", return_default=True) == 1: + self._params.put_bool("OffroadMode", True) diff --git a/selfdrive/ui/ui_state.py b/selfdrive/ui/ui_state.py index 0e1710c24..a86c84ada 100644 --- a/selfdrive/ui/ui_state.py +++ b/selfdrive/ui/ui_state.py @@ -12,7 +12,7 @@ from openpilot.selfdrive.ui.lib.prime_state import PrimeState from openpilot.system.ui.lib.application import gui_app from openpilot.system.hardware import HARDWARE, PC -from openpilot.selfdrive.ui.sunnypilot.ui_state import UIStateSP +from openpilot.selfdrive.ui.sunnypilot.ui_state import UIStateSP, DeviceSP BACKLIGHT_OFFROAD = 65 if HARDWARE.get_device_type() == "mici" else 50 @@ -192,8 +192,9 @@ class UIState(UIStateSP): self._param_update_time = time.monotonic() -class Device: +class Device(DeviceSP): def __init__(self): + DeviceSP.__init__(self) self._ignition = False self._interaction_time: float = -1 self._override_interactive_timeout: int | None = None @@ -284,6 +285,7 @@ class Device: def _set_awake(self, on: bool): if on != self._awake: + DeviceSP._set_awake(self, on) self._awake = on cloudlog.debug(f"setting display power {int(on)}") HARDWARE.set_display_power(on) diff --git a/system/ui/sunnypilot/widgets/list_view.py b/system/ui/sunnypilot/widgets/list_view.py index c71d6e226..bf78147a5 100644 --- a/system/ui/sunnypilot/widgets/list_view.py +++ b/system/ui/sunnypilot/widgets/list_view.py @@ -11,6 +11,7 @@ from openpilot.common.params import Params from openpilot.system.ui.lib.application import gui_app, MousePos, FontWeight from openpilot.system.ui.lib.text_measure import measure_text_cached from openpilot.system.ui.sunnypilot.widgets.toggle import ToggleSP +from openpilot.system.ui.widgets import Widget from openpilot.system.ui.widgets.button import Button, ButtonStyle from openpilot.system.ui.widgets.label import gui_label from openpilot.system.ui.widgets.list_view import ListItem, ToggleAction, ItemAction, MultipleButtonAction, ButtonAction, \ @@ -20,6 +21,19 @@ from openpilot.system.ui.sunnypilot.lib.styles import style from openpilot.system.ui.sunnypilot.widgets.option_control import OptionControlSP, LABEL_WIDTH +class Spacer(Widget): + def __init__(self, height: int = 1): + super().__init__() + self._rect = rl.Rectangle(0, 0, 0, height) + + def set_parent_rect(self, parent_rect: rl.Rectangle) -> None: + super().set_parent_rect(parent_rect) + self._rect.width = parent_rect.width + + def _render(self, _): + rl.draw_rectangle(int(self._rect.x), int(self._rect.y), int(self._rect.x + self._rect.width), int(self._rect.y), rl.Color(0,0,0,0)) + + class ToggleActionSP(ToggleAction): def __init__(self, initial_state: bool = False, width: int = style.TOGGLE_WIDTH, enabled: bool | Callable[[], bool] = True, callback: Callable[[bool], None] | None = None, param: str | None = None):