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.
		
		
		
		
			
				
					320 lines
				
				12 KiB
			
		
		
			
		
	
	
					320 lines
				
				12 KiB
			| 
								 
											2 weeks ago
										 
									 | 
							
								import copy
							 | 
						||
| 
								 | 
							
								import re
							 | 
						||
| 
								 | 
							
								from dataclasses import dataclass, field, replace
							 | 
						||
| 
								 | 
							
								from enum import Enum, IntFlag
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								from opendbc.car import Bus, CarSpecs, DbcDict, PlatformConfig, Platforms, uds
							 | 
						||
| 
								 | 
							
								from opendbc.car.lateral import AngleSteeringLimits
							 | 
						||
| 
								 | 
							
								from opendbc.car.structs import CarParams
							 | 
						||
| 
								 | 
							
								from opendbc.car.docs_definitions import CarFootnote, CarHarness, CarDocs, CarParts, Column, \
							 | 
						||
| 
								 | 
							
								                                                     Device
							 | 
						||
| 
								 | 
							
								from opendbc.car.fw_query_definitions import FwQueryConfig, LiveFwVersions, OfflineFwVersions, Request, StdQueries, p16
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								Ecu = CarParams.Ecu
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								class CarControllerParams:
							 | 
						||
| 
								 | 
							
								  STEER_STEP = 5        # LateralMotionControl, 20Hz
							 | 
						||
| 
								 | 
							
								  LKA_STEP = 3          # Lane_Assist_Data1, 33Hz
							 | 
						||
| 
								 | 
							
								  ACC_CONTROL_STEP = 2  # ACCDATA, 50Hz
							 | 
						||
| 
								 | 
							
								  LKAS_UI_STEP = 100    # IPMA_Data, 1Hz
							 | 
						||
| 
								 | 
							
								  ACC_UI_STEP = 20      # ACCDATA_3, 5Hz
							 | 
						||
| 
								 | 
							
								  BUTTONS_STEP = 5      # Steering_Data_FD1, 10Hz, but send twice as fast
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								  STEER_DRIVER_ALLOWANCE = 1.0  # Driver intervention threshold, Nm
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								  ANGLE_LIMITS: AngleSteeringLimits = AngleSteeringLimits(
							 | 
						||
| 
								 | 
							
								    0.02,  # Max curvature for steering command, m^-1
							 | 
						||
| 
								 | 
							
								    # Curvature rate limits
							 | 
						||
| 
								 | 
							
								    # Max curvature is limited by the EPS to an equivalent of ~2.0 m/s^2 at all speeds,
							 | 
						||
| 
								 | 
							
								    #  however max curvature rate linearly decreases as speed increases:
							 | 
						||
| 
								 | 
							
								    #  ~0.009 m^-1/sec at 7 m/s, ~0.002 m^-1/sec at 35 m/s
							 | 
						||
| 
								 | 
							
								    # Limit to ~2 m/s^3 up, ~3.3 m/s^3 down at 75 mph and match EPS limit at low speed
							 | 
						||
| 
								 | 
							
								    ([5, 25], [0.00045, 0.0001]),
							 | 
						||
| 
								 | 
							
								    ([5, 25], [0.00045, 0.00015])
							 | 
						||
| 
								 | 
							
								  )
							 | 
						||
| 
								 | 
							
								  CURVATURE_ERROR = 0.002  # ~6 degrees at 10 m/s, ~10 degrees at 35 m/s
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								  ACCEL_MAX = 2.0               # m/s^2 max acceleration
							 | 
						||
| 
								 | 
							
								  ACCEL_MIN = -3.5              # m/s^2 max deceleration
							 | 
						||
| 
								 | 
							
								  MIN_GAS = -0.5
							 | 
						||
| 
								 | 
							
								  INACTIVE_GAS = -5.0
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								  def __init__(self, CP):
							 | 
						||
| 
								 | 
							
								    pass
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								class FordSafetyFlags(IntFlag):
							 | 
						||
| 
								 | 
							
								  LONG_CONTROL = 1
							 | 
						||
| 
								 | 
							
								  CANFD = 2
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								class FordFlags(IntFlag):
							 | 
						||
| 
								 | 
							
								  # Static flags
							 | 
						||
| 
								 | 
							
								  CANFD = 1
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								class RADAR:
							 | 
						||
| 
								 | 
							
								  DELPHI_ESR = 'ford_fusion_2018_adas'
							 | 
						||
| 
								 | 
							
								  DELPHI_MRR = 'FORD_CADS'
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								class Footnote(Enum):
							 | 
						||
| 
								 | 
							
								  FOCUS = CarFootnote(
							 | 
						||
| 
								 | 
							
								    "Refers only to the Focus Mk4 (C519) available in Europe/China/Taiwan/Australasia, not the Focus Mk3 (C346) in " +
							 | 
						||
| 
								 | 
							
								    "North and South America/Southeast Asia.",
							 | 
						||
| 
								 | 
							
								    Column.MODEL,
							 | 
						||
| 
								 | 
							
								  )
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								@dataclass
							 | 
						||
| 
								 | 
							
								class FordCarDocs(CarDocs):
							 | 
						||
| 
								 | 
							
								  package: str = "Co-Pilot360 Assist+"
							 | 
						||
| 
								 | 
							
								  hybrid: bool = False
							 | 
						||
| 
								 | 
							
								  plug_in_hybrid: bool = False
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								  def init_make(self, CP: CarParams):
							 | 
						||
| 
								 | 
							
								    harness = CarHarness.ford_q4 if CP.flags & FordFlags.CANFD else CarHarness.ford_q3
							 | 
						||
| 
								 | 
							
								    if CP.carFingerprint in (CAR.FORD_BRONCO_SPORT_MK1, CAR.FORD_MAVERICK_MK1, CAR.FORD_F_150_MK14, CAR.FORD_F_150_LIGHTNING_MK1):
							 | 
						||
| 
								 | 
							
								      self.car_parts = CarParts([Device.threex_angled_mount, harness])
							 | 
						||
| 
								 | 
							
								    else:
							 | 
						||
| 
								 | 
							
								      self.car_parts = CarParts([Device.threex, harness])
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								    if harness == CarHarness.ford_q4:
							 | 
						||
| 
								 | 
							
								      self.setup_video = "https://www.youtube.com/watch?v=uUGkH6C_EQU"
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								    if CP.carFingerprint in (CAR.FORD_F_150_MK14, CAR.FORD_F_150_LIGHTNING_MK1, CAR.FORD_EXPEDITION_MK4):
							 | 
						||
| 
								 | 
							
								      self.setup_video = "https://www.youtube.com/watch?v=MewJc9LYp9M"
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								@dataclass
							 | 
						||
| 
								 | 
							
								class FordPlatformConfig(PlatformConfig):
							 | 
						||
| 
								 | 
							
								  dbc_dict: DbcDict = field(default_factory=lambda: {
							 | 
						||
| 
								 | 
							
								    Bus.pt: 'ford_lincoln_base_pt',
							 | 
						||
| 
								 | 
							
								    Bus.radar: RADAR.DELPHI_MRR,
							 | 
						||
| 
								 | 
							
								  })
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								  def init(self):
							 | 
						||
| 
								 | 
							
								    for car_docs in list(self.car_docs):
							 | 
						||
| 
								 | 
							
								      if car_docs.hybrid:
							 | 
						||
| 
								 | 
							
								        name = f"{car_docs.make} {car_docs.model} Hybrid {car_docs.years}"
							 | 
						||
| 
								 | 
							
								        self.car_docs.append(replace(copy.deepcopy(car_docs), name=name))
							 | 
						||
| 
								 | 
							
								      if car_docs.plug_in_hybrid:
							 | 
						||
| 
								 | 
							
								        name = f"{car_docs.make} {car_docs.model} Plug-in Hybrid {car_docs.years}"
							 | 
						||
| 
								 | 
							
								        self.car_docs.append(replace(copy.deepcopy(car_docs), name=name))
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								@dataclass
							 | 
						||
| 
								 | 
							
								class FordCANFDPlatformConfig(FordPlatformConfig):
							 | 
						||
| 
								 | 
							
								  dbc_dict: DbcDict = field(default_factory=lambda: {
							 | 
						||
| 
								 | 
							
								    Bus.pt: 'ford_lincoln_base_pt',
							 | 
						||
| 
								 | 
							
								  })
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								  def init(self):
							 | 
						||
| 
								 | 
							
								    super().init()
							 | 
						||
| 
								 | 
							
								    self.flags |= FordFlags.CANFD
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								@dataclass
							 | 
						||
| 
								 | 
							
								class FordF150LightningPlatform(FordCANFDPlatformConfig):
							 | 
						||
| 
								 | 
							
								  def init(self):
							 | 
						||
| 
								 | 
							
								    super().init()
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								    # Don't show in docs until this issue is resolved. See https://github.com/commaai/openpilot/issues/30302
							 | 
						||
| 
								 | 
							
								    self.car_docs = []
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								class CAR(Platforms):
							 | 
						||
| 
								 | 
							
								  FORD_BRONCO_SPORT_MK1 = FordPlatformConfig(
							 | 
						||
| 
								 | 
							
								    [FordCarDocs("Ford Bronco Sport 2021-24")],
							 | 
						||
| 
								 | 
							
								    CarSpecs(mass=1625, wheelbase=2.67, steerRatio=17.7),
							 | 
						||
| 
								 | 
							
								  )
							 | 
						||
| 
								 | 
							
								  FORD_ESCAPE_MK4 = FordPlatformConfig(
							 | 
						||
| 
								 | 
							
								    [
							 | 
						||
| 
								 | 
							
								      FordCarDocs("Ford Escape 2020-22", hybrid=True, plug_in_hybrid=True),
							 | 
						||
| 
								 | 
							
								      FordCarDocs("Ford Kuga 2020-23", "Adaptive Cruise Control with Lane Centering", hybrid=True, plug_in_hybrid=True),
							 | 
						||
| 
								 | 
							
								    ],
							 | 
						||
| 
								 | 
							
								    CarSpecs(mass=1750, wheelbase=2.71, steerRatio=16.7),
							 | 
						||
| 
								 | 
							
								  )
							 | 
						||
| 
								 | 
							
								  FORD_ESCAPE_MK4_5 = FordCANFDPlatformConfig(
							 | 
						||
| 
								 | 
							
								    [
							 | 
						||
| 
								 | 
							
								      FordCarDocs("Ford Escape 2023-24", hybrid=True, plug_in_hybrid=True, setup_video="https://www.youtube.com/watch?v=M6uXf4b2SHM"),
							 | 
						||
| 
								 | 
							
								      FordCarDocs("Ford Kuga Hybrid 2024", "All"),
							 | 
						||
| 
								 | 
							
								      FordCarDocs("Ford Kuga Plug-in Hybrid 2024", "All"),
							 | 
						||
| 
								 | 
							
								    ],
							 | 
						||
| 
								 | 
							
								    CarSpecs(mass=1750, wheelbase=2.71, steerRatio=16.7),
							 | 
						||
| 
								 | 
							
								  )
							 | 
						||
| 
								 | 
							
								  FORD_EXPLORER_MK6 = FordPlatformConfig(
							 | 
						||
| 
								 | 
							
								    [
							 | 
						||
| 
								 | 
							
								      FordCarDocs("Ford Explorer 2020-24", hybrid=True),  # Hybrid: Limited and Platinum only
							 | 
						||
| 
								 | 
							
								      FordCarDocs("Lincoln Aviator 2020-24", "Co-Pilot360 Plus", plug_in_hybrid=True),  # Hybrid: Grand Touring only
							 | 
						||
| 
								 | 
							
								    ],
							 | 
						||
| 
								 | 
							
								    CarSpecs(mass=2050, wheelbase=3.025, steerRatio=16.8),
							 | 
						||
| 
								 | 
							
								  )
							 | 
						||
| 
								 | 
							
								  FORD_EXPEDITION_MK4 = FordCANFDPlatformConfig(
							 | 
						||
| 
								 | 
							
								    [FordCarDocs("Ford Expedition 2022-24", "Co-Pilot360 Assist 2.0", hybrid=False)],
							 | 
						||
| 
								 | 
							
								    CarSpecs(mass=2000, wheelbase=3.69, steerRatio=17.0),
							 | 
						||
| 
								 | 
							
								  )
							 | 
						||
| 
								 | 
							
								  FORD_F_150_MK14 = FordCANFDPlatformConfig(
							 | 
						||
| 
								 | 
							
								    [FordCarDocs("Ford F-150 2021-23", "Co-Pilot360 Assist 2.0", hybrid=True)],
							 | 
						||
| 
								 | 
							
								    CarSpecs(mass=2000, wheelbase=3.69, steerRatio=17.0),
							 | 
						||
| 
								 | 
							
								  )
							 | 
						||
| 
								 | 
							
								  FORD_F_150_LIGHTNING_MK1 = FordF150LightningPlatform(
							 | 
						||
| 
								 | 
							
								    [FordCarDocs("Ford F-150 Lightning 2022-23", "Co-Pilot360 Assist 2.0")],
							 | 
						||
| 
								 | 
							
								    CarSpecs(mass=2948, wheelbase=3.70, steerRatio=16.9),
							 | 
						||
| 
								 | 
							
								  )
							 | 
						||
| 
								 | 
							
								  FORD_FOCUS_MK4 = FordPlatformConfig(
							 | 
						||
| 
								 | 
							
								    [FordCarDocs("Ford Focus 2018", "Adaptive Cruise Control with Lane Centering", footnotes=[Footnote.FOCUS], hybrid=True)],  # mHEV only
							 | 
						||
| 
								 | 
							
								    CarSpecs(mass=1350, wheelbase=2.7, steerRatio=15.0),
							 | 
						||
| 
								 | 
							
								  )
							 | 
						||
| 
								 | 
							
								  FORD_MAVERICK_MK1 = FordPlatformConfig(
							 | 
						||
| 
								 | 
							
								    [
							 | 
						||
| 
								 | 
							
								      FordCarDocs("Ford Maverick 2022", "LARIAT Luxury", hybrid=True),
							 | 
						||
| 
								 | 
							
								      FordCarDocs("Ford Maverick 2023-24", "Co-Pilot360 Assist", hybrid=True),
							 | 
						||
| 
								 | 
							
								    ],
							 | 
						||
| 
								 | 
							
								    CarSpecs(mass=1650, wheelbase=3.076, steerRatio=17.0),
							 | 
						||
| 
								 | 
							
								  )
							 | 
						||
| 
								 | 
							
								  FORD_MUSTANG_MACH_E_MK1 = FordCANFDPlatformConfig(
							 | 
						||
| 
								 | 
							
								    [FordCarDocs("Ford Mustang Mach-E 2021-24", "All", setup_video="https://www.youtube.com/watch?v=AR4_eTF3b_A")],
							 | 
						||
| 
								 | 
							
								    CarSpecs(mass=2200, wheelbase=2.984, steerRatio=17.0),  # TODO: check steer ratio
							 | 
						||
| 
								 | 
							
								  )
							 | 
						||
| 
								 | 
							
								  FORD_RANGER_MK2 = FordCANFDPlatformConfig(
							 | 
						||
| 
								 | 
							
								    [FordCarDocs("Ford Ranger 2024", "Adaptive Cruise Control with Lane Centering", setup_video="https://www.youtube.com/watch?v=2oJlXCKYOy0")],
							 | 
						||
| 
								 | 
							
								    CarSpecs(mass=2000, wheelbase=3.27, steerRatio=17.0),
							 | 
						||
| 
								 | 
							
								  )
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								# FW response contains a combined software and part number
							 | 
						||
| 
								 | 
							
								# A-Z except no I, O or W
							 | 
						||
| 
								 | 
							
								# e.g. NZ6A-14C204-AAA
							 | 
						||
| 
								 | 
							
								#      1222-333333-444
							 | 
						||
| 
								 | 
							
								# 1 = Model year hint (approximates model year/generation)
							 | 
						||
| 
								 | 
							
								# 2 = Platform hint
							 | 
						||
| 
								 | 
							
								# 3 = Part number
							 | 
						||
| 
								 | 
							
								# 4 = Software version
							 | 
						||
| 
								 | 
							
								FW_ALPHABET = b'A-HJ-NP-VX-Z'
							 | 
						||
| 
								 | 
							
								FW_PATTERN = re.compile(b'^(?P<model_year_hint>[' + FW_ALPHABET + b'])' +
							 | 
						||
| 
								 | 
							
								                        b'(?P<platform_hint>[0-9' + FW_ALPHABET + b']{3})-' +
							 | 
						||
| 
								 | 
							
								                        b'(?P<part_number>[0-9' + FW_ALPHABET + b']{5,6})-' +
							 | 
						||
| 
								 | 
							
								                        b'(?P<software_revision>[' + FW_ALPHABET + b']{2,})\x00*$')
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								def get_platform_codes(fw_versions: list[bytes] | set[bytes]) -> set[tuple[bytes, bytes]]:
							 | 
						||
| 
								 | 
							
								  codes = set()
							 | 
						||
| 
								 | 
							
								  for fw in fw_versions:
							 | 
						||
| 
								 | 
							
								    match = FW_PATTERN.match(fw)
							 | 
						||
| 
								 | 
							
								    if match is not None:
							 | 
						||
| 
								 | 
							
								      codes.add((match.group('platform_hint'), match.group('model_year_hint')))
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								  return codes
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								def match_fw_to_car_fuzzy(live_fw_versions: LiveFwVersions, vin: str, offline_fw_versions: OfflineFwVersions) -> set[str]:
							 | 
						||
| 
								 | 
							
								  candidates: set[str] = set()
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								  for candidate, fws in offline_fw_versions.items():
							 | 
						||
| 
								 | 
							
								    # Keep track of ECUs which pass all checks (platform hint, within model year hint range)
							 | 
						||
| 
								 | 
							
								    valid_found_ecus = set()
							 | 
						||
| 
								 | 
							
								    valid_expected_ecus = {ecu[1:] for ecu in fws if ecu[0] in PLATFORM_CODE_ECUS}
							 | 
						||
| 
								 | 
							
								    for ecu, expected_versions in fws.items():
							 | 
						||
| 
								 | 
							
								      addr = ecu[1:]
							 | 
						||
| 
								 | 
							
								      # Only check ECUs expected to have platform codes
							 | 
						||
| 
								 | 
							
								      if ecu[0] not in PLATFORM_CODE_ECUS:
							 | 
						||
| 
								 | 
							
								        continue
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								      # Expected platform codes & model year hints
							 | 
						||
| 
								 | 
							
								      codes = get_platform_codes(expected_versions)
							 | 
						||
| 
								 | 
							
								      expected_platform_codes = {code for code, _ in codes}
							 | 
						||
| 
								 | 
							
								      expected_model_year_hints = {model_year_hint for _, model_year_hint in codes}
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								      # Found platform codes & model year hints
							 | 
						||
| 
								 | 
							
								      codes = get_platform_codes(live_fw_versions.get(addr, set()))
							 | 
						||
| 
								 | 
							
								      found_platform_codes = {code for code, _ in codes}
							 | 
						||
| 
								 | 
							
								      found_model_year_hints = {model_year_hint for _, model_year_hint in codes}
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								      # Check platform code matches for any found versions
							 | 
						||
| 
								 | 
							
								      if not any(found_platform_code in expected_platform_codes for found_platform_code in found_platform_codes):
							 | 
						||
| 
								 | 
							
								        break
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								      # Check any model year hint within range in the database. Note that some models have more than one
							 | 
						||
| 
								 | 
							
								      # platform code per ECU which we don't consider as separate ranges
							 | 
						||
| 
								 | 
							
								      if not any(min(expected_model_year_hints) <= found_model_year_hint <= max(expected_model_year_hints) for
							 | 
						||
| 
								 | 
							
								                 found_model_year_hint in found_model_year_hints):
							 | 
						||
| 
								 | 
							
								        break
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								      valid_found_ecus.add(addr)
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								    # If all live ECUs pass all checks for candidate, add it as a match
							 | 
						||
| 
								 | 
							
								    if valid_expected_ecus.issubset(valid_found_ecus):
							 | 
						||
| 
								 | 
							
								      candidates.add(candidate)
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								  return candidates
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								# All of these ECUs must be present and are expected to have platform codes we can match
							 | 
						||
| 
								 | 
							
								PLATFORM_CODE_ECUS = (Ecu.abs, Ecu.fwdCamera, Ecu.fwdRadar, Ecu.eps)
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								DATA_IDENTIFIER_FORD_ASBUILT = 0xDE00
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								ASBUILT_BLOCKS: list[tuple[int, list]] = [
							 | 
						||
| 
								 | 
							
								  (1, [Ecu.debug, Ecu.fwdCamera, Ecu.eps]),
							 | 
						||
| 
								 | 
							
								  (2, [Ecu.abs, Ecu.debug, Ecu.eps]),
							 | 
						||
| 
								 | 
							
								  (3, [Ecu.abs, Ecu.debug, Ecu.eps]),
							 | 
						||
| 
								 | 
							
								  (4, [Ecu.debug, Ecu.fwdCamera]),
							 | 
						||
| 
								 | 
							
								  (5, [Ecu.debug]),
							 | 
						||
| 
								 | 
							
								  (6, [Ecu.debug]),
							 | 
						||
| 
								 | 
							
								  (7, [Ecu.debug]),
							 | 
						||
| 
								 | 
							
								  (8, [Ecu.debug]),
							 | 
						||
| 
								 | 
							
								  (9, [Ecu.debug]),
							 | 
						||
| 
								 | 
							
								  (16, [Ecu.debug, Ecu.fwdCamera]),
							 | 
						||
| 
								 | 
							
								  (18, [Ecu.fwdCamera]),
							 | 
						||
| 
								 | 
							
								  (20, [Ecu.fwdCamera]),
							 | 
						||
| 
								 | 
							
								  (21, [Ecu.fwdCamera]),
							 | 
						||
| 
								 | 
							
								]
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								def ford_asbuilt_block_request(block_id: int):
							 | 
						||
| 
								 | 
							
								  return bytes([uds.SERVICE_TYPE.READ_DATA_BY_IDENTIFIER]) + p16(DATA_IDENTIFIER_FORD_ASBUILT + block_id - 1)
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								def ford_asbuilt_block_response(block_id: int):
							 | 
						||
| 
								 | 
							
								  return bytes([uds.SERVICE_TYPE.READ_DATA_BY_IDENTIFIER + 0x40]) + p16(DATA_IDENTIFIER_FORD_ASBUILT + block_id - 1)
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								FW_QUERY_CONFIG = FwQueryConfig(
							 | 
						||
| 
								 | 
							
								  requests=[
							 | 
						||
| 
								 | 
							
								    # CAN and CAN FD queries are combined.
							 | 
						||
| 
								 | 
							
								    # FIXME: For CAN FD, ECUs respond with frames larger than 8 bytes on the powertrain bus
							 | 
						||
| 
								 | 
							
								    Request(
							 | 
						||
| 
								 | 
							
								      [StdQueries.TESTER_PRESENT_REQUEST, StdQueries.MANUFACTURER_SOFTWARE_VERSION_REQUEST],
							 | 
						||
| 
								 | 
							
								      [StdQueries.TESTER_PRESENT_RESPONSE, StdQueries.MANUFACTURER_SOFTWARE_VERSION_RESPONSE],
							 | 
						||
| 
								 | 
							
								      whitelist_ecus=[Ecu.abs, Ecu.debug, Ecu.engine, Ecu.eps, Ecu.fwdCamera, Ecu.fwdRadar, Ecu.shiftByWire],
							 | 
						||
| 
								 | 
							
								      logging=True,
							 | 
						||
| 
								 | 
							
								    ),
							 | 
						||
| 
								 | 
							
								    Request(
							 | 
						||
| 
								 | 
							
								      [StdQueries.TESTER_PRESENT_REQUEST, StdQueries.MANUFACTURER_SOFTWARE_VERSION_REQUEST],
							 | 
						||
| 
								 | 
							
								      [StdQueries.TESTER_PRESENT_RESPONSE, StdQueries.MANUFACTURER_SOFTWARE_VERSION_RESPONSE],
							 | 
						||
| 
								 | 
							
								      whitelist_ecus=[Ecu.abs, Ecu.debug, Ecu.engine, Ecu.eps, Ecu.fwdCamera, Ecu.fwdRadar, Ecu.shiftByWire],
							 | 
						||
| 
								 | 
							
								      bus=0,
							 | 
						||
| 
								 | 
							
								      auxiliary=True,
							 | 
						||
| 
								 | 
							
								    ),
							 | 
						||
| 
								 | 
							
								    *[Request(
							 | 
						||
| 
								 | 
							
								      [StdQueries.TESTER_PRESENT_REQUEST, ford_asbuilt_block_request(block_id)],
							 | 
						||
| 
								 | 
							
								      [StdQueries.TESTER_PRESENT_RESPONSE, ford_asbuilt_block_response(block_id)],
							 | 
						||
| 
								 | 
							
								      whitelist_ecus=ecus,
							 | 
						||
| 
								 | 
							
								      bus=0,
							 | 
						||
| 
								 | 
							
								      logging=True,
							 | 
						||
| 
								 | 
							
								    ) for block_id, ecus in ASBUILT_BLOCKS],
							 | 
						||
| 
								 | 
							
								  ],
							 | 
						||
| 
								 | 
							
								  extra_ecus=[
							 | 
						||
| 
								 | 
							
								    (Ecu.engine, 0x7e0, None),        # Powertrain Control Module
							 | 
						||
| 
								 | 
							
								                                      # Note: We are unlikely to get a response from behind the gateway
							 | 
						||
| 
								 | 
							
								    (Ecu.shiftByWire, 0x732, None),   # Gear Shift Module
							 | 
						||
| 
								 | 
							
								    (Ecu.debug, 0x7d0, None),         # Accessory Protocol Interface Module
							 | 
						||
| 
								 | 
							
								  ],
							 | 
						||
| 
								 | 
							
								  # Custom fuzzy fingerprinting function using platform and model year hints
							 | 
						||
| 
								 | 
							
								  match_fw_to_car_fuzzy=match_fw_to_car_fuzzy,
							 | 
						||
| 
								 | 
							
								)
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								DBC = CAR.create_dbc_map()
							 |