From 5e92608ab70bf21b0ddd05874a12bccaa0fbdef5 Mon Sep 17 00:00:00 2001 From: firestar5683 <168790843+firestar5683@users.noreply.github.com> Date: Sun, 1 Nov 2026 22:41:34 -0600 Subject: [PATCH] Keep first AOL arming available during native startup --- openpilot/selfdrive/selfdrived/selfdrived.py | 4 +- .../aol/tests/test_hkg_cancel_joined.py | 97 +++++++++++++++++++ .../aol/tests/test_ioniq_availability.py | 8 +- prebuilt.json | 2 +- 4 files changed, 105 insertions(+), 6 deletions(-) diff --git a/openpilot/selfdrive/selfdrived/selfdrived.py b/openpilot/selfdrive/selfdrived/selfdrived.py index 36bf943049..2e15880257 100755 --- a/openpilot/selfdrive/selfdrived/selfdrived.py +++ b/openpilot/selfdrive/selfdrived/selfdrived.py @@ -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) diff --git a/openpilot/starpilot/aol/tests/test_hkg_cancel_joined.py b/openpilot/starpilot/aol/tests/test_hkg_cancel_joined.py index 430dd9d1bc..931e709f9a 100644 --- a/openpilot/starpilot/aol/tests/test_hkg_cancel_joined.py +++ b/openpilot/starpilot/aol/tests/test_hkg_cancel_joined.py @@ -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() diff --git a/openpilot/starpilot/aol/tests/test_ioniq_availability.py b/openpilot/starpilot/aol/tests/test_ioniq_availability.py index cee36cfcf3..abeb4fbb2f 100644 --- a/openpilot/starpilot/aol/tests/test_ioniq_availability.py +++ b/openpilot/starpilot/aol/tests/test_ioniq_availability.py @@ -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) diff --git a/prebuilt.json b/prebuilt.json index 78e1a1676b..5a4e9a0d53 100644 --- a/prebuilt.json +++ b/prebuilt.json @@ -1 +1 @@ -{"protected_inventory_sha256": "b77124d8cb762c0142394084c7a9683df83856033a3d5890f4b26579e6103197", "schema_version": 1} +{"protected_inventory_sha256": "7a5367f4e7de8fcfda84fcef36fd862b3431f5a23aa9bc84342442e73cd129ef", "schema_version": 1}