Preserve Subaru lateral on fresh input after output expiry

This commit is contained in:
firestar5683
2026-11-03 10:09:06 -06:00
parent 80975369ee
commit dd9608eecd
3 changed files with 48 additions and 4 deletions
+3 -1
View File
@@ -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
@@ -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)
+1 -1
View File
@@ -1 +1 @@
{"protected_inventory_sha256": "a65a390042e7d02f1180f79f6ad3e95c80ce96be9f44cc1230ff7afe2fad885c", "schema_version": 1}
{"protected_inventory_sha256": "f3cbd9d2514303e843886fe9f41e889230548c79348ceebfe580f2ce930e5ba6", "schema_version": 1}