import re import unittest from opendbc.car import gen_empty_fingerprint from opendbc.car.structs import CarParams from opendbc.car.tesla.interface import CarInterface from opendbc.car.tesla.fingerprints import FW_VERSIONS from opendbc.car.tesla.radar_interface import RADAR_START_ADDR from opendbc.car.tesla.values import CAR, FSD_14_FW Ecu = CarParams.Ecu # Fields prefixed unknown_* we observe structurally but don't know the meaning of. # Only `platform` has evidence-backed semantic meaning (matches car_model in FW_VERSIONS). # # unknown_prefix is everything before the comma; we don't split it because we don't know what its # parts mean, but observed shape is: __ (), e.g. # TeMYG4 _ Main _ 0.0.0 (78) or TeM3 _ SP_XP002p2 _ 0.0.0 (23) # family package triplet build family package triplet build # # After the comma, the version string decomposes into: # platform : E/Y/X = car model (Model 3 / Y / X). The only field with known meaning. # variant_code : differentiator WITHIN a platform — hardware/trim/calibration bits packed # into <3-digit series>, e.g. '4HP015', '4003', 'L014', # 'PR003'. We don't fully know what the parts mean individually, but the # whole string identifies a specific variant within the car model. # software_major/minor : numeric components after the first '.' — conventional release numbers. # minor is optional (e.g. 'E4S014.27' has no minor). # # Suspected (not confirmed): for M3/MY, `TeM3_*` outer + no-leading-digit variant_code == HW3, and # `TeMYG4_*` outer + leading-'4' variant_code == HW4 (the 'G4' in TeMYG4 likely denotes Gen 4). # # Example full parse of 'TeMYG4_Main_0.0.0 (78),E4HP015.05.0': # unknown_prefix='TeMYG4_Main_0.0.0 (78)' # platform=E variant_code=4HP015 software_major=05 software_minor=0 FW_RE = re.compile( rb'^(?P.+),' + rb'(?P[EYX])' + rb'(?P\d?[A-Z]*\d{3})' + rb'\.(?P\d+)' + rb'(?:\.(?P\d+))?$' ) PLATFORM_TO_CAR = { b'E': CAR.TESLA_MODEL_3, b'Y': CAR.TESLA_MODEL_Y, b'X': CAR.TESLA_MODEL_X, } # Hypothesized FSD 14 profile, in terms of variant_code bookends (given software_major >= 4): # M3: variant_code starts with '4H', ends with '015' # MY: variant_code starts with '4', ends with '003' # Older series (M3 '014', MY '002') are never FSD 14. FSD_14_FW_RULE = { CAR.TESLA_MODEL_3: (b'4H', b'015'), CAR.TESLA_MODEL_Y: (b'4', b'003'), } class TestTeslaFingerprint(unittest.TestCase): def test_fw_platform_code(self): # Every EPS FW must parse and its platform letter must match the car it's filed under. for car_model, ecus in FW_VERSIONS.items(): for fw in ecus.get((Ecu.eps, 0x730, None), []): m = FW_RE.match(fw) assert m is not None, f"Unparsable FW: {fw}" assert PLATFORM_TO_CAR[m['platform']] == car_model, f"Platform letter {m['platform']!r} != {car_model.value}: {fw}" def test_fsd_14_fw(self): for car_model, ecus in FW_VERSIONS.items(): if car_model not in FSD_14_FW_RULE: continue variant_prefix, variant_suffix = FSD_14_FW_RULE[car_model] for fw in ecus.get((Ecu.eps, 0x730, None), []): m = FW_RE.match(fw) assert m is not None, f"Unparsable FW: {fw}" is_fsd_14 = fw in FSD_14_FW.get(car_model, []) expected = ( m['variant_code'].startswith(variant_prefix) and m['variant_code'].endswith(variant_suffix) and int(m['software_major']) >= 4 ) assert is_fsd_14 == expected, f"{fw}" def test_radar_detection(self): # Test radar availability detection for cars with radar DBC defined for radar in (True, False): fingerprint = gen_empty_fingerprint() if radar: fingerprint[1][RADAR_START_ADDR] = 8 CP = CarInterface.get_params(CAR.TESLA_MODEL_3, fingerprint, [], False, False, False) assert CP.radarUnavailable != radar def test_no_radar_car(self): # Model X doesn't have radar DBC defined, should always be unavailable for radar in (True, False): fingerprint = gen_empty_fingerprint() if radar: fingerprint[1][RADAR_START_ADDR] = 8 CP = CarInterface.get_params(CAR.TESLA_MODEL_X, fingerprint, [], False, False, False) assert CP.radarUnavailable # Always unavailable since no radar DBC