diff --git a/openpilot/selfdrive/ui/mici/layouts/home.py b/openpilot/selfdrive/ui/mici/layouts/home.py index ad926d2af8..8b21a1a98c 100644 --- a/openpilot/selfdrive/ui/mici/layouts/home.py +++ b/openpilot/selfdrive/ui/mici/layouts/home.py @@ -174,7 +174,7 @@ class MiciHomeLayout(Widget): if self._mouse_down_t is not None: if time.monotonic() - self._mouse_down_t > 0.5: # long gating for experimental mode - only allow toggle if longitudinal control is available - if ui_state.has_longitudinal_control: + if ui_state.has_longitudinal_control and ui_state.experimental_mode_confirmed: ui_state.experimental_mode = not ui_state.experimental_mode ui_state.params.put("ExperimentalMode", ui_state.experimental_mode, block=True) self._mouse_down_t = None diff --git a/openpilot/selfdrive/ui/mici/layouts/settings/toggles.py b/openpilot/selfdrive/ui/mici/layouts/settings/toggles.py index 5f75ef8903..57cbf9410b 100644 --- a/openpilot/selfdrive/ui/mici/layouts/settings/toggles.py +++ b/openpilot/selfdrive/ui/mici/layouts/settings/toggles.py @@ -1,7 +1,10 @@ +from collections.abc import Callable + from openpilot.cereal import log from openpilot.system.ui.widgets.scroller import NavScroller -from openpilot.selfdrive.ui.mici.widgets.button import BigParamControl, BigMultiParamToggle +from openpilot.selfdrive.ui.mici.widgets.button import BigParamControl, BigMultiParamToggle, BigToggle, GreyBigButton +from openpilot.selfdrive.ui.mici.widgets.dialog import BigConfirmationCircleButton from openpilot.system.ui.lib.application import gui_app from openpilot.selfdrive.ui.layouts.settings.common import restart_needed_callback from openpilot.selfdrive.ui.ui_state import ui_state @@ -9,12 +12,38 @@ from openpilot.selfdrive.ui.ui_state import ui_state PERSONALITY_TO_INT = log.LongitudinalPersonality.schema.enumerants +class ExperimentalModeConfirmPage(NavScroller): + def __init__(self, on_confirm: Callable[[], None]): + super().__init__() + + accept = BigConfirmationCircleButton("enable\nexperimental mode", + gui_app.texture("icons_mici/setup/driver_monitoring/dm_check.png", 64, 64), + lambda: self.dismiss(on_confirm)) + + self._scroller.add_widgets([ + GreyBigButton("enabling\nexperimental mode", "scroll to continue", + gui_app.texture("icons_mici/setup/warning.png", 64, 64)), + GreyBigButton("", "openpilot defaults to driving in chill mode."), + GreyBigButton("", "Experimental mode enables alpha-level features that aren't ready for chill mode."), + GreyBigButton("End-to-End Longitudinal Control"), + GreyBigButton("", "Let the driving model control the gas and brakes."), + GreyBigButton("", "openpilot will drive as it thinks a human would, including stopping for red lights and stop signs."), + GreyBigButton("", "The set speed will only act as an upper bound."), + GreyBigButton("", "This is an alpha quality feature; mistakes should be expected."), + GreyBigButton("New Driving Visualization"), + GreyBigButton("", "The path will change colors to communicate acceleration intent."), + GreyBigButton("", "Red for braking, green for acceleration, and gray for coasting."), + accept, + ]) + + class TogglesLayoutMici(NavScroller): def __init__(self): super().__init__() self._personality_toggle = BigMultiParamToggle("driving personality", "LongitudinalPersonality", ["aggressive", "standard", "relaxed"]) - self._experimental_btn = BigParamControl("experimental mode", "ExperimentalMode") + self._experimental_btn = BigToggle("experimental mode", initial_state=ui_state.params.get_bool("ExperimentalMode"), + toggle_callback=self._on_experimental_mode) is_metric_toggle = BigParamControl("use metric units", "IsMetric") ldw_toggle = BigParamControl("lane departure warnings", "IsLdwEnabled") always_on_dm_toggle = BigParamControl("always-on driver monitor", "AlwaysOnDM") @@ -85,3 +114,17 @@ class TogglesLayoutMici(NavScroller): # Refresh toggles from params to mirror external changes for key, item in self._refresh_toggles: item.set_checked(ui_state.params.get_bool(key)) + + def _on_experimental_mode(self, state: bool): + if state and not ui_state.params.get_bool("ExperimentalModeConfirmed"): + # Don't show enabled state until confirm + self._experimental_btn.set_checked(False) + + def on_confirm(): + ui_state.params.put_bool("ExperimentalModeConfirmed", True) + ui_state.params.put_bool("ExperimentalMode", True) + self._experimental_btn.set_checked(True) + + gui_app.push_widget(ExperimentalModeConfirmPage(on_confirm)) + else: + ui_state.params.put_bool("ExperimentalMode", state) diff --git a/openpilot/selfdrive/ui/ui_state.py b/openpilot/selfdrive/ui/ui_state.py index 66ebda55c2..dca583d107 100644 --- a/openpilot/selfdrive/ui/ui_state.py +++ b/openpilot/selfdrive/ui/ui_state.py @@ -76,6 +76,7 @@ class UIState: self.is_release = self.params.get_bool("IsReleaseBranch") self.always_on_dm: bool = self.params.get_bool("AlwaysOnDM") self.experimental_mode: bool = self.params.get_bool("ExperimentalMode") + self.experimental_mode_confirmed: bool = self.params.get_bool("ExperimentalModeConfirmed") self.usbgpu: bool = False self.usbgpu_compiled: bool = usbgpu_compiled() self.usbgpu_active: bool | None = self.params.get("UsbGpuActive") @@ -206,6 +207,7 @@ class UIState: self.is_metric = self.params.get_bool("IsMetric") self.always_on_dm = self.params.get_bool("AlwaysOnDM") self.experimental_mode = self.params.get_bool("ExperimentalMode") + self.experimental_mode_confirmed = self.params.get_bool("ExperimentalModeConfirmed") # keep usbgpu UI active until offroad transition when gpu disappears self.usbgpu = self.sm["deviceState"].chestnutPresent or (self.usbgpu and self.started) if not self.usbgpu_compiled: