Preserve armed lateral intent through temporary warnings

This commit is contained in:
firestar5683
2026-10-22 21:22:55 -05:00
parent 9ce2893646
commit 011e46b8ed
16 changed files with 446 additions and 98 deletions
@@ -90,7 +90,7 @@ def test_eps_recovery_actual_callers(identity, lateral_only, monkeypatch, armed_
def _exercise_actual_family_callers(identity, monkeypatch, *, scenario='brake', cancel_action=3, armed_intent=None,
lateral_only=False):
lateral_only=False, warning=None, unbelted=False):
from openpilot.cereal import messaging
from openpilot.selfdrive.car import card as card_module
from openpilot.starpilot.aol.wire import PandaSlot, SafetyState, encode_safety
@@ -107,6 +107,7 @@ def _exercise_actual_family_callers(identity, monkeypatch, *, scenario='brake',
now, tick, queued = [10_000_000_000], 0, []
received, previous_sd = {}, {}
first_engagement_ns = None
belt_off = [False]
try:
sd, controls, drive = case._authority_callers(False, cp=card.CP.as_reader().as_builder(), genuine_events=True)
if scenario in ('cancel', 'long_cancel', 'mapped_cancel', 'temporary', 'startup', 'startup_stopped'):
@@ -114,6 +115,14 @@ def _exercise_actual_family_callers(identity, monkeypatch, *, scenario='brake',
sd.aol_settings = read_settings(saved)
assert sd.aol_settings.cancel_actions == (cancel_action, 0, 0)
assert card.aol_card_intent.settings == sd.aol_settings
soft_warning = [False]
original_events = sd.update_events
def warning_events(*args, **kwargs):
original_events(*args, **kwargs)
if soft_warning[0]:
from openpilot.selfdrive.selfdrived.events import EventName
sd.events.add(getattr(EventName, warning))
local_patch.setattr(sd, 'update_events', warning_events)
assert sd.CP.to_dict() == card.CP.to_dict() == ci.CP.to_dict() == controls.CP.to_dict()
safety.init_tests()
safety.set_alternative_experience(int(card.CP.alternativeExperience))
@@ -146,6 +155,9 @@ def _exercise_actual_family_callers(identity, monkeypatch, *, scenario='brake',
frames = complete_frames(packer, card.CP, tick, main=main, engaged=engaged, brake=brake, cancel=cancel, reverse=reverse,
speed_kph=speed_kph, permanent=permanent, temporary=temporary)
frames.append(packer.make_can_msg('STEER', 0, {'STEER_ANGLE': 1.0}))
if belt_off[0]:
belt = packer.make_can_msg('SEATBELT', 0, {'DRIVER_SEATBELT': 0})
frames = [row for row in frames if not (row[0] == belt[0] and row[2] == 0)] + [belt]
safety.set_timer(now[0] // 1000)
prior_state = previous_sd.get('selfdriveState')
prior_axis = previous_sd.get('aolAxisState')
@@ -242,6 +254,10 @@ def _exercise_actual_family_callers(identity, monkeypatch, *, scenario='brake',
assert any(frame.address == 0x243 for frame in outgoing.sendcan)
for frame in outgoing.sendcan:
assert safety.safety_tx_hook(make_msg(frame.src, frame.address, dat=frame.dat))
if unbelted and belt_off[0]:
assert state.seatbeltUnlatched
assert not axis.desiredLongitudinal and not axis.longitudinalActive and not command.longActive
assert not safety.aol_get_permission_mask() & 2
return state, axis, host_state, command
for _ in range(120):
@@ -285,6 +301,16 @@ def _exercise_actual_family_callers(identity, monkeypatch, *, scenario='brake',
state, axis, host_state, command = step(engaged=True)
assert state.canValid and host_state.enabled and command.latActive
assert card.aol_card_intent.allowed_latch
if unbelted:
for _ in range(30):
state, axis, host_state, command = step(brake=True)
assert card.aol_card_intent.allowed_latch and axis.lateralActive and not axis.longitudinalActive
belt_off[0] = True
for _ in range(5):
state, axis, host_state, command = step()
assert state.seatbeltUnlatched and card.aol_card_intent.allowed_latch
assert axis.lateralActive and command.latActive and not command.longActive
assert safety.aol_get_permission_mask() == 1
if lateral_only:
for _ in range(30):
state, axis, host_state, command = step(brake=True)
@@ -292,6 +318,56 @@ def _exercise_actual_family_callers(identity, monkeypatch, *, scenario='brake',
for _ in range(30):
state, axis, host_state, command = step()
assert card.aol_card_intent.allowed_latch and axis.lateralActive and not axis.longitudinalActive
if scenario == 'warning':
from openpilot.selfdrive.selfdrived.events import EventName
for _ in range(30):
state, axis, host_state, command = step(brake=True)
for _ in range(30):
state, axis, host_state, command = step()
assert card.aol_card_intent.allowed_latch and axis.lateralActive and not command.longActive
soft_warning[0] = True
for paused_tick in range(350):
state, axis, host_state, command = step()
if any(event.softDisable for event in card.sm['onroadEvents']):
assert card.aol_transport_feedback.ordinary_warning_qualified
if paused_tick > 20:
assert safety.aol_get_request_mask() == safety.aol_get_permission_mask() == 0
assert card.aol_card_intent.allowed_latch, 'Ordinary warning erased armed Mazda lateral intent'
assert not command.latActive and not command.longActive
assert not axis.lateralActive and not axis.longitudinalActive
assert getattr(EventName, warning) in sd.events.names
soft_warning[0] = False
for _ in range(40):
state, axis, host_state, command = step()
assert card.aol_card_intent.allowed_latch
assert not command.longActive and not axis.longitudinalActive
if not any(event.softDisable for event in card.sm['onroadEvents']):
assert not card.aol_transport_feedback.ordinary_warning_qualified
assert axis.lateralActive and command.latActive, 'Healthy warning recovery failed to restore retained lateral'
return
if scenario == 'warning_active':
from openpilot.selfdrive.selfdrived.events import EventName
assert card.aol_card_intent.allowed_latch and host_state.enabled and command.latActive
soft_warning[0] = True
for paused_tick in range(350):
state, axis, host_state, command = step(engaged=True)
if any(event.softDisable for event in card.sm['onroadEvents']):
assert card.aol_transport_feedback.ordinary_warning_qualified
if paused_tick > 20:
assert safety.aol_get_request_mask() == safety.aol_get_permission_mask() == 0
assert card.aol_card_intent.allowed_latch, 'Ordinary warning erased armed Mazda lateral intent'
assert not command.latActive and not command.longActive
assert not axis.lateralActive and not axis.longitudinalActive
assert getattr(EventName, warning) in sd.events.names
soft_warning[0] = False
for _ in range(40):
state, axis, host_state, command = step()
assert card.aol_card_intent.allowed_latch
assert not command.longActive and not axis.longitudinalActive
if not any(event.softDisable for event in card.sm['onroadEvents']):
assert not card.aol_transport_feedback.ordinary_warning_qualified
assert axis.lateralActive and command.latActive, 'Healthy warning recovery failed to restore retained lateral'
return
if scenario == 'mapped_cancel':
for _ in range(5):
state, axis, host_state, command = step(cancel=True)
@@ -483,3 +559,21 @@ def test_main_on_startup_disabled_aol_actual_card_controls_native(identity, monk
safety.set_safety_hooks(structs.CarParams.SafetyModel.noOutput, 0)
exercise_startup(identity, False, False, False, monkeypatch, probe=probe)
@pytest.mark.parametrize('identity', (CAR.MAZDA_CX5_2022, CAR.MAZDA_CX9_2021))
@pytest.mark.parametrize('warning', ('calibrationInvalid', 'lowMemory', 'processNotRunning', 'radarTempUnavailable'))
def test_ordinary_warning_retains_intent_actual_callers(identity, warning, monkeypatch, armed_intent_decoder):
_exercise_actual_family_callers(identity, monkeypatch, scenario='warning', warning=warning, armed_intent=armed_intent_decoder)
@pytest.mark.parametrize('identity', (CAR.MAZDA_CX5_2022, CAR.MAZDA_CX9_2021))
@pytest.mark.parametrize('scenario', ('mapped_cancel', 'temporary'))
def test_unbelted_lateral_pause_recovery_actual_callers(identity, scenario, monkeypatch, armed_intent_decoder):
_exercise_actual_family_callers(identity, monkeypatch, scenario=scenario, cancel_action=3,
armed_intent=armed_intent_decoder, unbelted=True)
@pytest.mark.parametrize('identity', (CAR.MAZDA_CX5_2022, CAR.MAZDA_CX9_2021))
@pytest.mark.parametrize('warning', ('calibrationInvalid', 'lowMemory', 'processNotRunning', 'radarTempUnavailable'))
def test_active_ordinary_warning_retains_intent_actual_callers(identity, warning, monkeypatch, armed_intent_decoder):
_exercise_actual_family_callers(identity, monkeypatch, scenario='warning_active', warning=warning, armed_intent=armed_intent_decoder)
+4 -1
View File
@@ -658,7 +658,8 @@ class Car:
native,
latched=self.aol_card_intent.allowed_latch,
panda_ready=angle_panda_ready,
restricted=(restrict(self.CP, CS) or
restricted=(getattr(self.aol_transport_feedback, 'ordinary_warning_qualified', False) or
restrict(self.CP, CS) or
((angle_aol or mazda_aol or preap_aol or rivian_aol) and self.aol_card_intent.pause_lateral) or
(angle_aol and CS.brakePressed and (not CS.standstill) and (CS.vEgo < self.aol_settings.pause_brake_mps)) or
(volkswagen_aol and (self.aol_card_intent.pause_lateral or
@@ -877,6 +878,8 @@ class Car:
getattr(self, '_aol_can_observation', None), now_ns)
def aol_disarming_fault(self, CS, event_ns: int, now_ns: int, *, transport_observed_healthy=False) -> bool:
if not aol_policy_for(self.CP).intent_supported:
return True
from openpilot.starpilot.aol.vehicle import intent_disarming_fault
if getattr(self, 'aol_transport_feedback', None) is None:
self.aol_transport_feedback = TransportPauseFeedback(self.CP)
+3 -4
View File
@@ -1977,9 +1977,8 @@ class SelfdriveD:
cold_park_wait = self.cold_gm_park_readiness(CS, intent, now_ns)
self.aol_fault_reason = ('none' if cold_park_wait else
'availabilityPause' if ordinary_availability or cold_main_wait else
'critical' if self.aol_authority_lost or self.events.contains(ET.IMMEDIATE_DISABLE) or
aol_hard_soft_fault and not availability else
'availabilityPause' if availability else 'transportPause' if pause else 'none')
'critical' if self.aol_authority_lost or self.events.contains(ET.IMMEDIATE_DISABLE) else
'availabilityPause' if availability or aol_hard_soft_fault else 'transportPause' if pause else 'none')
if self.aol_authority_lost or pause:
self.events.add(EventName.controlsMismatch)
lost_active_aol |= pause
@@ -2050,7 +2049,7 @@ class SelfdriveD:
self.aol_lag_takeover_alert = True
if (self.aol_replay and source_current and not was_enabled and not self.enabled and
previous.lateral_active and previous.native_acknowledged and not self.aol_axis_decision.lateral_active and
self.aol_fault_reason == 'critical' and not self.aol_authority_lost and
self.aol_fault_reason in ('critical', 'availabilityPause') and not self.aol_authority_lost and
intent is not None and intent.allowedLatch and self.events.contains(ET.SOFT_DISABLE) and
not self.events.contains(ET.IMMEDIATE_DISABLE)):
self.aol_soft_takeover_alert = True
+6 -12
View File
@@ -122,16 +122,10 @@ def disarming_fault(events, CS, *, temporary_ui_process_failure: bool = False,
if event.name == log.OnroadEvent.EventName.controlsMismatch and temporary_source_pause:
continue
return True
if event.softDisable:
if event.name == log.OnroadEvent.EventName.selfdrivedLagging and temporary_selfdrive_lagging:
continue
if event.name == log.OnroadEvent.EventName.processNotRunning and temporary_ui_process_failure:
continue
if event.name in (log.OnroadEvent.EventName.steerTempUnavailable, log.OnroadEvent.EventName.seatbeltNotLatched):
continue
if event.name == log.OnroadEvent.EventName.wrongGear and CS.gearShifter != GearShifter.unknown:
continue
if event.softDisable and event.name == log.OnroadEvent.EventName.wrongGear and CS.gearShifter == GearShifter.unknown:
return True
# SOFT warnings withdraw commands through the normal runtime/state machine.
# They do not revoke the driver's separate armed lateral intent.
return False
@@ -401,11 +395,11 @@ class AolCardIntent:
rearm_safe = (CS.canValid and not CS.canTimeout and not self._calibration_fault and
not CS.steerFaultPermanent and not CS.steerFaultTemporary and
CS.gearShifter not in (GearShifter.park, GearShifter.neutral, GearShifter.reverse, GearShifter.unknown))
if self._calibration_inhibit and main_derived:
if self._calibration_inhibit and self._calibration_fault and main_derived:
self._calibration_rearm_required = True
elif self._calibration_rearm_required and fresh_edge and rearm_safe and self.settings.enabled:
self._calibration_rearm_required = False
if self._calibration_inhibit or self._calibration_rearm_required:
if self._calibration_fault or self._calibration_rearm_required:
self._record_latch_clear('calibration_inhibit')
self.allowed_latch = False
self._calibration_main = main
@@ -439,4 +433,4 @@ class AolCardIntent:
def output(self, CS) -> tuple[bool, bool, bool]:
driving = CS.gearShifter not in (GearShifter.neutral, GearShifter.park, GearShifter.reverse, GearShifter.unknown)
return (self.allowed_latch and self.settings.enabled and CS.canValid and driving,
self.pause_lateral, self.pause_longitudinal)
self.pause_lateral or self._calibration_inhibit, self.pause_longitudinal or self._calibration_inhibit)
@@ -299,14 +299,14 @@ def exercise_bolt_cancel_recovery(self, identity, removed, *, critical=None, com
events.add(getattr(EventName, communications))
if regen or brake:
events.add(EventName.pedalPressed)
if fault and critical in ('controlsMismatch', 'calibrationInvalid'):
if fault and critical in ('controlsMismatch', 'steerUnavailable'):
events.add(getattr(EventName, critical))
ctx['events'] = [int(event) for event in events.names]
if communication_loss:
self.assertTrue(events.contains(ET.NO_ENTRY), msg=ctx)
self.assertTrue(events.contains(ET.SOFT_DISABLE), msg=ctx)
self.assertEqual(events.contains(ET.IMMEDIATE_DISABLE),
bool(fault and critical in ('controlsMismatch', 'permanentEPS')), msg=ctx)
bool(fault and critical in ('controlsMismatch', 'steerUnavailable', 'permanentEPS')), msg=ctx)
if regen or brake:
self.assertIn(EventName.pedalPressed, events.names, msg=ctx)
self.assertTrue(events.contains(ET.USER_DISABLE), msg=ctx)
+46 -41
View File
@@ -275,14 +275,17 @@ class TestChryslerAol(unittest.TestCase):
intent.observe_calibration(cs, events=ready.to_msg())
self.assertFalse(intent.startup_calibration_pending)
self.assertTrue(intent.allowed_latch)
self.assertTrue(intent_disarming_fault(cp, cold.to_msg(), cs, startup_calibration_pending=False))
# A later calibration revocation is a real withdrawal, not another cold boot.
intent.update(cs, now_ns=1_010_000_000, fault_active=True)
self.assertFalse(intent_disarming_fault(cp, cold.to_msg(), cs, startup_calibration_pending=False))
# A later SOFT calibration warning pauses commands without erasing intent.
intent.update(cs, now_ns=1_010_000_000, fault_active=False)
self.assertTrue(intent.allowed_latch)
# A true immediate fault still requires a new driver gesture.
intent.update(cs, now_ns=1_020_000_000, fault_active=True)
self.assertFalse(intent.allowed_latch)
intent.update(cs, now_ns=1_020_000_000, fault_active=False)
intent.update(cs, now_ns=1_030_000_000, fault_active=False)
self.assertFalse(intent.allowed_latch)
def test_cold_readiness_never_exempts_other_faults_or_immediate_calibration(self):
def test_soft_readiness_and_true_immediate_calibration_remain_distinct(self):
from openpilot.cereal import log
from openpilot.selfdrive.selfdrived.events import Events
from openpilot.starpilot.aol.vehicle import intent_disarming_fault
@@ -292,7 +295,8 @@ class TestChryslerAol(unittest.TestCase):
log.OnroadEvent.EventName.doorOpen, log.OnroadEvent.EventName.canError):
events = Events()
events.add(name)
self.assertTrue(intent_disarming_fault(cp, events.to_msg(), cs, startup_calibration_pending=True), name)
self.assertEqual(intent_disarming_fault(cp, events.to_msg(), cs, startup_calibration_pending=True),
any(event.immediateDisable for event in events.to_msg()), name)
event = log.OnroadEvent.new_message(name=log.OnroadEvent.EventName.calibrationIncomplete, immediateDisable=True)
self.assertTrue(intent_disarming_fault(cp, [event], cs, startup_calibration_pending=True))
cs.steerFaultPermanent = True
@@ -304,39 +308,40 @@ class TestChryslerAol(unittest.TestCase):
from openpilot.cereal import log
from openpilot.selfdrive.selfdrived.events import Events
from openpilot.starpilot.car.chrysler.aol import cold_calibration_intent_current
cp = cp_for(CAR.JEEP_GRAND_CHEROKEE)
cs = state()
events = Events()
events.add(log.OnroadEvent.EventName.calibrationIncomplete)
axis = SimpleNamespace(intentProducerSessionId='card-1', intentIntegrityEpoch=0,
desiredLongitudinal=False, longitudinalActive=False, lateralActive=False,
desiredLateral=False, observedMonoTime=2_000_000_000,
faultReason='critical', faultEventMonoTime=1_900_000_000)
native = SimpleNamespace(pandaInventory=(object(),), requestedLongitudinal=False,
longitudinalAllowed=False, requestedLateral=False, lateralAllowed=False)
class Peers:
seen = valid = alive = {'onroadEvents': True}
logMonoTime = {'onroadEvents': 1_900_000_000}
def __getitem__(self, _):
return events.to_msg()
feedback = SimpleNamespace(failed=False, ever_lateral_active=False, signature=None, cold_main_wait_ns=0)
def observe():
return cold_calibration_intent_current(feedback, Peers(), cp, cs, axis, native, 2_010_000_000,
pending=True, producer='card-1')
self.assertTrue(observe())
for obj, field, value in ((feedback, 'failed', True), (feedback, 'ever_lateral_active', True),
(axis, 'intentProducerSessionId', 'restarted-card'), (axis, 'desiredLongitudinal', True),
(native, 'requestedLongitudinal', True), (native, 'lateralAllowed', True),
(cs, 'doorOpen', True), (cs, 'steeringDisengage', True)):
previous = getattr(obj, field)
setattr(obj, field, value)
self.assertFalse(observe(), field)
setattr(obj, field, previous)
for name in (log.OnroadEvent.EventName.canError, log.OnroadEvent.EventName.calibrationInvalid,
log.OnroadEvent.EventName.doorOpen):
events.add(name)
self.assertFalse(observe(), name)
events.clear()
for reason in ('critical', 'availabilityPause'):
cp = cp_for(CAR.JEEP_GRAND_CHEROKEE)
cs = state()
events = Events()
events.add(log.OnroadEvent.EventName.calibrationIncomplete)
Peers.logMonoTime['onroadEvents'] = 1
self.assertFalse(observe())
axis = SimpleNamespace(intentProducerSessionId='card-1', intentIntegrityEpoch=0,
desiredLongitudinal=False, longitudinalActive=False, lateralActive=False,
desiredLateral=False, observedMonoTime=2_000_000_000,
faultReason=reason, faultEventMonoTime=1_900_000_000)
native = SimpleNamespace(pandaInventory=(object(),), requestedLongitudinal=False,
longitudinalAllowed=False, requestedLateral=False, lateralAllowed=False)
class Peers:
seen = valid = alive = {'onroadEvents': True}
logMonoTime = {'onroadEvents': 1_900_000_000}
def __getitem__(self, _, events=events):
return events.to_msg()
feedback = SimpleNamespace(failed=False, ever_lateral_active=False, signature=None, cold_main_wait_ns=0)
def observe(feedback=feedback, cp=cp, cs=cs, axis=axis, native=native, Peers=Peers):
return cold_calibration_intent_current(feedback, Peers(), cp, cs, axis, native, 2_010_000_000,
pending=True, producer='card-1')
self.assertTrue(observe())
for obj, field, value in ((feedback, 'failed', True), (feedback, 'ever_lateral_active', True),
(axis, 'intentProducerSessionId', 'restarted-card'), (axis, 'desiredLongitudinal', True),
(native, 'requestedLongitudinal', True), (native, 'lateralAllowed', True),
(cs, 'doorOpen', True), (cs, 'steeringDisengage', True)):
previous = getattr(obj, field)
setattr(obj, field, value)
self.assertFalse(observe(), field)
setattr(obj, field, previous)
for name in (log.OnroadEvent.EventName.canError, log.OnroadEvent.EventName.calibrationInvalid,
log.OnroadEvent.EventName.doorOpen):
events.add(name)
self.assertFalse(observe(), name)
events.clear()
events.add(log.OnroadEvent.EventName.calibrationIncomplete)
Peers.logMonoTime['onroadEvents'] = 1
self.assertFalse(observe())
+13 -14
View File
@@ -490,7 +490,7 @@ class TestGmAol(unittest.TestCase):
events.add(log.OnroadEvent.EventName.selfdrivedLagging)
self.assertTrue(events.contains(ET.NO_ENTRY))
self.assertTrue(events.contains(ET.SOFT_DISABLE))
self.assertTrue(disarming_fault(events.to_msg(), cs)) # Other vehicle owners retain their existing law.
self.assertFalse(disarming_fault(events.to_msg(), cs)) # SOFT withdraws output while retaining driver intent.
for standard in (False, True):
with patch.object(selected, 'sm', {'onroadEvents': events.to_msg()}):
fault = selected.aol_disarming_fault(cs, 1_000_000_000, 1_000_000_001)
@@ -537,7 +537,7 @@ class TestGmAol(unittest.TestCase):
with OpenpilotPrefix(), patch.dict(os.environ, {'SIMULATION': '1', 'AOL_REPLAY_RUNTIME': '0'}):
settings = Params()
settings.put_bool('AlwaysOnLateral', True, block=True)
for extra in (log.OnroadEvent.EventName.controlsMismatch, log.OnroadEvent.EventName.processNotRunning, None):
for extra in (log.OnroadEvent.EventName.controlsMismatch, log.OnroadEvent.EventName.steerUnavailable, None):
with self.subTest(extra=extra):
selected = self.card(pedal_params(CAR.CHEVROLET_BOLT_CC_2018_2021, pedal=True, camera=True), settings)
cs = structs.CarState(canValid=True, gearShifter='drive', vEgo=20.)
@@ -893,7 +893,7 @@ class TestGmAol(unittest.TestCase):
self.assertEqual(any(requested), supported)
def test_calibration_recovery_requires_new_main_edge_through_actual_callers(self):
def test_calibration_pause_retains_intent_and_recovers_without_new_main_edge(self):
from openpilot.selfdrive.selfdrived.selfdrived import SelfdriveD
from openpilot.starpilot.aol.tests.test_runtime import sample_car_state
from openpilot.selfdrive.selfdrived.state import StateMachine
@@ -919,10 +919,10 @@ class TestGmAol(unittest.TestCase):
sd.sm = messaging.SubMaster(['aolIntentWire', 'aolSafetyWire', 'modelV2',
'extrinsicsCalibration', 'driverMonitoringState', 'pandaStates'])
cs = structs.CarState(canValid=True, vEgo=20., gearShifter='drive')
for tick, (phase, expected) in enumerate((('unbuckled', True), ('unbuckled', True), ('healthy', True), ('invalid', False), ('recovered', False),
('held', False), ('unmapped_lkas', False), ('unmapped_main', False),
('release_lkas', False), ('main_off', False), ('fault_edge', False),
('recovered_again', False), ('main_off', False), ('new_main', True),
for tick, (phase, expected) in enumerate((('unbuckled', True), ('unbuckled', True), ('healthy', True), ('invalid', False), ('recovered', True),
('held', True), ('unmapped_lkas', True), ('unmapped_main', True),
('release_lkas', True), ('main_off', False), ('fault_edge', False),
('recovered_again', True), ('main_off', False), ('new_main', True),
('temporary', False), ('healthy', True), ('reverse', False),
('healthy', True), ('model_stale', False), ('healthy', True))):
now = 1_000_000_000 + tick * 10_000_000
@@ -947,8 +947,7 @@ class TestGmAol(unittest.TestCase):
selected.aol_card_intent.update(cs)
selected.observe_aol_calibration(cs, now, False)
self.assertEqual(selected.aol_card_intent.allowed_latch,
phase not in ('invalid', 'recovered', 'held', 'unmapped_lkas', 'unmapped_main', 'release_lkas',
'main_off', 'fault_edge', 'recovered_again'))
phase != 'main_off')
intent = messaging.new_message('aolIntentWire', 0, valid=True, logMonoTime=now)
allowed, pause_lat, pause_long = selected.aol_card_intent.output(cs)
intent.aolIntentWire = encode_intent(IntentState('card', tick + 1, now, now, now + 30_000_000,
@@ -965,7 +964,7 @@ class TestGmAol(unittest.TestCase):
sd.sm.update_msgs(now / 1e9, [intent.as_reader(), receipt.as_reader(), model.as_reader(),
calibration.as_reader(), monitoring.as_reader()])
sd.aol_car_state_log_ns, sd.enabled, sd.active = now, False, False
sd.events.clear()
sd.events = events
if cs.seatbeltUnlatched:
sd.events.add(EventName.seatbeltNotLatched)
self.assertTrue(sd.events.contains(ET.NO_ENTRY))
@@ -1140,7 +1139,7 @@ class TestGmAol(unittest.TestCase):
self.exercise_bolt_cancel_recovery(identity, removed)
def test_bolt_cancel_fault_rearm(self):
for critical in ('controlsMismatch', 'calibrationInvalid', 'permanentEPS'):
for critical in ('controlsMismatch', 'steerUnavailable', 'permanentEPS'):
with self.subTest(critical=critical):
self.exercise_bolt_cancel_recovery(CAR.CHEVROLET_BOLT_CC_2018_2021, False, critical=critical)
@@ -1150,7 +1149,7 @@ class TestGmAol(unittest.TestCase):
self.exercise_bolt_cancel_recovery(CAR.CHEVROLET_BOLT_CC_2018_2021, False, communications=event)
def test_bolt_communications_preserves_critical_rearm(self):
for critical in ('controlsMismatch', 'calibrationInvalid', 'permanentEPS'):
for critical in ('controlsMismatch', 'steerUnavailable', 'permanentEPS'):
with self.subTest(critical=critical):
self.exercise_bolt_cancel_recovery(CAR.CHEVROLET_BOLT_CC_2018_2021, False,
critical=critical, communications='commIssueAvgFreq')
@@ -1384,7 +1383,7 @@ class TestGmAol(unittest.TestCase):
self.exercise_volt_ascm_acc_intent()
def test_volt_ascm_mixed_critical_acc_fault_requires_main_cycle(self):
for critical in ('controlsMismatch', 'calibrationInvalid', 'permanentEPS'):
for critical in ('controlsMismatch', 'steerUnavailable', 'permanentEPS'):
with self.subTest(critical=critical):
self.exercise_volt_ascm_acc_intent(critical=critical)
@@ -1475,7 +1474,7 @@ class TestGmAol(unittest.TestCase):
safety.safety_tick()
safety.set_aol_test_heartbeat(True)
events = car_events.update(cs, previous, structs.CarControl())
if fault and critical in ('controlsMismatch', 'calibrationInvalid'):
if fault and critical in ('controlsMismatch', 'steerUnavailable'):
events.add(getattr(EventName, critical))
self.assertEqual(EventName.accFaulted in events.names, fault)
if fault:
@@ -654,7 +654,7 @@ def test_actual_sd_belt_classification_wire_reaches_next_card_cycle(gesture, fau
now = NOW + tick * 10_000_000
current['now'], current['controls'] = now, False
cs = receive_card(now, gesture if tick < 3 else None, tick < 3, (tick + 1) % 4)
if tick == 2 and fault is not None:
if tick == 2 and fault == 'immediate':
assert card.aol_transport_feedback.failed and not card.aol_card_intent.allowed_latch
return
assert not card.aol_transport_feedback.failed and card.aol_card_intent.allowed_latch
@@ -679,8 +679,11 @@ def test_actual_sd_belt_classification_wire_reaches_next_card_cycle(gesture, fau
assert native.observedMonoTime == NOW and native.validUntilMonoTime == NOW + 200_000_000
assert native.requestedLateral and native.lateralAllowed and native.requestedLongitudinal and native.longitudinalAllowed
assert not axis.longitudinalActive and not command.longActive
if fault is not None:
if fault == 'immediate':
assert str(axis.faultReason) == 'critical'
elif fault == 'other_soft' and tick == 1:
assert str(axis.faultReason) == 'availabilityPause'
assert not axis.lateralActive and not command.latActive
else:
assert str(axis.faultReason) == 'none' and axis.lateralActive
assert not sd.enabled # Pedal/Cancel still disables ordinary LONG; rebuckling cannot resume it.
@@ -0,0 +1,161 @@
"""Parsed SDGM sources and serialized peers; native responses are controlled fixtures."""
from dataclasses import replace
import pytest
from opendbc.car.gm.tests.test_volt_sdgm_control import sdgm_params
from openpilot.cereal import messaging
from openpilot.common.params import Params
from openpilot.common.prefix import OpenpilotPrefix
from openpilot.selfdrive.selfdrived.events import Events, EventName
from openpilot.starpilot.aol.tests.test_gm_held_native import NOW, Peers, peers, _sdgm_pedal_state
from openpilot.starpilot.aol.transport_pause import TransportPauseFeedback
@pytest.mark.parametrize('c9', (False, True))
@pytest.mark.parametrize('warning', ('calibrationInvalid', 'lowMemory', 'processNotRunning', 'radarTempUnavailable'))
@pytest.mark.parametrize('native_state', ('held_full', 'withdrawn_long', 'all_off'))
def test_sdgm_warning_withdrawal_keeps_only_intent(c9, warning, native_state):
from openpilot.starpilot.aol.tests.test_gm import TestGmAol as GmFixture
with OpenpilotPrefix():
saved = Params()
saved.put_bool('AlwaysOnLateral', True, block=True)
card = GmFixture.card(sdgm_params(alpha=True, brake_c9=c9), saved)
try:
cp = card.CP
feedback = TransportPauseFeedback(cp)
cs = _sdgm_pedal_state(card, NOW, 0)
card.aol_card_intent.update(cs, fault_active=False, now_ns=NOW)
sm, native = peers(cp, card.slc_producer_session, NOW)
assert not feedback.observe(sm, cp, cs, NOW, latched=True, intent_producer_session=card.slc_producer_session)
assert feedback.lateral_baseline_ns == NOW
for tick in range(1, 6):
now = NOW + tick * 10_000_000
cs = _sdgm_pedal_state(card, now, tick)
controls = native_state == 'held_full'
current_native = replace(
native,
longitudinalAllowed=controls,
lateralAllowed=native_state != 'all_off',
requestedLateral=native_state != 'all_off',
requestedLongitudinal=native_state != 'all_off',
pandaInventory=tuple(replace(slot, controlsAllowed=controls) for slot in native.pandaInventory),
)
sm, _ = peers(cp, card.slc_producer_session, now, native=current_native, controls=controls, sequence=tick + 1, long_active=False)
axis = sm.messages['aolAxisState'].as_builder()
axis.aolAxisState.faultReason = 'availabilityPause'
axis.aolAxisState.faultEventMonoTime = now
axis.aolAxisState.desiredLateral = False
axis.aolAxisState.lateralActive = False
axis.aolAxisState.nativeAcknowledged = False
events = Events()
events.add(getattr(EventName, warning))
event = messaging.new_message('onroadEvents', len(events), valid=True, logMonoTime=now)
event.onroadEvents = events.to_msg()
sm = Peers([sm.messages['aolSafetyWire'], sm.messages['pandaStates'], axis.as_reader(), event.as_reader()])
if not c9 and warning == 'lowMemory' and native_state == 'all_off' and tick == 1:
_check_warning_guards(feedback, sm, cp, cs, now, card.slc_producer_session)
assert not feedback.observe(sm, cp, cs, now, latched=True, intent_producer_session=card.slc_producer_session), (native_state, feedback.__dict__)
assert feedback.ordinary_warning_qualified and not feedback.failed
card.aol_card_intent.update(cs, fault_active=False, now_ns=now)
assert card.aol_card_intent.allowed_latch
# Retention does not write back or manufacture native authority.
assert current_native == replace(
native,
longitudinalAllowed=controls,
lateralAllowed=native_state != 'all_off',
requestedLateral=native_state != 'all_off',
requestedLongitudinal=native_state != 'all_off',
pandaInventory=tuple(replace(slot, controlsAllowed=controls) for slot in native.pandaInventory),
)
finally:
card.vehicle_startup.close()
def _check_warning_guards(feedback, sm, cp, cs, now, producer):
from copy import copy
from openpilot.starpilot.aol.wire import decode_safety, encode_safety
for fault in (
'unarmed',
'producer',
'epoch',
'session',
'source_stale',
'source_future',
'can_invalid',
'can_timeout',
'permanent_eps',
'acc_fault',
'door',
'steering_off',
'main_off',
'gear_unknown',
'native_expired',
'native_future',
'inventory',
'native_rx',
'heartbeat',
'panda_rx',
'panda_heartbeat',
'immediate',
'event_expired',
'event_future',
):
tested = copy(feedback)
messages = {name: event.as_builder() for name, event in sm.messages.items()}
state = cs.as_reader().as_builder()
axis = messages['aolAxisState'].aolAxisState
native = decode_safety(messages['aolSafetyWire'].aolSafetyWire)
latched = fault != 'unarmed'
if fault == 'producer':
axis.intentProducerSessionId = 'different'
if fault == 'epoch':
axis.intentIntegrityEpoch += 1
if fault == 'session':
axis.sessionId = 'different'
if fault == 'source_stale':
axis.sourceCarStateMonoTime = now - 30_000_001
if fault == 'source_future':
axis.sourceCarStateMonoTime = now + 1
if fault == 'can_invalid':
state.canValid = False
if fault == 'can_timeout':
state.canTimeout = True
if fault == 'permanent_eps':
state.steerFaultPermanent = True
if fault == 'acc_fault':
state.accFaulted = True
if fault == 'door':
state.doorOpen = True
if fault == 'steering_off':
state.steeringDisengage = True
if fault == 'main_off':
state.cruiseState.available = False
if fault == 'gear_unknown':
state.gearShifter = 'unknown'
if fault == 'native_expired':
native = replace(native, validUntilMonoTime=now - 1)
if fault == 'native_future':
native = replace(native, observedMonoTime=now + 1, validUntilMonoTime=now + 200_000_001)
messages['aolSafetyWire'].logMonoTime = now + 1
if fault == 'inventory':
native = replace(native, pandaInventory=tuple(replace(slot, hardwareSerial='other') for slot in native.pandaInventory))
if fault == 'native_rx':
native = replace(native, pandaInventory=tuple(replace(slot, safetyRxChecksInvalid=True) for slot in native.pandaInventory))
if fault == 'heartbeat':
native = replace(native, pandaInventory=tuple(replace(slot, heartbeatLost=True) for slot in native.pandaInventory))
if fault == 'panda_rx':
messages['pandaStates'].pandaStates[0].safetyRxChecksInvalid = True
if fault == 'panda_heartbeat':
messages['pandaStates'].pandaStates[0].heartbeatLost = True
if fault == 'immediate':
messages['onroadEvents'].onroadEvents[0].immediateDisable = True
if fault == 'event_expired':
messages['onroadEvents'].logMonoTime = now - 1_500_000_001
if fault == 'event_future':
messages['onroadEvents'].logMonoTime = now + 1
messages['aolSafetyWire'].aolSafetyWire = encode_safety(native)
shadow = Peers([event.as_reader() for event in messages.values()])
assert not tested._ordinary_warning_intent_current(shadow, cp, state, axis, now, now, latched=latched, producer=producer), fault
@@ -19,7 +19,7 @@ def test_independent_aol_low_memory_withdrawal_selects_takeover_alert(identity):
assert axis.lateralActive and command.latActive and not host.enabled
axis, host, command = step(20, critical_event=log.OnroadEvent.EventName.lowMemory)
assert not axis.lateralActive and not command.latActive and not host.enabled
assert sd.aol_fault_reason == 'critical'
assert sd.aol_fault_reason == 'availabilityPause'
state = sd.pm.messages['selfdriveState'].selfdriveState
assert state.alertType == 'lowMemory/softDisable', state.to_dict()
assert state.alertStatus == log.SelfdriveState.AlertStatus.critical
+32 -16
View File
@@ -282,7 +282,7 @@ class CardIntentTests(unittest.TestCase):
self.assertTrue(direct.lateral_active)
self.assertFalse(direct.desired_longitudinal)
events.add(log.OnroadEvent.EventName.doorOpen)
self.assertTrue(disarming_fault(events.to_msg(), state))
self.assertFalse(disarming_fault(events.to_msg(), state))
self.assertTrue(aol_no_entry(events.names, state, paddle_only_cancel=False))
def test_temporary_eps_and_known_gears_preserve_latch_with_zero_steering(self):
@@ -328,7 +328,7 @@ class CardIntentTests(unittest.TestCase):
dm_lockout=False, pause_brake_mps=0)
self.assertTrue(decision.lateral_active)
def test_calibration_rearm_follows_live_mapping_without_arming_on_change(self):
def test_calibration_pause_retains_intent_and_mapping_change_cannot_rearm(self):
from dataclasses import replace
from openpilot.starpilot.aol.intent import AOL_TOGGLE
owner = AolCardIntent(AolSettings(True, 0., 0, 0, (0, 0, 0), (0, 0, 0)))
@@ -340,17 +340,26 @@ class CardIntentTests(unittest.TestCase):
invalid = Events()
invalid.add(log.OnroadEvent.EventName.calibrationInvalid)
owner.observe_calibration(state, events=invalid.to_msg())
self.assertFalse(owner.allowed_latch)
self.assertTrue(owner.allowed_latch)
self.assertEqual(owner.output(state)[1:], (True, True))
owner.settings = replace(owner.settings, lkas_action=AOL_TOGGLE)
owner.update(state)
owner.observe_calibration(state, events=invalid.to_msg())
self.assertFalse(owner.allowed_latch)
owner.update(state)
owner.observe_calibration(state, events=[])
self.assertFalse(owner.allowed_latch) # The mapping change is not an arming gesture.
self.assertTrue(owner.allowed_latch)
self.assertEqual(owner.output(state)[1:], (False, False))
state.buttonEvents = [car.CarState.ButtonEvent(type=car.CarState.ButtonEvent.Type.lkas, pressed=True)]
owner.update(state)
self.assertFalse(owner.allowed_latch)
state.buttonEvents = [car.CarState.ButtonEvent(type=car.CarState.ButtonEvent.Type.lkas, pressed=False)]
owner.update(state)
state.buttonEvents = []
owner.settings = replace(owner.settings, main_action=AOL_TOGGLE)
owner.update(state)
owner.settings = replace(owner.settings, main_action=0)
owner.update(state)
self.assertFalse(owner.allowed_latch) # Changing another mapping is not an arming gesture.
state.buttonEvents = [car.CarState.ButtonEvent(type=car.CarState.ButtonEvent.Type.lkas, pressed=True)]
owner.update(state)
owner.observe_calibration(state, events=[])
self.assertTrue(owner.allowed_latch)
def test_low_speed_brake_suppresses_output_without_destroying_explicit_latch(self):
@@ -382,7 +391,8 @@ class CardIntentTests(unittest.TestCase):
log.OnroadEvent.EventName.overheat):
events = Events()
events.add(name)
self.assertTrue(disarming_fault(events.to_msg(), state))
self.assertEqual(disarming_fault(events.to_msg(), state),
any(event.immediateDisable for event in events.to_msg()))
events = Events()
events.add(log.OnroadEvent.EventName.wrongGear)
self.assertFalse(disarming_fault(events.to_msg(), state)) # Events may lag the return to Drive.
@@ -2124,32 +2134,38 @@ class UiProcessIntentTests(unittest.TestCase):
owner.update(state)
self.assertFalse(owner.allowed_latch) # Physical CANCEL remains intentional withdrawal.
def test_context_denies_unknown_mixed_stale_missing_and_critical_faults(self):
def test_soft_process_context_guards_remain_distinct_from_immediate_faults(self):
card = self.make_card()
state = car_state()
card.sm.events.add(log.OnroadEvent.EventName.processNotRunning)
event_ns = 1_020_000_000
self.assertTrue(card.aol_disarming_fault(state, event_ns, event_ns + 1))
self.assertFalse(card.aol_disarming_fault(state, event_ns, event_ns + 1))
self.assertFalse(card.aol_process_fault_context.temporary_ui_failure(event_ns, event_ns + 1))
for failures in ({'controlsd'}, {'unknown_service'}, {'ui', 'modeld'}, set()):
with self.subTest(failures=failures):
card.aol_process_fault_context.history.clear()
self.observe(card, 1_000_000_000, failures)
self.assertTrue(card.aol_disarming_fault(state, event_ns, event_ns + 1))
self.assertFalse(card.aol_disarming_fault(state, event_ns, event_ns + 1))
self.assertFalse(card.aol_process_fault_context.temporary_ui_failure(event_ns, event_ns + 1))
self.observe(card, 1_100_000_000, {'ui'})
self.assertTrue(card.aol_disarming_fault(state, 1_120_000_000, 2_600_000_001))
self.assertFalse(card.aol_disarming_fault(state, 1_120_000_000, 2_600_000_001))
self.assertFalse(card.aol_process_fault_context.temporary_ui_failure(1_120_000_000, 2_600_000_001))
self.observe(card, 1_150_000_000, {'ui', 'modeld'})
self.assertTrue(card.aol_disarming_fault(state, 1_120_000_000, 1_160_000_000))
self.assertFalse(card.aol_disarming_fault(state, 1_120_000_000, 1_160_000_000))
self.assertFalse(card.aol_process_fault_context.temporary_ui_failure(1_120_000_000, 1_160_000_000))
card.aol_process_fault_context.history.clear()
self.observe(card, 1_100_000_000, {'ui'})
card.sm.processes[0].shouldBeRunning = False
card.aol_process_fault_context.observe(card.sm, 1_120_000_000)
self.assertTrue(card.aol_disarming_fault(state, 1_120_000_000, 1_120_000_001))
self.assertFalse(card.aol_disarming_fault(state, 1_120_000_000, 1_120_000_001))
self.assertFalse(card.aol_process_fault_context.temporary_ui_failure(1_120_000_000, 1_120_000_001))
self.observe(card, 1_100_000_000, {'ui'})
for source_flag in ('seen', 'valid', 'alive'):
with self.subTest(source_flag=source_flag):
getattr(card.sm, source_flag)['managerState'] = False
card.aol_process_fault_context.observe(card.sm, 1_120_000_000)
self.assertTrue(card.aol_disarming_fault(state, 1_120_000_000, 1_120_000_001))
self.assertFalse(card.aol_disarming_fault(state, 1_120_000_000, 1_120_000_001))
self.assertFalse(card.aol_process_fault_context.temporary_ui_failure(1_120_000_000, 1_120_000_001))
getattr(card.sm, source_flag)['managerState'] = True
self.observe(card, 1_100_000_000, {'ui'})
for name in (log.OnroadEvent.EventName.controlsMismatch, log.OnroadEvent.EventName.steerUnavailable):
@@ -299,6 +299,8 @@ class TransportPauseFeedback:
self.availability_producer = ''
self.availability_epoch = None
self.source_retention_started_ns = 0
self.ordinary_warning_retained = False
self.ordinary_warning_qualified = False
self.healthy_feedback_ns = 0
self.healthy_native_ns = 0
self.healthy_inventory = None
@@ -358,6 +360,69 @@ class TransportPauseFeedback:
return None
return native
def _ordinary_warning_intent_current(self, sm, cp, cs, axis, stamp, now_ns, *, latched, producer):
"""Retain armed intent through a fresh SOFT withdrawal; grant no output."""
from openpilot.starpilot.aol.vehicle import policy_for
from openpilot.starpilot.nostalgia import physical_cancel
from opendbc.car import structs
if (not latched or self.failed or self.paused or self.pending_event or self.retention_started_ns or
not self.session or not self.lateral_baseline_ns or not self.lateral_inventory or
not policy_for(cp).intent_supported or
policy_for(cp).cruise_main_required and not cs.cruiseState.available or
str(axis.sessionId) != self.session or
not producer or producer != self.availability_producer or axis.intentProducerSessionId != producer or
axis.intentIntegrityEpoch != self.availability_epoch or
not cs.canValid or cs.canTimeout or cs.steerFaultPermanent or cs.accFaulted or cs.doorOpen or
cs.steeringDisengage or
cs.gearShifter not in (structs.CarState.GearShifter.drive, structs.CarState.GearShifter.sport,
structs.CarState.GearShifter.low, structs.CarState.GearShifter.brake) or
not 0 < self.lateral_source_ns <= axis.sourceCarStateMonoTime <= stamp <= now_ns or
now_ns - axis.sourceCarStateMonoTime > AXIS_MAX_AGE_NS or
str(axis.faultReason) not in ('none', 'availabilityPause') or
not sm.seen['onroadEvents'] or not sm.valid['onroadEvents'] or not sm.alive['onroadEvents']):
return False
event_ns = int(sm.logMonoTime['onroadEvents'])
events = sm['onroadEvents']
if (not 0 < event_ns <= now_ns or now_ns - event_ns > 1_500_000_000 or
any(event.immediateDisable for event in events)):
return False
warning = any(event.softDisable for event in events)
recovering = bool(getattr(self, 'ordinary_warning_retained', False) and not warning)
if not warning and not recovering:
return False
stock_user = stock_lateral_user_event(cp)
# Physical withdrawals still go through the family button/pedal owner.
# This helper cannot rearm a latch that that owner explicitly clears.
if any(event.userDisable and event.name != stock_user and not (
event.name == log.OnroadEvent.EventName.pedalPressed and (cs.brakePressed or cs.regenBraking) or
event.name == log.OnroadEvent.EventName.buttonCancel and (physical_cancel(cs, cp) or
cp.brand == 'gm' and 0 < self.gm_withdrawal_ns == event_ns <= now_ns and
now_ns - event_ns <= SAFETY_MAX_AGE_NS)) for event in events):
return False
prior = self.signature
active_join = bool(warning and prior is not None and str(axis.faultReason) == 'none' and
axis.lateralActive and axis.desiredLateral and axis.nativeAcknowledged and
prior[9] and prior[11] and prior[13] and prior[14:16] == (producer, self.availability_epoch) and
0 < prior[8] <= now_ns <= prior[4] and now_ns - prior[8] <= AXIS_MAX_AGE_NS and
0 < axis.faultEventMonoTime < event_ns and now_ns - event_ns <= AXIS_MAX_AGE_NS)
withdrawn = bool(not axis.lateralActive and not axis.longitudinalActive and
not axis.desiredLongitudinal and
(warning and str(axis.faultReason) == 'availabilityPause' and axis.faultEventMonoTime == event_ns <= stamp or
recovering and str(axis.faultReason) == 'none'))
if not active_join and not withdrawn:
return False
native = _configured_transport_native(sm, cp, self.session, now_ns,
allow_restricted_lateral=withdrawn or bool(cs.steerFaultTemporary),
require_longitudinal=not withdrawn)
if (native is None or not native.pandaInventory or
(native.pandaSerial, tuple(slot.hardwareSerial for slot in native.pandaInventory)) != self.lateral_inventory or
native.observedMonoTime < self.lateral_native_ns or
active_join and not (native.requestedLateral and native.lateralAllowed)):
return False
self.ordinary_warning_retained = True
return True
def _temporary_host_intent_current(self, sm, cp, cs, axis, stamp, now_ns, *, latched, producer):
"""A fresh inactive proof may retain intent, never native/output authority."""
from openpilot.starpilot.nostalgia import physical_cancel
@@ -1083,6 +1148,7 @@ class TransportPauseFeedback:
def observe(self, sm, cp, cs, now_ns, *, latched=True, cancel_source_ns=0, brake_source_ns=0,
gm_withdrawal_source_ns=0, intent_producer_session='', main_arm_ns=0, startup_calibration_pending=False, intent_paused=False):
self.ordinary_warning_qualified = False
self.temporary_host_qualified = False
self.qualified = False
self.awaiting = False
@@ -1091,6 +1157,7 @@ class TransportPauseFeedback:
try:
axis = sm['aolAxisState']
if not latched or cp_identity(cp) != self.cp or self.session and str(axis.sessionId) != self.session:
self.ordinary_warning_retained = False
self.temporary_eps_event_ns = 0
self.ccnc_main_off_source_ns = 0
self.cold_park_waiting = False
@@ -1197,6 +1264,11 @@ class TransportPauseFeedback:
axis.desiredLateral and not axis.lateralActive):
self.failed = True
return True
if current and continuity and self._ordinary_warning_intent_current(sm, cp, cs, axis, stamp, now_ns,
latched=latched, producer=intent_producer_session):
self.ordinary_warning_qualified = any(event.softDisable for event in sm['onroadEvents'])
self.sequence, self.signature = int(axis.sequence), signature
return False
if current and continuity and self._temporary_host_intent_current(sm, cp, cs, axis, stamp, now_ns,
latched=latched, producer=intent_producer_session):
self.sequence, self.signature = int(axis.sequence), signature
@@ -1469,6 +1541,7 @@ class TransportPauseFeedback:
self.lateral_only_baseline = bool(lateral_baseline and self.availability_producer and
not axis.desiredLongitudinal and not axis.longitudinalActive and not native.requestedLongitudinal)
if lateral_baseline and native is not None:
self.ordinary_warning_retained = False
self.ever_lateral_active = True
self.lag_withdrawal_ns = 0
self.cold_main_wait_ns = 0
+2 -2
View File
@@ -86,7 +86,7 @@ def cold_calibration_intent_current(feedback, sm, cp, cs, axis, native, now_ns,
return False
cold = any(e.name == cal and e.softDisable and not e.immediateDisable for e in events)
if cold:
if (not pending or str(axis.faultReason) != 'critical' or not 0 < axis.faultEventMonoTime <= event_ns or
if (not pending or str(axis.faultReason) not in ('critical', 'availabilityPause') or not 0 < axis.faultEventMonoTime <= event_ns or
axis.desiredLateral or native.requestedLateral or native.lateralAllowed):
return False
feedback.cold_main_wait_ns = int(axis.observedMonoTime)
@@ -94,7 +94,7 @@ def cold_calibration_intent_current(feedback, sm, cp, cs, axis, native, now_ns,
# The old OFF response may precede the ready event. Its original transport
# and native leases are validated by the caller; this bounded join arms none.
return bool(0 < feedback.cold_main_wait_ns <= now_ns <= feedback.cold_main_wait_ns + SAFETY_MAX_AGE_NS and
str(axis.faultReason) in ('critical', 'none') and
str(axis.faultReason) in ('critical', 'availabilityPause', 'none') and
(not native.requestedLateral and not native.lateralAllowed or
str(axis.faultReason) == 'none' and axis.desiredLateral and
native.requestedLateral and native.lateralAllowed))
@@ -263,7 +263,7 @@ class TestVoltCc(unittest.TestCase):
with self.assertRaises(Registered):
Car(CI=ci, RI=RadarInterface(ci.CP))
self.assertEqual(len(captured), 1)
self.assertEqual(set(captured[0]), {'pandaStates', 'carControl', 'onroadEvents', 'deviceState'})
self.assertEqual(set(captured[0]), {'pandaStates', 'carControl', 'onroadEvents', 'deviceState', 'autoSteeringLearning'})
def test_actual_card_transport_epoch_and_individual_source_loss(self):
controls, card, ci, packer, sent, base, offset = fixture()
+1 -1
View File
@@ -1 +1 @@
{"protected_inventory_sha256": "42fc1f2e5fa03b715453bd816ac12bd7d82e88747c87f2bac2b229f4f1d134ba", "schema_version": 1}
{"protected_inventory_sha256": "fca1c3fd9221979c32c2dd4081b050561aca2b12c58936388a543cb94e915ef4", "schema_version": 1}
+1
View File
@@ -41,6 +41,7 @@ AOL_LIFECYCLE_TARGETS = (
"openpilot/starpilot/aol/tests/test_gm_steering_receive_order.py",
"openpilot/starpilot/aol/tests/test_gm_main_response.py",
"openpilot/starpilot/aol/tests/test_gm_held_native.py",
"openpilot/starpilot/aol/tests/test_gm_ordinary_warning.py",
"openpilot/starpilot/aol/tests/test_gm_startup_main.py",
"openpilot/starpilot/aol/tests/test_gm_pedal_order.py",
"openpilot/starpilot/aol/tests/test_gm_cancel_remap.py",