Files
StarPilot/openpilot/starpilot/flm/tests/test_physical_pose_queue.py
firestar5683 2cd7728be2 FLM Auto-Weave
2026-10-17 18:38:27 -05:00

122 lines
5.8 KiB
Python

"""The real optional observer retains original pose receipts until state brackets arrive."""
from types import SimpleNamespace
from unittest import mock
import pytest
from openpilot.cereal import messaging
from openpilot.starpilot.flm.runtime_adapter_registry import physical_observer
from openpilot.starpilot.flm.tests.test_live_calibration_paths import PhysicalSources
BASE = 20_000_000_000
def command(stamp, *, speed=20., angle=0., **changes):
values = {'identity': 'a' * 64, 'generation': 1, 'source_epoch': 'b' * 64,
'baseline_revision': 'baseline', 'controller_revision': 'controller', 'native_identity': 'native',
'state_ns': stamp, 'replay_inputs': (('vm.sR', 14.), ('vm.cF', 100000.), ('vm.cR', 100000.),
('vEgo', speed), ('steeringAngleDeg', angle))}
values.update(changes)
return SimpleNamespace(**values)
def sources(stamp):
dm = messaging.new_message('deviceMotion').deviceMotion
dm.timestamp = stamp
dm.inputsOK = dm.sensorsOK = dm.posenetOK = True
for name, xyz in (('angularVelocityDevice', (.05, .1, .02)), ('orientationNED', (.01, .02, .03))):
value = getattr(dm, name)
value.valid = True
value.x, value.y, value.z = xyz
cal = messaging.new_message('extrinsicsCalibration').extrinsicsCalibration
cal.calStatus, cal.validBlocks = 'calibrated', 50
cal.rpyCalib, cal.rpyCalibSpread = (0., 0., 0.), (0., .001, .001)
cs = SimpleNamespace(steeringPressed=False, brakePressed=False, gasPressed=False, regenBraking=False,
buttonEvents=(), canTimeout=False, canValid=True, steerFaultTemporary=False,
steerFaultPermanent=False, accFaulted=False)
return PhysicalSources({'deviceMotion': dm, 'extrinsicsCalibration': cal, 'carState': cs,
'lateralDelay': SimpleNamespace(lateralDelay=.1),
'driverMonitoringState': SimpleNamespace(noResponseForceDecel=False)}, stamp)
def fixture():
collector = SimpleNamespace(session='session', model_block_sequence=0, _calibration_generation='c' * 32,
_learning_current_native_allowed=True, ready=[], adapter=SimpleNamespace(command_domain='normalized_torque'))
collector.split = lambda *args, **kwargs: collector.ready.clear()
cp = SimpleNamespace(steerRatio=14., wheelbase=2.9, centerToFront=1.2, mass=2000.)
return collector, cp, sources(BASE)
def observe(collector, cp, sm, now):
with mock.patch('openpilot.starpilot.flm.runtime_adapter_registry.time.monotonic_ns', return_value=now), \
mock.patch('openpilot.starpilot.flm.runtime_adapter_registry.time.clock_gettime_ns', return_value=now):
return physical_observer(sm, cp, collector, now)
def add_state(collector, sm, stamp, **changes):
collector.ready.append((command(stamp, **changes), SimpleNamespace(native_allowed=True, apply_ns=stamp)))
sm.logMonoTime['carState'] = stamp
def test_actual_observer_retries_original_pose_after_later_right_bracket():
collector, cp, sm = fixture()
add_state(collector, sm, BASE - 10_000_000, speed=20., angle=0.)
assert observe(collector, cp, sm, BASE) is None
add_state(collector, sm, BASE + 10_000_000, speed=22., angle=2.)
sm.recv_time['deviceMotion'] = (BASE + 9_000_000) / 1e9 # A poll cannot renew the original receipt.
result = observe(collector, cp, sm, BASE + 10_000_000)
assert result is not None
assert result.mono_ns == result.source_boot_ns == result.received_ns == BASE
assert result.clock_offset_ns == result.clock_uncertainty_ns == 0
assert result.speed_mps == 21. and result.steering_angle_deg == 1.
assert result.yaw_rate == pytest.approx(.02)
assert observe(collector, cp, sm, BASE + 10_000_001) is None
@pytest.mark.parametrize('transition', ['native', 'source', 'reset', 'baseline', 'calibration', 'vm', 'override', 'fault', 'invalid', 'expired'])
def test_pending_pose_cannot_cross_health_or_identity_transition(transition):
collector, cp, sm = fixture()
add_state(collector, sm, BASE - 10_000_000)
assert observe(collector, cp, sm, BASE) is None
changes = {}
if transition == 'native':
changes['native_identity'] = 'new-native'
elif transition == 'source':
changes['source_epoch'] = 'd' * 64
elif transition == 'reset':
collector.model_block_sequence += 1
elif transition == 'baseline':
changes['baseline_revision'] = 'changed'
elif transition == 'calibration':
sm['extrinsicsCalibration'].rpyCalib = (0., .001, .001)
elif transition == 'vm':
changes['replay_inputs'] = (('vm.sR', 16.), ('vm.cF', 100000.), ('vm.cR', 100000.), ('vEgo', 20.), ('steeringAngleDeg', 0.))
elif transition == 'override':
sm['carState'].brakePressed = True
elif transition == 'fault':
sm['carState'].canValid = False
elif transition == 'invalid':
sm['deviceMotion'].inputsOK = False
add_state(collector, sm, BASE + 10_000_000, **changes)
now = BASE + (100_000_001 if transition == 'expired' else 10_000_000)
assert observe(collector, cp, sm, now) is None
assert not collector._physical_pose_queue
def test_newer_pose_does_not_replace_queued_original_or_its_receipt():
collector, cp, sm = fixture()
add_state(collector, sm, BASE - 10_000_000)
assert observe(collector, cp, sm, BASE) is None
sm['deviceMotion'].timestamp = BASE + 50_000_000
sm['deviceMotion'].angularVelocityDevice.z = .08
sm.recv_time['deviceMotion'] = (BASE + 50_000_000) / 1e9
add_state(collector, sm, BASE + 10_000_000)
result = observe(collector, cp, sm, BASE + 50_000_000)
assert result is not None and result.mono_ns == result.received_ns == BASE
assert result.yaw_rate == pytest.approx(.02)
add_state(collector, sm, BASE + 60_000_000)
result = observe(collector, cp, sm, BASE + 60_000_000)
assert result is not None and result.mono_ns == result.received_ns == BASE + 50_000_000
assert result.yaw_rate == pytest.approx(.08)