144 lines
5.6 KiB
Python
144 lines
5.6 KiB
Python
import random
|
|
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.fw_versions import build_fw_dict
|
|
from openpilot.selfdrive.car.ford.values import CAR, FW_QUERY_CONFIG, FW_PATTERN, 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.debug: 0x7D0, # Accessory Protocol Interface Module (APIM)
|
|
}
|
|
|
|
|
|
ECU_PART_NUMBER = {
|
|
Ecu.eps: [
|
|
b"14D003",
|
|
],
|
|
Ecu.abs: [
|
|
b"2D053",
|
|
],
|
|
Ecu.fwdRadar: [
|
|
b"14D049",
|
|
],
|
|
Ecu.fwdCamera: [
|
|
b"14F397", # Ford Q3
|
|
b"14H102", # Ford Q4
|
|
],
|
|
}
|
|
|
|
|
|
class TestFordFW:
|
|
def test_fw_query_config(self):
|
|
for (ecu, addr, subaddr) in FW_QUERY_CONFIG.extra_ecus:
|
|
assert ecu in ECU_ADDRESSES, "Unknown ECU"
|
|
assert addr == ECU_ADDRESSES[ecu], "ECU address mismatch"
|
|
assert subaddr is None, "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():
|
|
assert ecu in ECU_PART_NUMBER, "Unexpected ECU"
|
|
assert addr == ECU_ADDRESSES[ecu], "ECU address mismatch"
|
|
assert subaddr is None, "Unexpected ECU subaddress"
|
|
|
|
for fw in fws:
|
|
assert len(fw) == 24, "Expected ECU response to be 24 bytes"
|
|
|
|
match = FW_PATTERN.match(fw)
|
|
assert match is not None, f"Unable to parse FW: {fw!r}"
|
|
if match:
|
|
part_number = match.group("part_number")
|
|
assert part_number in ECU_PART_NUMBER[ecu], f"Unexpected part number for {fw!r}"
|
|
|
|
codes = get_platform_codes([fw])
|
|
assert 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",
|
|
])
|
|
assert results == {(b"X6A", b"J"), (b"Z6T", b"N"), (b"J6T", b"P"), (b"B5A", b"L")}
|
|
|
|
def test_fuzzy_match(self):
|
|
for platform, fw_by_addr in FW_VERSIONS.items():
|
|
# Ensure there's no overlaps in platform codes
|
|
for _ in range(20):
|
|
car_fw = []
|
|
for ecu, fw_versions in fw_by_addr.items():
|
|
ecu_name, addr, sub_addr = ecu
|
|
fw = random.choice(fw_versions)
|
|
car_fw.append({"ecu": ecu_name, "fwVersion": fw, "address": addr,
|
|
"subAddress": 0 if sub_addr is None else sub_addr})
|
|
|
|
CP = car.CarParams.new_message(carFw=car_fw)
|
|
matches = FW_QUERY_CONFIG.match_fw_to_car_fuzzy(build_fw_dict(CP.carFw), CP.carVin, FW_VERSIONS)
|
|
assert matches == {platform}
|
|
|
|
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",
|
|
],
|
|
# We consider all model year hints for ECU, even with different platform codes
|
|
(Ecu.fwdCamera, 0x706, None): [
|
|
b"LB5T-14F397-AD\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00",
|
|
b"NC5T-14F397-AF\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00",
|
|
],
|
|
}
|
|
expected_fingerprint = CAR.FORD_EXPLORER_MK6
|
|
|
|
# ensure that we fuzzy match on all non-exact FW with changed revisions
|
|
live_fw = {
|
|
(0x730, None): {b"L1MC-14D003-XX\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00"},
|
|
(0x760, None): {b"L1MC-2D053-XX\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00"},
|
|
(0x764, None): {b"LB5T-14D049-XX\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00"},
|
|
(0x706, None): {b"LB5T-14F397-XX\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00"},
|
|
}
|
|
candidates = FW_QUERY_CONFIG.match_fw_to_car_fuzzy(live_fw, '', {expected_fingerprint: offline_fw})
|
|
assert candidates == {expected_fingerprint}
|
|
|
|
# model year hint in between the range should match
|
|
live_fw[(0x706, None)] = {b"MB5T-14F397-XX\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00"}
|
|
candidates = FW_QUERY_CONFIG.match_fw_to_car_fuzzy(live_fw, '', {expected_fingerprint: offline_fw,})
|
|
assert candidates == {expected_fingerprint}
|
|
|
|
# unseen model year hint should not match
|
|
live_fw[(0x760, None)] = {b"M1MC-2D053-XX\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00"}
|
|
candidates = FW_QUERY_CONFIG.match_fw_to_car_fuzzy(live_fw, '', {expected_fingerprint: offline_fw})
|
|
assert len(candidates) == 0, "Should not match new model year hint"
|