diff --git a/openpilot/selfdrive/selfdrived/selfdrived.py b/openpilot/selfdrive/selfdrived/selfdrived.py index 403d2b9088..a95dce0037 100755 --- a/openpilot/selfdrive/selfdrived/selfdrived.py +++ b/openpilot/selfdrive/selfdrived/selfdrived.py @@ -1446,11 +1446,13 @@ class SelfdriveD: allow_independent_brake=True, allow_independent_gas=True, now_ns=now_ns): user_disable = False from opendbc.car.hyundai.blended_stock_aol import qualified as blended_stock_qualified + from opendbc.car.subaru.stock_aol import qualified as subaru_stock_qualified if (user_disable and fresh_successor and proof[6] and not proof[7] and proof[8] and not proof[9] and decision.desired_lateral and not decision.desired_longitudinal and not decision.longitudinal_active and not self.enabled and not self.active and ( self.CP.brand == 'toyota' and toyota_qualified(self.CP, marked_only=True) or - self.CP.brand == 'hyundai' and blended_stock_qualified(self.CP, marked_only=True) and + (self.CP.brand == 'hyundai' and blended_stock_qualified(self.CP, marked_only=True) or + self.CP.brand == 'subaru' and subaru_stock_qualified(self.CP, marked_only=True)) and not physical_cancel(CS, self.CP) and not any(physical_cancel(sample, self.CP) for sample, _ in getattr(self, 'car_state_samples', ()))) and CS.cruiseState.available and not CS.cruiseState.enabled and diff --git a/openpilot/starpilot/car/subaru/tests/test_stock_aol_callers.py b/openpilot/starpilot/car/subaru/tests/test_stock_aol_callers.py index 230667d424..f0d00c6fd9 100644 --- a/openpilot/starpilot/car/subaru/tests/test_stock_aol_callers.py +++ b/openpilot/starpilot/car/subaru/tests/test_stock_aol_callers.py @@ -23,7 +23,8 @@ from openpilot.starpilot.car.subaru.tests.native_fixture import subaru_native_pa @pytest.mark.parametrize('candidate', [CAR.SUBARU_ASCENT_2023, CAR.SUBARU_LEGACY_2025, CAR.SUBARU_CROSSTREK_2025, CAR.SUBARU_FORESTER, CAR.SUBARU_OUTBACK]) def test_actual_full_engage_brake_reverse_temporary_recovery_mainoff(candidate, subaru_native_pair, tmp_path, aol=True, manual=False, stop_resume=False, - hard_stop_native_first=False, main_only=False, preferences=None, firmware=None): + hard_stop_native_first=False, main_only=False, preferences=None, firmware=None, + deadline_gap=False): fw = firmware_records(candidate, FW_VERSIONS, 'subaru') if firmware is None else firmware rows = [] with actual_factory(candidate, gen_empty_fingerprint(), fw, '0' * 17, manual=manual, aol=aol, preferences=preferences) as (card, params, matches): @@ -62,8 +63,9 @@ def test_actual_full_engage_brake_reverse_temporary_recovery_mainoff(candidate, published = {} ticks = (list(range(125)) + list(range(125, 135)) * 30 + list(range(135, 460)) if hard_stop_native_first else range(460)) + gap_tick = 75 if main_only else 210 for elapsed, tick in enumerate(ticks): - now = base + elapsed * 10_000_000 + now = base + elapsed * 10_000_000 + (22_504_479 if deadline_gap and elapsed >= gap_tick else 0) cruise = not main_only and (30 <= tick < 110 or tick >= 410) brake = 90 <= tick < 110 reverse = 150 <= tick < 185 @@ -142,7 +144,19 @@ def test_actual_full_engage_brake_reverse_temporary_recovery_mainoff(candidate, _assert_hard_stop_fault_guards(card, cs, now + 2000) # Deliver each genuine10Hz native receipt once; do not refresh receive time/frequency from a cached snapshot. new_native = receipt if receipt is not None and (tick % 10 == 1 or hard_stop_receipt) else None + if deadline_gap and elapsed == gap_tick: + prior = driven['aolAxisState'].aolAxisState + assert prior.lateralActive and now + 2000 > prior.validUntilMonoTime + with patch('openpilot.selfdrive.controls.controlsd.time.monotonic_ns', return_value=now + 2000): + expired, _ = controls.state_control() + assert not expired.latActive and not expired.longActive driven_new = selfdrive_step(sd, published, new_native, previous_controls, cp, safety, now, calibration='calibrated') + if deadline_gap and elapsed == gap_tick: + current = driven_new['aolAxisState'].aolAxisState + assert current.lateralActive and not sd.aol_authority_lost, 'SUBARU_FRESH_SUCCESSOR_LOST_LATERAL' + assert current.validUntilMonoTime > prior.validUntilMonoTime + assert current.sourceCarStateMonoTime == published['carState'].logMonoTime + assert log.OnroadEvent.EventName.controlsMismatch not in sd.events.names driven.update(driven_new) order = None if aol and tick % 10 == 0: @@ -332,3 +346,31 @@ def test_actual_main_without_set_retains_native_first_stop_intent(subaru_native_ assert all(not row['controls'] and not row['enabled'] and not row['long'] for row in rows) assert all(row['lat'] and row['permission'] == 1 for row in rows if 65 <= row['tick'] < 90) assert all(not row['lat'] and row['cardLatch'] and row['permission'] == 0 for row in rows if 125 <= row['tick'] < 135) + + +@pytest.mark.parametrize('candidate', [CAR.SUBARU_IMPREZA_2020, CAR.SUBARU_CROSSTREK_2025, CAR.SUBARU_OUTBACK_2023]) +def test_actual_fresh_subaru_source_after_expired_output(candidate, subaru_native_pair, tmp_path): + test_actual_full_engage_brake_reverse_temporary_recovery_mainoff( + candidate, subaru_native_pair, tmp_path, main_only=True, deadline_gap=True) + + +@pytest.mark.parametrize('event', [log.OnroadEvent.EventName.buttonCancel, log.OnroadEvent.EventName.pedalPressed]) +def test_actual_subaru_expired_output_keeps_user_disable(event, subaru_native_pair, tmp_path): + original = SelfdriveD._expire_axis_continuity + + class ObservedVeto(Exception): + pass + + def check(sd, state, now_ns): + proof = getattr(sd, 'aol_step_input_proof', None) + if proof is not None and now_ns > proof[3] and sd.aol_axis_decision.lateral_active: + sd.events.add(event) + assert original(sd, state, now_ns) + assert not sd.aol_axis_decision.lateral_active and not sd.aol_axis_decision.longitudinal_active + assert sd.aol_authority_lost and event in sd.events.names + raise ObservedVeto + return original(sd, state, now_ns) + + with patch.object(SelfdriveD, '_expire_axis_continuity', check), pytest.raises(ObservedVeto): + test_actual_full_engage_brake_reverse_temporary_recovery_mainoff( + CAR.SUBARU_IMPREZA_2020, subaru_native_pair, tmp_path, main_only=True, deadline_gap=True) diff --git a/prebuilt.json b/prebuilt.json index ff4a0c1684..3f1c66ed17 100644 --- a/prebuilt.json +++ b/prebuilt.json @@ -1 +1 @@ -{"protected_inventory_sha256": "a65a390042e7d02f1180f79f6ad3e95c80ce96be9f44cc1230ff7afe2fad885c", "schema_version": 1} +{"protected_inventory_sha256": "f3cbd9d2514303e843886fe9f41e889230548c79348ceebfe580f2ce930e5ba6", "schema_version": 1}