@ -1,9 +1,7 @@
#!/usr/bin/env python3
import struct
import traceback
from collections import defaultdict
from dataclasses import dataclass , field
from typing import Any , List , Optional , Set , Tuple
from typing import Any , Optional , Set , Tuple
from tqdm import tqdm
import panda . python . uds as uds
@ -12,237 +10,16 @@ from selfdrive.car.ecu_addrs import get_ecu_addrs
from selfdrive . car . interfaces import get_interface_attr
from selfdrive . car . fingerprints import FW_VERSIONS
from selfdrive . car . isotp_parallel_query import IsoTpParallelQuery
from selfdrive . car . toyota . values import CAR as TOYOTA
from system . swaglog import cloudlog
Ecu = car . CarParams . Ecu
ESSENTIAL_ECUS = [ Ecu . engine , Ecu . eps , Ecu . abs , Ecu . fwdRadar , Ecu . fwdCamera , Ecu . vsa ]
FW_QUERY_CONFIGS = get_interface_attr ( ' FW_QUERY_CONFIG ' , ignore_none = True )
VERSIONS = get_interface_attr ( ' FW_VERSIONS ' , ignore_none = True )
def p16 ( val ) :
return struct . pack ( " !H " , val )
TESTER_PRESENT_REQUEST = bytes ( [ uds . SERVICE_TYPE . TESTER_PRESENT , 0x0 ] )
TESTER_PRESENT_RESPONSE = bytes ( [ uds . SERVICE_TYPE . TESTER_PRESENT + 0x40 , 0x0 ] )
SHORT_TESTER_PRESENT_REQUEST = bytes ( [ uds . SERVICE_TYPE . TESTER_PRESENT ] )
SHORT_TESTER_PRESENT_RESPONSE = bytes ( [ uds . SERVICE_TYPE . TESTER_PRESENT + 0x40 ] )
DEFAULT_DIAGNOSTIC_REQUEST = bytes ( [ uds . SERVICE_TYPE . DIAGNOSTIC_SESSION_CONTROL ,
uds . SESSION_TYPE . DEFAULT ] )
DEFAULT_DIAGNOSTIC_RESPONSE = bytes ( [ uds . SERVICE_TYPE . DIAGNOSTIC_SESSION_CONTROL + 0x40 ,
uds . SESSION_TYPE . DEFAULT , 0x0 , 0x32 , 0x1 , 0xf4 ] )
EXTENDED_DIAGNOSTIC_REQUEST = bytes ( [ uds . SERVICE_TYPE . DIAGNOSTIC_SESSION_CONTROL ,
uds . SESSION_TYPE . EXTENDED_DIAGNOSTIC ] )
EXTENDED_DIAGNOSTIC_RESPONSE = bytes ( [ uds . SERVICE_TYPE . DIAGNOSTIC_SESSION_CONTROL + 0x40 ,
uds . SESSION_TYPE . EXTENDED_DIAGNOSTIC , 0x0 , 0x32 , 0x1 , 0xf4 ] )
UDS_VERSION_REQUEST = bytes ( [ uds . SERVICE_TYPE . READ_DATA_BY_IDENTIFIER ] ) + \
p16 ( uds . DATA_IDENTIFIER_TYPE . APPLICATION_SOFTWARE_IDENTIFICATION )
UDS_VERSION_RESPONSE = bytes ( [ uds . SERVICE_TYPE . READ_DATA_BY_IDENTIFIER + 0x40 ] ) + \
p16 ( uds . DATA_IDENTIFIER_TYPE . APPLICATION_SOFTWARE_IDENTIFICATION )
HYUNDAI_VERSION_REQUEST_LONG = bytes ( [ uds . SERVICE_TYPE . READ_DATA_BY_IDENTIFIER ] ) + \
p16 ( 0xf100 ) # Long description
HYUNDAI_VERSION_REQUEST_MULTI = bytes ( [ uds . SERVICE_TYPE . READ_DATA_BY_IDENTIFIER ] ) + \
p16 ( uds . DATA_IDENTIFIER_TYPE . VEHICLE_MANUFACTURER_SPARE_PART_NUMBER ) + \
p16 ( uds . DATA_IDENTIFIER_TYPE . APPLICATION_SOFTWARE_IDENTIFICATION ) + \
p16 ( 0xf100 )
HYUNDAI_VERSION_RESPONSE = bytes ( [ uds . SERVICE_TYPE . READ_DATA_BY_IDENTIFIER + 0x40 ] )
TOYOTA_VERSION_REQUEST = b ' \x1a \x88 \x01 '
TOYOTA_VERSION_RESPONSE = b ' \x5a \x88 \x01 '
VOLKSWAGEN_VERSION_REQUEST_MULTI = bytes ( [ uds . SERVICE_TYPE . READ_DATA_BY_IDENTIFIER ] ) + \
p16 ( uds . DATA_IDENTIFIER_TYPE . VEHICLE_MANUFACTURER_SPARE_PART_NUMBER ) + \
p16 ( uds . DATA_IDENTIFIER_TYPE . VEHICLE_MANUFACTURER_ECU_SOFTWARE_VERSION_NUMBER ) + \
p16 ( uds . DATA_IDENTIFIER_TYPE . APPLICATION_DATA_IDENTIFICATION )
VOLKSWAGEN_VERSION_RESPONSE = bytes ( [ uds . SERVICE_TYPE . READ_DATA_BY_IDENTIFIER + 0x40 ] )
OBD_VERSION_REQUEST = b ' \x09 \x04 '
OBD_VERSION_RESPONSE = b ' \x49 \x04 '
DEFAULT_RX_OFFSET = 0x8
VOLKSWAGEN_RX_OFFSET = 0x6a
MAZDA_VERSION_REQUEST = bytes ( [ uds . SERVICE_TYPE . READ_DATA_BY_IDENTIFIER ] ) + \
p16 ( uds . DATA_IDENTIFIER_TYPE . VEHICLE_MANUFACTURER_ECU_SOFTWARE_NUMBER )
MAZDA_VERSION_RESPONSE = bytes ( [ uds . SERVICE_TYPE . READ_DATA_BY_IDENTIFIER + 0x40 ] ) + \
p16 ( uds . DATA_IDENTIFIER_TYPE . VEHICLE_MANUFACTURER_ECU_SOFTWARE_NUMBER )
NISSAN_DIAGNOSTIC_REQUEST_KWP = bytes ( [ uds . SERVICE_TYPE . DIAGNOSTIC_SESSION_CONTROL , 0xc0 ] )
NISSAN_DIAGNOSTIC_RESPONSE_KWP = bytes ( [ uds . SERVICE_TYPE . DIAGNOSTIC_SESSION_CONTROL + 0x40 , 0xc0 ] )
NISSAN_VERSION_REQUEST_KWP = b ' \x21 \x83 '
NISSAN_VERSION_RESPONSE_KWP = b ' \x61 \x83 '
NISSAN_VERSION_REQUEST_STANDARD = bytes ( [ uds . SERVICE_TYPE . READ_DATA_BY_IDENTIFIER ] ) + \
p16 ( uds . DATA_IDENTIFIER_TYPE . VEHICLE_MANUFACTURER_ECU_SOFTWARE_NUMBER )
NISSAN_VERSION_RESPONSE_STANDARD = bytes ( [ uds . SERVICE_TYPE . READ_DATA_BY_IDENTIFIER + 0x40 ] ) + \
p16 ( uds . DATA_IDENTIFIER_TYPE . VEHICLE_MANUFACTURER_ECU_SOFTWARE_NUMBER )
NISSAN_RX_OFFSET = 0x20
SUBARU_VERSION_REQUEST = bytes ( [ uds . SERVICE_TYPE . READ_DATA_BY_IDENTIFIER ] ) + \
p16 ( uds . DATA_IDENTIFIER_TYPE . APPLICATION_DATA_IDENTIFICATION )
SUBARU_VERSION_RESPONSE = bytes ( [ uds . SERVICE_TYPE . READ_DATA_BY_IDENTIFIER + 0x40 ] ) + \
p16 ( uds . DATA_IDENTIFIER_TYPE . APPLICATION_DATA_IDENTIFICATION )
CHRYSLER_VERSION_REQUEST = bytes ( [ uds . SERVICE_TYPE . READ_DATA_BY_IDENTIFIER ] ) + \
p16 ( 0xf132 )
CHRYSLER_VERSION_RESPONSE = bytes ( [ uds . SERVICE_TYPE . READ_DATA_BY_IDENTIFIER + 0x40 ] ) + \
p16 ( 0xf132 )
CHRYSLER_SOFTWARE_VERSION_REQUEST = bytes ( [ uds . SERVICE_TYPE . READ_DATA_BY_IDENTIFIER ] ) + \
p16 ( uds . DATA_IDENTIFIER_TYPE . SYSTEM_SUPPLIER_ECU_SOFTWARE_NUMBER )
CHRYSLER_SOFTWARE_VERSION_RESPONSE = bytes ( [ uds . SERVICE_TYPE . READ_DATA_BY_IDENTIFIER + 0x40 ] ) + \
p16 ( uds . DATA_IDENTIFIER_TYPE . SYSTEM_SUPPLIER_ECU_SOFTWARE_NUMBER )
CHRYSLER_RX_OFFSET = - 0x280
FORD_VERSION_REQUEST = bytes ( [ uds . SERVICE_TYPE . READ_DATA_BY_IDENTIFIER ] ) + \
p16 ( uds . DATA_IDENTIFIER_TYPE . VEHICLE_MANUFACTURER_ECU_SOFTWARE_NUMBER )
FORD_VERSION_RESPONSE = bytes ( [ uds . SERVICE_TYPE . READ_DATA_BY_IDENTIFIER + 0x40 ] ) + \
p16 ( uds . DATA_IDENTIFIER_TYPE . VEHICLE_MANUFACTURER_ECU_SOFTWARE_NUMBER )
@dataclass
class Request :
brand : str
request : List [ bytes ]
response : List [ bytes ]
whitelist_ecus : List [ int ] = field ( default_factory = list )
rx_offset : int = DEFAULT_RX_OFFSET
bus : int = 1
REQUESTS : List [ Request ] = [
# Subaru
Request (
" subaru " ,
[ TESTER_PRESENT_REQUEST , SUBARU_VERSION_REQUEST ] ,
[ TESTER_PRESENT_RESPONSE , SUBARU_VERSION_RESPONSE ] ,
) ,
# Hyundai
Request (
" hyundai " ,
[ HYUNDAI_VERSION_REQUEST_LONG ] ,
[ HYUNDAI_VERSION_RESPONSE ] ,
) ,
Request (
" hyundai " ,
[ HYUNDAI_VERSION_REQUEST_MULTI ] ,
[ HYUNDAI_VERSION_RESPONSE ] ,
) ,
# Honda
Request (
" honda " ,
[ UDS_VERSION_REQUEST ] ,
[ UDS_VERSION_RESPONSE ] ,
) ,
# Toyota
Request (
" toyota " ,
[ SHORT_TESTER_PRESENT_REQUEST , TOYOTA_VERSION_REQUEST ] ,
[ SHORT_TESTER_PRESENT_RESPONSE , TOYOTA_VERSION_RESPONSE ] ,
bus = 0 ,
) ,
Request (
" toyota " ,
[ SHORT_TESTER_PRESENT_REQUEST , OBD_VERSION_REQUEST ] ,
[ SHORT_TESTER_PRESENT_RESPONSE , OBD_VERSION_RESPONSE ] ,
bus = 0 ,
) ,
Request (
" toyota " ,
[ TESTER_PRESENT_REQUEST , DEFAULT_DIAGNOSTIC_REQUEST , EXTENDED_DIAGNOSTIC_REQUEST , UDS_VERSION_REQUEST ] ,
[ TESTER_PRESENT_RESPONSE , DEFAULT_DIAGNOSTIC_RESPONSE , EXTENDED_DIAGNOSTIC_RESPONSE , UDS_VERSION_RESPONSE ] ,
bus = 0 ,
) ,
# Volkswagen
Request (
" volkswagen " ,
[ VOLKSWAGEN_VERSION_REQUEST_MULTI ] ,
[ VOLKSWAGEN_VERSION_RESPONSE ] ,
whitelist_ecus = [ Ecu . srs , Ecu . eps , Ecu . fwdRadar ] ,
rx_offset = VOLKSWAGEN_RX_OFFSET ,
) ,
Request (
" volkswagen " ,
[ VOLKSWAGEN_VERSION_REQUEST_MULTI ] ,
[ VOLKSWAGEN_VERSION_RESPONSE ] ,
whitelist_ecus = [ Ecu . engine , Ecu . transmission ] ,
) ,
# Mazda
Request (
" mazda " ,
[ MAZDA_VERSION_REQUEST ] ,
[ MAZDA_VERSION_RESPONSE ] ,
) ,
# Nissan
Request (
" nissan " ,
[ NISSAN_DIAGNOSTIC_REQUEST_KWP , NISSAN_VERSION_REQUEST_KWP ] ,
[ NISSAN_DIAGNOSTIC_RESPONSE_KWP , NISSAN_VERSION_RESPONSE_KWP ] ,
) ,
Request (
" nissan " ,
[ NISSAN_DIAGNOSTIC_REQUEST_KWP , NISSAN_VERSION_REQUEST_KWP ] ,
[ NISSAN_DIAGNOSTIC_RESPONSE_KWP , NISSAN_VERSION_RESPONSE_KWP ] ,
rx_offset = NISSAN_RX_OFFSET ,
) ,
Request (
" nissan " ,
[ NISSAN_VERSION_REQUEST_STANDARD ] ,
[ NISSAN_VERSION_RESPONSE_STANDARD ] ,
rx_offset = NISSAN_RX_OFFSET ,
) ,
# Body
Request (
" body " ,
[ TESTER_PRESENT_REQUEST , UDS_VERSION_REQUEST ] ,
[ TESTER_PRESENT_RESPONSE , UDS_VERSION_RESPONSE ] ,
bus = 0 ,
) ,
# Chrysler / FCA / Stellantis
Request (
" chrysler " ,
[ CHRYSLER_VERSION_REQUEST ] ,
[ CHRYSLER_VERSION_RESPONSE ] ,
whitelist_ecus = [ Ecu . abs , Ecu . eps , Ecu . srs , Ecu . gateway , Ecu . fwdRadar , Ecu . fwdCamera , Ecu . combinationMeter ] ,
rx_offset = CHRYSLER_RX_OFFSET ,
) ,
Request (
" chrysler " ,
[ CHRYSLER_VERSION_REQUEST ] ,
[ CHRYSLER_VERSION_RESPONSE ] ,
whitelist_ecus = [ Ecu . abs , Ecu . hcp , Ecu . engine , Ecu . transmission ] ,
) ,
Request (
" chrysler " ,
[ CHRYSLER_SOFTWARE_VERSION_REQUEST ] ,
[ CHRYSLER_SOFTWARE_VERSION_RESPONSE ] ,
whitelist_ecus = [ Ecu . engine , Ecu . transmission ] ,
) ,
# Ford
Request (
" ford " ,
[ TESTER_PRESENT_REQUEST , FORD_VERSION_REQUEST ] ,
[ TESTER_PRESENT_RESPONSE , FORD_VERSION_RESPONSE ] ,
whitelist_ecus = [ Ecu . engine ] ,
) ,
Request (
" ford " ,
[ TESTER_PRESENT_REQUEST , FORD_VERSION_REQUEST ] ,
[ TESTER_PRESENT_RESPONSE , FORD_VERSION_RESPONSE ] ,
bus = 0 ,
whitelist_ecus = [ Ecu . eps , Ecu . abs , Ecu . fwdRadar , Ecu . fwdCamera , Ecu . shiftByWire ] ,
) ,
]
MODEL_TO_BRAND = { c : b for b , e in VERSIONS . items ( ) for c in e }
REQUESTS = [ ( brand , r ) for brand , config in FW_QUERY_CONFIGS . items ( ) for r in config . requests ]
def chunks ( l , n = 128 ) :
@ -261,9 +38,8 @@ def build_fw_dict(fw_versions, filter_brand=None):
def get_brand_addrs ( ) :
versions = get_interface_attr ( ' FW_VERSIONS ' , ignore_none = True )
brand_addrs = defaultdict ( set )
for brand , cars in versions . items ( ) :
for brand , cars in VERSIONS . items ( ) :
for fw in cars . values ( ) :
brand_addrs [ brand ] | = { ( addr , sub_addr ) for _ , addr , sub_addr in fw . keys ( ) }
return brand_addrs
@ -325,19 +101,19 @@ def match_fw_to_car_exact(fw_versions_dict):
for candidate , fws in candidates . items ( ) :
for ecu , expected_versions in fws . items ( ) :
config = FW_QUERY_CONFIGS [ MODEL_TO_BRAND [ candidate ] ]
ecu_type = ecu [ 0 ]
addr = ecu [ 1 : ]
found_versions = fw_versions_dict . get ( addr , set ( ) )
if ecu_type == Ecu . abs and candidate in ( TOYOTA . RAV4 , TOYOTA . COROLLA , TOYOTA . HIGHLANDER , TOYOTA . SIENNA , TOYOTA . LEXUS_IS ) and not len ( found_versions ) :
continue
# On some Toyota models, the engine can show on two different addresses
if ecu_type == Ecu . engine and candidate in ( TOYOTA . CAMRY , TOYOTA . COROLLA_TSS2 , TOYOTA . CHR , TOYOTA . LEXUS_IS ) and not len ( found_versions ) :
continue
found_versions = fw_versions_dict . get ( addr , set ( ) )
if not len ( found_versions ) :
# Some models can sometimes miss an ecu, or show on two different addresses
if candidate in config . non_essential_ecus . get ( ecu_type , [ ] ) :
continue
# Ignore non essential ecus
if ecu_type not in ESSENTIAL_ECUS and not len ( found_versions ) :
continue
# Ignore non essential ecus
if ecu_type not in ESSENTIAL_ECUS :
continue
# Virtual debug ecu doesn't need to match the database
if ecu_type == Ecu . debug :
@ -358,11 +134,10 @@ def match_fw_to_car(fw_versions, allow_exact=True, allow_fuzzy=True):
if allow_fuzzy :
exact_matches . append ( ( False , match_fw_to_car_fuzzy ) )
brands = get_interface_attr ( ' FW_VERSIONS ' , ignore_none = True ) . keys ( )
for exact_match , match_func in exact_matches :
# For each brand, attempt to fingerprint using all FW returned from its queries
matches = set ( )
for brand in brands :
for brand in VERSIONS . keys ( ) :
fw_versions_dict = build_fw_dict ( fw_versions , filter_brand = brand )
matches | = match_func ( fw_versions_dict )
@ -376,13 +151,9 @@ def get_present_ecus(logcan, sendcan):
queries = list ( )
parallel_queries = list ( )
responses = set ( )
versions = get_interface_attr ( ' FW_VERSIONS ' , ignore_none = True )
for r in REQUESTS :
if r . brand not in versions :
continue
for brand_versions in versions [ r . brand ] . values ( ) :
for brand , r in REQUESTS :
for brand_versions in VERSIONS [ brand ] . values ( ) :
for ecu_type , addr , sub_addr in brand_versions :
# Only query ecus in whitelist if whitelist is not empty
if len ( r . whitelist_ecus ) == 0 or ecu_type in r . whitelist_ecus :
@ -411,9 +182,9 @@ def get_brand_ecu_matches(ecu_rx_addrs):
""" Returns dictionary of brands and matches with ECUs in their FW versions """
brand_addrs = get_brand_addrs ( )
brand_matches = { r . brand : set ( ) for r in REQUESTS }
brand_matches = { brand : set ( ) for b rand , _ in REQUESTS }
brand_rx_offsets = set ( ( r . brand , r . rx_offset ) for r in REQUESTS )
brand_rx_offsets = set ( ( brand , r . rx_offset ) for brand , r in REQUESTS )
for addr , sub_addr , _ in ecu_rx_addrs :
# Since we can't know what request an ecu responded to, add matches for all possible rx offsets
for brand , rx_offset in brand_rx_offsets :
@ -442,7 +213,7 @@ def get_fw_versions_ordered(logcan, sendcan, ecu_rx_addrs, timeout=0.1, debug=Fa
def get_fw_versions ( logcan , sendcan , query_brand = None , extra = None , timeout = 0.1 , debug = False , progress = False ) :
versions = get_interface_attr ( ' FW_VERSIONS ' , ignore_none = True )
versions = VERSIONS . copy ( )
if query_brand is not None :
versions = { query_brand : versions [ query_brand ] }
@ -473,12 +244,12 @@ def get_fw_versions(logcan, sendcan, query_brand=None, extra=None, timeout=0.1,
# Get versions and build capnp list to put into CarParams
car_fw = [ ]
requests = [ r for r in REQUESTS if query_brand is None or r . brand == query_brand ]
requests = [ ( b rand , r ) for brand , r in REQUESTS if query_brand is None or brand == query_brand ]
for addr in tqdm ( addrs , disable = not progress ) :
for addr_chunk in chunks ( addr ) :
for r in requests :
for brand , r in requests :
try :
addrs = [ ( a , s ) for ( b , a , s ) in addr_chunk if b in ( r . brand , ' any ' ) and
addrs = [ ( a , s ) for ( b , a , s ) in addr_chunk if b in ( brand , ' any ' ) and
( len ( r . whitelist_ecus ) == 0 or ecu_types [ ( b , a , s ) ] in r . whitelist_ecus ) ]
if addrs :
@ -486,12 +257,12 @@ def get_fw_versions(logcan, sendcan, query_brand=None, extra=None, timeout=0.1,
for ( addr , rx_addr ) , version in query . get_data ( timeout ) . items ( ) :
f = car . CarParams . CarFw . new_message ( )
f . ecu = ecu_types . get ( ( r . brand , addr [ 0 ] , addr [ 1 ] ) , Ecu . unknown )
f . ecu = ecu_types . get ( ( brand , addr [ 0 ] , addr [ 1 ] ) , Ecu . unknown )
f . fwVersion = version
f . address = addr [ 0 ]
f . responseAddress = rx_addr
f . request = r . request
f . brand = r . brand
f . brand = brand
f . bus = r . bus
if addr [ 1 ] is not None :