Files
StarPilot/openpilot/starpilot/aol/tests/test_feedback_intent_boundary.py
T
2026-10-16 08:27:54 -05:00

132 lines
6.7 KiB
Python

import unittest
from unittest import mock
from openpilot.cereal import log, messaging
from openpilot.common.prefix import OpenpilotPrefix
from openpilot.selfdrive.selfdrived.events import Events
from openpilot.starpilot.aol.runtime import current_axis
from openpilot.starpilot.aol.tests import test_ioniq_availability as availability
from openpilot.starpilot.car.gm.aol import intent_disarming_fault
class TestFeedbackIntentBoundary(unittest.TestCase):
_authority_callers = availability.IoniqAvailabilityTests._authority_callers
_ioniq_availability_callers = availability.IoniqAvailabilityTests._ioniq_availability_callers
def accepted_long_output(self):
sd, controls, step, card, receive = self._ioniq_availability_callers()
axis, _, command = step(2)
self.assertTrue(axis.lateralActive and axis.longitudinalActive)
self.assertTrue(command.latActive and command.longActive)
proof = sd.pm.messages['aolAxisState']
self.assertTrue(receive(10_020_000_000, axis=proof, event=sd.pm.messages['onroadEvents']))
self.assertEqual(card.aol_transport_feedback.session, axis.sessionId)
self.assertFalse(card.aol_transport_feedback.failed)
return sd, controls, step, card, receive, proof
@staticmethod
def event_at(now, name=None):
events = Events()
if name is not None:
events.add(name)
message = messaging.new_message('onroadEvents', len(events), valid=True, logMonoTime=now)
message.onroadEvents = events.to_msg()
return message.as_reader()
def assert_expired_commands_off(self, controls, proof, now):
controls.sm.update_msgs(now / 1e9, [proof])
self.assertIsNone(current_axis(controls.sm, now_ns=now))
with mock.patch('openpilot.selfdrive.controls.controlsd.time.monotonic_ns', return_value=now):
command, _ = controls.state_control()
self.assertFalse(command.latActive or command.longActive)
def test_expired_output_does_not_create_driver_intent_fault(self):
with OpenpilotPrefix():
sd, controls, step, card, receive, proof = self.accepted_long_output()
# Advance healthy CAN/native through the real publisher, but retain the
# independently sampled old output at Card. No fault event occurred.
step(4, offset_ns=5_000_000)
now = 10_051_000_000
self.assertGreater(now, proof.aolAxisState.validUntilMonoTime)
empty = self.event_at(now)
self.assertFalse(intent_disarming_fault(card.CP, empty.onroadEvents, availability.car_state()))
self.assert_expired_commands_off(controls, proof, now)
self.assertTrue(receive(now, axis=proof, event=empty))
self.assertFalse(card.aol_transport_feedback.failed)
self.assertTrue(card.aol_card_intent.allowed_latch)
self.assertFalse(card.aol_transport_feedback.qualified or card.aol_transport_feedback.awaiting)
def test_absent_output_does_not_create_driver_intent_fault(self):
with OpenpilotPrefix():
_, controls, step, card, receive, proof = self.accepted_long_output()
step(4, offset_ns=5_000_000)
now = 10_051_000_000
self.assert_expired_commands_off(controls, proof, now)
# Missing output grants nothing; an empty actual event list is not a fault.
card.sm.valid['aolAxisState'] = False
self.assertTrue(receive(now, event=self.event_at(now)))
self.assertTrue(card.aol_card_intent.allowed_latch)
self.assertFalse(card.aol_transport_feedback.failed)
def test_expired_output_never_hides_native_identity_or_real_faults(self):
for fault in ('native_missing', 'native_denied', 'can_invalid', 'permanent_eps',
'session_restart', 'producer_restart', 'integrity_epoch', 'configuration',
'soft_event', 'immediate_event'):
with self.subTest(fault=fault), OpenpilotPrefix():
sd, controls, step, card, receive, proof = self.accepted_long_output()
kwargs = {}
if fault == 'native_missing':
kwargs['native_valid'] = False
elif fault == 'native_denied':
kwargs['allowed'] = False
step(4, offset_ns=5_000_000, **kwargs)
now = 10_051_000_000
changed = proof.as_builder()
state = availability.car_state()
name = None
if fault == 'can_invalid':
state.canValid = False
elif fault == 'permanent_eps':
state.steerFaultPermanent = True
elif fault == 'session_restart':
changed.aolAxisState.sessionId = 'restarted-selfdrive'
changed.aolAxisState.faultSessionId = 'restarted-selfdrive'
elif fault == 'producer_restart':
changed.aolAxisState.intentProducerSessionId = 'restarted-card'
elif fault == 'integrity_epoch':
changed.aolAxisState.intentIntegrityEpoch += 1
elif fault == 'configuration':
pandas = messaging.new_message('pandaStates', len(sd.sm['pandaStates']), valid=True,
logMonoTime=sd.sm.logMonoTime['pandaStates'])
pandas.pandaStates = sd.sm['pandaStates']
pandas.pandaStates[-1].safetyParam ^= 1
sd.sm.update_msgs(now / 1e9, [pandas.as_reader()])
elif fault == 'soft_event':
name = log.OnroadEvent.EventName.commIssue
elif fault == 'immediate_event':
name = log.OnroadEvent.EventName.steerUnavailable
self.assert_expired_commands_off(controls, changed.as_reader(), now)
self.assertFalse(receive(now, axis=changed.as_reader(), event=self.event_at(now, name), state=state))
self.assertFalse(card.aol_card_intent.allowed_latch)
def test_sustained_brake_refreshes_healthy_lateral_baseline_before_output_gap(self):
with OpenpilotPrefix():
sd, controls, step, card, receive = self._ioniq_availability_callers()
for tick in range(2, 32):
axis, _, command = step(tick, brake=True, native_request_long=False)
self.assertTrue(axis.lateralActive and command.latActive)
self.assertFalse(axis.longitudinalActive or command.longActive)
self.assertTrue(receive(10_000_000_000 + tick * 10_000_000,
axis=sd.pm.messages['aolAxisState'], event=sd.pm.messages['onroadEvents'],
state=availability.car_state(brake=True)))
proof = sd.pm.messages['aolAxisState']
deadline = proof.aolAxisState.validUntilMonoTime
now = deadline + 2_477_787
step(34, offset_ns=2_477_787, brake=True, native_request_long=False)
self.assert_expired_commands_off(controls, proof, now)
self.assertTrue(receive(now, axis=proof, event=sd.pm.messages['onroadEvents'],
state=availability.car_state(brake=True)))
self.assertTrue(card.aol_card_intent.allowed_latch)
self.assertFalse(card.aol_transport_feedback.failed)
self.assertEqual(proof.aolAxisState.validUntilMonoTime, deadline)