Files
StarPilot/openpilot/selfdrive/car/tests/test_carparams_first_publish.py
T
2026-10-19 17:24:05 -05:00

123 lines
5.4 KiB
Python

"""The first finalized CarParams must reach manager before periodic logging."""
import tempfile
import unittest
from types import SimpleNamespace
from unittest.mock import patch
from opendbc.car import DT_CTRL
from opendbc.car.hyundai.ioniq6_handoff import prepare_ioniq6_long_candidate
from opendbc.car.structs import car
from openpilot.cereal import messaging
from openpilot.common.params import Params
from openpilot.selfdrive.car.card import Car
from openpilot.selfdrive.car.tests.publication_fixture import initialize_publication_sources
from openpilot.selfdrive.car.cruise import VCruiseHelper
from openpilot.selfdrive.car.tests.test_hyundai_aol import candidate
from openpilot.system.manager.process_config import vision_slc_development
class Publisher:
def __init__(self):
self.messages: list[tuple[str, bytes]] = []
def send(self, service, message):
self.messages.append((service, message.to_bytes()))
def car_params(self):
return [messaging.log_from_bytes(payload).carParams for service, payload in self.messages
if service == 'carParams']
def card_for(test: unittest.TestCase, cp, publisher, frame):
card = Car.__new__(Car)
card.CP = cp
card.pm = publisher
test.enterContext(patch.object(card, 'sm', SimpleNamespace(frame=frame, all_checks=lambda _: True), create=True))
card.slc_replay = False
card.curve_replay = False
card.conditional_replay = False
card.aol_replay = False
card.last_actuators_output = car.CarControl.Actuators()
card.can_rcv_cum_timeout_counter = 0
test.enterContext(patch.object(card, 'rk', SimpleNamespace(remaining=0.0), create=True))
card.v_cruise_helper = VCruiseHelper(cp)
card.slc_receipts = []
initialize_publication_sources(card)
return card
class CarParamsFirstPublishTest(unittest.TestCase):
def test_ioniq_prearm_sample_does_not_delay_final_long_cp_or_vision(self):
stock, long_cp = candidate(False)
publisher = Publisher()
card = card_for(self, long_cp, publisher, 1)
sm = messaging.SubMaster(['carControl'])
sm.update_msgs(1.0, []) # Pre-create ignition sample consumes frame 0.
sm.update_msgs(1.01, []) # First normal Card update advances to frame 1.
self.assertEqual(sm.frame, 1)
self.enterContext(patch.object(card, 'sm', sm))
state = car.CarState(canValid=True)
with tempfile.TemporaryDirectory() as directory, patch.dict('os.environ',
{'SLC_REPLAY_RUNTIME': '0', 'SLC_VISION_DEVELOPMENT': '0'}):
params = Params(directory)
params.put_bool('SpeedLimitController', True, block=True)
params.put('SLCPriority1', 'Vision', block=True)
self.assertFalse(vision_slc_development(True, params, car.CarParams.new_message()))
# Display-only vision follows finalized vehicle capability, including stock longitudinal.
self.assertTrue(vision_slc_development(True, params, stock))
self.assertFalse(vision_slc_development(False, params, stock))
for flag in ('notCar', 'dashcamOnly', 'passive'):
unsupported = car.CarParams.new_message(**stock.to_dict())
setattr(unsupported, flag, True)
self.assertFalse(vision_slc_development(True, params, unsupported))
params.put_bool('SpeedLimitController', False, block=True)
self.assertTrue(vision_slc_development(True, params, stock)) # Display still requests vision.
params.put_bool('ShowSpeedLimits', False, block=True)
self.assertFalse(vision_slc_development(True, params, stock))
params.put_bool('ShowSpeedLimits', True, block=True)
params.put_bool('SpeedLimitController', True, block=True)
Car.state_publish(card, state, None)
published = publisher.car_params()
self.assertEqual(len(published), 1)
self.assertEqual(int(published[0].safetyConfigs[0].safetyParam),
int(long_cp.safetyConfigs[0].safetyParam))
self.assertTrue(vision_slc_development(True, params, published[0]))
for frame in (2, int(50. / DT_CTRL) - 1):
card.sm.frame = frame
Car.state_publish(card, state, None)
self.assertEqual(len(publisher.car_params()), 1)
card.sm.frame = int(50. / DT_CTRL)
Car.state_publish(card, state, None)
self.assertEqual(len(publisher.car_params()), 2)
def test_failed_prearm_publishes_only_final_stock_cp(self):
stock, abandoned_long_cp = candidate(False)
selected, pending = prepare_ioniq6_long_candidate(stock, abandoned_long_cp, enabled=False, is_release=False)
self.assertFalse(pending)
publisher = Publisher()
card = card_for(self, selected, publisher, 1)
Car.state_publish(card, car.CarState(canValid=True), None)
published = publisher.car_params()
self.assertEqual(len(published), 1)
self.assertEqual(int(published[0].safetyConfigs[0].safetyParam),
int(stock.safetyConfigs[0].safetyParam))
self.assertNotEqual(int(published[0].safetyConfigs[0].safetyParam),
int(abandoned_long_cp.safetyConfigs[0].safetyParam))
def test_ordinary_first_frame_does_not_duplicate_publication(self):
stock, _ = candidate(False)
publisher = Publisher()
card = card_for(self, stock, publisher, 0)
Car.state_publish(card, car.CarState(canValid=True), None)
card.sm.frame = 1
Car.state_publish(card, car.CarState(canValid=True), None)
self.assertEqual(len(publisher.car_params()), 1)
if __name__ == '__main__':
unittest.main()