Files
StarPilot/openpilot/starpilot/aol/tests/test_intent_projection.py
T
firestar5683 80fbb4f6db Feature registration
Register vehicle controls, planner modes, UI actions and Galaxy services after their providers exist. Wire the shared runtime entrypoints and cover their combined behavior.

Co-authored-by: Adeeb Shihadeh <adeebshihadeh@gmail.com>
Co-authored-by: Daniel Koepping <elkoled@gmail.com>
Co-authored-by: Danny <dannyirwin31@yahoo.com>
Co-authored-by: DimitriChrysafis <Dimitri.chrysafis@gmail.com>
Co-authored-by: Dom <168790843+firestar5683@users.noreply.github.com>
Co-authored-by: Harald Schäfer <harald.the.engineer@gmail.com>
Co-authored-by: Kacper Rączy <gfw.kra@gmail.com>
Co-authored-by: Prabhaav Pillai <prabhaav@msu.edu>
Co-authored-by: dirwin31 <83434411+dirwin31@users.noreply.github.com>
Co-authored-by: firestarsdog <229254897+firestarsdog@users.noreply.github.com>
Co-authored-by: stef <19478336+stefpi@users.noreply.github.com>
Co-authored-by: whoisdomi <dcantu83@gmail.com>

Co-authored-by: Intelli <contact@intelli.software>
Co-authored-by: haraschax <6804392+haraschax@users.noreply.github.com>
Co-authored-by: prabhaavp <prabhaav@msu.edu>
2026-10-14 20:02:39 -05:00

356 lines
21 KiB
Python

"""Real HKG callers with a controlled older SubMaster intent projection."""
from contextlib import ExitStack
from unittest.mock import Mock, patch
import pytest
from openpilot.cereal import log, messaging
from openpilot.common.prefix import OpenpilotPrefix
from openpilot.starpilot.aol.runtime import current_intent
from openpilot.starpilot.aol.wire import IntentState, encode_intent
def intent_receive(sd, **kwargs):
original_receive = messaging.recv_one_or_none
intent_sock = sd.aol_intent_companion.sock
receive_intent = Mock(**kwargs)
def receive(sock):
return receive_intent(sock) if sock is intent_sock else original_receive(sock)
return patch('openpilot.starpilot.aol.intent_companion.messaging.recv_one_or_none', side_effect=receive)
def test_actual_hkg_known_older_sm_and_raw_pair_preserve_current_axes_and_card_latch():
from openpilot.starpilot.aol.tests.test_runtime import IpcAxisContractTests
case = IpcAxisContractTests(methodName='runTest')
with ExitStack() as cleanup:
cleanup.callback(case.doCleanups)
# Deterministically force the possible receive order; route logs do not
# record which intent the consumer actually read at the warning boundary.
prior, source, latest, now = 1_081_445_990_726, 1_081_451_053_291, 1_081_455_731_297, 1_081_457_492_005
with OpenpilotPrefix():
pm = messaging.PubMaster(['aolIntentWire'])
sd, controls, step, card, feedback = case._transport_callers()
axis, _, command = step(0, offset_ns=prior - 10_000_000_000)
case.assertTrue(axis.lateralActive and axis.longitudinalActive and command.latActive and command.longActive)
feedback(prior, axis=sd.pm.messages['aolAxisState'], event=sd.pm.messages['onroadEvents'])
old = messaging.new_message('aolIntentWire', 0, valid=True, logMonoTime=prior)
old.aolIntentWire = encode_intent(sd.aol_last_intent)
for stamp, sequence in ((source, 2), (latest, 3)):
message = messaging.new_message('aolIntentWire', 0, valid=True, logMonoTime=stamp)
message.aolIntentWire = encode_intent(IntentState('authority-card', sequence, stamp, stamp,
stamp + 200_000_000, True, False, False, True, True))
pm.send('aolIntentWire', message)
axis, state, command = step(1, state_stamp=source, intent_message=old,
offset_ns=now - 10_010_000_000)
case.assertEqual(sd.sm.logMonoTime['aolIntentWire'], prior)
case.assertEqual(axis.sourceCarStateMonoTime, source)
case.assertEqual(sd.aol_last_intent.carStateLogMonoTime, source)
case.assertEqual(sd.aol_last_intent.observedMonoTime, source)
case.assertEqual(sd.aol_last_intent.validUntilMonoTime, source + 200_000_000)
case.assertTrue(axis.nativeAcknowledged and axis.lateralActive and axis.longitudinalActive)
case.assertTrue(command.latActive and command.longActive and sd.enabled)
case.assertFalse(sd.aol_authority_lost)
case.assertNotIn(log.OnroadEvent.EventName.controlsMismatch, sd.events.names)
case.assertNotEqual(state.alertText1, 'TAKE CONTROL IMMEDIATELY')
case.assertFalse(feedback(now, axis=sd.pm.messages['aolAxisState'], event=sd.pm.messages['onroadEvents']))
case.assertTrue(card.aol_card_intent.allowed_latch)
case.assertFalse(current_intent(sd.sm, car_state_ns=source, now_ns=source + 30_000_001,
companion=sd.aol_intent_companion))
@pytest.mark.parametrize('projection', ['C', 'A', 'B'])
@pytest.mark.parametrize('hard_guard', ['source_expired', 'native_invalid', 'native_deny', 'critical_event'])
def test_actual_hkg_verified_aged_sm_projection_preserves_current_axes_then_hard_guards(projection, hard_guard):
from openpilot.starpilot.aol.tests.test_runtime import IpcAxisContractTests
case = IpcAxisContractTests(methodName='runTest')
c, a, b, before, now = 341_047_855_007, 341_055_154_287, 341_083_039_471, 341_059_508_728, 341_084_947_345
def message(stamp, sequence):
event = messaging.new_message('aolIntentWire', 0, valid=True, logMonoTime=stamp)
event.aolIntentWire = encode_intent(IntentState('authority-card', sequence, stamp, stamp,
stamp + 200_000_000, True, False, False, True, True))
return event
old, source, latest = message(c, 1), message(a, 2), message(b, 3)
with ExitStack() as cleanup:
cleanup.callback(case.doCleanups)
with OpenpilotPrefix():
sd, _, step, card, feedback = case._transport_callers()
with intent_receive(sd, side_effect=[old.as_reader(), source.as_reader(), None]):
axis, _, command = step(0, state_stamp=c, intent_message=old, offset_ns=before - 10_000_000_000)
case.assertTrue(axis.lateralActive and axis.longitudinalActive and command.latActive and command.longActive)
feedback(before, axis=sd.pm.messages['aolAxisState'], event=sd.pm.messages['onroadEvents'])
epoch = sd.aol_intent_companion.integrity_epoch
selected = {'C': old, 'A': source, 'B': latest}[projection]
with intent_receive(sd, side_effect=[latest.as_reader(), None]):
axis, state, command = step(1, state_stamp=a, intent_message=selected, offset_ns=now - 10_010_000_000)
case.assertTrue(axis.nativeAcknowledged and axis.lateralActive and axis.longitudinalActive)
case.assertTrue(command.latActive and command.longActive and sd.enabled)
case.assertFalse(sd.aol_authority_lost)
case.assertEqual(sd.aol_intent_companion.integrity_epoch, epoch)
case.assertEqual(sd.aol_last_intent.carStateLogMonoTime, a)
case.assertNotIn(c, sd.aol_intent_companion.samples)
case.assertNotIn(log.OnroadEvent.EventName.controlsMismatch, sd.events.names)
case.assertNotEqual(state.alertText1, 'TAKE CONTROL IMMEDIATELY')
case.assertFalse(feedback(now, axis=sd.pm.messages['aolAxisState'], event=sd.pm.messages['onroadEvents']))
case.assertTrue(card.aol_card_intent.allowed_latch)
with intent_receive(sd, return_value=None):
repeated, _, repeated_command = step(2, state_stamp=a, intent_message=selected, offset_ns=now + 1 - 10_020_000_000)
case.assertTrue(repeated.lateralActive and repeated.longitudinalActive and repeated_command.latActive and repeated_command.longActive)
case.assertFalse(sd.aol_authority_lost)
case.assertEqual(sd.aol_intent_companion.integrity_epoch, epoch)
kwargs = ({'native_valid': False} if hard_guard == 'native_invalid' else
{'allowed': False} if hard_guard == 'native_deny' else
{'critical_event': log.OnroadEvent.EventName.controlsMismatch} if hard_guard == 'critical_event' else {})
failed_at = a + 30_000_001 if hard_guard == 'source_expired' else now + 2
with intent_receive(sd, return_value=None):
axis, _, command = step(3, state_stamp=a, intent_message=selected, offset_ns=failed_at - 10_030_000_000, **kwargs)
if hard_guard == 'source_expired':
case.assertEqual(axis.sourceCarStateMonoTime, 0)
case.assertEqual(str(axis.faultReason), 'transportPause')
case.assertFalse(sd.enabled or sd.active or axis.lateralActive or axis.longitudinalActive or command.latActive or command.longActive)
case.assertFalse(sd.aol_authority_lost)
case.assertFalse(feedback(failed_at, axis=sd.pm.messages['aolAxisState'], event=sd.pm.messages['onroadEvents']))
case.assertTrue(card.aol_card_intent.allowed_latch)
recovered_at = failed_at + 10_000_000
axis, _, command = step(4, offset_ns=recovered_at - 10_040_000_000, native_request_long=True)
case.assertEqual(axis.sourceCarStateMonoTime, recovered_at)
case.assertEqual(str(axis.faultReason), 'none')
case.assertTrue(axis.lateralActive and command.latActive)
case.assertFalse(sd.enabled or sd.active or axis.longitudinalActive or command.longActive)
case.assertFalse(sd.aol_authority_lost)
case.assertFalse(feedback(recovered_at, axis=sd.pm.messages['aolAxisState'], event=sd.pm.messages['onroadEvents']))
case.assertTrue(card.aol_card_intent.allowed_latch)
else:
case.assertEqual(str(axis.faultReason), 'critical')
case.assertFalse(axis.lateralActive or axis.longitudinalActive or command.latActive or command.longActive)
# The global event disables current output, but a healthy source/native
# does not create the separate sticky companion/native authority loss.
case.assertEqual(sd.aol_authority_lost, hard_guard != 'critical_event')
case.assertTrue(feedback(failed_at, axis=sd.pm.messages['aolAxisState'], event=sd.pm.messages['onroadEvents']))
case.assertFalse(card.aol_card_intent.allowed_latch)
@pytest.mark.parametrize('guard', ['healthy', 'latest_deny', 'latest_pause', 'future',
'native_invalid', 'native_deny', 'source_expired', 'overrun'])
def test_actual_hkg_drain_precedes_shared_decision_clock(guard):
from openpilot.selfdrive.selfdrived import selfdrived as sd_module
from openpilot.starpilot.aol.intent_companion import MAX_DRAIN
from openpilot.starpilot.aol.tests.test_runtime import IpcAxisContractTests
case = IpcAxisContractTests(methodName='runTest')
a = 10_000_000_000
before, after = a + 25_000_000, a + 28_000_000
original = messaging.new_message('aolIntentWire', 0, valid=True, logMonoTime=a)
original.aolIntentWire = encode_intent(IntentState('authority-card', 1, a, a,
a + 200_000_000, True, False, False, True, True))
b = after + 1 if guard == 'future' else a + 26_000_000
latest_value = IntentState('authority-card', 2, b, b, b + 200_000_000,
guard != 'latest_deny', guard == 'latest_pause', False, True, True)
latest = messaging.new_message('aolIntentWire', 0, valid=True, logMonoTime=b)
latest.aolIntentWire = encode_intent(latest_value)
with ExitStack() as cleanup:
cleanup.callback(case.doCleanups)
with OpenpilotPrefix():
sd, _, step, card, feedback = case._transport_callers()
with intent_receive(sd, return_value=None):
axis, _, command = step(0, intent_message=original)
assert axis.lateralActive and command.latActive
assert not feedback(a, axis=sd.pm.messages['aolAxisState'], event=sd.pm.messages['onroadEvents'])
calls = []
def receive(_sock):
calls.append(1)
# The actual SD decision clock changes while its queued read executes.
sd_module.time.monotonic_ns.return_value = a + 30_000_001 if guard == 'source_expired' else after
return latest.as_reader() if len(calls) == 1 or guard == 'overrun' else None
kwargs = {'native_valid': False} if guard == 'native_invalid' else {'allowed': False} if guard == 'native_deny' else {}
with intent_receive(sd, side_effect=receive):
axis, state, command = step(1, state_stamp=a, intent_message=original,
offset_ns=before - 10_010_000_000, **kwargs)
assert len(calls) == (MAX_DRAIN if guard == 'overrun' else 2)
if guard == 'healthy':
assert axis.sourceCarStateMonoTime == a
assert sd.aol_last_intent.observedMonoTime == a
assert sd.aol_last_intent.validUntilMonoTime == a + 200_000_000
assert axis.nativeAcknowledged and axis.lateralActive and command.latActive
assert not sd.aol_authority_lost
assert log.OnroadEvent.EventName.controlsMismatch not in sd.events.names
assert state.alertText1 != 'TAKE CONTROL IMMEDIATELY'
assert not feedback(after, axis=sd.pm.messages['aolAxisState'], event=sd.pm.messages['onroadEvents'])
assert card.aol_card_intent.allowed_latch
elif guard == 'latest_deny':
# Ordinary enabled control remains valid; the persistent AOL latch veto is folded.
assert sd.aol_last_intent.allowedLatch is False
assert log.OnroadEvent.EventName.controlsMismatch not in sd.events.names
else:
assert not axis.lateralActive and not command.latActive
if guard == 'source_expired':
assert axis.sourceCarStateMonoTime == 0
assert str(axis.faultReason) == 'transportPause'
assert not sd.enabled and not sd.active and not axis.longitudinalActive and not command.longActive
assert not sd.aol_authority_lost
publication_ns = int(sd.pm.messages['aolAxisState'].logMonoTime)
assert publication_ns == a + 30_000_001
assert not feedback(publication_ns, axis=sd.pm.messages['aolAxisState'], event=sd.pm.messages['onroadEvents'])
assert card.aol_card_intent.allowed_latch
elif guard in ('future', 'native_invalid', 'native_deny', 'overrun'):
assert str(axis.faultReason) == 'critical'
assert log.OnroadEvent.EventName.controlsMismatch in sd.events.names
assert feedback(after, axis=sd.pm.messages['aolAxisState'], event=sd.pm.messages['onroadEvents'])
assert not card.aol_card_intent.allowed_latch
@pytest.mark.parametrize('guard', [
'none', 'unknown', 'mutated', 'invalid', 'future', 'cache_expired', 'pair_expired', 'latest_invalid', 'future_observed',
])
def test_raw_observed_projection_restricts_without_renewing_authority(guard):
from types import SimpleNamespace
from openpilot.starpilot.aol.intent_companion import IntentCompanion
old_ns = 10_000_000_000
source_ns = old_ns + (201_000_000 if guard == 'cache_expired' else 50_000_000)
now_ns = source_ns + (31_000_000 if guard == 'pair_expired' else 1_000_000)
def message(stamp, sequence, *, paused=False, valid=True):
event = messaging.new_message('aolIntentWire', 0, valid=valid, logMonoTime=stamp)
event.aolIntentWire = encode_intent(IntentState('raw-card', sequence, stamp, stamp,
stamp + 200_000_000, True, paused, False, True, True))
return event
old = message(old_ns, 1, paused=True)
if guard == 'future_observed':
old.aolIntentWire = encode_intent(IntentState('raw-card', 1, old_ns, now_ns + 1,
now_ns + 200_000_000, True, True, False, True, True))
companion = IntentCompanion(object())
class ProjectionSource(SimpleNamespace):
def __getitem__(self, name):
return self.payload
sm = ProjectionSource(seen={'aolIntentWire': False}, valid={'aolIntentWire': True},
alive={'aolIntentWire': True}, logMonoTime={'aolIntentWire': old_ns})
sm.payload = old.aolIntentWire
with patch('openpilot.starpilot.aol.intent_companion.messaging.recv_one_or_none',
side_effect=[None] if guard == 'unknown' else [old, None]):
assert companion.current(sm, source_ns=0, now_ns=old_ns + 1_000_000) is None
assert companion.verified_sm_projection is None
sm.seen['aolIntentWire'] = True
if guard == 'mutated':
sm.payload = message(old_ns, 1).aolIntentWire
elif guard == 'invalid':
sm.valid['aolIntentWire'] = False
elif guard == 'future':
future = message(now_ns + 1, 4)
sm.logMonoTime['aolIntentWire'], sm.payload = now_ns + 1, future.aolIntentWire
source = message(source_ns, 2)
latest = message(now_ns, 3, valid=guard != 'latest_invalid')
with patch('openpilot.starpilot.aol.intent_companion.messaging.recv_one_or_none', side_effect=[source, latest, None]):
result = companion.current(sm, source_ns=source_ns, now_ns=now_ns)
if guard == 'none':
assert result is not None and result.allowedLatch and result.pauseLateral
assert result.carStateLogMonoTime == source_ns
assert result.observedMonoTime == source_ns
assert result.validUntilMonoTime == source_ns + 200_000_000
assert companion.integrity_epoch == 0
assert companion.raw_projections[old_ns][0].pauseLateral
assert old_ns not in companion.samples
else:
assert result is None
@pytest.mark.parametrize('fault', ['native_invalid', 'native_deny', 'critical_event'])
def test_actual_hkg_raw_known_unaccepted_sm_projection_preserves_full_axes_then_denies_fault(fault):
from openpilot.starpilot.aol.runtime import current_axis
from openpilot.starpilot.aol.tests.test_runtime import IpcAxisContractTests
case = IpcAxisContractTests(methodName='runTest')
c = 10_030_000_000
a, b, now = c + 7_299_280, c + 35_184_464, c + 37_092_338
def message(stamp, sequence):
event = messaging.new_message('aolIntentWire', 0, valid=True, logMonoTime=stamp)
event.aolIntentWire = encode_intent(IntentState('authority-card', sequence, stamp, stamp,
stamp + 200_000_000, True, False, False, True, True))
return event
with ExitStack() as cleanup:
cleanup.callback(case.doCleanups)
with OpenpilotPrefix():
sd, controls, step, card, feedback = case._ioniq_availability_callers(full_op=True)
prior = c - 10_000_000
axis, _, command = step(2)
case.assertEqual(card.CP.to_dict(), sd.CP.to_dict())
from openpilot.starpilot.aol.wire import decode_safety
native = decode_safety(sd.sm['aolSafetyWire'])
assert native is not None
case.assertTrue(native.pandaInventory)
case.assertTrue(axis.lateralActive and axis.longitudinalActive and command.latActive and command.longActive)
feedback(prior, axis=sd.pm.messages['aolAxisState'], event=sd.pm.messages['onroadEvents'])
# Keep the established output continuous while its unchanged source is fresh.
with patch('openpilot.selfdrive.selfdrived.selfdrived.time.monotonic_ns', return_value=c + 10_000_000):
sd.publish_axis_proof(sd.CS_prev)
feedback(c + 10_000_000, axis=sd.pm.messages['aolAxisState'], event=sd.pm.messages['onroadEvents'])
old, source, latest = message(c, 4), message(a, 5), message(b, 6)
with intent_receive(sd, side_effect=[old.as_reader(), None]):
case.assertTrue(sd.aol_intent_companion.drain())
# C was observed on the original socket, but never admitted as an SM triple.
case.assertNotEqual(sd.aol_intent_companion.verified_sm_projection[0], c)
epoch = sd.aol_intent_companion.integrity_epoch
with intent_receive(sd, side_effect=[source.as_reader(), latest.as_reader(), None]):
axis, state, command = step(3, state_stamp=a, intent_message=old, offset_ns=now - 10_030_000_000)
case.assertTrue(axis.nativeAcknowledged and axis.lateralActive and axis.longitudinalActive)
case.assertTrue(command.latActive and command.longActive and sd.enabled)
case.assertEqual(axis.sourceCarStateMonoTime, a)
case.assertEqual(sd.aol_last_intent.carStateLogMonoTime, a)
case.assertEqual(sd.aol_intent_companion.integrity_epoch, epoch)
case.assertNotIn(c, sd.aol_intent_companion.samples)
case.assertNotIn(log.OnroadEvent.EventName.controlsMismatch, sd.events.names)
case.assertNotEqual(state.alertText1, 'TAKE CONTROL IMMEDIATELY')
case.assertTrue(feedback(now, axis=sd.pm.messages['aolAxisState'], event=sd.pm.messages['onroadEvents']))
case.assertTrue(card.aol_card_intent.allowed_latch)
expired = int(sd.pm.messages['aolAxisState'].aolAxisState.validUntilMonoTime) + 1
case.assertIsNone(current_axis(controls.sm, now_ns=expired))
with patch('openpilot.selfdrive.controls.controlsd.time.monotonic_ns', return_value=expired):
expired_command, _ = controls.state_control()
case.assertFalse(expired_command.latActive or expired_command.longActive)
kwargs = ({'native_valid': False} if fault == 'native_invalid' else
{'allowed': False} if fault == 'native_deny' else
{'critical_event': log.OnroadEvent.EventName.controlsMismatch})
denied_at = expired + 1
fresh = message(denied_at, 7)
with intent_receive(sd, side_effect=[fresh.as_reader(), None]):
denied, _, denied_command = step(4, state_stamp=denied_at, intent_message=fresh,
offset_ns=denied_at - 10_040_000_000, **kwargs)
case.assertEqual(str(denied.faultReason), 'critical')
case.assertFalse(denied.lateralActive or denied.longitudinalActive or
denied_command.latActive or denied_command.longActive)
case.assertFalse(feedback(denied_at, axis=sd.pm.messages['aolAxisState'], event=sd.pm.messages['onroadEvents']))
case.assertFalse(card.aol_card_intent.allowed_latch)
def test_reduced_owner_subscribes_control_before_receiving_actual_command():
from openpilot.starpilot.aol.tests.test_runtime import IpcAxisContractTests
case = IpcAxisContractTests(methodName='runTest')
with ExitStack() as cleanup:
cleanup.callback(case.doCleanups)
with OpenpilotPrefix():
sd, controls, step, _, _ = case._transport_callers()
assert 'carControl' in sd.sm.seen
assert not sd.sm.seen['carControl'] and not sd.sm.valid['carControl']
assert sd.sm.logMonoTime['carControl'] == 0
# A subscribed-but-unseen packet cannot establish a held pedal override.
proof = ('authority-session', 1, 1, 30_000_001, 'authority-card', 0, True, True, True, True)
assert sd.held_axis_override_wait(sd.CS_prev, proof, 2) == (False, False)
_, _, command = step(0)
published = controls.pm.messages['carControl']
assert not sd.sm.seen['carControl']
step(1)
assert sd.sm.seen['carControl'] and sd.sm.valid['carControl']
assert sd.sm.logMonoTime['carControl'] == published.logMonoTime
assert sd.sm['carControl'].to_dict() == published.carControl.to_dict()
assert (sd.sm['carControl'].enabled, sd.sm['carControl'].latActive, sd.sm['carControl'].longActive) == (
command.enabled, command.latActive, command.longActive)