Files
StarPilot/openpilot/starpilot/conditional_mode/status.py
T
firestar5683 d8708b65c7 CEM
Implement conditional driving modes with explicit scene, button and traffic state ownership.

Co-authored-by: Dom <168790843+firestar5683@users.noreply.github.com>
Co-authored-by: Harald Schäfer <harald.the.engineer@gmail.com>
Co-authored-by: whoisdomi <dcantu83@gmail.com>
2026-10-14 20:02:39 -05:00

170 lines
6.0 KiB
Python

"""Versioned conditional-mode proposals carried by the sole planner publisher."""
from dataclasses import dataclass
import hashlib
import re
import secrets
from openpilot.cereal import messaging
from openpilot.starpilot.conditional_mode.host import HostProposal
from openpilot.starpilot.conditional_mode.policy import ModeChoice
from openpilot.starpilot.conditional_mode.runtime_settings import (
DocumentState,
SafeModeState,
SettingsSnapshot,
)
# Proposals reach selfdrived 30-70 ms after observation; 100 ms expired most before their successor arrived.
LIFETIME_NS = 150_000_000
MODEL_MAX_AGE_NS = 150_000_000
CHOICES = {ModeChoice.STOCK: 'stock', ModeChoice.CEM: 'conditionalExperimental', ModeChoice.CCM: 'conditionalChill'}
_HEX = re.compile(r'[0-9a-f]{64}\Z')
def settings_fingerprint(snapshot: SettingsSnapshot | None, *, owner=None) -> str | None:
if owner is not None:
from openpilot.starpilot.conditional_mode.card_settings import CardSettingsView
if type(owner) is CardSettingsView:
return owner.fingerprint(snapshot)
return _settings_fingerprint(snapshot)
def _settings_fingerprint(snapshot: SettingsSnapshot | None) -> str | None:
"""Bind two independent readers to the exact saved document and SafeMode."""
if (
snapshot is None
or snapshot.document_state not in (DocumentState.ABSENT, DocumentState.VALID)
or snapshot.safe_mode_state not in (SafeModeState.ABSENT_FALSE, SafeModeState.FALSE)
):
return None
digest = hashlib.sha256(b'StarPilot conditional preferences v1\0')
for raw in (snapshot.document_raw, snapshot.safe_mode_raw):
# Absence differs from a present empty file and from an explicit default.
digest.update(b'absent\0' if raw is None else len(raw).to_bytes(4, 'big') + raw)
return digest.hexdigest()
@dataclass(frozen=True)
class ModeObservation:
session: str
sequence: int
observed_ns: int
expires_ns: int
drive_id: int
model_ns: int
car_state_ns: int
settings_revision: int
fingerprint: str
choice: ModeChoice
override: bool | None
status: str
reason: str
status_code: int
class StatusPublisher:
def __init__(self):
self.session = secrets.token_hex(16)
self.sequence = 0
def attach(self, event, proposal: HostProposal, snapshot: SettingsSnapshot | None, *, now_ns: int, drive_id: int, model_ns: int, car_state_ns: int):
"""Attach a proposal without granting or changing native control authority."""
if event is None:
event = messaging.new_message('slcState')
event.valid = True
event.logMonoTime = now_ns
self.sequence += 1
fingerprint = settings_fingerprint(snapshot)
decision = proposal.decision
authority = proposal.projected.authority if proposal.projected is not None else None
permitted = (
fingerprint is not None
and proposal.status == 'proposed'
and decision is not None
and snapshot is not None
and proposal.settings_revision == snapshot.revision
and decision.qualified
and authority is not None
and authority.fresh
and authority.system_long_capable
and not authority.safe_mode
and authority.driving_enabled
and authority.long_active
and type(proposal.override_experimental) is bool
and proposal.override_experimental == decision.requested_experimental
and (proposal.choice in (ModeChoice.CEM, ModeChoice.CCM) or
proposal.choice is ModeChoice.STOCK and proposal.projected.scene.committed_stop is True and
decision.reason.value == 'cem_stop' and proposal.override_experimental is True)
)
event.slcState.conditionalMode = {
'version': 1,
'sessionId': self.session,
'sequence': self.sequence,
'observedMonoTime': now_ns,
'validUntilMonoTime': now_ns + LIFETIME_NS,
'driveStartMonoTime': drive_id,
'modelMonoTime': model_ns,
'carStateMonoTime': car_state_ns,
'settingsRevision': snapshot.revision if snapshot is not None else 0,
'settingsFingerprint': fingerprint or '',
'choice': CHOICES[proposal.choice],
'hasOverride': permitted,
'experimental': proposal.override_experimental if permitted else False,
'status': proposal.status,
'reason': decision.reason.value if decision is not None else '',
'statusCode': decision.status_code if decision is not None else 0,
}
return event
def observation(state, now_ns: int) -> ModeObservation | None:
"""Nested freshness is independent of the outer SLC availability flag."""
try:
value = state.conditionalMode
stamp, expiry = int(value.observedMonoTime), int(value.validUntilMonoTime)
model, car_state, drive = int(value.modelMonoTime), int(value.carStateMonoTime), int(value.driveStartMonoTime)
session, fingerprint = str(value.sessionId), str(value.settingsFingerprint)
choice = {wire: choice for choice, wire in CHOICES.items()}[str(value.choice)]
status, reason = str(value.status), str(value.reason)
if (
type(now_ns) is not int
or value.version != 1
or not re.fullmatch(r'[0-9a-f]{32}', session)
or value.sequence <= 0
or not 0 < stamp <= now_ns <= expiry
or not 0 < expiry - stamp <= LIFETIME_NS
or not 0 < drive <= model <= stamp
or not drive <= car_state <= stamp
or stamp - model > MODEL_MAX_AGE_NS
or stamp - car_state > LIFETIME_NS
or value.settingsRevision <= 0
or not _HEX.fullmatch(fingerprint)
or len(status) > 64
or len(reason) > 64
or value.statusCode > 255
or value.hasOverride
and (status != 'proposed' or choice is ModeChoice.STOCK and
(reason != 'cem_stop' or value.statusCode != 8 or not value.experimental))
):
return None
return ModeObservation(
session,
int(value.sequence),
stamp,
expiry,
drive,
model,
car_state,
int(value.settingsRevision),
fingerprint,
choice,
bool(value.experimental) if value.hasOverride else None,
status,
reason,
int(value.statusCode),
)
except (AttributeError, KeyError, TypeError, ValueError, OverflowError):
return None