Keep first AOL arming available during native startup

This commit is contained in:
firestar5683
2026-11-01 22:41:34 -06:00
parent f429155f0f
commit 5e92608ab7
4 changed files with 105 additions and 6 deletions
+3 -1
View File
@@ -1948,7 +1948,9 @@ class SelfdriveD:
native = current_native(self.sm, self.CP, now_ns=now_ns, axis_session_id=self.aol_session_id)
if native is None and self.initialized:
lost_active_aol = self.aol_axis_decision.lateral_active or self.aol_axis_decision.longitudinal_active
self.events.add(EventName.controlsMismatch)
native_missing_at_startup = (not self.sm.seen['aolSafetyWire'] and not self.enabled and
not self.active and not lost_active_aol)
self.events.add(EventName.commIssue if native_missing_at_startup else EventName.controlsMismatch)
self.aol_decision_native = native
if self.aol_replay:
previous_intent = getattr(self, 'aol_last_intent', None)
@@ -690,3 +690,100 @@ def test_nostalgia_paddle_expiry_cannot_restore_invalid_lateral(fault):
assert not axis.lateralActive and not command.latActive
finally:
case.doCleanups()
@pytest.mark.parametrize('alpha', (False, True))
def test_startup_native_wait_preserves_first_lkas_through_model_loading(alpha):
from opendbc.car.hyundai.tests.test_palisade_2023 import params as blended_params
from opendbc.car.hyundai.blended_longitudinal import candidate_from_stock
from openpilot.cereal import log
case = caller_fixture.IoniqAvailabilityTests(methodName='runTest')
with OpenpilotPrefix():
cp = blended_params('hdaii')
startup = None
if alpha:
from opendbc.car.hyundai.blended_longitudinal import BlendedStartup
from unittest.mock import Mock
stock = cp
cp = candidate_from_stock(stock, alpha_requested=True, native_qualified=True)
startup = BlendedStartup(stock, cp.as_reader().as_builder(), (Mock(), Mock()))
assert startup.prepare(admission=lambda: True).to_dict() == cp.to_dict()
case.addCleanup(startup.close)
Params().put('LKASButtonControl', AOL_TOGGLE, block=True)
try:
sd, _, drive, card, receive = case._ioniq_availability_callers(
False, initially_armed=False, cp=cp, cold_start=True, startup_owner=startup)
original_receive = sd.sm.update_msgs
missing = {'aolSafetyWire', 'modelV2'}
def receive_sources(now, messages):
return original_receive(now, [message for message in messages if message.which() not in missing])
for name in missing:
sd.sm.seen[name] = sd.sm.alive[name] = sd.sm.valid[name] = False
with patch.object(sd.sm, 'update_msgs', side_effect=receive_sources):
axis, _, command = drive(0, allowed=False, native_request_lat=False, native_request_long=False)
assert not axis.lateralActive and not axis.longitudinalActive
assert not command.latActive and not command.longActive
assert log.OnroadEvent.EventName.commIssue in sd.events.names
assert log.OnroadEvent.EventName.controlsMismatch not in sd.events.names
assert axis.faultReason == 'availabilityPause'
assert not receive(10_000_000_000, axis=sd.pm.messages['aolAxisState'], event=sd.pm.messages['onroadEvents'])
missing.remove('aolSafetyWire')
axis, _, command = drive(1, allowed=False, native_request_lat=False, native_request_long=False)
cs = caller_fixture.car_state()
cs.buttonEvents = [structs.CarState.ButtonEvent(type='lkas', pressed=True)]
assert receive(10_010_000_000, axis=sd.pm.messages['aolAxisState'], event=sd.pm.messages['onroadEvents'], state=cs)
assert not command.latActive and not command.longActive
assert axis.faultReason == 'availabilityPause'
for tick in range(2, 5):
axis, _, command = drive(tick, allowed=False, native_request_lat=False, native_request_long=False)
assert receive(10_000_000_000 + tick * 10_000_000,
axis=sd.pm.messages['aolAxisState'], event=sd.pm.messages['onroadEvents'])
assert not command.latActive and not command.longActive
assert axis.validUntilMonoTime - axis.observedMonoTime == 30_000_000
missing.clear()
for tick in range(5, 16):
axis, _, command = drive(tick, allowed=True, native_request_lat=True, native_request_long=False)
assert receive(10_000_000_000 + tick * 10_000_000,
axis=sd.pm.messages['aolAxisState'], event=sd.pm.messages['onroadEvents'])
assert axis.lateralActive and command.latActive and not command.longActive
assert not sd.enabled and not sd.active
assert card.aol_card_intent.allowed_latch
finally:
case.doCleanups()
@pytest.mark.parametrize('fault', ('invalid_native', 'missing_after_receipt', 'missing_while_enabled', 'rx_fault', 'relay_fault'))
def test_startup_native_wait_retains_fault_and_permission_checks(fault):
from opendbc.car.hyundai.tests.test_palisade_2023 import params as blended_params
from openpilot.cereal import log
case = caller_fixture.IoniqAvailabilityTests(methodName='runTest')
with OpenpilotPrefix():
try:
sd, _, drive = case._authority_callers(fault == 'missing_while_enabled', cp=blended_params('hdaii'), genuine_events=True)
original_receive = sd.sm.update_msgs
missing = set()
def receive_sources(now, messages):
return original_receive(now, [message for message in messages if message.which() not in missing])
if fault == 'missing_after_receipt':
drive(0, latched=False, allowed=False, native_request_lat=False, native_request_long=False)
assert sd.sm.seen['aolSafetyWire']
missing.add('aolSafetyWire')
elif fault == 'missing_while_enabled':
missing.add('aolSafetyWire')
sd.sm.seen['aolSafetyWire'] = sd.sm.valid['aolSafetyWire'] = sd.sm.alive['aolSafetyWire'] = False
with patch.object(sd.sm, 'update_msgs', side_effect=receive_sources):
axis, _, command = drive(30, latched=False, allowed=False, native_request_lat=False, native_request_long=False,
native_valid=fault != 'invalid_native', panda_fault=fault == 'rx_fault',
relay_fault=fault == 'relay_fault')
assert log.OnroadEvent.EventName.controlsMismatch in sd.events.names
assert not axis.lateralActive and not axis.longitudinalActive
assert not command.latActive and not command.longActive
assert axis.faultReason == 'critical'
finally:
case.doCleanups()
@@ -338,7 +338,7 @@ class IoniqAvailabilityTests(unittest.TestCase):
return sd.pm.messages['aolAxisState'].aolAxisState, sd.pm.messages['selfdriveState'].selfdriveState, command
return sd, controls, step
def _ioniq_availability_callers(self, full_op=True, *, initially_armed=True, stock_angle=False, cp=None):
def _ioniq_availability_callers(self, full_op=True, *, initially_armed=True, stock_angle=False, cp=None, cold_start=False, startup_owner=None):
from opendbc.car.hyundai.interface import CarInterface
from opendbc.car.hyundai.radar_interface import RadarInterface
from openpilot.selfdrive.car.card import Car
@@ -363,8 +363,8 @@ class IoniqAvailabilityTests(unittest.TestCase):
_, cp = candidate(True)
cp.safetyConfigs[-1].safetyParam |= 0x0800
sd, controls, step = self._authority_callers(full_op, cp=cp, genuine_events=True)
card_cp = cp.as_reader().as_builder()
card = Car(CI=CarInterface(card_cp), RI=RadarInterface(card_cp))
card_cp = cp.as_reader().as_builder() if startup_owner is None else startup_owner.owner.cp
card = Car(CI=CarInterface(card_cp), RI=RadarInterface(card_cp), startup_owner=startup_owner)
self.assertEqual(card.CP.to_dict(), sd.CP.to_dict())
self.assertEqual(card.CP.safetyConfigs[-1].safetyParam, cp.safetyConfigs[-1].safetyParam if stock_angle or supplied_profile else 0x8895)
self.assertTrue(card.aol_card_intent.explicit_latch)
@@ -412,7 +412,7 @@ class IoniqAvailabilityTests(unittest.TestCase):
kwargs['observed_state'] = state
return step(tick, **kwargs)
for tick in (0, 1):
for tick in (() if cold_start else (0, 1)):
axis, _, command = drive(tick)
if full_op:
self.assertTrue(axis.lateralActive and axis.longitudinalActive and command.latActive and command.longActive)
+1 -1
View File
@@ -1 +1 @@
{"protected_inventory_sha256": "b77124d8cb762c0142394084c7a9683df83856033a3d5890f4b26579e6103197", "schema_version": 1}
{"protected_inventory_sha256": "7a5367f4e7de8fcfda84fcef36fd862b3431f5a23aa9bc84342442e73cd129ef", "schema_version": 1}