mirror of
https://github.com/firestar5683/StarPilot.git
synced 2026-11-08 13:33:42 +08:00
72ef8a0dc7
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>
73 lines
3.4 KiB
Python
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()
|