mirror of
https://github.com/firestar5683/StarPilot.git
synced 2026-11-06 12:33:51 +08:00
89 lines
3.2 KiB
Python
89 lines
3.2 KiB
Python
"""Vehicle-scoped user choice, independent of learning compatibility and results."""
|
|
|
|
from dataclasses import dataclass
|
|
import json
|
|
|
|
from openpilot.starpilot.saved_source import read_saved
|
|
|
|
KEY = 'SteeringLearningPreferences'
|
|
MAX_BYTES = 4096
|
|
LEGACY_KEY = 'ForceAutoTuneOff'
|
|
|
|
|
|
@dataclass(frozen=True)
|
|
class LearningPreference:
|
|
enabled: bool
|
|
valid: bool
|
|
source: str
|
|
raw: bytes | None
|
|
|
|
|
|
def _identity(CP):
|
|
brand, fingerprint = str(CP.brand), str(CP.carFingerprint)
|
|
if not brand or not fingerprint or len(brand) > 64 or len(fingerprint) > 256:
|
|
raise ValueError('Invalid vehicle identity')
|
|
return brand, fingerprint
|
|
|
|
|
|
def _unique(pairs):
|
|
result = {}
|
|
for key, value in pairs:
|
|
if key in result:
|
|
raise ValueError('Duplicate field')
|
|
result[key] = value
|
|
return result
|
|
|
|
|
|
def _decode(raw):
|
|
if raw is None:
|
|
return {'version': 1, 'vehicles': {}}
|
|
if not raw or len(raw) > MAX_BYTES:
|
|
raise ValueError('Invalid preference size')
|
|
try:
|
|
document = json.loads(raw, object_pairs_hook=_unique)
|
|
except RecursionError as error:
|
|
raise ValueError('Preference nesting exceeds parser limits') from error
|
|
if (not isinstance(document, dict) or set(document) != {'version', 'vehicles'} or
|
|
type(document['version']) is not int or document['version'] != 1 or
|
|
not isinstance(document['vehicles'], dict)):
|
|
raise ValueError('Invalid preference document')
|
|
count = 0
|
|
for brand, vehicles in document['vehicles'].items():
|
|
if not brand or len(brand) > 64 or not isinstance(vehicles, dict):
|
|
raise ValueError('Invalid preference brand')
|
|
for fingerprint, enabled in vehicles.items():
|
|
if not fingerprint or len(fingerprint) > 256 or type(enabled) is not bool:
|
|
raise ValueError('Invalid vehicle preference')
|
|
count += 1
|
|
if count > 64:
|
|
raise ValueError('Too many vehicle preferences')
|
|
return document
|
|
|
|
|
|
def read_preference(params, CP, *, legacy_snapshot=None, preference_snapshot=None) -> LearningPreference:
|
|
raw, readable = read_saved(params, KEY, MAX_BYTES) if preference_snapshot is None else preference_snapshot
|
|
try:
|
|
if not readable:
|
|
raise ValueError('Unreadable preferences')
|
|
brand, fingerprint = _identity(CP)
|
|
vehicles = _decode(raw)['vehicles']
|
|
if fingerprint in vehicles.get(brand, {}):
|
|
return LearningPreference(vehicles[brand][fingerprint], True, 'scope', raw)
|
|
legacy, readable = read_saved(params, LEGACY_KEY, 1) if legacy_snapshot is None else legacy_snapshot
|
|
if not readable or legacy not in (None, b'0', b'1'):
|
|
raise ValueError('Invalid legacy preference')
|
|
return LearningPreference(legacy != b'1', True, 'default' if legacy is None else 'legacy', raw)
|
|
except (AttributeError, TypeError, ValueError, UnicodeError):
|
|
return LearningPreference(False, False, 'invalid', raw)
|
|
|
|
|
|
def replace_preference(raw: bytes | None, CP, enabled: bool) -> bytes:
|
|
if type(enabled) is not bool:
|
|
raise ValueError('Invalid preference')
|
|
brand, fingerprint = _identity(CP)
|
|
document = _decode(raw)
|
|
document['vehicles'].setdefault(brand, {})[fingerprint] = enabled
|
|
encoded = json.dumps(document, sort_keys=True, separators=(',', ':'), allow_nan=False).encode()
|
|
_decode(encoded)
|
|
return encoded
|