From 61915eb9144d8f921177341dfcd353119000e435 Mon Sep 17 00:00:00 2001 From: Nayan Date: Tue, 14 Apr 2026 23:36:06 -0400 Subject: [PATCH] [MICI] ui: always offroad (#1695) * always offroad ui * remove * lint * better * fix sync issues * fix sync issues * update for upstream sync * move it all to top settings panel * not red * no home screen, just buttons --------- Co-authored-by: Jason Wen --- selfdrive/ui/mici/layouts/main.py | 1 - .../ui/sunnypilot/mici/layouts/settings.py | 59 ++++++++++++++++++- selfdrive/ui/sunnypilot/ui_state.py | 1 + .../assets/icons_mici/always_offroad.png | 3 + .../assets/icons_mici/disable_offroad.png | 3 + 5 files changed, 65 insertions(+), 2 deletions(-) create mode 100644 sunnypilot/selfdrive/assets/icons_mici/always_offroad.png create mode 100644 sunnypilot/selfdrive/assets/icons_mici/disable_offroad.png diff --git a/selfdrive/ui/mici/layouts/main.py b/selfdrive/ui/mici/layouts/main.py index 2f41e1f17..e7dfd3410 100644 --- a/selfdrive/ui/mici/layouts/main.py +++ b/selfdrive/ui/mici/layouts/main.py @@ -13,7 +13,6 @@ from openpilot.system.ui.lib.application import gui_app if gui_app.sunnypilot_ui(): from openpilot.selfdrive.ui.sunnypilot.mici.layouts.settings import SettingsLayoutSP as SettingsLayout - ONROAD_DELAY = 2.5 # seconds diff --git a/selfdrive/ui/sunnypilot/mici/layouts/settings.py b/selfdrive/ui/sunnypilot/mici/layouts/settings.py index 9e160521c..85a782ecb 100644 --- a/selfdrive/ui/sunnypilot/mici/layouts/settings.py +++ b/selfdrive/ui/sunnypilot/mici/layouts/settings.py @@ -5,18 +5,32 @@ 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.mici.layouts.settings import settings as OP -from openpilot.selfdrive.ui.mici.widgets.button import BigButton +from openpilot.selfdrive.ui.mici.layouts.settings.device import DeviceLayoutMici +from openpilot.selfdrive.ui.mici.widgets.button import BigButton, BigCircleButton +from openpilot.selfdrive.ui.mici.widgets.dialog import BigConfirmationDialog, BigDialog from openpilot.selfdrive.ui.sunnypilot.mici.layouts.sunnylink import SunnylinkLayoutMici from openpilot.selfdrive.ui.sunnypilot.mici.layouts.models import ModelsLayoutMici +from openpilot.selfdrive.ui.ui_state import ui_state from openpilot.system.ui.lib.application import gui_app +from openpilot.system.ui.lib.multilang import tr ICON_SIZE = 70 +BIG_ICON_SIZE = 110 class SettingsLayoutSP(OP.SettingsLayout): def __init__(self): OP.SettingsLayout.__init__(self) + device_panel = DeviceLayoutMici() + self._scroller._items[2].set_click_callback(lambda: gui_app.push_widget(device_panel)) + + self.icon_offroad_enable = gui_app.texture("../../sunnypilot/selfdrive/assets/icons_mici/always_offroad.png", BIG_ICON_SIZE, + BIG_ICON_SIZE) + self.icon_offroad_disable = gui_app.texture("../../sunnypilot/selfdrive/assets/icons_mici/disable_offroad.png", BIG_ICON_SIZE, + BIG_ICON_SIZE) + self.icon_offroad_slider = gui_app.texture("icons_mici/settings/device/lkas.png", BIG_ICON_SIZE, BIG_ICON_SIZE) + sunnylink_panel = SunnylinkLayoutMici(back_callback=gui_app.pop_widget) sunnylink_btn = BigButton("sunnylink", "", gui_app.texture("icons_mici/settings/developer/ssh.png", ICON_SIZE, ICON_SIZE)) sunnylink_btn.set_click_callback(lambda: gui_app.push_widget(sunnylink_panel)) @@ -25,10 +39,53 @@ class SettingsLayoutSP(OP.SettingsLayout): models_btn = BigButton("models", "", gui_app.texture("../../sunnypilot/selfdrive/assets/offroad/icon_models.png", ICON_SIZE, ICON_SIZE)) models_btn.set_click_callback(lambda: gui_app.push_widget(models_panel)) + # onroad: enable button sits at the front (left of toggles) + self._enable_offroad_btn_onroad = BigCircleButton(self.icon_offroad_enable, red=True) + self._enable_offroad_btn_onroad.set_click_callback(lambda: self._handle_always_offroad(True)) + self._enable_offroad_btn_onroad.set_visible(lambda: ui_state.started and not ui_state.always_offroad) + + # offroad: enable button sits at the end (right of developer) + self._enable_offroad_btn_offroad = BigCircleButton(self.icon_offroad_enable, red=True) + self._enable_offroad_btn_offroad.set_click_callback(lambda: self._handle_always_offroad(True)) + self._enable_offroad_btn_offroad.set_visible(lambda: not ui_state.started and not ui_state.always_offroad) + + self._disable_offroad_btn = BigCircleButton(self.icon_offroad_disable, red=False) + self._disable_offroad_btn.set_click_callback(lambda: self._handle_always_offroad(False)) + self._disable_offroad_btn.set_visible(lambda: ui_state.always_offroad) + items = self._scroller._items.copy() items.insert(1, sunnylink_btn) items.insert(2, models_btn) + + # front slots (only one ever visible at a time): exit-always-offroad, then enable-onroad + items.insert(0, self._enable_offroad_btn_onroad) + items.insert(0, self._disable_offroad_btn) + # end slot: enable-offroad (right of developer) + items.append(self._enable_offroad_btn_offroad) + self._scroller._items.clear() for item in items: self._scroller.add_widget(item) + + def _update_state(self): + super()._update_state() + + def _handle_always_offroad(self, enable: bool): + + def _set_offroad_status(status: bool): + if not ui_state.engaged: + ui_state.params.put_bool("OffroadMode", status) + ui_state.always_offroad = status + + if not enable: + dlg = BigConfirmationDialog(tr("slide to exit always offroad"), self.icon_offroad_slider, red=False, + confirm_callback=lambda: _set_offroad_status(False)) + else: + if ui_state.engaged: + gui_app.push_widget(BigDialog(tr("disengage to enable always offroad"), "", )) + return + + dlg = BigConfirmationDialog(tr("slide to force offroad"), self.icon_offroad_slider, red=True, + confirm_callback=lambda: _set_offroad_status(True)) + gui_app.push_widget(dlg) diff --git a/selfdrive/ui/sunnypilot/ui_state.py b/selfdrive/ui/sunnypilot/ui_state.py index 7766c353a..2a48e9eee 100644 --- a/selfdrive/ui/sunnypilot/ui_state.py +++ b/selfdrive/ui/sunnypilot/ui_state.py @@ -146,6 +146,7 @@ class UIStateSP: self.true_v_ego_ui = self.params.get_bool("TrueVEgoUI") self.turn_signals = self.params.get_bool("ShowTurnSignals") self.boot_offroad_mode = self.params.get("DeviceBootMode", return_default=True) + self.always_offroad = self.params.get_bool("OffroadMode") class DeviceSP: diff --git a/sunnypilot/selfdrive/assets/icons_mici/always_offroad.png b/sunnypilot/selfdrive/assets/icons_mici/always_offroad.png new file mode 100644 index 000000000..56f35669c --- /dev/null +++ b/sunnypilot/selfdrive/assets/icons_mici/always_offroad.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:e459241d896824f5e8207d568847acab3dedd41caae7af59d4c17e043663b0c9 +size 4035 diff --git a/sunnypilot/selfdrive/assets/icons_mici/disable_offroad.png b/sunnypilot/selfdrive/assets/icons_mici/disable_offroad.png new file mode 100644 index 000000000..146734aaf --- /dev/null +++ b/sunnypilot/selfdrive/assets/icons_mici/disable_offroad.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:27a0fca872d4586f578d246890b83674cdb7ecb03f58b2b0379b4b64a5816053 +size 3908