mirror of
https://github.com/firestar5683/StarPilot.git
synced 2026-11-14 08:23:59 +08:00
706e760db0
Centralize vehicle lookup, firmware identification and dashboard limits while retaining the stock vehicle registry.
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()
|