Volkswagen: fingerprint on VIN chassis code (#32148)

* add function signature and behavior comment

* add test

* move chassis codes to platform config!

* add a shared chassis code test

* function

* test matching

* this commit isn't complete yet

* Revert "this commit isn't complete yet"

This reverts commit ae77d5cd54.

* need to check WMI

* TODO: test WMI

* test wmi

* radar FW sanity check

* fix test

* fixes from merge

fixes from merge

* whoops

* fix static analysis!

* do match_fw_to_car

match_fw_to_car takes vin

* makes sense to keep it one function, and we can return exact or fuzzy!

* clean up

* kinda pointless

* fix more tests

* back to function being only fuzzy

* revert test_fw_fingerprint

* revert test_fw_fingerprint

* simplify

* clean up/fixes

* rename test

* less duplicatey WMI descriptions

* fix

* convert to enum

* I am confident about these WMIs

* these are also good

* we support 5N AUS/NZ and NAR (North American) AX Tiguans

fixes

* Tiguan also Mexico

* only one user for caddy

* got from the test route

* check that the gateway type matches the platform (each platform has 1 or 2 types)

* ~gateway~ -> exact FW match

* remove re

* ensure WMIs are set

* actually no reason to delete

* move comment up to the platform config

* proper wmis typing

* spacing

* flip
pull/31956/head^2
Shane Smiskol 1 year ago committed by GitHub
parent 02920d67b7
commit 6acf763db4
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
  1. 4
      selfdrive/car/car_helpers.py
  2. 4
      selfdrive/car/fw_query_definitions.py
  3. 12
      selfdrive/car/fw_versions.py
  4. 2
      selfdrive/car/hyundai/tests/test_hyundai.py
  5. 2
      selfdrive/car/hyundai/values.py
  6. 8
      selfdrive/car/tests/test_fw_fingerprint.py
  7. 2
      selfdrive/car/toyota/tests/test_toyota.py
  8. 2
      selfdrive/car/toyota/values.py
  9. 43
      selfdrive/car/volkswagen/tests/test_volkswagen.py
  10. 170
      selfdrive/car/volkswagen/values.py
  11. 4
      selfdrive/debug/test_fw_query_on_routes.py
  12. 2
      tools/car_porting/auto_fingerprint.py

@ -139,10 +139,10 @@ def fingerprint(logcan, sendcan, num_pandas):
# VIN query only reliably works through OBDII # VIN query only reliably works through OBDII
vin_rx_addr, vin_rx_bus, vin = get_vin(logcan, sendcan, (0, 1)) 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) 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 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: else:
vin_rx_addr, vin_rx_bus, vin = -1, -1, VIN_UNKNOWN vin_rx_addr, vin_rx_bus, vin = -1, -1, VIN_UNKNOWN
exact_fw_match, fw_candidates, car_fw = True, set(), [] exact_fw_match, fw_candidates, car_fw = True, set(), []

@ -97,9 +97,9 @@ class FwQueryConfig:
non_essential_ecus: dict[capnp.lib.capnp._EnumModule, list[str]] = field(default_factory=dict) non_essential_ecus: dict[capnp.lib.capnp._EnumModule, list[str]] = field(default_factory=dict)
# Ecus added for data collection, not to be fingerprinted on # 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) 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 # 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): def __post_init__(self):
for i in range(len(self.requests)): for i in range(len(self.requests)):

@ -144,8 +144,8 @@ def match_fw_to_car_exact(live_fw_versions: LiveFwVersions, match_brand: str = N
return set(candidates.keys()) - invalid 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, def match_fw_to_car(fw_versions: list[capnp.lib.capnp._DynamicStructBuilder], vin: str,
log: bool = True) -> tuple[bool, set[str]]: allow_exact: bool = True, allow_fuzzy: bool = True, log: bool = True) -> tuple[bool, set[str]]:
# Try exact matching first # Try exact matching first
exact_matches: list[tuple[bool, MatchFwToCar]] = [] exact_matches: list[tuple[bool, MatchFwToCar]] = []
if allow_exact: 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 # If specified and no matches so far, fall back to brand's fuzzy fingerprinting function
config = FW_QUERY_CONFIGS[brand] config = FW_QUERY_CONFIGS[brand]
if not exact_match and not len(matches) and config.match_fw_to_car_fuzzy is not None: 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): if len(matches):
return exact_match, matches return exact_match, matches
@ -237,7 +237,7 @@ def set_obd_multiplexing(params: Params, obd_multiplexing: bool):
cloudlog.warning("OBD multiplexing set successfully") 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]: 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""" """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) all_car_fw.extend(car_fw)
# If there is a match using this brand's FW alone, finish querying early # 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: if len(matches) == 1:
break break
@ -381,7 +381,7 @@ if __name__ == "__main__":
t = time.time() 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) 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()
print("Found FW versions") print("Found FW versions")

@ -209,7 +209,7 @@ class TestHyundaiFingerprint(unittest.TestCase):
"subAddress": 0 if sub_addr is None else sub_addr}) "subAddress": 0 if sub_addr is None else sub_addr})
CP = car.CarParams.new_message(carFw=car_fw) 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: if len(matches) == 1:
self.assertEqual(list(matches)[0], platform) self.assertEqual(list(matches)[0], platform)
else: else:

@ -568,7 +568,7 @@ def get_platform_codes(fw_versions: list[bytes]) -> set[tuple[bytes, bytes | Non
return codes 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 # 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 # to distinguish between hybrid and ICE. All EVs so far are either exclusively
# electric or specify electric in the platform code. # electric or specify electric in the platform code.

@ -49,7 +49,7 @@ class TestFwFingerprint(unittest.TestCase):
fw.append({"ecu": ecu_name, "fwVersion": random.choice(fw_versions), 'brand': brand, fw.append({"ecu": ecu_name, "fwVersion": random.choice(fw_versions), 'brand': brand,
"address": addr, "subAddress": 0 if sub_addr is None else sub_addr}) "address": addr, "subAddress": 0 if sub_addr is None else sub_addr})
CP.carFw = fw 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: if not test_non_essential:
self.assertFingerprints(matches, car_model) self.assertFingerprints(matches, car_model)
else: else:
@ -72,8 +72,8 @@ class TestFwFingerprint(unittest.TestCase):
fw.append({"ecu": ecu_name, "fwVersion": random.choice(fw_versions), 'brand': brand, fw.append({"ecu": ecu_name, "fwVersion": random.choice(fw_versions), 'brand': brand,
"address": addr, "subAddress": 0 if sub_addr is None else sub_addr}) "address": addr, "subAddress": 0 if sub_addr is None else sub_addr})
CP.carFw = fw CP.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)
brand_matches = config.match_fw_to_car_fuzzy(build_fw_dict(CP.carFw), VERSIONS[brand]) 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 both have matches, they must agree
if len(matches) == 1 and len(brand_matches) == 1: 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, fw.append({"ecu": ecu_name, "fwVersion": random.choice(ecus[ecu]), 'brand': brand,
"address": addr, "subAddress": 0 if sub_addr is None else sub_addr}) "address": addr, "subAddress": 0 if sub_addr is None else sub_addr})
CP = car.CarParams.new_message(carFw=fw) 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 # Assert no match if there are not enough unique ECUs
unique_ecus = {(f['address'], f['subAddress']) for f in fw} unique_ecus = {(f['address'], f['subAddress']) for f in fw}

@ -160,7 +160,7 @@ class TestToyotaFingerprint(unittest.TestCase):
"subAddress": 0 if sub_addr is None else sub_addr}) "subAddress": 0 if sub_addr is None else sub_addr})
CP = car.CarParams.new_message(carFw=car_fw) 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: if len(matches) == 1:
self.assertEqual(list(matches)[0], platform) self.assertEqual(list(matches)[0], platform)
else: else:

@ -415,7 +415,7 @@ def get_platform_codes(fw_versions: list[bytes]) -> dict[bytes, set[bytes]]:
return dict(codes) 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() candidates = set()
for candidate, fws in offline_fw_versions.items(): for candidate, fws in offline_fw_versions.items():

@ -2,8 +2,13 @@
import re import re
import unittest 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 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 # TODO: determine the unknown groups
SPARE_PART_FW_PATTERN = re.compile(b'\xf1\x87(?P<gateway>[0-9][0-9A-Z]{2})(?P<unknown>[0-9][0-9A-Z][0-9])(?P<unknown2>[0-9A-Z]{2}[0-9])([A-Z0-9]| )') SPARE_PART_FW_PATTERN = re.compile(b'\xf1\x87(?P<gateway>[0-9][0-9A-Z]{2})(?P<unknown>[0-9][0-9A-Z][0-9])(?P<unknown2>[0-9A-Z]{2}[0-9])([A-Z0-9]| )')
@ -17,6 +22,44 @@ class TestVolkswagenPlatformConfigs(unittest.TestCase):
for fw in fws: for fw in fws:
self.assertNotEqual(SPARE_PART_FW_PATTERN.match(fw), None, f"Bad FW: {fw}") 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__": if __name__ == "__main__":
unittest.main() unittest.main()

@ -1,6 +1,6 @@
from collections import namedtuple from collections import namedtuple
from dataclasses import dataclass, field from dataclasses import dataclass, field
from enum import Enum, IntFlag from enum import Enum, IntFlag, StrEnum
from cereal import car from cereal import car
from panda.python import uds from panda.python import uds
@ -109,6 +109,27 @@ class CANBUS:
cam = 2 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): class VolkswagenFlags(IntFlag):
# Detected flags # Detected flags
STOCK_HCA_PRESENT = 1 STOCK_HCA_PRESENT = 1
@ -120,10 +141,14 @@ class VolkswagenFlags(IntFlag):
@dataclass @dataclass
class VolkswagenMQBPlatformConfig(PlatformConfig): class VolkswagenMQBPlatformConfig(PlatformConfig):
dbc_dict: DbcDict = field(default_factory=lambda: dbc_dict('vw_mqb_2010', None)) 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 @dataclass
class VolkswagenPQPlatformConfig(PlatformConfig): class VolkswagenPQPlatformConfig(VolkswagenMQBPlatformConfig):
dbc_dict: DbcDict = field(default_factory=lambda: dbc_dict('vw_golf_mk4', None)) dbc_dict: DbcDict = field(default_factory=lambda: dbc_dict('vw_golf_mk4', None))
def init(self): def init(self):
@ -176,7 +201,9 @@ class VWCarDocs(CarDocs):
# FW_VERSIONS for that existing CAR. # FW_VERSIONS for that existing CAR.
class CAR(Platforms): 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 2018-23", video_link="https://youtu.be/FAomFKPFlDA"),
VWCarDocs("Volkswagen Arteon R 2020-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"), VWCarDocs("Volkswagen CC 2018-22", video_link="https://youtu.be/FAomFKPFlDA"),
], ],
VolkswagenCarSpecs(mass=1733, wheelbase=2.84), 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 2018-23"),
VWCarDocs("Volkswagen Atlas Cross Sport 2020-22"), VWCarDocs("Volkswagen Atlas Cross Sport 2020-22"),
@ -194,15 +223,19 @@ class CAR(Platforms):
VWCarDocs("Volkswagen Teramont X 2021-22"), VWCarDocs("Volkswagen Teramont X 2021-22"),
], ],
VolkswagenCarSpecs(mass=2011, wheelbase=2.98), 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 2019"),
VWCarDocs("Volkswagen Caddy Maxi 2019"), VWCarDocs("Volkswagen Caddy Maxi 2019"),
], ],
VolkswagenCarSpecs(mass=1613, wheelbase=2.6, minSteerSpeed=21 * CV.KPH_TO_MS), 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 Crafter 2017-23", video_link="https://youtu.be/4100gLeabmo"),
VWCarDocs("Volkswagen e-Crafter 2018-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"), VWCarDocs("MAN eTGE 2020-23", video_link="https://youtu.be/4100gLeabmo"),
], ],
VolkswagenCarSpecs(mass=2100, wheelbase=3.64, minSteerSpeed=50 * CV.KPH_TO_MS), 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 e-Golf 2014-20"),
VWCarDocs("Volkswagen Golf 2015-20", auto_resume=False), VWCarDocs("Volkswagen Golf 2015-20", auto_resume=False),
@ -224,71 +259,95 @@ class CAR(Platforms):
VWCarDocs("Volkswagen Golf SportsVan 2015-20"), VWCarDocs("Volkswagen Golf SportsVan 2015-20"),
], ],
VolkswagenCarSpecs(mass=1397, wheelbase=2.62), 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 2018-24"),
VWCarDocs("Volkswagen Jetta GLI 2021-24"), VWCarDocs("Volkswagen Jetta GLI 2021-24"),
], ],
VolkswagenCarSpecs(mass=1328, wheelbase=2.71), 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 2015-22", footnotes=[Footnote.PASSAT]),
VWCarDocs("Volkswagen Passat Alltrack 2015-22"), VWCarDocs("Volkswagen Passat Alltrack 2015-22"),
VWCarDocs("Volkswagen Passat GTE 2015-22"), VWCarDocs("Volkswagen Passat GTE 2015-22"),
], ],
VolkswagenCarSpecs(mass=1551, wheelbase=2.79), 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")], [VWCarDocs("Volkswagen Passat NMS 2017-22")],
VolkswagenCarSpecs(mass=1503, wheelbase=2.80, minSteerSpeed=50 * CV.KPH_TO_MS, minEnableSpeed=20 * CV.KPH_TO_MS), 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 2018-23", footnotes=[Footnote.VW_MQB_A0]),
VWCarDocs("Volkswagen Polo GTI 2018-23", footnotes=[Footnote.VW_MQB_A0]), VWCarDocs("Volkswagen Polo GTI 2018-23", footnotes=[Footnote.VW_MQB_A0]),
], ],
VolkswagenCarSpecs(mass=1230, wheelbase=2.55), 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("Volkswagen Sharan 2018-22"),
VWCarDocs("SEAT Alhambra 2018-20"), VWCarDocs("SEAT Alhambra 2018-20"),
], ],
VolkswagenCarSpecs(mass=1639, wheelbase=2.92, minSteerSpeed=50 * CV.KPH_TO_MS), 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")], [VWCarDocs("Volkswagen Taos 2022-23")],
VolkswagenCarSpecs(mass=1498, wheelbase=2.69), 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])], [VWCarDocs("Volkswagen T-Cross 2021", footnotes=[Footnote.VW_MQB_A0])],
VolkswagenCarSpecs(mass=1150, wheelbase=2.60), 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 2018-24"),
VWCarDocs("Volkswagen Tiguan eHybrid 2021-23"), VWCarDocs("Volkswagen Tiguan eHybrid 2021-23"),
], ],
VolkswagenCarSpecs(mass=1715, wheelbase=2.74), 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")], [VWCarDocs("Volkswagen Touran 2016-23")],
VolkswagenCarSpecs(mass=1516, wheelbase=2.79), 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 Caravelle 2020"),
VWCarDocs("Volkswagen California 2021-23"), VWCarDocs("Volkswagen California 2021-23"),
], ],
VolkswagenCarSpecs(mass=1926, wheelbase=3.00, minSteerSpeed=14.0), 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])], [VWCarDocs("Volkswagen T-Roc 2018-22", footnotes=[Footnote.VW_MQB_A0])],
VolkswagenCarSpecs(mass=1413, wheelbase=2.63), 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 2014-19"),
VWCarDocs("Audi A3 Sportback e-tron 2017-18"), VWCarDocs("Audi A3 Sportback e-tron 2017-18"),
@ -296,55 +355,109 @@ class CAR(Platforms):
VWCarDocs("Audi S3 2015-17"), VWCarDocs("Audi S3 2015-17"),
], ],
VolkswagenCarSpecs(mass=1335, wheelbase=2.61), 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")], [VWCarDocs("Audi Q2 2018")],
VolkswagenCarSpecs(mass=1205, wheelbase=2.61), 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")], [VWCarDocs("Audi Q3 2019-23")],
VolkswagenCarSpecs(mass=1623, wheelbase=2.68), 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 Ateca 2018"),
VWCarDocs("SEAT Leon 2014-20"), VWCarDocs("SEAT Leon 2014-20"),
], ],
VolkswagenCarSpecs(mass=1300, wheelbase=2.64), 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])], [VWCarDocs("Škoda Fabia 2022-23", footnotes=[Footnote.VW_MQB_A0])],
VolkswagenCarSpecs(mass=1266, wheelbase=2.56), 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 Kamiq 2021-23", footnotes=[Footnote.VW_MQB_A0, Footnote.KAMIQ]),
VWCarDocs("Škoda Scala 2020-23", footnotes=[Footnote.VW_MQB_A0]), VWCarDocs("Škoda Scala 2020-23", footnotes=[Footnote.VW_MQB_A0]),
], ],
VolkswagenCarSpecs(mass=1230, wheelbase=2.66), 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")], [VWCarDocs("Škoda Karoq 2019-23")],
VolkswagenCarSpecs(mass=1278, wheelbase=2.66), 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")], [VWCarDocs("Škoda Kodiaq 2017-23")],
VolkswagenCarSpecs(mass=1569, wheelbase=2.79), 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 2015-19"),
VWCarDocs("Škoda Octavia RS 2016"), VWCarDocs("Škoda Octavia RS 2016"),
VWCarDocs("Škoda Octavia Scout 2017-19"), VWCarDocs("Škoda Octavia Scout 2017-19"),
], ],
VolkswagenCarSpecs(mass=1388, wheelbase=2.68), 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")], [VWCarDocs("Škoda Superb 2015-22")],
VolkswagenCarSpecs(mass=1505, wheelbase=2.84), 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 # 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. # 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)}, non_essential_ecus={Ecu.eps: list(CAR)},
extra_ecus=[(Ecu.fwdCamera, 0x74f, None)], extra_ecus=[(Ecu.fwdCamera, 0x74f, None)],
match_fw_to_car_fuzzy=match_fw_to_car_fuzzy,
) )
DBC = CAR.create_dbc_map() DBC = CAR.create_dbc_map()

@ -78,8 +78,8 @@ if __name__ == "__main__":
print("not in supported cars") print("not in supported cars")
break break
_, exact_matches = match_fw_to_car(car_fw, allow_exact=True, allow_fuzzy=False) _, exact_matches = match_fw_to_car(car_fw, CP.carVin, allow_exact=True, allow_fuzzy=False)
_, fuzzy_matches = match_fw_to_car(car_fw, allow_exact=False, allow_fuzzy=True) _, 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): if (len(exact_matches) == 1) and (list(exact_matches)[0] == live_fingerprint):
good_exact += 1 good_exact += 1

@ -41,7 +41,7 @@ if __name__ == "__main__":
carPlatform = CP.carFingerprint carPlatform = CP.carFingerprint
if args.platform is None: # attempt to auto-determine platform with other fuzzy fingerprints 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: if len(possible_platforms) != 1:
print(f"Unable to auto-determine platform, possible platforms: {possible_platforms}") print(f"Unable to auto-determine platform, possible platforms: {possible_platforms}")

Loading…
Cancel
Save