diff --git a/selfdrive/ui/layouts/settings/developer.py b/selfdrive/ui/layouts/settings/developer.py
index cfef0f84d1..143bb2c262 100644
--- a/selfdrive/ui/layouts/settings/developer.py
+++ b/selfdrive/ui/layouts/settings/developer.py
@@ -1,5 +1,6 @@
from openpilot.common.params import Params
from openpilot.selfdrive.ui.widgets.ssh_key import ssh_key_item
+from openpilot.selfdrive.ui.ui_state import ui_state
from openpilot.system.ui.widgets import Widget
from openpilot.system.ui.widgets.list_view import toggle_item
from openpilot.system.ui.widgets.scroller import Scroller
@@ -10,11 +11,15 @@ DESCRIPTIONS = {
"ADB (Android Debug Bridge) allows connecting to your device over USB or over the network. " +
"See https://docs.comma.ai/how-to/connect-to-comma for more info."
),
- 'joystick_debug_mode': "Preview the driver facing camera to ensure that driver monitoring has good visibility. (vehicle must be off)",
'ssh_key': (
"Warning: This grants SSH access to all public keys in your GitHub settings. Never enter a GitHub username " +
"other than your own. A comma employee will NEVER ask you to add their GitHub username."
),
+ 'alpha_longitudinal': (
+ "WARNING: openpilot longitudinal control is in alpha for this car and will disable Automatic Emergency Braking (AEB).
" +
+ "On this car, openpilot defaults to the car's built-in ACC instead of openpilot's longitudinal control. " +
+ "Enable this to switch to openpilot longitudinal control. Enabling Experimental mode is recommended when enabling openpilot longitudinal control alpha."
+ ),
}
@@ -22,32 +27,55 @@ class DeveloperLayout(Widget):
def __init__(self):
super().__init__()
self._params = Params()
+ self._is_release = self._params.get_bool("IsReleaseBranch")
+
+ # Build items and keep references for callbacks/state updates
+ self._adb_toggle = toggle_item(
+ "Enable ADB",
+ description=DESCRIPTIONS["enable_adb"],
+ initial_state=self._params.get_bool("AdbEnabled"),
+ callback=self._on_enable_adb,
+ )
+
+ # SSH enable toggle + SSH key management
+ self._ssh_toggle = toggle_item(
+ "Enable SSH",
+ description="",
+ initial_state=self._params.get_bool("SshEnabled"),
+ callback=self._on_enable_ssh,
+ )
+ self._ssh_keys = ssh_key_item("SSH Keys", description=DESCRIPTIONS["ssh_key"])
+
+ self._joystick_toggle = toggle_item(
+ "Joystick Debug Mode",
+ description="",
+ initial_state=self._params.get_bool("JoystickDebugMode"),
+ callback=self._on_joystick_debug_mode,
+ )
+
+ self._long_maneuver_toggle = toggle_item(
+ "Longitudinal Maneuver Mode",
+ description="",
+ initial_state=self._params.get_bool("LongitudinalManeuverMode"),
+ callback=self._on_long_maneuver_mode,
+ )
+
+ self._alpha_long_toggle = toggle_item(
+ "openpilot Longitudinal Control (Alpha)",
+ description=DESCRIPTIONS["alpha_longitudinal"],
+ initial_state=self._params.get_bool("AlphaLongitudinalEnabled"),
+ callback=self._on_alpha_long_enabled,
+ )
+
+ self._alpha_long_toggle.set_description(self._alpha_long_toggle.description + " Changing this setting will restart openpilot if the car is powered on.")
+
items = [
- toggle_item(
- "Enable ADB",
- description=DESCRIPTIONS["enable_adb"],
- initial_state=self._params.get_bool("AdbEnabled"),
- callback=self._on_enable_adb,
- ),
- ssh_key_item("SSH Key", description=DESCRIPTIONS["ssh_key"]),
- toggle_item(
- "Joystick Debug Mode",
- description=DESCRIPTIONS["joystick_debug_mode"],
- initial_state=self._params.get_bool("JoystickDebugMode"),
- callback=self._on_joystick_debug_mode,
- ),
- toggle_item(
- "Longitudinal Maneuver Mode",
- description="",
- initial_state=self._params.get_bool("LongitudinalManeuverMode"),
- callback=self._on_long_maneuver_mode,
- ),
- toggle_item(
- "openpilot Longitudinal Control (Alpha)",
- description="",
- initial_state=self._params.get_bool("AlphaLongitudinalEnabled"),
- callback=self._on_alpha_long_enabled,
- ),
+ self._adb_toggle,
+ self._ssh_toggle,
+ self._ssh_keys,
+ self._joystick_toggle,
+ self._long_maneuver_toggle,
+ self._alpha_long_toggle,
]
self._scroller = Scroller(items, line_separator=True, spacing=0)
@@ -55,7 +83,61 @@ class DeveloperLayout(Widget):
def _render(self, rect):
self._scroller.render(rect)
- def _on_enable_adb(self): pass
- def _on_joystick_debug_mode(self): pass
- def _on_long_maneuver_mode(self): pass
- def _on_alpha_long_enabled(self): pass
+ def show_event(self):
+ self._update_toggles()
+
+ def _update_toggles(self):
+ # Hide non-release toggles on release builds
+ for item in (self._adb_toggle, self._joystick_toggle, self._long_maneuver_toggle, self._alpha_long_toggle):
+ item.set_visible(not self._is_release)
+
+ # CP gating
+ if ui_state.CP is not None:
+ alpha_avail = ui_state.CP.alphaLongitudinalAvailable
+ if not alpha_avail or self._is_release:
+ self._alpha_long_toggle.set_visible(False)
+ self._params.remove("AlphaLongitudinalEnabled")
+ else:
+ self._alpha_long_toggle.set_visible(True)
+
+ self._long_maneuver_toggle.action_item.set_enabled(ui_state.has_longitudinal_control and ui_state.is_offroad)
+ else:
+ self._long_maneuver_toggle.action_item.set_enabled(False)
+ self._alpha_long_toggle.set_visible(False)
+
+ # TODO: make a param control list item so we don't need to manage internal state as much here
+ # refresh toggles from params to mirror external changes
+ for key, item in (
+ ("AdbEnabled", self._adb_toggle),
+ ("SshEnabled", self._ssh_toggle),
+ ("JoystickDebugMode", self._joystick_toggle),
+ ("LongitudinalManeuverMode", self._long_maneuver_toggle),
+ ("AlphaLongitudinalEnabled", self._alpha_long_toggle),
+ ):
+ item.action_item.set_state(self._params.get_bool(key))
+
+ def _update_state(self):
+ # Disable toggles that require onroad restart
+ # TODO: we can do an onroad cycle, but alpha long toggle requires a deinit function to re-enable radar and not fault
+ for item in (self._adb_toggle, self._joystick_toggle, self._long_maneuver_toggle, self._alpha_long_toggle):
+ item.action_item.set_enabled(ui_state.is_offroad)
+
+ def _on_enable_adb(self, state: bool):
+ self._params.put_bool("AdbEnabled", state)
+
+ def _on_enable_ssh(self, state: bool):
+ self._params.put_bool("SshEnabled", state)
+
+ def _on_joystick_debug_mode(self, state: bool):
+ self._params.put_bool("JoystickDebugMode", state)
+ self._params.put_bool("LongitudinalManeuverMode", False)
+ self._long_maneuver_toggle.action_item.set_state(False)
+
+ def _on_long_maneuver_mode(self, state: bool):
+ self._params.put_bool("LongitudinalManeuverMode", state)
+ self._params.put_bool("JoystickDebugMode", False)
+ self._joystick_toggle.action_item.set_state(False)
+
+ def _on_alpha_long_enabled(self, state: bool):
+ self._params.put_bool("AlphaLongitudinalEnabled", state)
+ self._update_toggles()
diff --git a/selfdrive/ui/layouts/settings/toggles.py b/selfdrive/ui/layouts/settings/toggles.py
index 01f663d4b9..fddcf32fbd 100644
--- a/selfdrive/ui/layouts/settings/toggles.py
+++ b/selfdrive/ui/layouts/settings/toggles.py
@@ -34,6 +34,7 @@ class TogglesLayout(Widget):
def __init__(self):
super().__init__()
self._params = Params()
+ self._is_release = self._params.get_bool("IsReleaseBranch")
# param, title, desc, icon, needs_restart
self._toggle_defs = {
@@ -158,8 +159,6 @@ class TogglesLayout(Widget):
"The Experimental mode logo will also be shown in the top right corner."
)
- is_release = self._params.get_bool("IsReleaseBranch")
-
if ui_state.CP is not None:
if ui_state.has_longitudinal_control:
self._toggles["ExperimentalMode"].action_item.set_enabled(True)
@@ -176,7 +175,7 @@ class TogglesLayout(Widget):
long_desc = unavailable + " openpilot longitudinal control may come in a future update."
if ui_state.CP.getAlphaLongitudinalAvailable():
- if is_release:
+ if self._is_release:
long_desc = unavailable + " " + ("An alpha version of openpilot longitudinal control can be tested, along with " +
"Experimental mode, on non-release branches.")
else:
diff --git a/selfdrive/ui/tests/test_ui/raylib_screenshots.py b/selfdrive/ui/tests/test_ui/raylib_screenshots.py
index e77a2d4d7e..fb42b94d6d 100755
--- a/selfdrive/ui/tests/test_ui/raylib_screenshots.py
+++ b/selfdrive/ui/tests/test_ui/raylib_screenshots.py
@@ -76,7 +76,7 @@ def setup_settings_developer(click, pm: PubMaster):
def setup_keyboard(click, pm: PubMaster):
setup_settings_developer(click, pm)
- click(1930, 270)
+ click(1930, 470)
def setup_pair_device(click, pm: PubMaster):