You can not select more than 25 topics
			Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
		
		
		
		
		
			
		
			
				
					
					
						
							144 lines
						
					
					
						
							5.7 KiB
						
					
					
				
			
		
		
	
	
							144 lines
						
					
					
						
							5.7 KiB
						
					
					
				#!/usr/bin/env python3
 | 
						|
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"
 | 
						|
 |