mirror of
https://github.com/firestar5683/StarPilot.git
synced 2026-11-08 05:23:41 +08:00
134 lines
5.8 KiB
Python
134 lines
5.8 KiB
Python
"""Reviewed saved-setting mapping for the opt-in system-longitudinal SLC runtime."""
|
|
|
|
import math
|
|
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, ...] = ()
|
|
# Preserve the validated saved choice for same-drive advisory consumers.
|
|
fallback_choice: int = 2
|
|
# Saved intent must still retire pending work when vehicle capabilities disable control.
|
|
requested: bool = False
|
|
|
|
|
|
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 _number(values: Mapping[str, object], key: str, default: float = 0.0) -> float:
|
|
value = values.get(key, default)
|
|
if (type(value) is not int and type(value) is not float) or not math.isfinite(value):
|
|
raise ValueError(f"{key} must be finite")
|
|
return float(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 None
|
|
if document is od.NEEDS_REVIEW:
|
|
raise ValueError('saved SLC offsets need review')
|
|
metric = _bool(values, "IsMetric") if document is None else False # presentation units do not own adopted control
|
|
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])
|
|
fallback = values.get("SLCFallback", 2)
|
|
override = values.get("SLCOverride", 1)
|
|
if type(fallback) is not int or fallback not in (0, 1, 2):
|
|
raise ValueError("invalid saved SLC fallback")
|
|
if type(override) is not int or override not in (0, 1, 2):
|
|
raise ValueError("invalid saved SLC override")
|
|
# Only accepted-history fallback belongs to this control domain. Saved set
|
|
# speed and experimental fallback choices never create a source observation.
|
|
confirmation = _bool(values, "SLCConfirmation")
|
|
policy = acc.Policy(confirm_lower=confirmation, confirm_higher=confirmation,
|
|
fallback_previous=fallback == 2, display_only=not control)
|
|
offsets = (document if isinstance(document, od.OffsetDocument) else
|
|
od.adopt_legacy(metric, tuple(_number(values, f"Offset{i + 1}") for i in range(7)))).schedule()
|
|
# SLCOverride remains a validated legacy preference; only set-speed changes override SLC.
|
|
return Settings(control, display, sel.SelectionPolicy(mode, slots, False), policy, offsets,
|
|
fallback_choice=fallback, requested=control)
|
|
except (ValueError, OverflowError) as error:
|
|
return _disabled(str(error))
|
|
|
|
|
|
SAVED_KEYS = ("SpeedLimitController", "ShowSpeedLimits", "IsMetric", "SLCPriority1", "SLCPriority2",
|
|
"SLCFallback", "SLCOverride", "SLCConfirmation",
|
|
*(f"Offset{i}" for i in range(1, 8)))
|
|
|
|
|
|
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' or key.startswith('Offset')):
|
|
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:
|
|
raw = Path(params.get_param_path(key)).read_bytes()
|
|
if key.startswith('Offset'):
|
|
value = float(raw)
|
|
elif key in ('SLCFallback', '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):
|
|
return _disabled(f'Invalid saved {key}')
|
|
return parse(values)
|