Keep Ford source fault alerts on the ordinary timer

This commit is contained in:
firestar5683
2026-11-10 23:07:59 -06:00
parent 8507c129c7
commit 1d4bd99a02
4 changed files with 41 additions and 12 deletions
+12 -4
View File
@@ -1138,8 +1138,16 @@ class SelfdriveD:
clear_event_types.add(ET.NO_ENTRY)
pers = LONGITUDINAL_PERSONALITY_MAP[self.personality]
soft_disable_time = self.state_machine.soft_disable_timer
if (getattr(self, 'aol_ordinary_source_pause', False) and self.enabled and
self.state_machine.state in (log.SelfdriveState.OpenpilotState.enabled, log.SelfdriveState.OpenpilotState.overriding) and
availability_only(self.events.to_msg())):
# A publication-time fault precedes the next ordinary timer transition.
# Present its initial soft warning without ticking or resetting that timer.
from openpilot.selfdrive.selfdrived.state import SOFT_DISABLE_TIME
soft_disable_time = int(SOFT_DISABLE_TIME / DT_CTRL)
alerts = self.events.create_alerts(self.state_machine.current_alert_types, [self.CP, CS, self.sm, self.is_metric,
self.state_machine.soft_disable_timer, pers])
soft_disable_time, pers])
if getattr(self, 'aol_lag_takeover_alert', False) and not self.events.contains(ET.IMMEDIATE_DISABLE):
lag = Events(self.events.event_rules)
lag.add(EventName.selfdrivedLagging)
@@ -1306,10 +1314,10 @@ class SelfdriveD:
self.aol_fault_reason = 'availabilityPause'
self.aol_car_state_log_ns = 0
self.conditional_car_state_valid = False
# A late source fault still enters the real state machine before alerts
# consume its timer; appending SOFT_DISABLE alone leaves an expired timer.
# If publication crosses the cutoff after StateMachine ran, emit the fault
# now; its existing next-cycle transition owns the timer and driver state.
if self.enabled and ET.SOFT_DISABLE not in self.state_machine.current_alert_types:
self.enabled, self.active = self.state_machine.update(self.events)
self.state_machine.current_alert_types.append(ET.SOFT_DISABLE)
return True
def _expire_axis_source(self, CS, now_ns):
@@ -85,20 +85,37 @@ def test_ford_long_only_health_and_proof_refusals_remain_immediate(fault):
assert not axis.lateralActive and not axis.longitudinalActive
def test_late_publication_fault_starts_real_soft_disable_timer():
def test_late_publication_fault_preserves_timer_and_uses_initial_soft_warning():
with ExitStack() as cleanup:
sd, _, drive = callers(cleanup)
# The last valid axis proof expires at +30ms; output must still withdraw.
now = sd.aol_car_state_log_ns + 30_000_001
with patch('openpilot.selfdrive.selfdrived.selfdrived.time.monotonic_ns', return_value=now):
assert sd.retain_ordinary_source_state(sd.CS_prev, now)
assert sd.state_machine.state == log.SelfdriveState.OpenpilotState.softDisabling, 'LATE_SOURCE_FAULT_TIMER'
assert 0 < sd.state_machine.soft_disable_timer <= 300
with patch.object(sd.state_machine, 'update', wraps=sd.state_machine.update) as updates:
assert sd.retain_ordinary_source_state(sd.CS_prev, now)
assert updates.call_count == 0
assert sd.state_machine.state == log.SelfdriveState.OpenpilotState.enabled, 'LATE_SOURCE_SINGLE_TRANSITION'
assert sd.state_machine.soft_disable_timer == 0
sd.update_alerts(sd.CS_prev)
assert log.OnroadEvent.EventName.commIssue in sd.events.names
assert not sd.aol_axis_decision.lateral_active and not sd.aol_axis_decision.longitudinal_active
assert sd.AM.current_alert.alert_text_1 == 'TAKE CONTROL IMMEDIATELY'
assert sd.AM.current_alert.alert_status == log.SelfdriveState.AlertStatus.userPrompt
assert sd.AM.current_alert.alert_status == log.SelfdriveState.AlertStatus.userPrompt, 'LATE_SOURCE_WARNING_SEVERITY'
assert sd.AM.current_alert.audible_alert == log.SelfdriveState.AudibleAlert.warningSoft
assert sd.AM.current_alert.duration == int(2. / DT_CTRL)
assert sd.AM.current_alert.alert_text_2 == 'Communication Issue Between Processes'
@pytest.mark.parametrize('timer', (40, 0))
def test_started_or_expired_countdown_keeps_immediate_takeover(timer):
with ExitStack() as cleanup:
sd, _, _ = callers(cleanup)
now = sd.aol_car_state_log_ns + 30_000_001
with patch('openpilot.selfdrive.selfdrived.selfdrived.time.monotonic_ns', return_value=now):
assert sd.retain_ordinary_source_state(sd.CS_prev, now)
sd.state_machine.state = log.SelfdriveState.OpenpilotState.softDisabling
sd.state_machine.soft_disable_timer = timer
sd.update_alerts(sd.CS_prev)
assert sd.state_machine.soft_disable_timer == timer
assert sd.AM.current_alert.alert_status == log.SelfdriveState.AlertStatus.critical
assert sd.AM.current_alert.audible_alert == log.SelfdriveState.AudibleAlert.warningImmediate
@@ -22,8 +22,12 @@ def car_state(*, brake=False):
def assert_ordinary_source_pause(sd, *, publication_only=False):
"""Host loss withdraws commands while the existing state machine owns life."""
assert sd.enabled and sd.active
assert sd.state_machine.state == log.SelfdriveState.OpenpilotState.softDisabling
assert 0 < sd.state_machine.soft_disable_timer <= 300
if publication_only:
assert sd.state_machine.state == log.SelfdriveState.OpenpilotState.enabled
assert sd.state_machine.soft_disable_timer == 0
else:
assert sd.state_machine.state == log.SelfdriveState.OpenpilotState.softDisabling
assert 0 < sd.state_machine.soft_disable_timer <= 300
assert log.OnroadEvent.EventName.commIssue in sd.events.names
assert log.OnroadEvent.EventName.controlsMismatch not in sd.events.names
assert not sd.events.contains(ET.ENABLE)
+1 -1
View File
@@ -1 +1 @@
{"protected_inventory_sha256": "9aace2884a3f72c6099f4dc83984ed6d3ec9178f4609d3588db782d800e9f8c7", "schema_version": 1}
{"protected_inventory_sha256": "83b21005d009cbd5a2c0711b8e9647874520d9553960cf99eb78a3ff34550c3e", "schema_version": 1}