mirror of
https://github.com/firestar5683/StarPilot.git
synced 2026-11-08 05:23:41 +08:00
7bce4121e5
Use one saved schedule with editable zero defaults. Preserve current schedules and round displayed offsets and edits to whole units without conversion drift.
127 lines
5.3 KiB
Python
127 lines
5.3 KiB
Python
"""Reviewed saved-setting mapping for the opt-in system-longitudinal SLC runtime."""
|
|
|
|
from collections.abc import Mapping
|
|
from dataclasses import dataclass
|
|
from pathlib import Path
|
|
|
|
from openpilot.starpilot.speed_limits import acceptance as acc
|
|
from openpilot.starpilot.speed_limits import offset_document as od
|
|
from openpilot.starpilot.speed_limits import selection as sel
|
|
from openpilot.starpilot.speed_limits import speed_domain as sd
|
|
|
|
MPH_TO_MPS = od.MPH_TO_MPS
|
|
KPH_TO_MPS = od.KPH_TO_MPS
|
|
IMPERIAL_BOUNDS = od.IMPERIAL_BOUNDS
|
|
METRIC_BOUNDS = od.METRIC_BOUNDS
|
|
LABELS = {"Dashboard": sel.Source.DASHBOARD, "Map Data": sel.Source.MAP, "Maps": sel.Source.MAP, "Vision": sel.Source.VISION}
|
|
|
|
|
|
@dataclass(frozen=True)
|
|
class Settings:
|
|
enabled: bool
|
|
display: bool
|
|
selection: sel.SelectionPolicy
|
|
acceptance: acc.Policy
|
|
offsets: sd.OffsetSchedule
|
|
errors: tuple[str, ...] = ()
|
|
# Saved intent must still retire pending work when vehicle capabilities disable control.
|
|
requested: bool = False
|
|
advance_seconds: int = 0
|
|
|
|
|
|
def _bool(values: Mapping[str, object], key: str, default: bool = False) -> bool:
|
|
value = values.get(key, default)
|
|
if type(value) is not bool:
|
|
raise ValueError(f"{key} must be Boolean")
|
|
return value
|
|
|
|
|
|
def _disabled(reason: str) -> Settings:
|
|
selection = sel.SelectionPolicy(sel.SelectionMode.ORDERED, (sel.Source.DASHBOARD, sel.Source.MAP), False)
|
|
return Settings(False, False, selection, acc.Policy(display_only=True),
|
|
sd.OffsetSchedule((sd.OffsetBand(0.0, None, 0.0),)), (reason,))
|
|
|
|
|
|
def parse(values: Mapping[str, object]) -> Settings:
|
|
"""Translate saved labels without changing their meaning or enabling unsupported output."""
|
|
try:
|
|
control = _bool(values, "SpeedLimitController")
|
|
display = control or _bool(values, "ShowSpeedLimits", True)
|
|
document = (od.validate(values['SLCOffsetSchedule']) if 'SLCOffsetSchedule' in values else
|
|
od.default_document(_bool(values, 'IsMetric')))
|
|
if not isinstance(document, od.OffsetDocument):
|
|
raise ValueError('saved SLC offsets need review')
|
|
first = values.get("SLCPriority1", "Vision")
|
|
second = values.get("SLCPriority2", "Dashboard")
|
|
if type(first) is not str or type(second) is not str:
|
|
raise ValueError("invalid saved SLC source priority")
|
|
mode = {"Highest": sel.SelectionMode.HIGHEST, "Lowest": sel.SelectionMode.LOWEST}.get(first, sel.SelectionMode.ORDERED)
|
|
if first not in (*LABELS, "Highest", "Lowest") or second not in LABELS:
|
|
raise ValueError("invalid saved SLC source priority")
|
|
slots = (LABELS.get(first), LABELS[second])
|
|
override = values.get("SLCOverride", 1)
|
|
if type(override) is not int or override not in (0, 1, 2):
|
|
raise ValueError("invalid saved SLC override")
|
|
confirmation = _bool(values, "SLCConfirmation")
|
|
advance = values.get('SLCAdvanceSeconds', 0)
|
|
if type(advance) is not int or not 0 <= advance <= 10:
|
|
advance = 0 # An invalid optional timer cannot enable early adoption.
|
|
policy = acc.Policy(confirm_lower=confirmation, confirm_higher=confirmation,
|
|
display_only=not control)
|
|
offsets = document.schedule()
|
|
# SLCOverride remains a validated legacy preference; only set-speed changes override SLC.
|
|
return Settings(control, display, sel.SelectionPolicy(mode, slots, False), policy, offsets,
|
|
requested=control, advance_seconds=advance)
|
|
except (ValueError, OverflowError) as error:
|
|
return _disabled(str(error))
|
|
|
|
|
|
SAVED_KEYS = ("SpeedLimitController", "ShowSpeedLimits", "IsMetric", "SLCPriority1", "SLCPriority2",
|
|
"SLCOverride", "SLCConfirmation", "SLCAdvanceSeconds")
|
|
|
|
|
|
def read_params(params) -> Settings:
|
|
values = {}
|
|
try:
|
|
with Path(params.get_param_path('SLCOffsetSchedule')).open('rb') as stream:
|
|
raw_document = stream.read(od.MAX_DOCUMENT_BYTES + 1)
|
|
except FileNotFoundError:
|
|
raw_document = None
|
|
except OSError:
|
|
return _disabled('Invalid saved SLCOffsetSchedule')
|
|
if raw_document is not None:
|
|
try:
|
|
values['SLCOffsetSchedule'] = od.decode(raw_document)
|
|
except (ValueError, OverflowError):
|
|
return _disabled('Invalid saved SLCOffsetSchedule')
|
|
for key in SAVED_KEYS:
|
|
if raw_document is not None and key == 'IsMetric':
|
|
continue
|
|
# Typed Params reads turn malformed numbers/strings into absence and accept
|
|
# any non-"1" Boolean as false. Neither may silently alter confirmation or
|
|
# units. Decode the saved bytes explicitly; only an absent file uses defaults.
|
|
try:
|
|
with Path(params.get_param_path(key)).open('rb') as stream:
|
|
raw = stream.read(8 if key == 'SLCAdvanceSeconds' else -1)
|
|
if key == 'SLCAdvanceSeconds':
|
|
if raw not in tuple(str(seconds).encode() for seconds in range(11)):
|
|
raise ValueError('invalid advance time')
|
|
value = int(raw)
|
|
elif key == 'SLCOverride':
|
|
value = int(raw)
|
|
elif key in ('SLCPriority1', 'SLCPriority2'):
|
|
value = raw.decode('utf-8')
|
|
else:
|
|
if raw not in (b'0', b'1'):
|
|
raise ValueError('invalid Boolean')
|
|
value = raw == b'1'
|
|
values[key] = value
|
|
except FileNotFoundError:
|
|
continue
|
|
except (OSError, ValueError, OverflowError):
|
|
if key == 'SLCAdvanceSeconds':
|
|
values[key] = 0
|
|
continue
|
|
return _disabled(f'Invalid saved {key}')
|
|
return parse(values)
|