diff --git a/selfdrive/car/ford/tests/__init__.py b/selfdrive/car/ford/tests/__init__.py new file mode 100644 index 0000000000..e69de29bb2 diff --git a/selfdrive/car/ford/tests/test_ford.py b/selfdrive/car/ford/tests/test_ford.py new file mode 100755 index 0000000000..11746777f0 --- /dev/null +++ b/selfdrive/car/ford/tests/test_ford.py @@ -0,0 +1,80 @@ +#!/usr/bin/env python3 +import unittest +from parameterized import parameterized +from typing import Dict, Iterable, Optional, Tuple + +import capnp + +from cereal import car +from selfdrive.car.ford.values import FW_QUERY_CONFIG, 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 + ], + Ecu.engine: [ + b"14C204", + ], +} + + +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, Optional[int]], Iterable[bytes]]): + for (ecu, addr, subaddr), fws in fw_versions.items(): + self.assertIn(ecu, ECU_ADDRESSES, "Unknown ECU") + self.assertEqual(addr, ECU_ADDRESSES[ecu], "ECU address mismatch") + self.assertIsNone(subaddr, "Unexpected ECU subaddress") + + # Software part number takes the form: PREFIX-CORE-SUFFIX + # Prefix changes based on the family of part. It includes the model year + # and likely the platform. + # Core identifies the type of the item (e.g. 14D003 = PSCM, 14C204 = PCM). + # Suffix specifies the version of the part. -AA would be followed by -AB. + # Small increments in the suffix are usually compatible. + # Details: https://forscan.org/forum/viewtopic.php?p=70008#p70008 + for fw in fws: + self.assertEqual(len(fw), 24, "Expected ECU response to be 24 bytes") + + # TODO: parse with regex, don't need detailed error message + fw_parts = fw.rstrip(b'\x00').split(b'-') + self.assertEqual(len(fw_parts), 3, "Expected FW to be in format: prefix-core-suffix") + + prefix, core, suffix = fw_parts + self.assertEqual(len(prefix), 4, "Expected FW prefix to be 4 characters") + self.assertIn(len(core), (5, 6), "Expected FW core to be 5-6 characters") + self.assertIn(core, ECU_FW_CORE[ecu], f"Unexpected FW core for {ecu}") + self.assertIn(len(suffix), (2, 3), "Expected FW suffix to be 2-3 characters") + + +if __name__ == "__main__": + unittest.main()