From ded2acecede5b15a0cf517fe42256417a188dea7 Mon Sep 17 00:00:00 2001 From: firestar5683 <168790843+firestar5683@users.noreply.github.com> Date: Tue, 10 Nov 2026 22:36:34 -0600 Subject: [PATCH] Preserve independent HKG lateral across cancel deadline joins --- openpilot/selfdrive/selfdrived/selfdrived.py | 6 ++ .../aol/tests/test_hkg_cancel_deadline.py | 61 +++++++++++++++++++ prebuilt.json | 2 +- 3 files changed, 68 insertions(+), 1 deletion(-) create mode 100644 openpilot/starpilot/aol/tests/test_hkg_cancel_deadline.py diff --git a/openpilot/selfdrive/selfdrived/selfdrived.py b/openpilot/selfdrive/selfdrived/selfdrived.py index e7507a8051..ebbd3e66de 100755 --- a/openpilot/selfdrive/selfdrived/selfdrived.py +++ b/openpilot/selfdrive/selfdrived/selfdrived.py @@ -1487,6 +1487,12 @@ class SelfdriveD: # An already-off PCM is not a new cruise cancellation. Only a fresh, # independently checked LAT source may replace the expired LAT-only proof. user_disable = any(event.userDisable and event.name != EventName.pcmDisable for event in self.events.to_msg()) + if user_disable and fresh_successor and self.CP.brand == 'hyundai': + from openpilot.starpilot.car.hyundai.aol import physical_cancel_lateral_allowed + native = current_native(self.sm, self.CP, now_ns=now_ns, axis_session_id=self.aol_session_id) + if physical_cancel_lateral_allowed(self.CP, CS, intent, native, settings=self.aol_settings, + previous=decision, source_ns=source_ns, now_ns=now_ns, axis_session_id=self.aol_session_id): + user_disable = any(event.userDisable and event.name != EventName.buttonCancel for event in self.events.to_msg()) if user_disable and fresh_successor and self.ford_source_deadline_supported(): from openpilot.starpilot.car.ford.aol import physical_cancel_lateral_allowed native = current_native(self.sm, self.CP, now_ns=now_ns, axis_session_id=self.aol_session_id) diff --git a/openpilot/starpilot/aol/tests/test_hkg_cancel_deadline.py b/openpilot/starpilot/aol/tests/test_hkg_cancel_deadline.py new file mode 100644 index 0000000000..6dfd937b77 --- /dev/null +++ b/openpilot/starpilot/aol/tests/test_hkg_cancel_deadline.py @@ -0,0 +1,61 @@ +from contextlib import ExitStack +from unittest import mock + +import pytest + +from opendbc.car.structs import car +from opendbc.car.hyundai.values import CAR +from opendbc.car.hyundai.tests.test_ioniq5pe_stock import params +from opendbc.car.hyundai.ioniq5pe_long_profile import candidate as pe_long +from opendbc.car.hyundai.ev9_longitudinal import candidate as ev9_long +from openpilot.cereal import log +from openpilot.common.params import Params +from openpilot.common.prefix import OpenpilotPrefix +from openpilot.starpilot.aol.intent import read_settings +from openpilot.starpilot.aol.tests import test_ioniq_availability as fixture +from openpilot.starpilot.aol.tests.test_input_deadline_continuity import BASE, intent + + +@pytest.mark.parametrize('restriction', ['none', 'coast', 'native_denied', 'native_invalid', 'critical', 'cancel_mapping', 'expired_source', 'latch_off']) +@pytest.mark.parametrize('identity', [CAR.HYUNDAI_IONIQ_5_PE, CAR.KIA_EV9]) +def test_cancel_crossing_output_deadline_keeps_fresh_independent_lateral(identity, restriction): + case = fixture.IoniqAvailabilityTests(methodName='runTest') + with ExitStack() as cleanup: + cleanup.callback(case.doCleanups) + with OpenpilotPrefix(): + saved = Params() + saved.put('LKASButtonControl', 9, block=True) + for key in ('CancelButtonControl', 'LongCancelButtonControl', 'VeryLongCancelButtonControl'): + saved.put(key, 0, block=True) + if restriction in ('coast', 'cancel_mapping'): + saved.put('LongCancelButtonControl', 8 if restriction == 'coast' else 9, block=True) + cp = (pe_long if identity == CAR.HYUNDAI_IONIQ_5_PE else ev9_long)( + params(candidate=identity), enabled=True, is_release=False) + cp.alternativeExperience = 32 + sd, controls, step = case._authority_callers(True, cp=cp, genuine_events=True) + sd.aol_settings = read_settings(saved) + for tick in (0, 1): + step(tick) + prior, _, command = step(2, offset_ns=4_266_961, state_stamp=BASE, intent_message=intent(BASE, 3)) + assert prior.lateralActive and prior.longitudinalActive and command.latActive and command.longActive + now = BASE + 34_638_124 + source = now - (31_000_000 if restriction == 'expired_source' else 24_218_150) + assert now - prior.validUntilMonoTime == 371_163 + with mock.patch('openpilot.selfdrive.controls.controlsd.time.monotonic_ns', return_value=now): + expired, _ = controls.state_control() + assert not expired.latActive and not expired.longActive + state = fixture.car_state() + state.vEgoRaw = state.vEgo + state.buttonEvents = [car.CarState.ButtonEvent(type='cancel', pressed=True)] + axis, _, command = step(5, offset_ns=4_638_124, state_stamp=source, + intent_message=intent(source, 4, latched=restriction != 'latch_off'), observed_state=state, + allowed=restriction != 'native_denied', native_valid=restriction != 'native_invalid', + critical_event=log.OnroadEvent.EventName.controlsMismatch if restriction == 'critical' else None) + assert axis.sourceCarStateMonoTime == (0 if restriction == 'expired_source' else source) + assert not axis.longitudinalActive and not command.longActive and not sd.enabled + if restriction in ('none', 'coast'): + assert axis.lateralActive and command.latActive + assert not sd.aol_authority_lost and sd.aol_last_intent.allowedLatch + assert log.OnroadEvent.EventName.controlsMismatch not in sd.events.names + else: + assert not axis.lateralActive and not command.latActive diff --git a/prebuilt.json b/prebuilt.json index d983f57224..6cf3304b87 100644 --- a/prebuilt.json +++ b/prebuilt.json @@ -1 +1 @@ -{"protected_inventory_sha256": "fb78a23b1998f94730ef0e7f177e9477f8bee39b6960c9328939e91c60e8f2f9", "schema_version": 1} +{"protected_inventory_sha256": "f8e452106b5698682f884462e64e20b46664de48f31ca48b964759a66f05e54b", "schema_version": 1}