Files
StarPilot/openpilot/starpilot/tests/test_can_subscription_contract.py
T
firestar5683 72ef8a0dc7 Vehicle Contracts
Centralize vehicle lookup, firmware identification and dashboard limits while retaining the stock vehicle registry.

Co-authored-by: firestarsdog <229254897+firestarsdog@users.noreply.github.com>

Co-authored-by: prabhaavp <prabhaav@msu.edu>
2026-10-14 19:56:17 -05:00

73 lines
3.4 KiB
Python

"""CAN subscription migration contracts using the actual upstream Python parser.
These synthetic messages test required/optional freshness semantics only. They
do not qualify a vehicle, checksum algorithm, native safety mode or CAN hardware.
"""
import math
import unittest
from opendbc.can import CANPacker, CANParser
class TestCanSubscriptionContract(unittest.TestCase):
REQUIRED = "ESP_B"
OPTIONAL = "DI_torque1"
START_NS = 1_000_000_000
PERIOD_NS = 20_000_000
def parser(self, optional_frequency):
return CANParser("tesla_can", [(self.REQUIRED, 50), (self.OPTIONAL, optional_frequency)], 0)
def schedule(self, parser, first_frame, count, names):
packer = CANPacker("tesla_can")
validity = []
for frame in range(first_frame, first_frame + count):
now_ns = self.START_NS + frame * self.PERIOD_NS
parser.update([(now_ns, [packer.make_can_msg(name, 0, {}) for name in names])])
# The parser currently debounces on property reads, so read once per frame.
validity.append(parser.can_valid)
return validity
def timestamps(self, parser, name):
self.assertTrue(parser.ts_nanos[name], "fixture message must have actual signals")
return set(parser.ts_nanos[name].values())
def test_missing_optional_message_does_not_invalidate_required_traffic(self):
parser = self.parser(math.nan)
self.assertTrue(all(self.schedule(parser, 0, 60, [self.REQUIRED])))
self.assertEqual(self.timestamps(parser, self.REQUIRED), {self.START_NS + 59 * self.PERIOD_NS})
# Aggregate CAN validity must never be interpreted as optional signal freshness.
self.assertEqual(self.timestamps(parser, self.OPTIONAL), {0})
def test_optional_signal_retains_old_timestamp_after_it_stops(self):
parser = self.parser(math.nan)
self.assertTrue(all(self.schedule(parser, 0, 60, [self.REQUIRED, self.OPTIONAL])))
optional_last_seen = self.START_NS + 59 * self.PERIOD_NS
self.assertEqual(self.timestamps(parser, self.OPTIONAL), {optional_last_seen})
self.assertTrue(all(self.schedule(parser, 60, 60, [self.REQUIRED])))
self.assertEqual(self.timestamps(parser, self.OPTIONAL), {optional_last_seen})
self.assertEqual(self.timestamps(parser, self.REQUIRED), {self.START_NS + 119 * self.PERIOD_NS})
def test_optional_traffic_does_not_hide_loss_of_required_message(self):
parser = self.parser(math.nan)
self.assertTrue(all(self.schedule(parser, 0, 60, [self.REQUIRED, self.OPTIONAL])))
after_loss = self.schedule(parser, 60, 60, [self.OPTIONAL])
self.assertFalse(any(after_loss[-10:]))
self.assertEqual(self.timestamps(parser, self.REQUIRED), {self.START_NS + 59 * self.PERIOD_NS})
self.assertEqual(self.timestamps(parser, self.OPTIONAL), {self.START_NS + 119 * self.PERIOD_NS})
def test_zero_frequency_is_required_and_learns_received_frequency(self):
parser = self.parser(0)
self.assertFalse(any(self.schedule(parser, 0, 60, [self.REQUIRED])))
self.assertEqual(self.timestamps(parser, self.OPTIONAL), {0})
# Real packets restore validity; zero is not an optional-message declaration.
self.assertTrue(all(self.schedule(parser, 60, 60, [self.REQUIRED, self.OPTIONAL])))
after_loss = self.schedule(parser, 120, 60, [self.REQUIRED])
self.assertFalse(any(after_loss[-10:]))
self.assertEqual(self.timestamps(parser, self.OPTIONAL), {self.START_NS + 119 * self.PERIOD_NS})
if __name__ == "__main__":
unittest.main()