mirror of
https://github.com/firestar5683/StarPilot.git
synced 2026-10-28 00:53:45 +08:00
a82935f326
Integrate onroad views, configurable widgets, local settings, steering-profile contracts, Bluetooth and Android Auto with their device and runtime services. Co-authored-by: Adeeb Shihadeh <adeebshihadeh@gmail.com> Co-authored-by: Daniel Koepping <elkoled@gmail.com> Co-authored-by: Danny <dannyirwin31@yahoo.com> Co-authored-by: DimitriChrysafis <Dimitri.chrysafis@gmail.com> Co-authored-by: Dom <168790843+firestar5683@users.noreply.github.com> Co-authored-by: Harald Schäfer <harald.the.engineer@gmail.com> Co-authored-by: Prabhaav Pillai <prabhaav@msu.edu> Co-authored-by: Shane Smiskol <shane@smiskol.com> Co-authored-by: dirwin31 <83434411+dirwin31@users.noreply.github.com> Co-authored-by: firestarsdog <229254897+firestarsdog@users.noreply.github.com> Co-authored-by: stef <19478336+stefpi@users.noreply.github.com> Co-authored-by: whoisdomi <dcantu83@gmail.com> Co-authored-by: Intelli <contact@intelli.software> Co-authored-by: pharmacomaniac <blittle65@gmail.com> Co-authored-by: prabhaavp <prabhaav@msu.edu>
139 lines
6.3 KiB
Python
139 lines
6.3 KiB
Python
"""Native display projection of saved choice and selfdrived's actual mode receipt."""
|
|
|
|
from dataclasses import dataclass
|
|
|
|
from openpilot.starpilot.conditional_mode.effective_status import EffectiveModeAck, LIFETIME_NS, observation
|
|
from openpilot.starpilot.conditional_mode.policy import ModeChoice
|
|
from openpilot.starpilot.conditional_mode.preferences import MAX_DOCUMENT_BYTES, PreferenceError, decode_preferences
|
|
from openpilot.starpilot.saved_source import read_saved
|
|
|
|
|
|
@dataclass(frozen=True)
|
|
class ConditionalDisplay:
|
|
choice: ModeChoice
|
|
effective_experimental: bool
|
|
reason: str
|
|
status_code: int
|
|
planner_session: str
|
|
planner_sequence: int
|
|
|
|
|
|
def configured_choice(params) -> ModeChoice | None:
|
|
"""Read saved configuration only; it says nothing about active control."""
|
|
try:
|
|
raw, readable = read_saved(params, 'ConditionalModeConfig', MAX_DOCUMENT_BYTES)
|
|
if not readable:
|
|
return None
|
|
return ModeChoice.CEM if raw is None else decode_preferences(raw).mode
|
|
except (AttributeError, PreferenceError, OSError, TypeError, ValueError):
|
|
return None
|
|
|
|
|
|
class ConditionalDisplayProjector:
|
|
"""Show fresh acknowledgments while the current selfdrive mode agrees."""
|
|
|
|
def __init__(self):
|
|
self.drive_id = 0
|
|
self.session: str | None = None
|
|
self.sequence = 0
|
|
self.last: EffectiveModeAck | None = None
|
|
self.retired: tuple[str, ...] = ()
|
|
|
|
def reset(self) -> None:
|
|
self.drive_id = 0
|
|
self.session = None
|
|
self.sequence = 0
|
|
self.last = None
|
|
self.retired = ()
|
|
|
|
def project(self, state, *, now_ns: int, event_ns: int, selfdrive_ns: int, drive_id: int,
|
|
selfdrive_experimental: bool, selfdrive_enabled: bool, long_active: bool,
|
|
car_valid: bool, system_long: bool) -> ConditionalDisplay | None:
|
|
if type(drive_id) is not int or drive_id <= 0:
|
|
self.reset()
|
|
return None
|
|
if self.drive_id != drive_id:
|
|
self.reset()
|
|
self.drive_id = drive_id
|
|
# A render frame can outlast the control receipt. Its display remains bounded
|
|
# separately; callers issuing commands still use the original receipt expiry.
|
|
display_fresh = (type(event_ns) is int and type(now_ns) is int and
|
|
0 < event_ns <= now_ns <= event_ns + 200_000_000)
|
|
value = observation(state, event_ns) if state is not None and display_fresh else None
|
|
if (value is None or type(event_ns) is not int or event_ns != value.observed_ns or
|
|
type(selfdrive_ns) is not int or not 0 < selfdrive_ns <= now_ns <= selfdrive_ns + 200_000_000 or
|
|
value.selfdrive_state_ns > selfdrive_ns + LIFETIME_NS or
|
|
type(selfdrive_experimental) is not bool or type(selfdrive_enabled) is not bool or
|
|
type(long_active) is not bool or type(car_valid) is not bool or type(system_long) is not bool or
|
|
value.drive_id != drive_id or value.effective_experimental is not selfdrive_experimental):
|
|
return None
|
|
if value.session in self.retired:
|
|
return None
|
|
if self.session is not None and value.session != self.session:
|
|
self.retired = (*self.retired[-7:], self.session)
|
|
self.session = value.session
|
|
self.sequence = value.sequence
|
|
self.last = value
|
|
return None # One new-source barrier after selfdrived restart.
|
|
if value.session == self.session:
|
|
if value.sequence < self.sequence or value.sequence == self.sequence and value != self.last:
|
|
return None
|
|
if value.sequence > self.sequence and self.last is not None and value.observed_ns <= self.last.observed_ns:
|
|
return None
|
|
self.session = value.session
|
|
self.sequence = value.sequence
|
|
self.last = value
|
|
if (not value.accepted or not selfdrive_enabled or not long_active or not car_valid or not system_long or
|
|
value.planner_session is None or value.reason is None):
|
|
return None
|
|
return ConditionalDisplay(value.choice, value.effective_experimental, value.reason, value.status_code,
|
|
value.planner_session, value.planner_sequence)
|
|
|
|
|
|
class ConditionalPerceptionProjector:
|
|
"""Display the planner's current scene without treating it as control approval."""
|
|
|
|
def __init__(self):
|
|
self.drive_id = 0
|
|
self.session = None
|
|
self.sequence = 0
|
|
self.last = None
|
|
self.retired = ()
|
|
|
|
def project(self, state, *, now_ns: int, event_ns: int, drive_id: int,
|
|
choice: ModeChoice | None, lateral_active: bool, selfdrive_enabled: bool,
|
|
car_valid: bool, system_long: bool) -> ConditionalDisplay | None:
|
|
from openpilot.starpilot.conditional_mode.status import observation as scene_observation
|
|
|
|
if type(drive_id) is not int or drive_id <= 0:
|
|
self.__init__()
|
|
return None
|
|
if drive_id != self.drive_id:
|
|
self.__init__()
|
|
self.drive_id = drive_id
|
|
fresh = (type(now_ns) is int and type(event_ns) is int and
|
|
drive_id <= event_ns <= now_ns <= event_ns + 200_000_000)
|
|
# Other planner owners can create the envelope before this scene is sampled.
|
|
# Bound both clocks for presentation; command consumers retain proposal expiry.
|
|
try:
|
|
scene_ns = int(state.conditionalMode.observedMonoTime) if state is not None else 0
|
|
except (AttributeError, TypeError, ValueError, OverflowError):
|
|
scene_ns = 0
|
|
scene_fresh = fresh and drive_id <= scene_ns <= now_ns <= scene_ns + 200_000_000
|
|
value = scene_observation(state, scene_ns) if scene_fresh else None
|
|
if (value is None or value.drive_id != drive_id or
|
|
value.choice is not choice or choice not in (ModeChoice.CEM, ModeChoice.CCM) or
|
|
value.status not in ('proposed', 'inactive_axis') or not car_valid or not system_long or
|
|
not (lateral_active or selfdrive_enabled) or value.session in self.retired):
|
|
return None
|
|
if self.session is not None and value.session != self.session:
|
|
self.retired = (*self.retired[-7:], self.session)
|
|
self.session, self.sequence, self.last = value.session, value.sequence, value
|
|
return None
|
|
if value.session == self.session and (
|
|
value.sequence < self.sequence or value.sequence == self.sequence and value != self.last or
|
|
value.sequence > self.sequence and self.last is not None and value.observed_ns <= self.last.observed_ns):
|
|
return None
|
|
self.session, self.sequence, self.last = value.session, value.sequence, value
|
|
return ConditionalDisplay(value.choice, False, value.reason, value.status_code, value.session, value.sequence)
|