Auto Print Population

This commit is contained in:
firestar5683
2026-10-16 13:09:27 -05:00
parent 4fa668d81a
commit e62a51821d
4 changed files with 151 additions and 3 deletions
+1 -1
View File
@@ -18,7 +18,7 @@ class EvidenceSnapshot(SimpleNamespace):
class EvidenceSource:
SERVICES = ('deviceState', 'pandaStates', 'carState', 'selfdriveState')
SERVICES = ('deviceState', 'pandaStates', 'carState', 'selfdriveState', 'carParams')
def __init__(self, messages=None, *, mono=time.monotonic_ns,
boot=lambda: time.clock_gettime_ns(getattr(time, 'CLOCK_BOOTTIME', time.CLOCK_MONOTONIC))):
+26 -1
View File
@@ -133,6 +133,7 @@ class LiveContextSource:
"""Samples registered Params and the two actual publisher clock domains."""
MAX_CP_BYTES = 1_048_576
MAX_LIVE_CP_AGE_NS = 100_000_000_000 # Card republishes finalized parameters every 50 seconds.
def __init__(self, params, messages=None, *, mono_clock=time.monotonic_ns, boot_clock=boot_time_ns,
evidence_wait_ms=750, wait_clock=time.monotonic, borrowed_messages=False):
@@ -159,7 +160,7 @@ class LiveContextSource:
return None
if self.messages is None:
from openpilot.cereal import messaging
self.messages = messaging.SubMaster(["deviceState", "pandaStates", "carState", "selfdriveState"])
self.messages = messaging.SubMaster(["deviceState", "pandaStates", "carState", "selfdriveState", "carParams"])
return cast(Callable[[], object], self.messages.snapshot)() if hasattr(self.messages, "snapshot") else self.messages
def close(self) -> None:
@@ -280,6 +281,30 @@ class LiveContextSource:
return False
def runtime_vehicle(self):
try:
with self.lock:
if self.closed:
return None, None
sm = self._messages() if self.messages is not None else None
if sm is not None:
if not self.borrowed_messages and not hasattr(self.messages, 'snapshot'):
sm.update(0)
now = self.mono_clock()
stamp = int(sm.logMonoTime.get('carParams', 0))
floor = max(self.device_after_mono_ns, getattr(sm, 'after_mono_ns', 0))
if (sm.seen.get('carParams', False) and sm.valid.get('carParams', False) and sm.alive.get('carParams', False) and
floor < stamp <= now and now - stamp <= self.MAX_LIVE_CP_AGE_NS):
from opendbc.car.values import PLATFORMS
from opendbc.car.mock.values import CAR as MOCK
from opendbc.car.structs import car
cp = sm['carParams']
if (cp.schema.node.id == car.CarParams.schema.node.id and not cp.notCar and
cp.carFingerprint in PLATFORMS and cp.carFingerprint != MOCK.MOCK):
raw = cp.as_builder().to_bytes()
if len(raw) <= self.MAX_CP_BYTES:
return cp, b'live-vehicle:' + raw
except (OSError, RuntimeError, AttributeError, KeyError, TypeError, ValueError, OverflowError):
pass
cp_raw, readable = read_saved(self.params, "CarParamsPersistent", self.MAX_CP_BYTES)
cp = None
if readable and cp_raw is not None:
@@ -0,0 +1,123 @@
"""Detected vehicle telemetry populates Galaxy without changing Auto selection."""
import json
import tempfile
from pathlib import Path
from unittest.mock import patch
import pytest
from opendbc.car import gen_empty_fingerprint
from opendbc.car.car_helpers import interfaces
from openpilot.cereal import messaging
from openpilot.common.params import Params
from openpilot.common.prefix import OpenpilotPrefix
from openpilot.starpilot.galaxy.settings import LiveContextSource, SettingsChanged, SettingsGateway
from openpilot.starpilot.schema_cache import put_cache
from openpilot.starpilot.vehicle_selection import encode
@pytest.fixture
def detected():
with OpenpilotPrefix(), tempfile.TemporaryDirectory() as directory:
params = Params(directory)
params.put_bool('OpenpilotEnabledToggle', True, block=True)
now = [100_000_000_000]
sm = messaging.SubMaster(['deviceState', 'pandaStates', 'carState', 'selfdriveState', 'carParams'])
source = LiveContextSource(params, messages=sm, borrowed_messages=True, mono_clock=lambda: now[0])
gateway = SettingsGateway(params, source)
with patch.object(source, 'offroad', return_value=True):
yield params, now, sm, source, gateway
gateway.close()
def publish(detected, identity='HYUNDAI_IONIQ_6', *, valid=True, stamp=None):
_, now, sm, _, _ = detected
cp = interfaces[identity].get_params(identity, gen_empty_fingerprint(), [], False, False, False)
cp.fingerprintSource = 'can'
now[0] += 1_000_000_000
event = messaging.new_message('carParams', valid=valid, logMonoTime=now[0] if stamp is None else stamp)
event.carParams = cp
sm.update_msgs(now[0] / 1e9, [event.as_reader()])
return cp
@pytest.mark.parametrize('identity', ['HYUNDAI_IONIQ_6', 'CHEVROLET_VOLT_ASCM', 'TOYOTA_COROLLA_TSS2'])
def test_actual_gateway_auto_exposes_learning_and_same_vehicle_fields_as_manual(detected, identity):
params, _, _, source, gateway = detected
assert source.sample().cp is None
cp = publish(detected, identity)
before = cp.to_dict()
pages = {name: gateway.page(name, 'session', b'generation') for name in ('torque', 'vehicle')}
learning = next(row for row in pages['torque']['rows'] if row['label'] == 'Use StarPilot Auto Learn')
assert learning['available']
params.put('VehicleSelection', json.loads(encode(identity)), block=True)
for name, automatic in pages.items():
manual = gateway.page(name, 'session', b'generation')
assert [(r['label'], r['available'], r['value']) for r in automatic['rows']] == [
(r['label'], r['available'], r['value']) for r in manual['rows']]
assert cp.to_dict() == before
for key in ('CarParams', 'CarParamsPersistent', 'CarParamsCache'):
assert not Path(params.get_param_path(key)).exists()
def test_auto_learning_can_be_saved_and_republication_does_not_reset_context(detected):
params, _, _, source, gateway = detected
publish(detected)
token = source.sample().cp_raw
page = gateway.page('torque', 'session', b'generation')
index = next(i for i, row in enumerate(page['rows']) if row['label'] == 'Use StarPilot Auto Learn')
intent = gateway.preview(page['view'], index, 0, 'session', b'generation', value='Off')
publish(detected)
assert source.sample().cp_raw == token
assert gateway.confirm(intent['intent'], 'session', b'generation')
saved = gateway.page('torque', 'session', b'generation')
assert next(row for row in saved['rows'] if row['label'] == 'Use StarPilot Auto Learn')['value'] == 'Off'
assert params.get('VehicleSelection') is None
def test_new_detected_vehicle_invalidates_previous_vehicle_edit(detected):
_, _, _, _, gateway = detected
publish(detected)
page = gateway.page('torque', 'session', b'generation')
index = next(i for i, row in enumerate(page['rows']) if row['label'] == 'Use StarPilot Auto Learn')
intent = gateway.preview(page['view'], index, 0, 'session', b'generation', value='Off')
publish(detected, 'TOYOTA_COROLLA_TSS2')
with pytest.raises(SettingsChanged):
gateway.confirm(intent['intent'], 'session', b'generation')
@pytest.mark.parametrize('variation', ['invalid', 'future', 'old', 'stale', 'not_seen', 'not_alive', 'oversized', 'reset'])
def test_unusable_live_vehicle_falls_back_to_verified_cache(detected, variation):
params, now, sm, source, _ = detected
cached = interfaces['TOYOTA_COROLLA_TSS2'].get_params('TOYOTA_COROLLA_TSS2', gen_empty_fingerprint(), [], False, False, False)
put_cache(params, 'CarParamsPersistent', cached, block=True)
before = Path(params.get_param_path('CarParamsPersistent')).read_bytes()
publish(detected)
if variation == 'invalid':
sm.valid['carParams'] = False
elif variation == 'future':
sm.logMonoTime['carParams'] = now[0] + 1
elif variation == 'old':
sm.logMonoTime['carParams'] = source.device_after_mono_ns
elif variation == 'stale':
now[0] += source.MAX_LIVE_CP_AGE_NS + 1
elif variation == 'not_seen':
sm.seen['carParams'] = False
elif variation == 'not_alive':
sm.alive['carParams'] = False
elif variation == 'oversized':
source.MAX_CP_BYTES = 1
assert source.runtime_vehicle()[0] is None
return
elif variation == 'reset':
sm.after_mono_ns = now[0]
cp, _ = source.runtime_vehicle()
assert cp.carFingerprint == cached.carFingerprint
assert Path(params.get_param_path('CarParamsPersistent')).read_bytes() == before
def test_explicit_manual_choice_remains_an_offroad_override(detected):
params, _, _, source, _ = detected
publish(detected)
params.put('VehicleSelection', json.loads(encode('TOYOTA_COROLLA_TSS2')), block=True)
assert source.runtime_vehicle()[0].carFingerprint == 'HYUNDAI_IONIQ_6'
assert source.sample().cp.carFingerprint == 'TOYOTA_COROLLA_TSS2'
@@ -27,7 +27,7 @@ class Messages:
self.data = {'deviceState': NS(started=started),
'pandaStates': [NS(pandaType='uno', ignitionLine=ignition, ignitionCan=False, controlsAllowed=False, safetyModel='noOutput')],
'carState': NS(canValid=True, canTimeout=False, standstill=True, gearShifter='park'),
'selfdriveState': NS(enabled=False, active=False)}
'selfdriveState': NS(enabled=False, active=False), 'carParams': NS(notCar=True)}
self.seen = self.alive = self.valid = dict.fromkeys(EvidenceSource.SERVICES, True)
self.updated = dict(self.seen)
self.logMonoTime = {name: boot if name == 'pandaStates' else mono for name in EvidenceSource.SERVICES}