From e9abadd2f486a8d95d70042cbf9d02c69423422c Mon Sep 17 00:00:00 2001 From: firestar5683 <168790843+firestar5683@users.noreply.github.com> Date: Wed, 21 Oct 2026 10:18:27 -0500 Subject: [PATCH] Capture F1 Volt withdrawal receipts in Card --- openpilot/selfdrive/car/card.py | 4 +- .../aol/tests/test_gm_volt_f1_admission.py | 37 +++++++++++++++++++ .../aol/tests/test_gm_volt_f1_lifecycle.py | 18 +++++---- prebuilt.json | 2 +- 4 files changed, 52 insertions(+), 9 deletions(-) diff --git a/openpilot/selfdrive/car/card.py b/openpilot/selfdrive/car/card.py index 8d97d27e19..4ba1dbdf46 100755 --- a/openpilot/selfdrive/car/card.py +++ b/openpilot/selfdrive/car/card.py @@ -488,10 +488,12 @@ class Car: # Update carState from CAN CS = self.CI.update(can_list) + from openpilot.starpilot.car.gm.aol import f1_conventional_lateral_owner cancel_boot_ns = (time.clock_gettime_ns(time.CLOCK_BOOTTIME) if (getattr(self.CI.CS, 'ev6_aol_source_healthy', False) or qualified_honda_stock_aol(self.CP, marked_only=True) or - qualified_gm(self.CP) and is_bolt_present_no_acc_pedal_profile(self.CP) and + qualified_gm(self.CP) and (is_bolt_present_no_acc_pedal_profile(self.CP) or + f1_conventional_lateral_owner(self.CP)) and int(self.CP.alternativeExperience) in (32, 96)) else 0) self.observe_ioniq6_long_authority(can_list, CS) media_owner = getattr(self, 'ioniq6_media', None) diff --git a/openpilot/starpilot/aol/tests/test_gm_volt_f1_admission.py b/openpilot/starpilot/aol/tests/test_gm_volt_f1_admission.py index c348f01f4c..bf30297b79 100644 --- a/openpilot/starpilot/aol/tests/test_gm_volt_f1_admission.py +++ b/openpilot/starpilot/aol/tests/test_gm_volt_f1_admission.py @@ -292,3 +292,40 @@ def test_f1_physical_withdrawal_keeps_only_current_granted_lateral(gesture, case assert not current['aolAxisState'].longitudinalActive and not native.longitudinalAllowed finally: card.vehicle_startup.close() + + +@pytest.mark.parametrize('gesture', ['foot', 'analog', 'paddle']) +def test_f1_actual_state_update_binds_physical_receipt(gesture): + from openpilot.cereal import messaging + from openpilot.starpilot.aol.tests.test_gm import TestGmAol as GmCardFixture + now = 10_020_000_000 + with OpenpilotPrefix(), ExitStack() as cleanup: + settings = Params() + settings.put_bool('AlwaysOnLateral', True, block=True) + card = GmCardFixture.card(params(), settings) + cleanup.callback(card.vehicle_startup.close) + packer = CANPacker(DBC[card.CP.carFingerprint][Bus.pt]) + for i in range(100): + cs, _ = tick(card.CI, packer, now - 1_000_000_000 + i * 10_000_000, counter=i % 4, active=False) + + class Capture: + def update(self, rows): + self.rows = rows + + capture = Capture() + _, frames = tick(capture, packer, now, counter=0, active=False, brake=gesture == 'foot', analog=6 if gesture == 'analog' else 0, regen=gesture == 'paddle') + event = messaging.new_message('can', len(frames), valid=True, logMonoTime=now) + for dst, (address, data, bus) in zip(event.can, frames, strict=True): + dst.address, dst.dat, dst.src = address, data, bus + # Only CAN transport and clocks are fixtures; state_update parses the real bytes. + with ( + patch('openpilot.selfdrive.car.card.messaging.drain_sock_raw', return_value=[event.to_bytes()]), + patch('openpilot.selfdrive.car.card.time.monotonic_ns', return_value=now), + patch('openpilot.selfdrive.car.card.time.clock_gettime_ns', return_value=now), + ): + cs, _ = card.state_update(defer_radar=True) + assert cs.canValid and not cs.canTimeout + assert cs.brakePressed == (gesture in ('foot', 'analog')) and cs.regenBraking == (gesture == 'paddle') + observed = card._aol_can_observation + assert observed[0] is cs and observed[1:] == (now, now, now) + assert card.aol_gm_withdrawal_source(cs, now) == now diff --git a/openpilot/starpilot/aol/tests/test_gm_volt_f1_lifecycle.py b/openpilot/starpilot/aol/tests/test_gm_volt_f1_lifecycle.py index ca1e59683f..4349e89b1d 100644 --- a/openpilot/starpilot/aol/tests/test_gm_volt_f1_lifecycle.py +++ b/openpilot/starpilot/aol/tests/test_gm_volt_f1_lifecycle.py @@ -281,15 +281,19 @@ def exercise_f1(main_action=0, gesture='none'): for a, data, bus in packets: safety.safety_fwd_hook(bus, a) assert safety.safety_rx_hook(native.make_CANPacket(a, bus, data)) - cs = card.CI.update([(now, packets)]) + raw = messaging.new_message('can', len(packets), valid=True, logMonoTime=now) + for dst, (address, data, bus) in zip(raw.can, packets, strict=True): + dst.address, dst.dat, dst.src = address, data, bus + with ( + patch('openpilot.selfdrive.car.card.messaging.drain_sock_raw', return_value=[raw.to_bytes()]), + patch.object(card.sm, 'update', side_effect=lambda timeout, _now=now: receive_card(_now)), + patch('openpilot.selfdrive.car.card.time.monotonic_ns', return_value=now), + patch('openpilot.selfdrive.car.card.time.clock_gettime_ns', return_value=now), + ): + cs, _ = card.state_update(defer_radar=True) safety.safety_tick() assert cs.canValid and not cs.canTimeout - receive_card(now) - card.update_cruise_speed(cs) - card._aol_can_observation = (cs, now, now, now) - fault = card.aol_disarming_fault(cs, int(card.sm.logMonoTime['onroadEvents']), now) - card.aol_card_intent.update(cs, fault_active=fault, now_ns=now) - card.observe_aol_calibration(cs, now, False) + assert card._aol_can_observation[0] is cs and card._aol_can_observation[1:] == (now, now, now) allowed = card.aol_card_intent.output(cs)[0] request = int(allowed) | (2 if sd.aol_axis_decision.desired_longitudinal else 0) safety.aol_set_host_request(request) diff --git a/prebuilt.json b/prebuilt.json index 5d1d33d97f..b1e4e0a7a4 100644 --- a/prebuilt.json +++ b/prebuilt.json @@ -1 +1 @@ -{"protected_inventory_sha256": "f3bc5b02144b32847406e289e06bdeec27313ed197780dac269c5d0d4444f823", "schema_version": 1} +{"protected_inventory_sha256": "723440ff36d2f3e497f2dcf510392adbb9e5c08b7b75f47a2763d86d560acc4e", "schema_version": 1}