Files
StarPilot/openpilot/starpilot/spot_monitor/tests/test_observation.py
T
firestar5683 9d298304c3 Vision
Implement vision annotation observations, inference and preferences through a dedicated runtime owner.
2026-10-14 20:02:39 -05:00

161 lines
8.1 KiB
Python

"""Serialized V-ASM wire and historical @136 compatibility tests."""
import math
import unittest
from openpilot.cereal import log
from openpilot.starpilot.spot_monitor.inference import MODEL_SHA256
from openpilot.starpilot.spot_monitor.observation import ObservationReader
SESSION = "a" * 32
OTHER_SESSION = "c" * 32
FINGERPRINT = "b" * 64
M = 1_000_000
# Actual frozen StarPilotLateralManeuverPlanDEPRECATED Event bytes, emitted by
# the frozen parser in a separate process (Cap'n Proto IDs cannot be loaded
# twice into one process). The historical field remains curvature in 1/m.
FROZEN_CURVATURE_EVENT = bytes.fromhex(
"00000000050000000000000002000100000000000000000086000000000000000000000001000000cdcc4c3c00000000")
def packet(*, session=SESSION, sequence=1, frame_id=0, observed=1000*M,
eof=900*M, side_eof=None, side_frame_id=0, valid=True):
msg = log.Event.new_message()
msg.init("spotMonitorState")
msg.valid = valid
msg.logMonoTime = observed
obs = msg.spotMonitorState.init("observation")
obs.version = 1
obs.producerSessionId = session
obs.sequence = sequence
obs.modelSha256 = MODEL_SHA256
obs.settingsFingerprint = FINGERPRINT
obs.observedMonoTime = observed
obs.observedBootTime = observed
obs.sourceFrameId = frame_id
obs.sourceFrameEofBootTime = eof
obs.validUntilBootTime = eof + 500*M
left = obs.init("left")
left.status = "warning"
left.confidence = 0.96
left.warning = True
left.sourceFrameId = side_frame_id
left.sourceFrameEofBootTime = eof if side_eof is None else side_eof
left.sourceObservedMonoTime = left.sourceFrameEofBootTime
left.validUntilBootTime = left.sourceFrameEofBootTime + 3000*M
return msg
def read(reader, msg, *, now=1100*M, fingerprint=FINGERPRINT, boot_offset=0):
msg.clear_write_flag() # The cached-event regression serializes one builder twice.
with log.Event.from_bytes(msg.to_bytes()) as event:
return reader.read(event, now_mono_ns=now, now_boot_ns=now + boot_offset,
settings_fingerprint=fingerprint)
class ObservationWireTest(unittest.TestCase):
def test_historical_curvature_has_no_vasm_version(self):
with log.Event.from_bytes(FROZEN_CURVATURE_EVENT) as event:
self.assertEqual(event.which(), "spotMonitorState")
self.assertAlmostEqual(event.spotMonitorState.desiredCurvature, 0.0125)
self.assertEqual(event.spotMonitorState.observation.version, 0)
self.assertFalse(ObservationReader().read(event, now_mono_ns=1100*M,
now_boot_ns=1100*M,
settings_fingerprint=FINGERPRINT).display_left.warning)
def test_serialized_warning_expires_even_when_cached_event_is_reused(self):
reader = ObservationReader()
first = packet()
self.assertTrue(read(reader, first).display_left.warning)
self.assertTrue(reader.current_at(now_mono_ns=1200*M, now_boot_ns=1200*M,
settings_fingerprint=FINGERPRINT).display_left.warning)
# The camera may publish at 1 Hz; validated warnings retain the original
# 3-second hold even though a new frame must be admitted within 500 ms.
self.assertTrue(read(reader, first, now=1401*M).display_left.warning)
self.assertFalse(reader.current_at(now_mono_ns=3901*M, now_boot_ns=3901*M,
settings_fingerprint=FINGERPRINT).display_left.warning)
def test_new_frame_does_not_renew_other_side_hold(self):
reader = ObservationReader()
self.assertTrue(read(reader, packet()).display_left.warning)
later = packet(sequence=2, frame_id=1, observed=1500*M, eof=1450*M,
side_eof=900*M)
self.assertTrue(read(reader, later, now=1550*M).display_left.warning)
very_late = packet(sequence=3, frame_id=2, observed=4000*M, eof=3950*M,
side_eof=900*M)
self.assertFalse(read(reader, very_late, now=4050*M).display_left.warning)
def test_replay_invalid_loss_and_session_retirement(self):
reader = ObservationReader()
self.assertTrue(read(reader, packet()).display_left.warning)
self.assertTrue(read(reader, packet(sequence=1, valid=False)).display_left.warning)
self.assertFalse(read(reader, packet(sequence=2, frame_id=1, observed=1200*M,
eof=1150*M, valid=False), now=1250*M).display_left.warning)
self.assertTrue(read(reader, packet(session=OTHER_SESSION, observed=1400*M,
eof=1350*M), now=1450*M).display_left.warning)
self.assertTrue(read(reader, packet(session=SESSION, observed=1500*M,
eof=1450*M, valid=False), now=1550*M).display_left.warning)
def test_invalid_reset_retires_old_session_before_delayed_valid_packet(self):
reader = ObservationReader()
self.assertTrue(read(reader, packet()).display_left.warning)
self.assertFalse(read(reader, packet(session=OTHER_SESSION, sequence=0,
observed=1200*M, eof=0, valid=False),
now=1250*M).display_left.warning)
# The old producer's later sequence remains in-flight after its reset.
self.assertFalse(read(reader, packet(session=SESSION, sequence=2, frame_id=1,
observed=1300*M, eof=1250*M), now=1350*M).display_left.warning)
self.assertTrue(read(reader, packet(session=OTHER_SESSION, observed=1400*M,
eof=1350*M), now=1450*M).display_left.warning)
def test_backward_clock_cannot_resurrect_expired_warning(self):
reader = ObservationReader()
self.assertTrue(read(reader, packet()).display_left.warning)
self.assertFalse(reader.current_at(now_mono_ns=3901*M, now_boot_ns=3901*M,
settings_fingerprint=FINGERPRINT).display_left.warning)
self.assertFalse(reader.current_at(now_mono_ns=1100*M, now_boot_ns=1100*M,
settings_fingerprint=FINGERPRINT).display_left.warning)
self.assertFalse(read(reader, packet(sequence=2, frame_id=1,
observed=1200*M, eof=1150*M), now=1250*M).display_left.warning)
def test_bad_source_settings_hash_and_resume_fail_closed(self):
for mutate in (lambda m: setattr(m.spotMonitorState.observation, "modelSha256", "0" * 64),
lambda m: setattr(m.spotMonitorState.observation, "settingsFingerprint", "0" * 64),
lambda m: setattr(m.spotMonitorState.observation, "validUntilBootTime", 9000*M),
lambda m: setattr(m.spotMonitorState.observation.left, "sourceObservedMonoTime", 500*M),
lambda m: setattr(m.spotMonitorState.observation.left, "confidence", math.nan)):
with self.subTest(mutate=mutate):
msg = packet()
mutate(msg)
self.assertFalse(read(ObservationReader(), msg).display_left.warning)
reader = ObservationReader()
self.assertTrue(read(reader, packet()).display_left.warning)
self.assertFalse(reader.current_at(now_mono_ns=1200*M, now_boot_ns=10200*M,
settings_fingerprint=FINGERPRINT).display_left.warning)
# A queued pre-resume packet cannot claim the new BOOT/MONO epoch.
self.assertFalse(read(reader, packet(sequence=2, frame_id=1, observed=1150*M,
eof=1100*M), now=1200*M, boot_offset=9000*M).display_left.warning)
def test_nonzero_boot_offset_and_future_frame_bounds(self):
def shifted():
msg = packet()
wire = msg.spotMonitorState.observation
wire.observedBootTime += 9000*M
wire.sourceFrameEofBootTime += 9000*M
wire.validUntilBootTime += 9000*M
wire.left.sourceFrameEofBootTime += 9000*M
wire.left.validUntilBootTime += 9000*M
return msg
self.assertTrue(read(ObservationReader(), shifted(), now=1100*M,
boot_offset=9000*M).display_left.warning)
future = shifted()
future.spotMonitorState.observation.sourceFrameEofBootTime = 10150*M
self.assertFalse(read(ObservationReader(), future, now=1100*M,
boot_offset=9000*M).display_left.warning)
if __name__ == "__main__":
unittest.main()