#!/usr/bin/env python3 import unittest from collections.abc import Iterable import capnp from hypothesis import settings, given, strategies as st from parameterized import parameterized from cereal import car from openpilot.selfdrive.car.ford.values import CAR, FW_QUERY_CONFIG, get_platform_codes from openpilot.selfdrive.car.ford.fingerprints import FW_VERSIONS Ecu = car.CarParams.Ecu ECU_ADDRESSES = { Ecu.eps: 0x730, # Power Steering Control Module (PSCM) Ecu.abs: 0x760, # Anti-Lock Brake System (ABS) Ecu.fwdRadar: 0x764, # Cruise Control Module (CCM) Ecu.fwdCamera: 0x706, # Image Processing Module A (IPMA) Ecu.engine: 0x7E0, # Powertrain Control Module (PCM) Ecu.shiftByWire: 0x732, # Gear Shift Module (GSM) } ECU_FW_CORE = { Ecu.eps: [ b"14D003", ], Ecu.abs: [ b"2D053", ], Ecu.fwdRadar: [ b"14D049", ], Ecu.fwdCamera: [ b"14F397", # Ford Q3 b"14H102", # Ford Q4 ], } class TestFordFW(unittest.TestCase): def test_fw_query_config(self): for (ecu, addr, subaddr) in FW_QUERY_CONFIG.extra_ecus: self.assertIn(ecu, ECU_ADDRESSES, "Unknown ECU") self.assertEqual(addr, ECU_ADDRESSES[ecu], "ECU address mismatch") self.assertIsNone(subaddr, "Unexpected ECU subaddress") @parameterized.expand(FW_VERSIONS.items()) def test_fw_versions(self, car_model: str, fw_versions: dict[tuple[capnp.lib.capnp._EnumModule, int, int | None], Iterable[bytes]]): for (ecu, addr, subaddr), fws in fw_versions.items(): self.assertIn(ecu, ECU_FW_CORE, "Unexpected ECU") self.assertEqual(addr, ECU_ADDRESSES[ecu], "ECU address mismatch") self.assertIsNone(subaddr, "Unexpected ECU subaddress") for fw in fws: codes = get_platform_codes([fw]) self.assertEqual(1, len(codes), f"Unable to parse FW: {fw!r}") @settings(max_examples=100) @given(data=st.data()) def test_platform_codes_fuzzy_fw(self, data): """Ensure function doesn't raise an exception""" fw_strategy = st.lists(st.binary()) fws = data.draw(fw_strategy) get_platform_codes(fws) def test_platform_codes_spot_check(self): # Asserts basic platform code parsing behavior for a few cases results = get_platform_codes([ b"JX6A-14C204-BPL\x00\x00\x00\x00\x00\x00\x00\x00\x00", b"NZ6T-14F397-AC\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00", b"PJ6T-14H102-ABJ\x00\x00\x00\x00\x00\x00\x00\x00\x00", b"LB5A-14C204-EAC\x00\x00\x00\x00\x00\x00\x00\x00\x00", ]) self.assertEqual(results, {(b"X6A-14C204", b"J-BPL"), (b"Z6T-14F397", b"N-AAC"), (b"J6T-14H102", b"P-ABJ"), (b"B5A-14C204", b"L-EAC")}) def test_match_fw_fuzzy(self): offline_fw = { (Ecu.eps, 0x730, None): [ b"L1MC-14D003-AJ\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00", b"L1MC-14D003-AL\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00", ], (Ecu.abs, 0x760, None): [ b"L1MC-2D053-BA\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00", b"L1MC-2D053-BD\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00", ], (Ecu.fwdRadar, 0x764, None): [ b"LB5T-14D049-AB\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00", b"LB5T-14D049-AD\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00", ], (Ecu.fwdCamera, 0x706, None): [ b"LB5T-14F397-AD\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00", b"LB5T-14F397-AF\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00", ], } live_fw = { (0x730, None): {b"L1MC-14D003-AK\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00"}, (0x760, None): {b"L1MC-2D053-BB\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00"}, (0x764, None): {b"LB5T-14D049-AC\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00"}, (0x706, None): {b"LB5T-14F397-AE\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00"}, } # for this test, check that none of the live FW matches matches the offline FW exactly for (_, addr, subaddr), fws in offline_fw.items(): live_ecu_fw = live_fw.get((addr, subaddr), set()) self.assertEqual(0, len(set(fws).intersection(live_ecu_fw))) expected_fingerprint = CAR.EXPLORER_MK6 candidates = FW_QUERY_CONFIG.match_fw_to_car_fuzzy(live_fw, { expected_fingerprint: offline_fw, }) self.assertEqual(candidates, {expected_fingerprint}) if __name__ == "__main__": unittest.main()