Files
StarPilot/openpilot/starpilot/aol/tests/test_ioniq_native_dispatch.py
T
firestar5683 80fbb4f6db Feature registration
Register vehicle controls, planner modes, UI actions and Galaxy services after their providers exist. Wire the shared runtime entrypoints and cover their combined behavior.

Co-authored-by: Adeeb Shihadeh <adeebshihadeh@gmail.com>
Co-authored-by: Daniel Koepping <elkoled@gmail.com>
Co-authored-by: Danny <dannyirwin31@yahoo.com>
Co-authored-by: DimitriChrysafis <Dimitri.chrysafis@gmail.com>
Co-authored-by: Dom <168790843+firestar5683@users.noreply.github.com>
Co-authored-by: Harald Schäfer <harald.the.engineer@gmail.com>
Co-authored-by: Kacper Rączy <gfw.kra@gmail.com>
Co-authored-by: Prabhaav Pillai <prabhaav@msu.edu>
Co-authored-by: dirwin31 <83434411+dirwin31@users.noreply.github.com>
Co-authored-by: firestarsdog <229254897+firestarsdog@users.noreply.github.com>
Co-authored-by: stef <19478336+stefpi@users.noreply.github.com>
Co-authored-by: whoisdomi <dcantu83@gmail.com>

Co-authored-by: Intelli <contact@intelli.software>
Co-authored-by: haraschax <6804392+haraschax@users.noreply.github.com>
Co-authored-by: prabhaavp <prabhaav@msu.edu>
2026-10-14 20:02:39 -05:00

113 lines
6.3 KiB
Python

"""Exact Ioniq admission avoids unrelated profiles, without caching authority."""
import unittest
from contextlib import ExitStack
from dataclasses import replace
from unittest import mock
from openpilot.cereal import messaging
from openpilot.selfdrive.car.tests.test_hyundai_aol import candidate
from openpilot.starpilot.aol.runtime import current_native
from openpilot.starpilot.aol.vehicle import native_matches_cp
from openpilot.starpilot.aol.wire import PandaSlot, SafetyState, encode_safety
from openpilot.starpilot.car.hyundai import aol
from opendbc.car.hyundai.values import HyundaiFlags
from opendbc.car.structs import car
class TestIoniqNativeDispatch(unittest.TestCase):
def profiles(self):
for alternate in (False, True):
for cp in candidate(alternate):
cp.safetyConfigs[0].safetyParam |= 0x0800
yield cp
def test_exact_profile_revalidates_without_visiting_unrelated_owners(self):
unrelated = ('qualified_blended_alpha', 'qualified_blended_stock', 'qualified_angle_aol',
'qualified_classic_long', 'qualified_classic_scc', 'qualified_canfd_long',
'qualified_canfd_stock', 'qualified_non_scc', 'qualified_ccnc_ev_stock')
for cp in self.profiles():
with self.subTest(word=cp.safetyConfigs[0].safetyParam), ExitStack() as stack:
for name in unrelated:
stack.enter_context(mock.patch.object(aol, name, side_effect=AssertionError(name)))
qualifier = stack.enter_context(mock.patch.object(aol, 'qualified_ioniq6', wraps=aol.qualified_ioniq6))
model, param = int(cp.safetyConfigs[0].safetyModel.raw), cp.safetyConfigs[0].safetyParam
self.assertTrue(native_matches_cp(cp, model, param))
cp.radarUnavailable = True
self.assertFalse(native_matches_cp(cp, model, param))
cp.radarUnavailable = False
self.assertTrue(native_matches_cp(cp, model, param))
self.assertEqual(qualifier.call_count, 3)
def test_exact_qualifier_contract_and_outer_native_word_check_are_preserved(self):
changes = (('brand', 'mock'), ('radarUnavailable', True), ('passive', True),
('dashcamOnly', True), ('notCar', True), ('openpilotLongitudinalControl', None),
('pcmCruise', None), ('alternativeExperience', 32),
('steerControlType', car.CarParams.SteerControlType.angle))
for original in self.profiles():
mutations = [original]
for field, value in changes:
cp = original.as_reader().as_builder()
setattr(cp, field, not getattr(cp, field) if value is None else value)
mutations.append(cp)
for flag in (HyundaiFlags.CANFD, HyundaiFlags.EV, HyundaiFlags.CANFD_LKA_STEER_MSG,
HyundaiFlags.CANFD_LKA_STEER_MSG_ALT, HyundaiFlags.CANFD_ALT_BUTTONS,
HyundaiFlags.CANFD_ANGLE_STEERING, HyundaiFlags.CANFD_CAMERA_SCC):
cp = original.as_reader().as_builder()
cp.flags ^= int(flag)
mutations.append(cp)
for cp in mutations:
model, param = int(cp.safetyConfigs[0].safetyModel.raw), cp.safetyConfigs[0].safetyParam
with self.subTest(word=param, flags=cp.flags, profile=cp.to_dict()):
expected = aol.qualified_ioniq6(cp)
self.assertEqual(aol.native_accepts_cp(cp, model, param), expected)
# Preserve the direct port API; exact native words are checked by dispatch.
self.assertEqual(aol.native_accepts_cp(cp, model + 1, param ^ 1), expected)
self.assertEqual(native_matches_cp(cp, model, param), expected)
self.assertFalse(native_matches_cp(cp, model + 1, param))
self.assertFalse(native_matches_cp(cp, model, param ^ 1))
for configs in ([], [original.safetyConfigs[0], original.safetyConfigs[0]]):
cp = original.as_reader().as_builder()
cp.safetyConfigs = configs
self.assertFalse(native_matches_cp(cp, int(car.CarParams.SafetyModel.hyundaiCanfd), 0x8895))
def test_native_payload_health_session_validity_and_original_lease_still_apply(self):
_, cp = candidate(True)
cp.safetyConfigs[0].safetyParam |= 0x0800
now = 1_000_000_000
model, param = int(cp.safetyConfigs[0].safetyModel.raw), cp.safetyConfigs[0].safetyParam
slot = PandaSlot(0, 'panda', model, param, cp.alternativeExperience, True)
good = SafetyState(1, True, now, now + 200_000_000, model, param,
True, True, True, True, 'panda', 'axis', now, (slot,))
sm = messaging.SubMaster(['aolSafetyWire'])
def receive(value, *, valid=True, stamp=now):
nonlocal sm
sm = messaging.SubMaster(['aolSafetyWire'])
envelope = messaging.new_message('aolSafetyWire', 0, valid=valid, logMonoTime=stamp)
envelope.aolSafetyWire = encode_safety(value)
sm.update_msgs(now / 1e9, [envelope.as_reader()])
receive(good)
self.assertIsNotNone(current_native(sm, cp, now_ns=now, axis_session_id='axis'))
self.assertIsNotNone(current_native(sm, cp, now_ns=now + 200_000_000, axis_session_id='axis'))
self.assertIsNone(current_native(sm, cp, now_ns=now + 200_000_001, axis_session_id='axis'))
self.assertIsNone(current_native(sm, cp, now_ns=now, axis_session_id='different'))
receive(good, valid=False)
self.assertIsNone(current_native(sm, cp, now_ns=now, axis_session_id='axis'))
for bad in (replace(good, compatible=False), replace(good, protocolVersion=2),
replace(good, safetyParam=param ^ 1), replace(good, safetyModel=model + 1),
replace(good, observedMonoTime=now + 1),
replace(good, pandaInventory=(replace(slot, heartbeatLost=True),)),
replace(good, pandaInventory=(replace(slot, safetyRxChecksInvalid=True),)),
replace(good, pandaInventory=(replace(slot, faults=1),)),
replace(good, pandaInventory=(replace(slot, alternativeExperience=32),))):
with self.subTest(native=bad):
receive(bad)
self.assertIsNone(current_native(sm, cp, now_ns=now, axis_session_id='axis'))
# A changed permission is observed immediately; no previous grant is reused.
receive(replace(good, lateralAllowed=False, longitudinalAllowed=False))
denied = current_native(sm, cp, now_ns=now, axis_session_id='axis')
self.assertIsNotNone(denied)
assert denied is not None
self.assertFalse(denied.lateralAllowed or denied.longitudinalAllowed)