diff --git a/selfdrive/car/car_helpers.py b/selfdrive/car/car_helpers.py index 0e2443d330..66097339b1 100644 --- a/selfdrive/car/car_helpers.py +++ b/selfdrive/car/car_helpers.py @@ -139,10 +139,10 @@ def fingerprint(logcan, sendcan, num_pandas): # VIN query only reliably works through OBDII vin_rx_addr, vin_rx_bus, vin = get_vin(logcan, sendcan, (0, 1)) ecu_rx_addrs = get_present_ecus(logcan, sendcan, num_pandas=num_pandas) - car_fw = get_fw_versions_ordered(logcan, sendcan, ecu_rx_addrs, num_pandas=num_pandas) + car_fw = get_fw_versions_ordered(logcan, sendcan, vin, ecu_rx_addrs, num_pandas=num_pandas) cached = False - exact_fw_match, fw_candidates = match_fw_to_car(car_fw) + exact_fw_match, fw_candidates = match_fw_to_car(car_fw, vin) else: vin_rx_addr, vin_rx_bus, vin = -1, -1, VIN_UNKNOWN exact_fw_match, fw_candidates, car_fw = True, set(), [] diff --git a/selfdrive/car/fw_query_definitions.py b/selfdrive/car/fw_query_definitions.py index 236ade49bb..ad4dcdc8f1 100755 --- a/selfdrive/car/fw_query_definitions.py +++ b/selfdrive/car/fw_query_definitions.py @@ -97,9 +97,9 @@ class FwQueryConfig: non_essential_ecus: dict[capnp.lib.capnp._EnumModule, list[str]] = field(default_factory=dict) # Ecus added for data collection, not to be fingerprinted on extra_ecus: list[tuple[capnp.lib.capnp._EnumModule, int, int | None]] = field(default_factory=list) - # Function a brand can implement to provide better fuzzy matching. Takes in FW versions, + # Function a brand can implement to provide better fuzzy matching. Takes in FW versions and VIN, # returns set of candidates. Only will match if one candidate is returned - match_fw_to_car_fuzzy: Callable[[LiveFwVersions, OfflineFwVersions], set[str]] | None = None + match_fw_to_car_fuzzy: Callable[[LiveFwVersions, str, OfflineFwVersions], set[str]] | None = None def __post_init__(self): for i in range(len(self.requests)): diff --git a/selfdrive/car/fw_versions.py b/selfdrive/car/fw_versions.py index d7444ebf7d..f2aff2e6f9 100755 --- a/selfdrive/car/fw_versions.py +++ b/selfdrive/car/fw_versions.py @@ -144,8 +144,8 @@ def match_fw_to_car_exact(live_fw_versions: LiveFwVersions, match_brand: str = N return set(candidates.keys()) - invalid -def match_fw_to_car(fw_versions: list[capnp.lib.capnp._DynamicStructBuilder], allow_exact: bool = True, allow_fuzzy: bool = True, - log: bool = True) -> tuple[bool, set[str]]: +def match_fw_to_car(fw_versions: list[capnp.lib.capnp._DynamicStructBuilder], vin: str, + allow_exact: bool = True, allow_fuzzy: bool = True, log: bool = True) -> tuple[bool, set[str]]: # Try exact matching first exact_matches: list[tuple[bool, MatchFwToCar]] = [] if allow_exact: @@ -163,7 +163,7 @@ def match_fw_to_car(fw_versions: list[capnp.lib.capnp._DynamicStructBuilder], al # If specified and no matches so far, fall back to brand's fuzzy fingerprinting function config = FW_QUERY_CONFIGS[brand] if not exact_match and not len(matches) and config.match_fw_to_car_fuzzy is not None: - matches |= config.match_fw_to_car_fuzzy(fw_versions_dict, VERSIONS[brand]) + matches |= config.match_fw_to_car_fuzzy(fw_versions_dict, vin, VERSIONS[brand]) if len(matches): return exact_match, matches @@ -237,7 +237,7 @@ def set_obd_multiplexing(params: Params, obd_multiplexing: bool): cloudlog.warning("OBD multiplexing set successfully") -def get_fw_versions_ordered(logcan, sendcan, ecu_rx_addrs: set[EcuAddrBusType], timeout: float = 0.1, num_pandas: int = 1, +def get_fw_versions_ordered(logcan, sendcan, vin: str, ecu_rx_addrs: set[EcuAddrBusType], timeout: float = 0.1, num_pandas: int = 1, debug: bool = False, progress: bool = False) -> list[capnp.lib.capnp._DynamicStructBuilder]: """Queries for FW versions ordering brands by likelihood, breaks when exact match is found""" @@ -253,7 +253,7 @@ def get_fw_versions_ordered(logcan, sendcan, ecu_rx_addrs: set[EcuAddrBusType], all_car_fw.extend(car_fw) # If there is a match using this brand's FW alone, finish querying early - _, matches = match_fw_to_car(car_fw, log=False) + _, matches = match_fw_to_car(car_fw, vin, log=False) if len(matches) == 1: break @@ -381,7 +381,7 @@ if __name__ == "__main__": t = time.time() fw_vers = get_fw_versions(logcan, sendcan, query_brand=args.brand, extra=extra, num_pandas=num_pandas, debug=args.debug, progress=True) - _, candidates = match_fw_to_car(fw_vers) + _, candidates = match_fw_to_car(fw_vers, vin) print() print("Found FW versions") diff --git a/selfdrive/car/hyundai/tests/test_hyundai.py b/selfdrive/car/hyundai/tests/test_hyundai.py index 2e4edcffc6..0753b372e1 100755 --- a/selfdrive/car/hyundai/tests/test_hyundai.py +++ b/selfdrive/car/hyundai/tests/test_hyundai.py @@ -209,7 +209,7 @@ class TestHyundaiFingerprint(unittest.TestCase): "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), FW_VERSIONS) + matches = FW_QUERY_CONFIG.match_fw_to_car_fuzzy(build_fw_dict(CP.carFw), CP.carVin, FW_VERSIONS) if len(matches) == 1: self.assertEqual(list(matches)[0], platform) else: diff --git a/selfdrive/car/hyundai/values.py b/selfdrive/car/hyundai/values.py index aa24a1f353..3339fd8009 100644 --- a/selfdrive/car/hyundai/values.py +++ b/selfdrive/car/hyundai/values.py @@ -568,7 +568,7 @@ def get_platform_codes(fw_versions: list[bytes]) -> set[tuple[bytes, bytes | Non return codes -def match_fw_to_car_fuzzy(live_fw_versions, offline_fw_versions) -> set[str]: +def match_fw_to_car_fuzzy(live_fw_versions, vin, offline_fw_versions) -> set[str]: # Non-electric CAN FD platforms often do not have platform code specifiers needed # to distinguish between hybrid and ICE. All EVs so far are either exclusively # electric or specify electric in the platform code. diff --git a/selfdrive/car/tests/test_fw_fingerprint.py b/selfdrive/car/tests/test_fw_fingerprint.py index 4a17fc34ec..96e81272e4 100755 --- a/selfdrive/car/tests/test_fw_fingerprint.py +++ b/selfdrive/car/tests/test_fw_fingerprint.py @@ -49,7 +49,7 @@ class TestFwFingerprint(unittest.TestCase): fw.append({"ecu": ecu_name, "fwVersion": random.choice(fw_versions), 'brand': brand, "address": addr, "subAddress": 0 if sub_addr is None else sub_addr}) CP.carFw = fw - _, matches = match_fw_to_car(CP.carFw, allow_fuzzy=False) + _, matches = match_fw_to_car(CP.carFw, CP.carVin, allow_fuzzy=False) if not test_non_essential: self.assertFingerprints(matches, car_model) else: @@ -72,8 +72,8 @@ class TestFwFingerprint(unittest.TestCase): fw.append({"ecu": ecu_name, "fwVersion": random.choice(fw_versions), 'brand': brand, "address": addr, "subAddress": 0 if sub_addr is None else sub_addr}) CP.carFw = fw - _, matches = match_fw_to_car(CP.carFw, allow_exact=False, log=False) - brand_matches = config.match_fw_to_car_fuzzy(build_fw_dict(CP.carFw), VERSIONS[brand]) + _, matches = match_fw_to_car(CP.carFw, CP.carVin, allow_exact=False, log=False) + brand_matches = config.match_fw_to_car_fuzzy(build_fw_dict(CP.carFw), CP.carVin, VERSIONS[brand]) # If both have matches, they must agree if len(matches) == 1 and len(brand_matches) == 1: @@ -94,7 +94,7 @@ class TestFwFingerprint(unittest.TestCase): fw.append({"ecu": ecu_name, "fwVersion": random.choice(ecus[ecu]), 'brand': brand, "address": addr, "subAddress": 0 if sub_addr is None else sub_addr}) CP = car.CarParams.new_message(carFw=fw) - _, matches = match_fw_to_car(CP.carFw, allow_exact=False, log=False) + _, matches = match_fw_to_car(CP.carFw, CP.carVin, allow_exact=False, log=False) # Assert no match if there are not enough unique ECUs unique_ecus = {(f['address'], f['subAddress']) for f in fw} diff --git a/selfdrive/car/toyota/tests/test_toyota.py b/selfdrive/car/toyota/tests/test_toyota.py index ba131a0185..e2a9b46eb4 100755 --- a/selfdrive/car/toyota/tests/test_toyota.py +++ b/selfdrive/car/toyota/tests/test_toyota.py @@ -160,7 +160,7 @@ class TestToyotaFingerprint(unittest.TestCase): "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), FW_VERSIONS) + matches = FW_QUERY_CONFIG.match_fw_to_car_fuzzy(build_fw_dict(CP.carFw), CP.carVin, FW_VERSIONS) if len(matches) == 1: self.assertEqual(list(matches)[0], platform) else: diff --git a/selfdrive/car/toyota/values.py b/selfdrive/car/toyota/values.py index 179e3f97e3..4f6fdef1ba 100644 --- a/selfdrive/car/toyota/values.py +++ b/selfdrive/car/toyota/values.py @@ -415,7 +415,7 @@ def get_platform_codes(fw_versions: list[bytes]) -> dict[bytes, set[bytes]]: return dict(codes) -def match_fw_to_car_fuzzy(live_fw_versions, offline_fw_versions) -> set[str]: +def match_fw_to_car_fuzzy(live_fw_versions, vin, offline_fw_versions) -> set[str]: candidates = set() for candidate, fws in offline_fw_versions.items(): diff --git a/selfdrive/car/volkswagen/tests/test_volkswagen.py b/selfdrive/car/volkswagen/tests/test_volkswagen.py index 85da10a0c2..92569e194e 100755 --- a/selfdrive/car/volkswagen/tests/test_volkswagen.py +++ b/selfdrive/car/volkswagen/tests/test_volkswagen.py @@ -2,8 +2,13 @@ import re import unittest +from cereal import car +from openpilot.selfdrive.car.volkswagen.values import CAR, FW_QUERY_CONFIG, WMI from openpilot.selfdrive.car.volkswagen.fingerprints import FW_VERSIONS +Ecu = car.CarParams.Ecu + +CHASSIS_CODE_PATTERN = re.compile('[A-Z0-9]{2}') # TODO: determine the unknown groups SPARE_PART_FW_PATTERN = re.compile(b'\xf1\x87(?P[0-9][0-9A-Z]{2})(?P[0-9][0-9A-Z][0-9])(?P[0-9A-Z]{2}[0-9])([A-Z0-9]| )') @@ -17,6 +22,44 @@ class TestVolkswagenPlatformConfigs(unittest.TestCase): for fw in fws: self.assertNotEqual(SPARE_PART_FW_PATTERN.match(fw), None, f"Bad FW: {fw}") + def test_chassis_codes(self): + for platform in CAR: + with self.subTest(platform=platform): + self.assertTrue(len(platform.config.wmis) > 0, "WMIs not set") + self.assertTrue(len(platform.config.chassis_codes) > 0, "Chassis codes not set") + self.assertTrue(all(CHASSIS_CODE_PATTERN.match(cc) for cc in + platform.config.chassis_codes), "Bad chassis codes") + + # No two platforms should share chassis codes + for comp in CAR: + if platform == comp: + continue + self.assertEqual(set(), platform.config.chassis_codes & comp.config.chassis_codes, + f"Shared chassis codes: {comp}") + + def test_custom_fuzzy_fingerprinting(self): + for platform in CAR: + expected_radar_fw = FW_VERSIONS[platform][Ecu.fwdRadar, 0x757, None] + + with self.subTest(platform=platform): + for wmi in WMI: + for chassis_code in platform.config.chassis_codes | {"00"}: + vin = ["0"] * 17 + vin[0:3] = wmi + vin[6:8] = chassis_code + vin = "".join(vin) + + # Check a few FW cases - expected, unexpected + for radar_fw in expected_radar_fw + [b'\xf1\x877H9907572AA\xf1\x890396']: + should_match = ((wmi in platform.config.wmis and chassis_code in platform.config.chassis_codes) and + radar_fw in expected_radar_fw) + + live_fws = {(0x757, None): [radar_fw]} + matches = FW_QUERY_CONFIG.match_fw_to_car_fuzzy(live_fws, vin, FW_VERSIONS) + + expected_matches = {platform} if should_match else set() + self.assertEqual(expected_matches, matches, "Bad match") + if __name__ == "__main__": unittest.main() diff --git a/selfdrive/car/volkswagen/values.py b/selfdrive/car/volkswagen/values.py index 375ca78104..5e332c98c4 100644 --- a/selfdrive/car/volkswagen/values.py +++ b/selfdrive/car/volkswagen/values.py @@ -1,6 +1,6 @@ from collections import namedtuple from dataclasses import dataclass, field -from enum import Enum, IntFlag +from enum import Enum, IntFlag, StrEnum from cereal import car from panda.python import uds @@ -109,6 +109,27 @@ class CANBUS: cam = 2 +class WMI(StrEnum): + VOLKSWAGEN_USA_SUV = "1V2" + VOLKSWAGEN_USA_CAR = "1VW" + VOLKSWAGEN_MEXICO_SUV = "3VV" + VOLKSWAGEN_MEXICO_CAR = "3VW" + VOLKSWAGEN_ARGENTINA = "8AW" + VOLKSWAGEN_BRASIL = "9BW" + SAIC_VOLKSWAGEN = "LSV" + SKODA = "TMB" + SEAT = "VSS" + AUDI_EUROPE_MPV = "WA1" + AUDI_GERMANY_CAR = "WAU" + MAN = "WMA" + AUDI_SPORT = "WUA" + VOLKSWAGEN_COMMERCIAL = "WV1" + VOLKSWAGEN_COMMERCIAL_BUS_VAN = "WV2" + VOLKSWAGEN_EUROPE_SUV = "WVG" + VOLKSWAGEN_EUROPE_CAR = "WVW" + VOLKSWAGEN_GROUP_RUS = "XW8" + + class VolkswagenFlags(IntFlag): # Detected flags STOCK_HCA_PRESENT = 1 @@ -120,10 +141,14 @@ class VolkswagenFlags(IntFlag): @dataclass class VolkswagenMQBPlatformConfig(PlatformConfig): dbc_dict: DbcDict = field(default_factory=lambda: dbc_dict('vw_mqb_2010', None)) + # Volkswagen uses the VIN WMI and chassis code to match in the absence of the comma power + # on camera-integrated cars, as we lose too many ECUs to reliably identify the vehicle + chassis_codes: set[str] = field(default_factory=set) + wmis: set[WMI] = field(default_factory=set) @dataclass -class VolkswagenPQPlatformConfig(PlatformConfig): +class VolkswagenPQPlatformConfig(VolkswagenMQBPlatformConfig): dbc_dict: DbcDict = field(default_factory=lambda: dbc_dict('vw_golf_mk4', None)) def init(self): @@ -176,7 +201,9 @@ class VWCarDocs(CarDocs): # FW_VERSIONS for that existing CAR. class CAR(Platforms): - VOLKSWAGEN_ARTEON_MK1 = VolkswagenMQBPlatformConfig( # Chassis AN + config: VolkswagenMQBPlatformConfig | VolkswagenPQPlatformConfig + + VOLKSWAGEN_ARTEON_MK1 = VolkswagenMQBPlatformConfig( [ VWCarDocs("Volkswagen Arteon 2018-23", video_link="https://youtu.be/FAomFKPFlDA"), VWCarDocs("Volkswagen Arteon R 2020-23", video_link="https://youtu.be/FAomFKPFlDA"), @@ -184,8 +211,10 @@ class CAR(Platforms): VWCarDocs("Volkswagen CC 2018-22", video_link="https://youtu.be/FAomFKPFlDA"), ], VolkswagenCarSpecs(mass=1733, wheelbase=2.84), + chassis_codes={"AN"}, + wmis={WMI.VOLKSWAGEN_EUROPE_CAR}, ) - VOLKSWAGEN_ATLAS_MK1 = VolkswagenMQBPlatformConfig( # Chassis CA + VOLKSWAGEN_ATLAS_MK1 = VolkswagenMQBPlatformConfig( [ VWCarDocs("Volkswagen Atlas 2018-23"), VWCarDocs("Volkswagen Atlas Cross Sport 2020-22"), @@ -194,15 +223,19 @@ class CAR(Platforms): VWCarDocs("Volkswagen Teramont X 2021-22"), ], VolkswagenCarSpecs(mass=2011, wheelbase=2.98), + chassis_codes={"CA"}, + wmis={WMI.VOLKSWAGEN_USA_SUV}, ) - VOLKSWAGEN_CADDY_MK3 = VolkswagenPQPlatformConfig( # Chassis 2K + VOLKSWAGEN_CADDY_MK3 = VolkswagenPQPlatformConfig( [ VWCarDocs("Volkswagen Caddy 2019"), VWCarDocs("Volkswagen Caddy Maxi 2019"), ], VolkswagenCarSpecs(mass=1613, wheelbase=2.6, minSteerSpeed=21 * CV.KPH_TO_MS), + chassis_codes={"2K"}, + wmis={WMI.VOLKSWAGEN_COMMERCIAL_BUS_VAN}, ) - VOLKSWAGEN_CRAFTER_MK2 = VolkswagenMQBPlatformConfig( # Chassis SY/SZ + VOLKSWAGEN_CRAFTER_MK2 = VolkswagenMQBPlatformConfig( [ VWCarDocs("Volkswagen Crafter 2017-23", video_link="https://youtu.be/4100gLeabmo"), VWCarDocs("Volkswagen e-Crafter 2018-23", video_link="https://youtu.be/4100gLeabmo"), @@ -211,8 +244,10 @@ class CAR(Platforms): VWCarDocs("MAN eTGE 2020-23", video_link="https://youtu.be/4100gLeabmo"), ], VolkswagenCarSpecs(mass=2100, wheelbase=3.64, minSteerSpeed=50 * CV.KPH_TO_MS), + chassis_codes={"SY", "SZ"}, + wmis={WMI.VOLKSWAGEN_COMMERCIAL, WMI.MAN}, ) - VOLKSWAGEN_GOLF_MK7 = VolkswagenMQBPlatformConfig( # Chassis 5G/AU/BA/BE + VOLKSWAGEN_GOLF_MK7 = VolkswagenMQBPlatformConfig( [ VWCarDocs("Volkswagen e-Golf 2014-20"), VWCarDocs("Volkswagen Golf 2015-20", auto_resume=False), @@ -224,71 +259,95 @@ class CAR(Platforms): VWCarDocs("Volkswagen Golf SportsVan 2015-20"), ], VolkswagenCarSpecs(mass=1397, wheelbase=2.62), + chassis_codes={"5G", "AU", "BA", "BE"}, + wmis={WMI.VOLKSWAGEN_MEXICO_CAR, WMI.VOLKSWAGEN_EUROPE_CAR}, ) - VOLKSWAGEN_JETTA_MK7 = VolkswagenMQBPlatformConfig( # Chassis BU + VOLKSWAGEN_JETTA_MK7 = VolkswagenMQBPlatformConfig( [ VWCarDocs("Volkswagen Jetta 2018-24"), VWCarDocs("Volkswagen Jetta GLI 2021-24"), ], VolkswagenCarSpecs(mass=1328, wheelbase=2.71), + chassis_codes={"BU"}, + wmis={WMI.VOLKSWAGEN_MEXICO_CAR, WMI.VOLKSWAGEN_EUROPE_CAR}, ) - VOLKSWAGEN_PASSAT_MK8 = VolkswagenMQBPlatformConfig( # Chassis 3G + VOLKSWAGEN_PASSAT_MK8 = VolkswagenMQBPlatformConfig( [ VWCarDocs("Volkswagen Passat 2015-22", footnotes=[Footnote.PASSAT]), VWCarDocs("Volkswagen Passat Alltrack 2015-22"), VWCarDocs("Volkswagen Passat GTE 2015-22"), ], VolkswagenCarSpecs(mass=1551, wheelbase=2.79), + chassis_codes={"3G"}, + wmis={WMI.VOLKSWAGEN_EUROPE_CAR}, ) - VOLKSWAGEN_PASSAT_NMS = VolkswagenPQPlatformConfig( # Chassis A3 + VOLKSWAGEN_PASSAT_NMS = VolkswagenPQPlatformConfig( [VWCarDocs("Volkswagen Passat NMS 2017-22")], VolkswagenCarSpecs(mass=1503, wheelbase=2.80, minSteerSpeed=50 * CV.KPH_TO_MS, minEnableSpeed=20 * CV.KPH_TO_MS), + chassis_codes={"A3"}, + wmis={WMI.VOLKSWAGEN_USA_CAR}, ) - VOLKSWAGEN_POLO_MK6 = VolkswagenMQBPlatformConfig( # Chassis AW + VOLKSWAGEN_POLO_MK6 = VolkswagenMQBPlatformConfig( [ VWCarDocs("Volkswagen Polo 2018-23", footnotes=[Footnote.VW_MQB_A0]), VWCarDocs("Volkswagen Polo GTI 2018-23", footnotes=[Footnote.VW_MQB_A0]), ], VolkswagenCarSpecs(mass=1230, wheelbase=2.55), + chassis_codes={"AW"}, + wmis={WMI.VOLKSWAGEN_EUROPE_CAR}, ) - VOLKSWAGEN_SHARAN_MK2 = VolkswagenPQPlatformConfig( # Chassis 7N + VOLKSWAGEN_SHARAN_MK2 = VolkswagenPQPlatformConfig( [ VWCarDocs("Volkswagen Sharan 2018-22"), VWCarDocs("SEAT Alhambra 2018-20"), ], VolkswagenCarSpecs(mass=1639, wheelbase=2.92, minSteerSpeed=50 * CV.KPH_TO_MS), + chassis_codes={"7N"}, + wmis={WMI.VOLKSWAGEN_EUROPE_CAR}, ) - VOLKSWAGEN_TAOS_MK1 = VolkswagenMQBPlatformConfig( # Chassis B2 + VOLKSWAGEN_TAOS_MK1 = VolkswagenMQBPlatformConfig( [VWCarDocs("Volkswagen Taos 2022-23")], VolkswagenCarSpecs(mass=1498, wheelbase=2.69), + chassis_codes={"B2"}, + wmis={WMI.VOLKSWAGEN_MEXICO_SUV, WMI.VOLKSWAGEN_ARGENTINA}, ) - VOLKSWAGEN_TCROSS_MK1 = VolkswagenMQBPlatformConfig( # Chassis C1 + VOLKSWAGEN_TCROSS_MK1 = VolkswagenMQBPlatformConfig( [VWCarDocs("Volkswagen T-Cross 2021", footnotes=[Footnote.VW_MQB_A0])], VolkswagenCarSpecs(mass=1150, wheelbase=2.60), + chassis_codes={"C1"}, + wmis={WMI.VOLKSWAGEN_EUROPE_SUV}, ) - VOLKSWAGEN_TIGUAN_MK2 = VolkswagenMQBPlatformConfig( # Chassis 5N/AD/AX/BW + VOLKSWAGEN_TIGUAN_MK2 = VolkswagenMQBPlatformConfig( [ VWCarDocs("Volkswagen Tiguan 2018-24"), VWCarDocs("Volkswagen Tiguan eHybrid 2021-23"), ], VolkswagenCarSpecs(mass=1715, wheelbase=2.74), + chassis_codes={"5N", "AD", "AX", "BW"}, + wmis={WMI.VOLKSWAGEN_EUROPE_SUV, WMI.VOLKSWAGEN_MEXICO_SUV}, ) - VOLKSWAGEN_TOURAN_MK2 = VolkswagenMQBPlatformConfig( # Chassis 1T + VOLKSWAGEN_TOURAN_MK2 = VolkswagenMQBPlatformConfig( [VWCarDocs("Volkswagen Touran 2016-23")], VolkswagenCarSpecs(mass=1516, wheelbase=2.79), + chassis_codes={"1T"}, + wmis={WMI.VOLKSWAGEN_EUROPE_SUV}, ) - VOLKSWAGEN_TRANSPORTER_T61 = VolkswagenMQBPlatformConfig( # Chassis 7H/7L + VOLKSWAGEN_TRANSPORTER_T61 = VolkswagenMQBPlatformConfig( [ VWCarDocs("Volkswagen Caravelle 2020"), VWCarDocs("Volkswagen California 2021-23"), ], VolkswagenCarSpecs(mass=1926, wheelbase=3.00, minSteerSpeed=14.0), + chassis_codes={"7H", "7L"}, + wmis={WMI.VOLKSWAGEN_COMMERCIAL_BUS_VAN}, ) - VOLKSWAGEN_TROC_MK1 = VolkswagenMQBPlatformConfig( # Chassis A1 + VOLKSWAGEN_TROC_MK1 = VolkswagenMQBPlatformConfig( [VWCarDocs("Volkswagen T-Roc 2018-22", footnotes=[Footnote.VW_MQB_A0])], VolkswagenCarSpecs(mass=1413, wheelbase=2.63), + chassis_codes={"A1"}, + wmis={WMI.VOLKSWAGEN_EUROPE_SUV}, ) - AUDI_A3_MK3 = VolkswagenMQBPlatformConfig( # Chassis 8V/FF + AUDI_A3_MK3 = VolkswagenMQBPlatformConfig( [ VWCarDocs("Audi A3 2014-19"), VWCarDocs("Audi A3 Sportback e-tron 2017-18"), @@ -296,55 +355,109 @@ class CAR(Platforms): VWCarDocs("Audi S3 2015-17"), ], VolkswagenCarSpecs(mass=1335, wheelbase=2.61), + chassis_codes={"8V", "FF"}, + wmis={WMI.AUDI_GERMANY_CAR, WMI.AUDI_SPORT}, ) - AUDI_Q2_MK1 = VolkswagenMQBPlatformConfig( # Chassis GA + AUDI_Q2_MK1 = VolkswagenMQBPlatformConfig( [VWCarDocs("Audi Q2 2018")], VolkswagenCarSpecs(mass=1205, wheelbase=2.61), + chassis_codes={"GA"}, + wmis={WMI.AUDI_GERMANY_CAR}, ) - AUDI_Q3_MK2 = VolkswagenMQBPlatformConfig( # Chassis 8U/F3/FS + AUDI_Q3_MK2 = VolkswagenMQBPlatformConfig( [VWCarDocs("Audi Q3 2019-23")], VolkswagenCarSpecs(mass=1623, wheelbase=2.68), + chassis_codes={"8U", "F3", "FS"}, + wmis={WMI.AUDI_EUROPE_MPV, WMI.AUDI_GERMANY_CAR}, ) - SEAT_ATECA_MK1 = VolkswagenMQBPlatformConfig( # Chassis 5F + SEAT_ATECA_MK1 = VolkswagenMQBPlatformConfig( [ VWCarDocs("SEAT Ateca 2018"), VWCarDocs("SEAT Leon 2014-20"), ], VolkswagenCarSpecs(mass=1300, wheelbase=2.64), + chassis_codes={"5F"}, + wmis={WMI.SEAT}, ) - SKODA_FABIA_MK4 = VolkswagenMQBPlatformConfig( # Chassis PJ + SKODA_FABIA_MK4 = VolkswagenMQBPlatformConfig( [VWCarDocs("Škoda Fabia 2022-23", footnotes=[Footnote.VW_MQB_A0])], VolkswagenCarSpecs(mass=1266, wheelbase=2.56), + chassis_codes={"PJ"}, + wmis={WMI.SKODA}, ) - SKODA_KAMIQ_MK1 = VolkswagenMQBPlatformConfig( # Chassis NW + SKODA_KAMIQ_MK1 = VolkswagenMQBPlatformConfig( [ VWCarDocs("Škoda Kamiq 2021-23", footnotes=[Footnote.VW_MQB_A0, Footnote.KAMIQ]), VWCarDocs("Škoda Scala 2020-23", footnotes=[Footnote.VW_MQB_A0]), ], VolkswagenCarSpecs(mass=1230, wheelbase=2.66), + chassis_codes={"NW"}, + wmis={WMI.SKODA}, ) - SKODA_KAROQ_MK1 = VolkswagenMQBPlatformConfig( # Chassis NU + SKODA_KAROQ_MK1 = VolkswagenMQBPlatformConfig( [VWCarDocs("Škoda Karoq 2019-23")], VolkswagenCarSpecs(mass=1278, wheelbase=2.66), + chassis_codes={"NU"}, + wmis={WMI.SKODA}, ) - SKODA_KODIAQ_MK1 = VolkswagenMQBPlatformConfig( # Chassis NS + SKODA_KODIAQ_MK1 = VolkswagenMQBPlatformConfig( [VWCarDocs("Škoda Kodiaq 2017-23")], VolkswagenCarSpecs(mass=1569, wheelbase=2.79), + chassis_codes={"NS"}, + wmis={WMI.SKODA, WMI.VOLKSWAGEN_GROUP_RUS}, ) - SKODA_OCTAVIA_MK3 = VolkswagenMQBPlatformConfig( # Chassis NE + SKODA_OCTAVIA_MK3 = VolkswagenMQBPlatformConfig( [ VWCarDocs("Škoda Octavia 2015-19"), VWCarDocs("Škoda Octavia RS 2016"), VWCarDocs("Škoda Octavia Scout 2017-19"), ], VolkswagenCarSpecs(mass=1388, wheelbase=2.68), + chassis_codes={"NE"}, + wmis={WMI.SKODA}, ) - SKODA_SUPERB_MK3 = VolkswagenMQBPlatformConfig( # Chassis 3V/NP + SKODA_SUPERB_MK3 = VolkswagenMQBPlatformConfig( [VWCarDocs("Škoda Superb 2015-22")], VolkswagenCarSpecs(mass=1505, wheelbase=2.84), + chassis_codes={"3V", "NP"}, + wmis={WMI.SKODA}, ) +def match_fw_to_car_fuzzy(live_fw_versions, vin, offline_fw_versions) -> set[str]: + candidates = set() + + # Check the WMI and chassis code to determine the platform + wmi = vin[:3] + chassis_code = vin[6:8] + + for platform in CAR: + valid_ecus = set() + for ecu, expected_versions in offline_fw_versions[platform].items(): + addr = ecu[1:] + if ecu[0] not in CHECK_FUZZY_ECUS: + continue + + # Sanity check that a subset of Volkswagen FW is in the database + found_versions = live_fw_versions.get(addr, []) + if not any(found_version in expected_versions for found_version in found_versions): + break + + valid_ecus.add(ecu[0]) + + if valid_ecus != CHECK_FUZZY_ECUS: + continue + + if wmi in platform.config.wmis and chassis_code in platform.config.chassis_codes: + candidates.add(platform) + + return {str(c) for c in candidates} + + +# These ECUs are required to match to gain a VIN match +# TODO: do we want to check camera when we add its FW? +CHECK_FUZZY_ECUS = {Ecu.fwdRadar} + # All supported cars should return FW from the engine, srs, eps, and fwdRadar. Cars # with a manual trans won't return transmission firmware, but all other cars will. # @@ -382,6 +495,7 @@ FW_QUERY_CONFIG = FwQueryConfig( ]], non_essential_ecus={Ecu.eps: list(CAR)}, extra_ecus=[(Ecu.fwdCamera, 0x74f, None)], + match_fw_to_car_fuzzy=match_fw_to_car_fuzzy, ) DBC = CAR.create_dbc_map() diff --git a/selfdrive/debug/test_fw_query_on_routes.py b/selfdrive/debug/test_fw_query_on_routes.py index 3c5733520e..e338110a7d 100755 --- a/selfdrive/debug/test_fw_query_on_routes.py +++ b/selfdrive/debug/test_fw_query_on_routes.py @@ -78,8 +78,8 @@ if __name__ == "__main__": print("not in supported cars") break - _, exact_matches = match_fw_to_car(car_fw, allow_exact=True, allow_fuzzy=False) - _, fuzzy_matches = match_fw_to_car(car_fw, allow_exact=False, allow_fuzzy=True) + _, exact_matches = match_fw_to_car(car_fw, CP.carVin, allow_exact=True, allow_fuzzy=False) + _, fuzzy_matches = match_fw_to_car(car_fw, CP.carVin, allow_exact=False, allow_fuzzy=True) if (len(exact_matches) == 1) and (list(exact_matches)[0] == live_fingerprint): good_exact += 1 diff --git a/tools/car_porting/auto_fingerprint.py b/tools/car_porting/auto_fingerprint.py index 8b0ae6762d..0a6b602a15 100755 --- a/tools/car_porting/auto_fingerprint.py +++ b/tools/car_porting/auto_fingerprint.py @@ -41,7 +41,7 @@ if __name__ == "__main__": carPlatform = CP.carFingerprint if args.platform is None: # attempt to auto-determine platform with other fuzzy fingerprints - _, possible_platforms = match_fw_to_car(carFw, log=False) + _, possible_platforms = match_fw_to_car(carFw, carVin, log=False) if len(possible_platforms) != 1: print(f"Unable to auto-determine platform, possible platforms: {possible_platforms}")