import pyray as rl from collections.abc import Callable from openpilot.common.time_helpers import system_time_valid from openpilot.system.ui.widgets.scroller import Scroller from openpilot.selfdrive.ui.mici.widgets.button import BigButton, BigToggle, BigParamControl from openpilot.selfdrive.ui.mici.widgets.dialog import BigDialog, BigInputDialog from openpilot.system.ui.lib.application import gui_app from openpilot.system.ui.widgets import NavWidget from openpilot.selfdrive.ui.layouts.settings.common import restart_needed_callback from openpilot.selfdrive.ui.ui_state import ui_state from openpilot.selfdrive.ui.widgets.ssh_key import SshKeyAction class DeveloperLayoutMici(NavWidget): def __init__(self, back_callback: Callable): super().__init__() self.set_back_callback(back_callback) def github_username_callback(username: str): if username: ssh_keys = SshKeyAction() ssh_keys._fetch_ssh_key(username) if not ssh_keys._error_message: self._ssh_keys_btn.set_value(username) else: dlg = BigDialog("", ssh_keys._error_message) gui_app.set_modal_overlay(dlg) def ssh_keys_callback(): github_username = ui_state.params.get("GithubUsername") or "" dlg = BigInputDialog("enter GitHub username", github_username, confirm_callback=github_username_callback) if not system_time_valid(): dlg = BigDialog("Please connect to Wi-Fi to fetch your key", "") gui_app.set_modal_overlay(dlg) return gui_app.set_modal_overlay(dlg) txt_ssh = gui_app.texture("icons_mici/settings/developer/ssh.png", 77, 44) github_username = ui_state.params.get("GithubUsername") or "" self._ssh_keys_btn = BigButton("SSH keys", "Not set" if not github_username else github_username, icon=txt_ssh) self._ssh_keys_btn.set_click_callback(ssh_keys_callback) # adb, ssh, ssh keys, debug mode, joystick debug mode, longitudinal maneuver mode, ip address # ******** Main Scroller ******** self._adb_toggle = BigParamControl("enable ADB", "AdbEnabled") self._ssh_toggle = BigParamControl("enable SSH", "SshEnabled") self._joystick_toggle = BigToggle("joystick debug mode", initial_state=ui_state.params.get_bool("JoystickDebugMode"), toggle_callback=self._on_joystick_debug_mode) self._long_maneuver_toggle = BigToggle("longitudinal maneuver mode", initial_state=ui_state.params.get_bool("LongitudinalManeuverMode"), toggle_callback=self._on_long_maneuver_mode) self._alpha_long_toggle = BigToggle("alpha longitudinal", initial_state=ui_state.params.get_bool("AlphaLongitudinalEnabled"), toggle_callback=self._on_alpha_long_enabled) self._debug_mode_toggle = BigParamControl("ui debug mode", "ShowDebugInfo", toggle_callback=lambda checked: (gui_app.set_show_touches(checked), gui_app.set_show_fps(checked))) self._scroller = Scroller([ self._adb_toggle, self._ssh_toggle, self._ssh_keys_btn, self._joystick_toggle, self._long_maneuver_toggle, self._alpha_long_toggle, self._debug_mode_toggle, ], snap_items=False) # Toggle lists self._refresh_toggles = ( ("AdbEnabled", self._adb_toggle), ("SshEnabled", self._ssh_toggle), ("JoystickDebugMode", self._joystick_toggle), ("LongitudinalManeuverMode", self._long_maneuver_toggle), ("AlphaLongitudinalEnabled", self._alpha_long_toggle), ("ShowDebugInfo", self._debug_mode_toggle), ) onroad_blocked_toggles = (self._adb_toggle, self._joystick_toggle) release_blocked_toggles = (self._joystick_toggle, self._long_maneuver_toggle, self._alpha_long_toggle) engaged_blocked_toggles = (self._long_maneuver_toggle, self._alpha_long_toggle) # Hide non-release toggles on release builds for item in release_blocked_toggles: item.set_visible(not ui_state.is_release) # Disable toggles that require offroad for item in onroad_blocked_toggles: item.set_enabled(lambda: ui_state.is_offroad()) # Disable toggles that require not engaged for item in engaged_blocked_toggles: item.set_enabled(lambda: not ui_state.engaged) # Set initial state if ui_state.params.get_bool("ShowDebugInfo"): gui_app.set_show_touches(True) gui_app.set_show_fps(True) ui_state.add_offroad_transition_callback(self._update_toggles) def show_event(self): super().show_event() self._scroller.show_event() self._update_toggles() def _render(self, rect: rl.Rectangle): self._scroller.render(rect) def _update_toggles(self): ui_state.update_params() # CP gating if ui_state.CP is not None: alpha_avail = ui_state.CP.alphaLongitudinalAvailable if not alpha_avail or ui_state.is_release: self._alpha_long_toggle.set_visible(False) ui_state.params.remove("AlphaLongitudinalEnabled") else: self._alpha_long_toggle.set_visible(True) long_man_enabled = ui_state.has_longitudinal_control and ui_state.is_offroad() self._long_maneuver_toggle.set_enabled(long_man_enabled) if not long_man_enabled: self._long_maneuver_toggle.set_checked(False) ui_state.params.put_bool("LongitudinalManeuverMode", False) else: self._long_maneuver_toggle.set_enabled(False) self._alpha_long_toggle.set_visible(False) # 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_joystick_debug_mode(self, state: bool): ui_state.params.put_bool("JoystickDebugMode", state) ui_state.params.put_bool("LongitudinalManeuverMode", False) self._long_maneuver_toggle.set_checked(False) def _on_long_maneuver_mode(self, state: bool): ui_state.params.put_bool("LongitudinalManeuverMode", state) ui_state.params.put_bool("JoystickDebugMode", False) self._joystick_toggle.set_checked(False) restart_needed_callback(state) def _on_alpha_long_enabled(self, state: bool): # TODO: show confirmation dialog before enabling ui_state.params.put_bool("AlphaLongitudinalEnabled", state) restart_needed_callback(state) self._update_toggles()