Files
StarPilot/openpilot/selfdrive/ui/mici/layouts/settings/developer.py
T
2026-10-25 19:43:58 -05:00

220 lines
11 KiB
Python

from collections.abc import Callable
from openpilot.common.params import Params
from openpilot.common.time_helpers import system_time_valid
from openpilot.system.ui.widgets.scroller import NavScroller
from openpilot.selfdrive.ui.mici.widgets.button import BigButton, BigToggle, BigParamControl, BigCircleParamControl, GreyBigButton
from openpilot.selfdrive.ui.mici.widgets.dialog import BigDialog, BigInputDialog, 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
from openpilot.starpilot.car.hyundai.aol import ioniq6_settings_capable
from openpilot.starpilot.car.ford.longitudinal_preferences import release_long_available as ford_release_long_available
from opendbc.car.volkswagen.meb_modes import MEB_CARS, settings_capable as meb_settings_capable
from openpilot.starpilot.car.volkswagen.preferences import stock_long_requested
from openpilot.selfdrive.ui.widgets.ssh_key import SshKeyFetcher
class AlphaLongConfirmPage(NavScroller):
def __init__(self, on_confirm: Callable[[], None], *, system_long_default: bool = False):
super().__init__()
accept = BigConfirmationCircleButton("enable alpha\nlongitudinal",
gui_app.texture("icons_mici/setup/driver_monitoring/dm_check.png", 64, 64),
lambda: self.dismiss(on_confirm))
self._scroller.add_widgets([
GreyBigButton("enabling alpha longitudinal", "scroll to continue",
gui_app.texture("icons_mici/setup/warning.png", 64, 64)),
GreyBigButton("", "WARNING: alpha longitudinal control may disable Automatic Emergency Braking (AEB)"),
GreyBigButton("", "Use StarPilot speed control instead of stock adaptive cruise control." if system_long_default else
"On this car, openpilot defaults to the stock system's built-in ACC."),
GreyBigButton("", "This requests openpilot longitudinal control after a verified vehicle takeover at startup."),
accept,
])
class DeveloperLayoutMici(NavScroller):
def __init__(self, on_spacepilot_exit: Callable[[], None] | None = None):
super().__init__()
if not isinstance(ui_state.params, Params):
raise RuntimeError("Developer settings require the device UI")
self._ssh_fetcher = SshKeyFetcher(ui_state.params)
# Only the native C4 shell supplies the immediate return-to-driving owner.
if on_spacepilot_exit is not None:
from openpilot.common.hardware import HARDWARE
if HARDWARE.get_device_type() == 'mici':
from openpilot.starpilot.ui.spacepilot.native import VersionButton
self._scroller.add_widget(VersionButton(on_spacepilot_exit))
def github_username_callback(username: str):
if username:
self._ssh_keys_btn.set_value("Loading...")
self._ssh_keys_btn.set_enabled(False)
def on_response(error):
self._ssh_keys_btn.set_enabled(True)
if error is None:
self._ssh_keys_btn.set_value(username)
else:
self._ssh_keys_btn.set_value("Not set")
gui_app.push_widget(BigDialog("", error))
self._ssh_fetcher.fetch(username, on_response)
else:
self._ssh_fetcher.clear()
self._ssh_keys_btn.set_value("Not set")
def ssh_keys_callback():
github_username = ui_state.params.get("GithubUsername") or ""
dlg = BigInputDialog("enter GitHub username...", github_username, minimum_length=0, confirm_callback=github_username_callback)
if not system_time_valid():
dlg = BigDialog("", "Please connect to Wi-Fi to fetch your key.")
gui_app.push_widget(dlg)
return
gui_app.push_widget(dlg)
txt_ssh = gui_app.texture("icons_mici/settings/developer/ssh.png", 56, 64)
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,
description="Grant SSH access to all public keys in your GitHub settings. Only enter your own username.")
self._ssh_keys_btn.set_click_callback(ssh_keys_callback)
from openpilot.starpilot.connect.settings import CloudProviderConfirmation
from openpilot.starpilot.connect.provider import PROVIDERS, status
self._cloud_provider_btn = BigButton('Cloud Provider', 'Changes apply after the next reboot')
def refresh_cloud():
try:
current = status()
self._cloud_provider_btn.set_value(PROVIDERS[current['selected']].label)
except (OSError, ValueError, TypeError):
self._cloud_provider_btn.set_value('Unavailable')
def choose_cloud():
try:
current = status()
target = next(name for name in PROVIDERS if name != current['selected'])
except (OSError, ValueError, TypeError, StopIteration):
gui_app.push_widget(BigDialog('', 'Cloud Provider is unavailable. Refresh and try again.'))
return
gui_app.push_widget(CloudProviderConfirmation(target, current['revision'], refresh_cloud))
self._cloud_provider_btn.set_click_callback(choose_cloud)
self._cloud_provider_btn.set_enabled(ui_state.is_offroad)
refresh_cloud()
# adb, ssh, ssh keys, debug mode, joystick debug mode, ip address
# ******** Main Scroller ********
self._adb_toggle = BigCircleParamControl(gui_app.texture("icons_mici/adb_short.png", 82, 82), "AdbEnabled", icon_offset=(0, 12),
description="Use Android Debug Bridge (ADB) over USB or the network.", title="enable ADB")
self._ssh_toggle = BigCircleParamControl(gui_app.texture("icons_mici/ssh_short.png", 82, 82), "SshEnabled", icon_offset=(0, 12),
description="Access the device remotely using your SSH keys.", title="enable SSH")
self._joystick_toggle = BigToggle("joystick debug\nmode", initial_state=ui_state.params.get_bool("JoystickDebugMode"),
toggle_callback=self._on_joystick_debug_mode, description="Control the car with a joystick for debugging.")
self._alpha_long_toggle = BigToggle("alpha longitudinal", initial_state=ui_state.params.get_bool("AlphaLongitudinalEnabled"),
toggle_callback=self._on_alpha_long_enabled,
description="Use alpha openpilot longitudinal control instead of stock ACC. This may disable Automatic Emergency " +
"Braking (AEB).")
self._navigation_toggle = BigParamControl("navigation (alpha)", "NavigationEnabled",
description="Turn-by-turn navigation with a route map. Set a destination from comma connect.")
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)),
description="Show touch locations and the UI frame rate.")
self._cellular_off_toggle = BigParamControl("disable cellular", "CellularDisabled",
description="Hang up cellular data and don't redial. The modem stays registered and the " +
"SIM is untouched. Remote access over cellular data is unavailable while this is enabled.")
self._scroller.add_widgets([
self._adb_toggle,
self._ssh_toggle,
self._ssh_keys_btn,
self._cloud_provider_btn,
self._joystick_toggle,
self._alpha_long_toggle,
self._debug_mode_toggle,
self._navigation_toggle,
self._cellular_off_toggle,
])
# Toggle lists
self._refresh_toggles = (
("AdbEnabled", self._adb_toggle),
("SshEnabled", self._ssh_toggle),
("JoystickDebugMode", self._joystick_toggle),
("AlphaLongitudinalEnabled", self._alpha_long_toggle),
("ShowDebugInfo", self._debug_mode_toggle),
("NavigationEnabled", self._navigation_toggle),
("CellularDisabled", self._cellular_off_toggle),
)
onroad_blocked_toggles = (self._adb_toggle, self._joystick_toggle)
release_blocked_toggles = (self._joystick_toggle, self._alpha_long_toggle, self._navigation_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())
self._alpha_long_toggle.set_enabled(lambda: ui_state.is_offroad())
# 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 _update_state(self):
super()._update_state()
self._ssh_fetcher.update()
def show_event(self):
super().show_event()
self._update_toggles()
def _update_toggles(self):
ui_state.update_params()
# CP gating
if ui_state.CP is not None:
alpha_avail = (meb_settings_capable(ui_state.CP) if ui_state.CP.brand == 'volkswagen' and ui_state.CP.carFingerprint in MEB_CARS
else ui_state.CP.alphaLongitudinalAvailable or ioniq6_settings_capable(ui_state.CP))
if not alpha_avail or (ui_state.is_release and not (meb_settings_capable(ui_state.CP) or ford_release_long_available(ui_state.CP))):
self._alpha_long_toggle.set_visible(False)
else:
self._alpha_long_toggle.set_visible(True)
else:
self._alpha_long_toggle.set_visible(False)
# Refresh toggles from params to mirror external changes
for key, item in self._refresh_toggles:
value = (not stock_long_requested(ui_state.params) if key == "AlphaLongitudinalEnabled" and meb_settings_capable(ui_state.CP)
else ui_state.params.get_bool(key))
item.set_checked(value)
def _on_joystick_debug_mode(self, state: bool):
ui_state.params.put_bool("JoystickDebugMode", state, block=True)
ui_state.params.put_bool("LongitudinalManeuverMode", False, block=True)
ui_state.params.put_bool("LateralManeuverMode", False, block=True)
def _on_alpha_long_enabled(self, state: bool):
def do_toggle(_state: bool):
if (not ui_state.is_offroad() or (_state and (ui_state.CP is None or
not (meb_settings_capable(ui_state.CP) if ui_state.CP.brand == 'volkswagen' and ui_state.CP.carFingerprint in MEB_CARS
else ui_state.CP.alphaLongitudinalAvailable or ioniq6_settings_capable(ui_state.CP)) or
ui_state.is_release and not (meb_settings_capable(ui_state.CP) or ford_release_long_available(ui_state.CP))))):
self._update_toggles()
return
ui_state.params.put_bool("AlphaLongitudinalEnabled", _state, block=True)
restart_needed_callback()
self._update_toggles()
if state:
# Don't show enabled state until confirm
self._alpha_long_toggle.set_checked(False)
page = (AlphaLongConfirmPage(lambda: do_toggle(True), system_long_default=True) if meb_settings_capable(ui_state.CP)
else AlphaLongConfirmPage(lambda: do_toggle(True)))
gui_app.push_widget(page)
else:
do_toggle(False)