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_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(), []

@ -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)):

@ -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")

@ -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:

@ -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.

@ -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}

@ -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:

@ -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():

@ -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<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:
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()

@ -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()

@ -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

@ -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}")

Loading…
Cancel
Save