"""Authenticated local projection of the shared saved-feature owner.""" from openpilot.starpilot.ui.lane_change_feature import confirm_question as lane_change_question from collections.abc import Callable from dataclasses import dataclass, replace import hashlib import json import math import os import secrets import threading import time from typing import Protocol, cast from openpilot.starpilot.ui.feature_settings_owner import FeatureSettingsOwner from openpilot.starpilot.galaxy.setting_descriptions import description from openpilot.starpilot.galaxy.localization_settings import LocalizationSettingsOwner from openpilot.starpilot.ui.controller_feature import SETUP_ACTION, SETUP_QUESTION from openpilot.starpilot.ui.vehicle_bool import VEHICLE_BOOL_KEYS, confirmation_question as vehicle_question from openpilot.starpilot.ui.conditional_feature import confirmation_question as conditional_question from openpilot.starpilot.ui.appearance_owner import AppearanceOwner, LABELS as APPEARANCE_LABELS from openpilot.starpilot.ui.display_owner import AUTO_PREFIX as DISPLAY_AUTO_PREFIX, DisplayOwner, LABELS as DISPLAY_LABELS from openpilot.starpilot.ui.sounds_owner import SoundsOwner from openpilot.starpilot.audio.alert_volume import SPECS as SOUND_SPECS from openpilot.starpilot.ui.presentation import Profile from openpilot.common.hardware import HARDWARE from openpilot.starpilot.ui.feature_settings_state import ( FeatureRow, FeatureSettingsRequest, FEATURE_CONFIRM_ACTIONS, is_long_confirm_action, row_change, row_default, ) from openpilot.starpilot.saved_source import read_saved from openpilot.starpilot.galaxy.developer_settings import DRIVER_MONITORING_RESET, DeveloperSettingsOwner from openpilot.starpilot.speed_limits.vision_gate import diagnostic_choice_enabled from openpilot.starpilot.parked_evidence import ParkedEvidence, RESUME_SKEW_NS, fresh_offroad, host_parked from openpilot.starpilot.ui.pip_owner import EDITOR as PIP_EDITOR, FORMAT_PREFIX as PIP_FORMAT_PREFIX, RESET as PIP_RESET, PiPOwner, validated_editor_draft from openpilot.starpilot.ui.pip_preferences import read_pip from openpilot.starpilot.ui.sentry_owner import RESET as SENTRY_RESET, SentryOwner from openpilot.starpilot.ui.vasm_owner import ANNOTATION as VASM_ANNOTATION, RESET as VASM_RESET, VASMOwner, validated_annotation PAGES = frozenset(("hub", "localization", "developer", "vehicle", "slc", "lane", "lane_change", "profiles", "traffic", "aggressive", "standard", "relaxed", "curve", "torque", "aol", "wheel", "appearance", "display", "data", "sounds", "pip", "sentry", "vasm", "conditional", "conditional/cem", "conditional/ccm", *(f"{name}/{category}" for name in ("traffic", "aggressive", "standard", "relaxed") for category in ("acceleration", "braking", "following")))) CONFIRM_ACTIONS = FEATURE_CONFIRM_ACTIONS | {DRIVER_MONITORING_RESET} MAX_VIEWS = 64 MAX_INTENTS = 64 TTL = 60.0 # Android Auto lists binary controls only. CAR_CONTROL_SECTIONS = ( ("lateral", "Lateral (Steering)", ("aol", "lane_change", "torque", "lane")), ("longitudinal", "Longitudinal (Speed & Following)", ("profiles", "conditional", "curve", "slc", "aggressive", "standard", "relaxed", "traffic")), ("wheel", "Wheel Controls", ("wheel",)), ("visual", "Visual (Display & UI)", ("appearance", "pip")), ("sounds", "Sounds & Alerts", ("sounds",)), ("device", "Device & Data", ("display", "data")), ("localization", "Localization", ("localization",)), ("developer", "Developer", ("developer",)), ) class SettingsUnavailable(Exception): pass class SettingsChanged(Exception): pass def boot_time_ns() -> int: return time.clock_gettime_ns(getattr(time, "CLOCK_BOOTTIME", time.CLOCK_MONOTONIC)) @dataclass(frozen=True) class AuthorityContext: parked: bool cp: object | None cp_raw: bytes | None metric: bool = False editing_saved_vehicle: bool = False configuration_longitudinal: bool = False configuration_vehicle: bool = False class ContextSource(Protocol): def sample(self) -> AuthorityContext: ... def _qualified(ctx: AuthorityContext, group: str) -> bool: cp = ctx.cp if group == "preferences": return True if group == "parked_preferences": return ctx.parked if group == "lane_live": from openpilot.starpilot.lateral.lane_runtime import runtime_supported return bool(cp is not None and not getattr(cp, "notCar", True) and not getattr(cp, "dashcamOnly", True) and not getattr(cp, "passive", True) and runtime_supported(cp)) if cp is None: return False try: if not cp.carFingerprint: return False if group == "toyota_cruise": from openpilot.starpilot.controllers.toyota_cruise import capability return capability(cp) is not None if group == "vehicle": from opendbc.car.toyota.interface import toyota_auto_hold_supported return toyota_auto_hold_supported(cp) if group == "switchback_wheel": from openpilot.starpilot.feature_capabilities import lateral_supported return lateral_supported(cp) if group in ("conditional", "conditional_wheel", "long_output"): return bool((cp.openpilotLongitudinalControl or ctx.configuration_longitudinal) and not cp.notCar and not cp.dashcamOnly and not cp.passive) if group == "slc": return bool((cp.openpilotLongitudinalControl or ctx.configuration_longitudinal) and not cp.notCar and not cp.dashcamOnly and not cp.passive) if group == "long": return bool((cp.openpilotLongitudinalControl or ctx.configuration_longitudinal) and not cp.notCar and not cp.dashcamOnly and not cp.passive) if group in ("aol", "aol_wheel") and ctx.configuration_vehicle and ctx.parked: from openpilot.starpilot.aol.vehicle import configuration_settings_policy policy = configuration_settings_policy(cp) if policy is not None: return bool(policy.settings_supported and not cp.notCar and not cp.passive) if group in ("torque", "aol", "aol_wheel"): return bool(not cp.notCar and not cp.dashcamOnly and not cp.passive) return group in ("lane", "lane_change") and not cp.notCar and not cp.dashcamOnly and not cp.passive except (AttributeError, TypeError, ValueError): return False def _onroad_preference(page: str, key: str) -> bool: # Regular saved choices are consumed by their runtime owners. Only explicit # multi-key reset/repair operations need the offroad transaction boundary; # their owners independently recheck that requirement at commit time. return bool(page != "developer" and key and key != SETUP_ACTION and key not in ( "IsRhdDetected", "AlphaLongitudinalEnabled", "reset_profiles", "curve_reset", "slc_adopt", "slc_reset", "conditional:reset", "conditional:manual_reset", ) and not is_long_confirm_action(key) and not key.startswith("torque_repair:")) class LiveContextSource: """Samples registered Params and the two actual publisher clock domains.""" MAX_CP_BYTES = 1_048_576 MAX_LIVE_CP_AGE_NS = 100_000_000_000 # Card republishes finalized parameters every 50 seconds. def __init__(self, params, messages=None, *, mono_clock=time.monotonic_ns, boot_clock=boot_time_ns, evidence_wait_ms=750, wait_clock=time.monotonic, borrowed_messages=False): if borrowed_messages and messages is None: raise ValueError("Borrowed authority requires a message source") self.borrowed_messages = borrowed_messages self.params = params self.messages = messages self.mono_clock = mono_clock self.boot_clock = boot_clock self.evidence_wait_ms = evidence_wait_ms self.wait_clock = wait_clock self.lock = threading.Lock() self.closed = False self.physical = None self.device_offset_ns: int | None = None # A pre-start cached Python message can look mono-fresh after suspend. # Require a publisher stamp strictly after this collector starts. self.device_after_mono_ns = getattr(messages, "after_mono_ns", mono_clock()) self.last_pair_offset_ns: int | None = None def _messages(self): if self.closed: return None if self.messages is None: from openpilot.cereal import messaging self.messages = messaging.SubMaster(["deviceState", "pandaStates", "carState", "selfdriveState", "carParams"]) return cast(Callable[[], object], self.messages.snapshot)() if hasattr(self.messages, "snapshot") else self.messages def close(self) -> None: with self.lock: self.closed = True if self.physical is not None: self.physical.close() self.physical = None if self.messages is not None and not self.borrowed_messages: for sock in getattr(self.messages, "sock", {}).values(): close = getattr(sock, "close", None) if close is not None: close() self.messages = None def observe_borrowed_device_clock(self) -> None: """Capture device publisher clock evidence at the collector's update frame. A borrowed collector's updated flag lasts only one UI frame. This observes clock metadata without advancing sockets or reading saved settings. """ if not self.borrowed_messages: return with self.lock: if self.closed or self.messages is None: return before = self.mono_clock() boot = self.boot_clock() now = self.mono_clock() if now < before or now - before > RESUME_SKEW_NS: self.device_after_mono_ns = max(self.device_after_mono_ns, now) self.device_offset_ns = None return offset = boot - ((before + now) // 2) if self.last_pair_offset_ns is not None and abs(offset - self.last_pair_offset_ns) > RESUME_SKEW_NS: self.device_after_mono_ns = max(self.device_after_mono_ns, now) self.device_offset_ns = None self.last_pair_offset_ns = offset sm = self.messages if sm.updated['deviceState'] and int(sm.logMonoTime['deviceState']) > self.device_after_mono_ns: self.device_offset_ns = offset def parked(self) -> bool: """Settings and offroad operations follow the effective drive state.""" return self.offroad() def offroad(self) -> bool: """Effective offroad mode also permits setup with the car powered on.""" if host_parked(): return not self.closed offroad, readable = read_saved(self.params, "IsOffroad", 8) if not readable or offroad != b"1": return False with self.lock: if self.closed: return False try: deadline = self.wait_clock() + self.evidence_wait_ms / 1000 sm = self._messages() timeout = 0 while True: if sm is None: return False if hasattr(sm, "after_mono_ns"): self.device_after_mono_ns = max(self.device_after_mono_ns, sm.after_mono_ns) self.device_offset_ns = sm.device_offset_ns elif not self.borrowed_messages: sm.update(timeout) mono_before = self.mono_clock() boot_now = self.boot_clock() mono_now = self.mono_clock() if mono_now < mono_before or mono_now - mono_before > RESUME_SKEW_NS: return False pair_offset = boot_now - ((mono_before + mono_now) // 2) if self.last_pair_offset_ns is not None and abs(pair_offset - self.last_pair_offset_ns) > RESUME_SKEW_NS: self.device_after_mono_ns = max(self.device_after_mono_ns, mono_now) self.device_offset_ns = None self.last_pair_offset_ns = pair_offset if sm.updated["deviceState"] and int(sm.logMonoTime["deviceState"]) > self.device_after_mono_ns: self.device_offset_ns = pair_offset pandas = sm["pandaStates"] if sm.seen["deviceState"] and sm["deviceState"].started: return False evidence = ParkedEvidence( True, bool(sm.seen["deviceState"]), bool(sm.alive["deviceState"]), bool(sm.valid["deviceState"]), bool(sm["deviceState"].started), int(sm.logMonoTime["deviceState"]), int(sm.recv_time["deviceState"] * 1e9), self.device_offset_ns, self.device_after_mono_ns, bool(sm.seen["pandaStates"]), bool(sm.alive["pandaStates"]), bool(sm.valid["pandaStates"]), int(sm.logMonoTime["pandaStates"]), int(sm.recv_time["pandaStates"] * 1e9), tuple(bool(p.ignitionLine or p.ignitionCan) for p in pandas), ) if fresh_offroad(evidence, now_mono_ns=mono_now, now_boot_ns=boot_now): offroad, readable = read_saved(self.params, "IsOffroad", 8) return bool(readable and offroad == b"1") remaining_ms = int((deadline - self.wait_clock()) * 1000) if remaining_ms <= 0: return False timeout = min(50, remaining_ms) except (OSError, RuntimeError, AttributeError, TypeError, ValueError, OverflowError): return False def configuration_allowed(self) -> bool: """Allow configuration in effective offroad mode or a fresh stationary Park.""" if self.offroad(): return True with self.lock: if self.closed: return False try: self._messages() if not self.borrowed_messages and not hasattr(self.messages, 'snapshot'): self.messages.update(0) if self.physical is None: from openpilot.starpilot.drive_state.evidence import PhysicalSource self.physical = PhysicalSource(self.messages, mono=self.mono_clock, boot=self.boot_clock) return self.physical.allowed() except (OSError, RuntimeError, AttributeError, KeyError, TypeError, ValueError): return False def runtime_vehicle(self): try: with self.lock: if self.closed: return None, None sm = self._messages() if self.messages is not None else None if sm is not None: if not self.borrowed_messages and not hasattr(self.messages, 'snapshot'): sm.update(0) now = self.mono_clock() stamp = int(sm.logMonoTime.get('carParams', 0)) floor = max(self.device_after_mono_ns, getattr(sm, 'after_mono_ns', 0)) if (sm.seen.get('carParams', False) and sm.valid.get('carParams', False) and sm.alive.get('carParams', False) and floor < stamp <= now and now - stamp <= self.MAX_LIVE_CP_AGE_NS): from opendbc.car.values import PLATFORMS from opendbc.car.mock.values import CAR as MOCK from opendbc.car.structs import car cp = sm['carParams'] if (cp.schema.node.id == car.CarParams.schema.node.id and not cp.notCar and cp.carFingerprint in PLATFORMS and cp.carFingerprint != MOCK.MOCK): raw = cp.as_builder().to_bytes() if len(raw) <= self.MAX_CP_BYTES: return cp, b'live-vehicle:' + raw except (OSError, RuntimeError, AttributeError, KeyError, TypeError, ValueError, OverflowError): pass cp_raw, readable = read_saved(self.params, "CarParamsPersistent", self.MAX_CP_BYTES) cp = None if readable and cp_raw is not None: try: from openpilot.cereal import messaging from opendbc.car.structs import car from openpilot.starpilot.schema_cache import inspect_cache inspected = inspect_cache("CarParamsPersistent", cp_raw) if inspected.status == "valid" and inspected.payload is not None: cp = messaging.log_from_bytes(inspected.payload, car.CarParams) except (OSError, RuntimeError, TypeError, ValueError, OverflowError): cp = None return cp, cp_raw if readable else None def sample(self) -> AuthorityContext: parked = self.configuration_allowed() cp, cp_raw = self.runtime_vehicle() readable = True editing_saved = configurable_long = configuration_vehicle = False if parked and self.offroad(): from openpilot.starpilot.galaxy.vehicle_configuration import settings_configuration cp, cp_raw, editing_saved, configurable_long = settings_configuration(self.params, cp, cp_raw if readable else None) configuration_vehicle = cp is not None readable = True units, unit_readable = read_saved(self.params, "IsMetric", 8) return AuthorityContext(parked, cp, cp_raw if readable else None, bool(unit_readable and units == b"1"), editing_saved, configurable_long, configuration_vehicle) @dataclass(frozen=True) class _View: session: bytes generation: bytes page: str rows: tuple[FeatureRow, ...] cp_raw: bytes | None expires: float @dataclass(frozen=True) class _Intent: session: bytes generation: bytes page: str request: FeatureSettingsRequest cp_raw: bytes | None expires: float reset_row: FeatureRow | None = None recovery_default: bool = False def _session_key(token: str) -> bytes: return hashlib.sha256(token.encode("ascii")).digest() def _projection(row: FeatureRow) -> dict: return {"label": row.label, "value": row.value, "choices": list(row.choices), "step": row.step, "minimum": row.minimum, "maximum": row.maximum, "unit": row.unit, "available": row.available, "reason": description(row), "page": row.page if row.page in PAGES else "", "action": bool(row.key and row.available and not row.page), "confirm": bool(row.key in CONFIRM_ACTIONS or is_long_confirm_action(row.key) or row.key in (PIP_RESET, PIP_EDITOR, VASM_RESET, VASM_ANNOTATION) or row.key.startswith(PIP_FORMAT_PREFIX)), "repairValue": row.repair_value, "defaultValue": row.default_value, "resetAvailable": row_default(row) is not None, "displayScale": "lane-strength" if row.key == "LaneCenteringStrength" else None} def _question(row: FeatureRow, request: FeatureSettingsRequest) -> str: key = request.key if key == "LaneCenteringStrength": raw = float(request.value) level = (1 + (raw - row.minimum) * 4 / (100 - row.minimum) if raw <= 100 else 5 + (raw - 100) * 5 / (row.maximum - 100)) return f"Set lane centering strength to {level:.2f}? 5 is normal." if key == "UseStarPilotLongitudinalPlanner": return f"Select {request.value} for {row.label.lower()}? The selected planner starts with the next driving process." if key in ("CustomCruise", "CustomCruiseLong"): if row.capability and row.capability[0] == "toyota": return f"Save {row.label.lower()} as {request.value}? Toyota cruise preferences refresh while this compatible drive is active." value = f"{request.value} {row.unit}" if row.unit else request.value return f"Set {row.label.lower()} to {value}? Cruise-button increments refresh during the drive." if key in SOUND_SPECS: value = request.value + ("%" if request.value != "Auto" and not request.value.endswith("%") else "") return f"Save {row.label.lower()} as {value}? The normal alert sound still decides when it plays." if key in DISPLAY_LABELS or key.startswith(DISPLAY_AUTO_PREFIX): value = request.value + (" " + row.unit if row.unit and request.value != "Auto" and not request.value.endswith(row.unit) else "") return f"Save {row.label.lower()} as {value}? This changes only the display." if key in APPEARANCE_LABELS: return f"Save {row.label} as {request.value}? This changes only the display." if key == VASM_RESET: return "Replace saved spot-monitor settings with Off and unconfigured defaults? Saved regions will be lost." if key == VASM_ANNOTATION: annotation = validated_annotation(request.value) if annotation is None: return "Invalid camera regions" return "Save these camera window regions?" if key.startswith("vasm:"): value = f"{request.value} {row.unit}".rstrip() return f"Save {row.label.lower()} as {value}?" if key == SENTRY_RESET: return "Replace invalid saved motion settings with Off and the default sensitivity and warning time?" if key == DRIVER_MONITORING_RESET: return "Clear saved driver monitoring wheel-side detection? Automatic detection will run again on the next drive." if key.startswith("sentry:"): value = f"{request.value} {row.unit}".rstrip() return (f"Save {row.label.lower()} as {value}? This is a saved choice; " + "development opt-in and fresh parked sensor evidence remain separate.") if key == PIP_RESET: return "Replace the saved side-camera crop with the default? Prior crop alignment will be lost." if key == PIP_EDITOR: return "Save these source-image crop positions? Confirm alignment on the device before using the preview." if key.startswith(PIP_FORMAT_PREFIX): return f"{row.label}? This replaces the saved crop; check alignment on the device." if key == "slc_adopt": return "Keep these saved offsets and speed ranges when units change? Prior saved values remain but become inactive." if key == "slc_reset": return f"Reset speed-limit offsets to zero? {row.value}. Saved control may resume if its switch is on." if key == "curve_reset": return "Erase learned curve data? Curve Speed Controller keeps its current On or Off choice." if key.startswith("conditional:"): return conditional_question(request, row.label, row.unit) if key == "reset_profiles": return "Reset invalid saved profiles and turn their saved switch off?" if key in ("profile:global_acceleration", "profile:global_braking"): return (f"Save {row.label.lower()} as {request.value}? Personalities set to Selected Profile follow this choice; " + "explicit personality overrides stay unchanged.") if key == "torque_gain_rebase": return "Apply the saved steering response to the selected controller and current vehicle tune? Friction and other models stay unchanged." if key == "torque_rebase": return f"Reapply saved torque values ({row.value}) with the current vehicle tune?" if key == "LateralControllerSelection": return f"Select {request.value}? The controller and its feedback state are created when the driving process starts." if key == SETUP_ACTION: return SETUP_QUESTION if key.startswith("torque_"): return f"{row.label}? Prior saved values remain but may become inactive." if key.startswith("long_"): from openpilot.starpilot.ui.long_profile_feature import long_confirm_question return long_confirm_question(row) if key.startswith("lane_change:"): return lane_change_question(request) if key in VEHICLE_BOOL_KEYS: return vehicle_question(request) if key == "ToyotaAutoHold": return f"Set automatic brake hold to {request.value}? The next startup configures the required brake-hold safety permissions." value = f"{request.value} {row.unit}".rstrip() return f"Set {row.label.lower()} to {value}?" class SettingsGateway: def __init__(self, params, context: ContextSource | None = None, *, clock=time.monotonic, starport=None): self.params = params self.context = context if context is not None else LiveContextSource(params) self.clock = clock self.starport = starport self.lock = threading.Lock() self.views: dict[str, _View] = {} self.intents: dict[str, _Intent] = {} def close(self) -> None: close = getattr(self.context, "close", None) if close is not None: close() def diagnostics(self) -> dict: """Read the same bounded, non-secret presentation used by driving settings.""" with self.lock: ctx = self.context.sample() sections = [] for page in ("torque", "lane", "lane_change", "aol", "conditional", "profiles", "slc", "curve"): state = self._state(page, ctx) sections.append({"title": state.title, "rows": [{"label": row.label, "value": row.value} for row in state.rows[:64]]}) cp = ctx.cp vehicle = {"available": cp is not None, "fingerprint": str(getattr(cp, "carFingerprint", "")), "brand": str(getattr(cp, "brand", "")), "longitudinal": bool(getattr(cp, "openpilotLongitudinalControl", False)), "steering": str(getattr(cp, "steerControlType", ""))} return {"schemaVersion": 1, "vehicle": vehicle, "sections": sections, "snapshot": [{"label": "Units", "value": "Metric" if ctx.metric else "Imperial"}], "note": "Saved configuration is shown here. It does not confirm that a feature is active while driving. Change settings in Toggles."} def _context(self, page: str) -> AuthorityContext: return self.context.sample() def _owner(self, ctx: AuthorityContext, *, live: bool = False, session_valid: Callable[[], bool] = lambda: True) -> FeatureSettingsOwner: def current() -> AuthorityContext: if not session_valid(): return AuthorityContext(False, None, None) fresh = self.context.sample() if live else ctx return fresh if fresh.cp_raw == ctx.cp_raw else AuthorityContext(False, None, fresh.cp_raw) def authorized(group: str) -> bool: if not session_valid(): return False fresh = self.context.sample() if live else ctx return fresh.cp_raw == ctx.cp_raw and _qualified(fresh, group) return FeatureSettingsOwner(self.params, authorized, vehicle_fingerprint=lambda: getattr(current().cp, "carFingerprint", None), vehicle_params=lambda: current().cp, vision_development=lambda: diagnostic_choice_enabled(os.environ, current().cp), show_cruise_intervals=True, configuration_longitudinal=lambda: current().configuration_longitudinal, configuration_vehicle=lambda: current().configuration_vehicle, galaxy=lambda: not current().editing_saved_vehicle) def _pip_owner(self, ctx: AuthorityContext, *, live: bool = False, session_valid: Callable[[], bool] = lambda: True) -> PiPOwner: def parked() -> bool: if not session_valid(): return False fresh = self.context.sample() if live else ctx return fresh.cp_raw == ctx.cp_raw return PiPOwner(self.params, parked, lambda: False) def _appearance_owner(self, ctx: AuthorityContext, *, live: bool = False, session_valid: Callable[[], bool] = lambda: True) -> AppearanceOwner: def authorized() -> bool: if not session_valid(): return False fresh = self.context.sample() if live else ctx return fresh.cp_raw == ctx.cp_raw return AppearanceOwner(self.params, authorized) def _sounds_owner(self, ctx: AuthorityContext, *, live: bool = False, session_valid: Callable[[], bool] = lambda: True) -> SoundsOwner: def authorized() -> bool: if not session_valid(): return False fresh = self.context.sample() if live else ctx return fresh.cp_raw == ctx.cp_raw return SoundsOwner(self.params, authorized) def _display_owner(self, ctx: AuthorityContext, *, live: bool = False, session_valid: Callable[[], bool] = lambda: True) -> DisplayOwner: def parked() -> bool: if not session_valid(): return False fresh = self.context.sample() if live else ctx return fresh.cp_raw == ctx.cp_raw return DisplayOwner(self.params, parked) def _sentry_owner(self, ctx: AuthorityContext, *, live: bool = False, session_valid: Callable[[], bool] = lambda: True) -> SentryOwner: def parked() -> bool: if not session_valid(): return False fresh = self.context.sample() if live else ctx return fresh.cp_raw == ctx.cp_raw return SentryOwner(self.params, parked) def _vasm_owner(self, ctx: AuthorityContext, *, live: bool = False, session_valid: Callable[[], bool] = lambda: True) -> VASMOwner: def parked() -> bool: if not session_valid(): return False fresh = self.context.sample() if live else ctx return fresh.cp_raw == ctx.cp_raw return VASMOwner(self.params, parked) def _developer_owner(self, ctx: AuthorityContext, *, live: bool = False, session_valid: Callable[[], bool] = lambda: True) -> DeveloperSettingsOwner: def authorized() -> bool: if not session_valid(): return False fresh = self.context.sample() if live else ctx return bool(fresh.parked and not fresh.editing_saved_vehicle and fresh.cp_raw == ctx.cp_raw) return DeveloperSettingsOwner(self.params, authorized, panda_wake=self._owner(ctx, live=live, session_valid=session_valid).panda_wake, starport=self.starport) def _localization_owner(self, ctx: AuthorityContext, *, live: bool = False, session_valid: Callable[[], bool] = lambda: True) -> LocalizationSettingsOwner: def authorized() -> bool: return session_valid() def parked() -> bool: return session_valid() and (self.context.sample() if live else ctx).parked return LocalizationSettingsOwner(self.params, authorized, parked) def _state(self, page: str, ctx: AuthorityContext): if page == "localization": return self._localization_owner(ctx).snapshot() if page == "developer": return self._developer_owner(ctx).snapshot() if page == "appearance": return self._appearance_owner(ctx).snapshot(Profile.COMPACT, include_large=True) if page == "sounds": return self._sounds_owner(ctx).snapshot() if page == "display": return self._display_owner(ctx).snapshot(Profile.COMPACT if HARDWARE.get_device_type() == "mici" else Profile.LARGE) if page == "pip": return self._pip_owner(ctx).snapshot(include_editor=True) if page == "sentry": return self._sentry_owner(ctx).snapshot() if page == "vasm": return self._vasm_owner(ctx).snapshot() state = self._owner(ctx).snapshot(page, parked=ctx.parked, system_long=_qualified(ctx, "long"), lateral_context=_qualified(ctx, "lane") or (page == "lane" and _qualified(ctx, "lane_live")), metric=ctx.metric, configure_while_driving=True) if ctx.editing_saved_vehicle: from opendbc.car.values import PLATFORMS platform = PLATFORMS[ctx.cp.carFingerprint] label = platform.config.car_docs[0].name if platform.config.car_docs else str(ctx.cp.carFingerprint).replace("_", " ").title() return replace(state, subtitle=f"{label} ยท Saved vehicle settings. {state.subtitle}") return state def _clean(self) -> None: now = self.clock() self.views = {key: value for key, value in self.views.items() if value.expires > now} self.intents = {key: value for key, value in self.intents.items() if value.expires > now} def page(self, page: str, token: str, generation: bytes) -> dict: if page not in PAGES: raise SettingsUnavailable("Unknown page") ctx = self._context(page) state = self._state(page, ctx) editor = None if page == "vasm": editor, editor_source = self._vasm_owner(ctx).editor_with_source() row_source = next((row.source for row in state.rows if row.key), None) if any(row.key for row in state.rows) and editor_source != row_source: raise SettingsChanged("Saved camera regions changed") elif page == "pip": editor, editor_dependencies = self._pip_owner(ctx).editor_with_source() row_dependencies = state.rows[0].dependencies if state.rows else None if row_dependencies is not None and editor_dependencies != row_dependencies: raise SettingsChanged("Saved camera crop changed") with self.lock: self._clean() if len(self.views) >= MAX_VIEWS: self.views.pop(next(iter(self.views))) view_id = secrets.token_urlsafe(24) self.views[view_id] = _View(_session_key(token), generation, page, state.rows, ctx.cp_raw, self.clock() + TTL) scope = hashlib.sha256(_session_key(token) + generation + (ctx.cp_raw or b"")).digest() result = {"page": page, "title": state.title, "subtitle": state.subtitle, "parked": state.parked, "vehicleIdentified": bool(ctx.cp is not None and getattr(ctx.cp, "carFingerprint", None)), "rows": [{**_projection(row), "revision": hashlib.sha256(scope + repr(row).encode()).hexdigest()} for row in state.rows], "view": view_id} if page == "hub" or any(row.page for row in state.rows): rows_by_page = {page: state} visible: dict[str, bool] = {} def page_state(name: str): if name not in rows_by_page: rows_by_page[name] = self._state(name, ctx) return rows_by_page[name] def has_car_control(name: str, visited: frozenset[str] = frozenset()) -> bool: if name in visited or name not in PAGES: return False if name in visible: return visible[name] try: rows = page_state(name).rows except SettingsUnavailable: visible[name] = False return False found = any((len(row.choices) == 2 and set(row.choices) == {"On", "Off"}) or has_car_control(row.page, visited | {name}) for row in rows) visible[name] = found return found if page == "hub": result["sections"] = [] for section_id, title, names in CAR_CONTROL_SECTIONS: pages = [{"id": name, "title": page_state(name).title} for name in names if has_car_control(name)] if pages: result["sections"].append({"id": section_id, "title": title, "pages": pages}) else: for source, projected in zip(state.rows, result["rows"], strict=False): if source.page: projected["carVisible"] = has_car_control(source.page) if page == "vasm": result["editor"] = editor result["editorRow"] = next((index for index, row in enumerate(state.rows) if row.key == VASM_ANNOTATION), -1) elif page == "pip": result["editor"] = editor result["editorRow"] = next((index for index, row in enumerate(state.rows) if row.key == PIP_EDITOR and row.available), -1) return result def preview(self, view_id: str, index: int, direction: int, token: str, generation: bytes, *, draft: dict | None = None, value: str | int | float | None = None, reset_default: bool = False, recovery_default: bool = False) -> dict: with self.lock: self._clean() view = self.views.get(view_id) if view is None or view.session != _session_key(token) or view.generation != generation or \ type(index) is not int or not 0 <= index < len(view.rows) or type(direction) is not int or direction not in (-1, 0, 1): raise SettingsChanged("Refresh settings") ctx = self._context(view.page) if ctx.cp_raw != view.cp_raw: raise SettingsChanged("Vehicle context changed") state = self._state(view.page, ctx) if state.rows != view.rows: raise SettingsChanged("Saved settings changed") row = state.rows[index] if ((not row.available and not (reset_default and recovery_default and ctx.parked and row_default(row, allow_inactive=True) is not None)) or not (ctx.parked or _onroad_preference(view.page, row.key))): raise SettingsChanged("Settings are unavailable") if draft is not None and not ((view.page == "vasm" and row.key == VASM_ANNOTATION) or (view.page == "pip" and row.key == PIP_EDITOR)): raise SettingsChanged("Unexpected camera region draft") special = (row.key in CONFIRM_ACTIONS or is_long_confirm_action(row.key) or row.key in (PIP_RESET, PIP_EDITOR, SENTRY_RESET, VASM_RESET, VASM_ANNOTATION) or row.key.startswith(PIP_FORMAT_PREFIX)) if reset_default: if direction != 0 or value is not None or draft is not None: raise SettingsChanged("Invalid default reset") request = row_default(row, allow_inactive=recovery_default) if request is None: raise SettingsChanged("Default is unavailable") elif value is not None: if direction != 0 or draft is not None or special or row.repair_value or type(value) not in (str, int, float): raise SettingsChanged("Invalid value selection") if isinstance(value, str) and len(value) > 128: raise SettingsChanged("Invalid value selection") if row.choices and isinstance(value, str) and value in row.choices: selected = value elif row.step > 0 and (not row.choices or "Auto" in row.choices): try: number = float(value) except (ValueError, OverflowError) as error: raise SettingsChanged("Invalid numeric value") from error if not math.isfinite(number) or not row.minimum <= number <= row.maximum: raise SettingsChanged("Value outside setting range") steps = (number - row.minimum) / row.step if not math.isclose(steps, round(steps), rel_tol=0, abs_tol=1e-7): raise SettingsChanged("Value does not match setting step") selected = str(number) elif row.choices: raise SettingsChanged("Unavailable choice") else: raise SettingsChanged("Unavailable value selection") request = FeatureSettingsRequest(row.key, row.source, selected, confirmation=True, related_source=row.related_source, vehicle_fingerprint=row.vehicle_fingerprint, capability=row.capability, dependencies=row.dependencies, display_unit=row.display_unit) elif special: if direction != 0: raise SettingsChanged("Invalid action") value = ("Off" if row.value == "On" else "On") if row.key == SETUP_ACTION else "confirm" if row.key == VASM_ANNOTATION: if draft is None: raise SettingsChanged("Camera regions are required") try: value = json.dumps(draft, sort_keys=True, separators=(",", ":"), allow_nan=False) except (ValueError, TypeError, OverflowError, RecursionError) as error: raise SettingsChanged("Invalid camera regions") from error if validated_annotation(value) is None: raise SettingsChanged("Invalid camera regions") elif row.key == PIP_EDITOR: if draft is None: raise SettingsChanged("Camera crop is required") try: value = json.dumps(draft, sort_keys=True, separators=(",", ":"), allow_nan=False) except (ValueError, TypeError, OverflowError, RecursionError) as error: raise SettingsChanged("Invalid camera crop") from error if validated_editor_draft(value, read_pip(self.params).mask) is None: raise SettingsChanged("Invalid camera crop") request = FeatureSettingsRequest(row.key, row.source, value, confirmation=True, related_source=row.related_source, vehicle_fingerprint=row.vehicle_fingerprint, capability=row.capability, dependencies=row.dependencies, display_unit=row.display_unit) else: if direction == 0: raise SettingsChanged("Invalid action") request = row_change(row, direction) if request is None: raise SettingsChanged("Unavailable choice") request = replace(request, confirmation=True) with self.lock: self._clean() if len(self.intents) >= MAX_INTENTS: self.intents.pop(next(iter(self.intents))) intent_id = secrets.token_urlsafe(24) self.intents[intent_id] = _Intent(_session_key(token), generation, view.page, request, ctx.cp_raw, self.clock() + TTL, row if reset_default else None, recovery_default) question = _question(row, request) if reset_default: question = f"Reset {row.label} to its default? " + question return {"intent": intent_id, "question": question, "proposed": request.value} def confirm(self, intent_id: str, token: str, generation: bytes, *, session_valid: Callable[[], bool] = lambda: True) -> bool: with self.lock: self._clean() intent = self.intents.get(intent_id) if intent is None or intent.session != _session_key(token) or intent.generation != generation: raise SettingsChanged("Confirmation expired") del self.intents[intent_id] ctx = self._context(intent.page) if not session_valid() or not (ctx.parked or _onroad_preference(intent.page, intent.request.key)) or ctx.cp_raw != intent.cp_raw: raise SettingsChanged("Vehicle changed or this operation requires offroad mode") if intent.reset_row is not None: fresh_rows = self._state(intent.page, ctx).rows if intent.reset_row not in fresh_rows or row_default(intent.reset_row, allow_inactive=intent.recovery_default) != intent.request: raise SettingsChanged("Default or saved settings changed") if intent.page == "pip": return self._pip_owner(ctx, live=True, session_valid=session_valid).apply(intent.request) if intent.page == "appearance": return self._appearance_owner(ctx, live=True, session_valid=session_valid).apply(intent.request) if intent.page == "sounds": return self._sounds_owner(ctx, live=True, session_valid=session_valid).apply(intent.request) if intent.page == "display": return self._display_owner(ctx, live=True, session_valid=session_valid).apply(intent.request) if intent.page == "sentry": return self._sentry_owner(ctx, live=True, session_valid=session_valid).apply(intent.request) if intent.page == "vasm": return self._vasm_owner(ctx, live=True, session_valid=session_valid).apply(intent.request) if intent.page == "localization": return self._localization_owner(ctx, live=True, session_valid=session_valid).apply(intent.request) if intent.page == "developer": return self._developer_owner(ctx, live=True, session_valid=session_valid).apply(intent.request) owner = self._owner(ctx, live=True, session_valid=session_valid) return owner.apply(intent.request, reset_default=True) if intent.recovery_default else owner.apply(intent.request)