#!/usr/bin/env python3 
							 
						 
					
						
							
								
							 
							
								
									
										 
									 
								
							 
							
								 
							 
							
							
								import  random 
							 
						 
					
						
							
								
							 
							
								
							 
							
								 
							 
							
							
								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 . 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) 
 
							 
						 
					
						
							
								
									
										
											 
										 
										
											
												Ford: log interesting module configuration data (#31569)
* Ford: log interesting module configuration data
Ford ECUs have what is called "As-Built Data" which is configured at the
factory/workshop to set what packages/features are enabled on the car.
But they also contain vehicle specific information (VIN, make, model,
weight, wheel base...), DTC information and driver preferences.
I dumped the CAN traffic for the FORScan diagnostic tool to see how it
requests this information from the ECUs.
<details>
<summary>FORScan communication with IPMA (camera)</summary>
<pre>
{'addr': 1798, 'type': 'request', 'service': 'READ_DATA_BY_IDENTIFIER', 'hex': '0200'}
{'addr': 1806, 'type': 'negative_response', 'hex': '2231'}
{'addr': 1798, 'type': 'request', 'service': 'TESTER_PRESENT', 'hex': '00'}
{'addr': 1806, 'type': 'positive_response', 'service': 'TESTER_PRESENT', 'hex': '00'}
{'addr': 1798, 'type': 'request', 'service': 'READ_DATA_BY_IDENTIFIER', 'hex': 'f190'}
{'addr': 1806, 'type': 'negative_response', 'hex': '2231'}
{'addr': 1798, 'type': 'request', 'service': 'READ_DATA_BY_IDENTIFIER', 'hex': 'de00'}
{'addr': 1806, 'type': 'positive_response', 'service': 'READ_DATA_BY_IDENTIFIER', 'hex': 'de00020799dbaa10296516a440000000000000000000000000'}
{'addr': 1798, 'type': 'request', 'service': 'READ_DATA_BY_IDENTIFIER', 'hex': 'f113'}
{'addr': 1806, 'type': 'positive_response', 'service': 'READ_DATA_BY_IDENTIFIER', 'data': b'\xf1\x13JX7T-19H406-CH\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00', 'hex': 'f1134a5837542d3139483430362d434800000000000000000000'}
{'addr': 1798, 'type': 'request', 'service': 'READ_DATA_BY_IDENTIFIER', 'hex': 'f188'}
{'addr': 1806, 'type': 'positive_response', 'service': 'READ_DATA_BY_IDENTIFIER', 'data': b'\xf1\x88JX7T-14F397-AH\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00', 'hex': 'f1884a5837542d3134463339372d414800000000000000000000'}
{'addr': 1798, 'type': 'request', 'service': 'READ_DATA_BY_IDENTIFIER', 'hex': 'f120'}
{'addr': 1806, 'type': 'positive_response', 'service': 'READ_DATA_BY_IDENTIFIER', 'data': b'\xf1 JX7T-14F397-BF\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00', 'hex': 'f1204a5837542d3134463339372d424600000000000000000000'}
{'addr': 1798, 'type': 'request', 'service': 'READ_DATA_BY_IDENTIFIER', 'hex': 'f121'}
{'addr': 1806, 'type': 'negative_response', 'hex': '2231'}
{'addr': 1798, 'type': 'request', 'service': 'READ_DATA_BY_IDENTIFIER', 'hex': 'f124'}
{'addr': 1806, 'type': 'positive_response', 'service': 'READ_DATA_BY_IDENTIFIER', 'data': b'\xf1$JX7T-14F398-AG\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00', 'hex': 'f1244a5837542d3134463339382d414700000000000000000000'}
{'addr': 1798, 'type': 'request', 'service': 'READ_DATA_BY_IDENTIFIER', 'hex': 'f125'}
{'addr': 1806, 'type': 'positive_response', 'service': 'READ_DATA_BY_IDENTIFIER', 'data': b'\xf1%JX7T-14F398-BF\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00', 'hex': 'f1254a5837542d3134463339382d424600000000000000000000'}
{'addr': 1798, 'type': 'request', 'service': 'READ_DATA_BY_IDENTIFIER', 'hex': 'f126'}
{'addr': 1806, 'type': 'negative_response', 'hex': '2231'}
{'addr': 1798, 'type': 'request', 'service': 'READ_DATA_BY_IDENTIFIER', 'hex': 'f10a'}
{'addr': 1806, 'type': 'negative_response', 'hex': '2231'}
{'addr': 1798, 'type': 'request', 'service': 'READ_DATA_BY_IDENTIFIER', 'hex': 'f111'}
{'addr': 1806, 'type': 'positive_response', 'service': 'READ_DATA_BY_IDENTIFIER', 'data': b'\xf1\x11JX7T-14F403-CA\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00', 'hex': 'f1114a5837542d3134463430332d434100000000000000000000'}
{'addr': 1798, 'type': 'request', 'service': 'READ_DATA_BY_IDENTIFIER', 'hex': 'f18c'}
{'addr': 1806, 'type': 'positive_response', 'service': 'READ_DATA_BY_IDENTIFIER', 'data': b'\xf1\x8c182762191\x00\x00\x00\x00\x00\x00\x00', 'hex': 'f18c31383237363231393100000000000000'}
{'addr': 1798, 'type': 'request', 'service': 'READ_DATA_BY_IDENTIFIER', 'hex': 'f162'}
{'addr': 1806, 'type': 'negative_response', 'hex': '2231'}
{'addr': 1798, 'type': 'request', 'service': 'READ_DATA_BY_IDENTIFIER', 'hex': 'f110'}
{'addr': 1806, 'type': 'positive_response', 'service': 'READ_DATA_BY_IDENTIFIER', 'data': b'\xf1\x10DS-JX7T-19H406-AD\x00\x00\x00\x00\x00\x00\x00', 'hex': 'f11044532d4a5837542d3139483430362d414400000000000000'}
{'addr': 1798, 'type': 'request', 'service': 'READ_DATA_BY_IDENTIFIER', 'hex': '0202'}
{'addr': 1806, 'type': 'negative_response', 'hex': '2231'}
{'addr': 1798, 'type': 'request', 'service': 'READ_DATA_BY_IDENTIFIER', 'hex': 'd100'}
{'addr': 1806, 'type': 'positive_response', 'service': 'READ_DATA_BY_IDENTIFIER', 'hex': 'd10001'}
{'addr': 1798, 'type': 'request', 'service': 'READ_DATA_BY_IDENTIFIER', 'hex': 'd700'}
{'addr': 1806, 'type': 'positive_response', 'service': 'READ_DATA_BY_IDENTIFIER', 'hex': 'd70001010101'}
{'addr': 1798, 'type': 'request', 'service': 'READ_DATA_BY_IDENTIFIER', 'hex': 'd701'}
{'addr': 1806, 'type': 'positive_response', 'service': 'READ_DATA_BY_IDENTIFIER', 'hex': 'd70101020000'}
{'addr': 1798, 'type': 'request', 'service': 'READ_DATA_BY_IDENTIFIER', 'hex': 'dd01'}
{'addr': 1806, 'type': 'positive_response', 'service': 'READ_DATA_BY_IDENTIFIER', 'hex': 'dd010102f8'}
{'addr': 1798, 'type': 'request', 'service': 'READ_DATA_BY_IDENTIFIER', 'hex': 'f113'}
{'addr': 1806, 'type': 'positive_response', 'service': 'READ_DATA_BY_IDENTIFIER', 'data': b'\xf1\x13JX7T-19H406-CH\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00', 'hex': 'f1134a5837542d3139483430362d434800000000000000000000'}
{'addr': 1798, 'type': 'request', 'service': 'READ_DATA_BY_IDENTIFIER', 'hex': 'fd08'}
{'addr': 1806, 'type': 'positive_response', 'service': 'READ_DATA_BY_IDENTIFIER', 'hex': 'fd0800000500500100300000000000000000000000300000000000000000200100400300100200001200f00300500000000000000300c00b00400200000000000000000000000000000000200f01201e01501400a00200200400700d02501d01700700e06405005e05503401100a000000002002002001000000'}
{'addr': 1798, 'type': 'request', 'service': 'READ_DATA_BY_IDENTIFIER', 'hex': 'fd09'}
{'addr': 1806, 'type': 'positive_response', 'service': 'READ_DATA_BY_IDENTIFIER', 'hex': 'fd09ffec0001fef60002'}
{'addr': 1798, 'type': 'request', 'service': 'READ_DATA_BY_IDENTIFIER', 'hex': 'de00'}
{'addr': 1806, 'type': 'positive_response', 'service': 'READ_DATA_BY_IDENTIFIER', 'hex': 'de00020799dbaa10296516a440000000000000000000000000'}
{'addr': 1798, 'type': 'request', 'service': 'READ_DTC_INFORMATION', 'hex': '028f'}
{'addr': 1806, 'type': 'positive_response', 'service': 'READ_DTC_INFORMATION', 'hex': '02ff50019768c253002cc401862cc418862c'}
... skip DTC requests ...
{'addr': 1798, 'type': 'request', 'service': 'READ_DATA_BY_IDENTIFIER', 'hex': 'de00'}
{'addr': 1806, 'type': 'positive_response', 'service': 'READ_DATA_BY_IDENTIFIER', 'hex': 'de00020799dbaa10296516a440000000000000000000000000'}
{'addr': 1798, 'type': 'request', 'service': 'READ_DATA_BY_IDENTIFIER', 'hex': 'de01'}
{'addr': 1806, 'type': 'positive_response', 'service': 'READ_DATA_BY_IDENTIFIER', 'hex': 'de01fd5616db5fffff557fe1f842080000000800000008000000080000000819bfe00f7c00000000000000000000000000000000'}
{'addr': 1798, 'type': 'request', 'service': 'READ_DATA_BY_IDENTIFIER', 'hex': 'de02'}
{'addr': 1806, 'type': 'positive_response', 'service': 'READ_DATA_BY_IDENTIFIER', 'hex': 'de02800000008000000080000000800000008337fc00800000008000000080000000800000008337fc0000000000000000000000'}
{'addr': 1798, 'type': 'request', 'service': 'READ_DATA_BY_IDENTIFIER', 'hex': 'de03'}
{'addr': 1806, 'type': 'positive_response', 'service': 'READ_DATA_BY_IDENTIFIER', 'hex': 'de03fffc26c3800000000000'}
{'addr': 1798, 'type': 'request', 'service': 'READ_DATA_BY_IDENTIFIER', 'hex': 'de04'}
{'addr': 1806, 'type': 'positive_response', 'service': 'READ_DATA_BY_IDENTIFIER', 'hex': 'de04546a8c0000000000'}
{'addr': 1798, 'type': 'request', 'service': 'READ_DATA_BY_IDENTIFIER', 'hex': 'de05'}
{'addr': 1806, 'type': 'negative_response', 'hex': '2231'}
{'addr': 1798, 'type': 'request', 'service': 'READ_DATA_BY_IDENTIFIER', 'hex': 'de06'}
{'addr': 1806, 'type': 'negative_response', 'hex': '2231'}
{'addr': 1798, 'type': 'request', 'service': 'READ_DATA_BY_IDENTIFIER', 'hex': 'de07'}
{'addr': 1806, 'type': 'negative_response', 'hex': '2231'}
{'addr': 1798, 'type': 'request', 'service': 'READ_DATA_BY_IDENTIFIER', 'hex': 'de08'}
{'addr': 1806, 'type': 'negative_response', 'hex': '2231'}
{'addr': 1798, 'type': 'request', 'service': 'READ_DATA_BY_IDENTIFIER', 'hex': 'de09'}
{'addr': 1806, 'type': 'negative_response', 'hex': '2231'}
{'addr': 1798, 'type': 'request', 'service': 'READ_DATA_BY_IDENTIFIER', 'hex': 'de0a'}
{'addr': 1806, 'type': 'negative_response', 'hex': '2231'}
{'addr': 1798, 'type': 'request', 'service': 'READ_DATA_BY_IDENTIFIER', 'hex': 'de0b'}
{'addr': 1806, 'type': 'negative_response', 'hex': '2231'}
{'addr': 1798, 'type': 'request', 'service': 'READ_DATA_BY_IDENTIFIER', 'hex': 'de0c'}
{'addr': 1806, 'type': 'negative_response', 'hex': '2231'}
{'addr': 1798, 'type': 'request', 'service': 'READ_DATA_BY_IDENTIFIER', 'hex': 'de0d'}
{'addr': 1806, 'type': 'negative_response', 'hex': '2231'}
{'addr': 1798, 'type': 'request', 'service': 'READ_DATA_BY_IDENTIFIER', 'hex': 'de0e'}
{'addr': 1806, 'type': 'negative_response', 'hex': '2231'}
</pre>
</details>
Using UDS service `READ_DATA_BY_IDENTIFIER`, we can read the As-Built
blocks from `0xDExx` with no diagnostic session/security access necessary.
I used [Online As-Built databases](https://cyanlabs.net/asbuilt-db/) and
various coding spreadsheets shared online to find values we might be
interested in using for fingerprinting (both vehicle parameters and
identifying the platform).
ABS:
- Payload tier (Base, Mid Payload Upgrade, Heavy Duty Payload
  Upgrade...)
- Wheelbase
- Steering ratio
- Cruise Control Mode (Normal, Adaptive)
- Enable Stop and Go
PSCM:
- Enable Lane Keeping Aid
- Enable Traffic Jam Assist
- Enable Lane Centering Assist
IPMA (Q4):
- Steering ratio
- Wheelbase
APIM (Sync 3 and Sync 4):
- Steering ratio
- Vehicle weight
- Wheelbase
There are more potentially useful signals which I haven't included
although they might not be necessary:
- Vehicle (Ford platform code, like "C344" or "C519" - although the
  source of the mapping from index to code is FORScan and not Ford
themselves unless we can find a better source).
- Fuel type
- Vehicle length/height/front track/rear track
- Tire circumference (could be useful for converting wheel speed rad/s
  to m/s)
- Steering angle source (Pinion, Wheel)
- Country code (letters, e.g. US, CA or UK)
- Transmission type
- CAN network architecture
- More feature flags (the APIM also stores settings for ACC, LCA, BLIS)
The full list of settings I have found is
[here](https://github.com/incognitojam/op-notebooks/blob/main/ford/settings.py).
* FwQueryConfig: add data_requests
* add car_data to CarInterface get_params
* Revert "add car_data to CarInterface get_params"
This reverts commit aa161a6b82082705db97bea2c4317e1888a74845.
* test_ford: add APIM ecu address
* Revert "FwQueryConfig: add data_requests"
This reverts commit dc5484a9b80be5bc61a7fdf55560b8813bc43ef7.
* fix block numbers and add extra queries
* bump test_fw_query_timing
* add missing query whitelists
* simplify asbuilt requests
* use forscan block ids
* formatting
---------
Co-authored-by: Shane Smiskol <shane@smiskol.com>
old-commit-hash: c724d1c86ce4044bfc03f1e7941871466f51a708
											 
										 
										
											2 years ago 
										
									 
								 
							 
							
								
									
										 
									 
								
							 
							
								 
							 
							
							
								  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 ( 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_PART_NUMBER ,  " Unexpected ECU " ) 
 
							 
						 
					
						
							
								
							 
							
								
							 
							
								 
							 
							
							
								      self . assertEqual ( addr ,  ECU_ADDRESSES [ ecu ] ,  " ECU address mismatch " ) 
 
							 
						 
					
						
							
								
							 
							
								
							 
							
								 
							 
							
							
								      self . assertIsNone ( subaddr ,  " Unexpected ECU subaddress " ) 
 
							 
						 
					
						
							
								
							 
							
								
							 
							
								 
							 
							
							
								
							 
						 
					
						
							
								
							 
							
								
							 
							
								 
							 
							
							
								      for  fw  in  fws : 
 
							 
						 
					
						
							
								
							 
							
								
							 
							
								 
							 
							
							
								        self . assertEqual ( len ( fw ) ,  24 ,  " Expected ECU response to be 24 bytes " ) 
 
							 
						 
					
						
							
								
							 
							
								
							 
							
								 
							 
							
							
								
							 
						 
					
						
							
								
							 
							
								
									
										 
									 
								
							 
							
								 
							 
							
							
								        match  =  FW_PATTERN . match ( fw ) 
 
							 
						 
					
						
							
								
							 
							
								
							 
							
								 
							 
							
							
								        self . assertIsNotNone ( match ,  f " Unable to parse FW:  { fw !r} " ) 
 
							 
						 
					
						
							
								
							 
							
								
							 
							
								 
							 
							
							
								        if  match : 
 
							 
						 
					
						
							
								
							 
							
								
							 
							
								 
							 
							
							
								          part_number  =  match . group ( " part_number " ) 
 
							 
						 
					
						
							
								
							 
							
								
							 
							
								 
							 
							
							
								          self . assertIn ( part_number ,  ECU_PART_NUMBER [ ecu ] ,  f " Unexpected part number for  { fw !r} " ) 
 
							 
						 
					
						
							
								
							 
							
								
							 
							
								 
							 
							
							
								
							 
						 
					
						
							
								
							 
							
								
							 
							
								 
							 
							
							
								        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 " ,  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 ) 
 
							 
						 
					
						
							
								
							 
							
								
							 
							
								 
							 
							
							
								        self . assertEqual ( 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 } ) 
 
							 
						 
					
						
							
								
							 
							
								
							 
							
								 
							 
							
							
								    self . assertEqual ( 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 , } ) 
 
							 
						 
					
						
							
								
							 
							
								
							 
							
								 
							 
							
							
								    self . assertEqual ( 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 } ) 
 
							 
						 
					
						
							
								
							 
							
								
							 
							
								 
							 
							
							
								    self . assertEqual ( len ( candidates ) ,  0 ,  " Should not match new model year hint " ) 
 
							 
						 
					
						
							
								
							 
							
								
							 
							
								 
							 
							
							
								
							 
						 
					
						
							
								
							 
							
								
							 
							
								 
							 
							
							
								
							 
						 
					
						
							
								
							 
							
								
							 
							
								 
							 
							
							
								if  __name__  ==  " __main__ " : 
							 
						 
					
						
							
								
							 
							
								
							 
							
								 
							 
							
							
								  unittest . main ( )