Fuzzy match on ECU FW versions (#20687)

* Fuzzy match on 3+ ECUs

* reuse dict

* exclude some shared ecus to be sure

* show alert for fuzzy match

* use title case

* require community toggle

* refactor

* do both exact and fuzzy in test script

* update test script

* add fuzz test and lower matches to >= 2

* strip alert length

* sort mismatches

* add fw tests to test_startup

* bump cereal
old-commit-hash: e4f73fbda5
commatwo_master
Willem Melching 4 years ago committed by GitHub
parent c0ac9bb63c
commit ee0e80a6d4
  1. 2
      cereal
  2. 15
      selfdrive/car/car_helpers.py
  3. 82
      selfdrive/car/fw_versions.py
  4. 3
      selfdrive/car/tests/test_fw_fingerprint.py
  5. 7
      selfdrive/controls/controlsd.py
  6. 11
      selfdrive/controls/lib/events.py
  7. 63
      selfdrive/controls/tests/test_startup.py
  8. 52
      selfdrive/debug/internal/fuzz_fw_fingerprint.py
  9. 77
      selfdrive/debug/test_fw_query_on_routes.py

@ -1 +1 @@
Subproject commit 957147cb8428d984a776ca468f866160f3b71bbc Subproject commit b39c6fc26d93ca776b27a2e4005b12ae85e7bacc

@ -13,7 +13,7 @@ from cereal import car
EventName = car.CarEvent.EventName EventName = car.CarEvent.EventName
def get_startup_event(car_recognized, controller_available): def get_startup_event(car_recognized, controller_available, fuzzy_fingerprint):
if comma_remote and tested_branch: if comma_remote and tested_branch:
event = EventName.startup event = EventName.startup
else: else:
@ -23,6 +23,8 @@ def get_startup_event(car_recognized, controller_available):
event = EventName.startupNoCar event = EventName.startupNoCar
elif car_recognized and not controller_available: elif car_recognized and not controller_available:
event = EventName.startupNoControl event = EventName.startupNoControl
elif car_recognized and fuzzy_fingerprint:
event = EventName.startupFuzzyFingerprint
return event return event
@ -104,10 +106,10 @@ def fingerprint(logcan, sendcan):
_, vin = get_vin(logcan, sendcan, bus) _, vin = get_vin(logcan, sendcan, bus)
car_fw = get_fw_versions(logcan, sendcan, bus) car_fw = get_fw_versions(logcan, sendcan, bus)
fw_candidates = match_fw_to_car(car_fw) exact_fw_match, fw_candidates = match_fw_to_car(car_fw)
else: else:
vin = VIN_UNKNOWN vin = VIN_UNKNOWN
fw_candidates, car_fw = set(), [] exact_fw_match, fw_candidates, car_fw = True, set(), []
cloudlog.warning("VIN %s", vin) cloudlog.warning("VIN %s", vin)
Params().put("CarVin", vin) Params().put("CarVin", vin)
@ -152,23 +154,25 @@ def fingerprint(logcan, sendcan):
frame += 1 frame += 1
exact_match = True
source = car.CarParams.FingerprintSource.can source = car.CarParams.FingerprintSource.can
# If FW query returns exactly 1 candidate, use it # If FW query returns exactly 1 candidate, use it
if len(fw_candidates) == 1: if len(fw_candidates) == 1:
car_fingerprint = list(fw_candidates)[0] car_fingerprint = list(fw_candidates)[0]
source = car.CarParams.FingerprintSource.fw source = car.CarParams.FingerprintSource.fw
exact_match = exact_fw_match
if fixed_fingerprint: if fixed_fingerprint:
car_fingerprint = fixed_fingerprint car_fingerprint = fixed_fingerprint
source = car.CarParams.FingerprintSource.fixed source = car.CarParams.FingerprintSource.fixed
cloudlog.warning("fingerprinted %s", car_fingerprint) cloudlog.warning("fingerprinted %s", car_fingerprint)
return car_fingerprint, finger, vin, car_fw, source return car_fingerprint, finger, vin, car_fw, source, exact_match
def get_car(logcan, sendcan): def get_car(logcan, sendcan):
candidate, fingerprints, vin, car_fw, source = fingerprint(logcan, sendcan) candidate, fingerprints, vin, car_fw, source, exact_match = fingerprint(logcan, sendcan)
if candidate is None: if candidate is None:
cloudlog.warning("car doesn't match any fingerprints: %r", fingerprints) cloudlog.warning("car doesn't match any fingerprints: %r", fingerprints)
@ -179,5 +183,6 @@ def get_car(logcan, sendcan):
car_params.carVin = vin car_params.carVin = vin
car_params.carFw = car_fw car_params.carFw = car_fw
car_params.fingerprintSource = source car_params.fingerprintSource = source
car_params.fuzzyFingerprint = not exact_match
return CarInterface(car_params, CarController, CarState), car_params return CarInterface(car_params, CarController, CarState), car_params

@ -2,6 +2,7 @@
import struct import struct
import traceback import traceback
from typing import Any from typing import Any
from collections import defaultdict
from tqdm import tqdm from tqdm import tqdm
@ -136,15 +137,67 @@ def chunks(l, n=128):
yield l[i:i + n] yield l[i:i + n]
def match_fw_to_car(fw_versions): def build_fw_dict(fw_versions):
candidates = FW_VERSIONS
invalid = []
fw_versions_dict = {} fw_versions_dict = {}
for fw in fw_versions: for fw in fw_versions:
addr = fw.address addr = fw.address
sub_addr = fw.subAddress if fw.subAddress != 0 else None sub_addr = fw.subAddress if fw.subAddress != 0 else None
fw_versions_dict[(addr, sub_addr)] = fw.fwVersion fw_versions_dict[(addr, sub_addr)] = fw.fwVersion
return fw_versions_dict
def match_fw_to_car_fuzzy(fw_versions_dict, log=True, exclude=None):
"""Do a fuzzy FW match. This function will return a match, and the number of firmware version
that were matched uniquely to that specific car. If multiple ECUs uniquely match to different cars
the match is rejected."""
# These ECUs are known to be shared between models (EPS only between hybrid/ICE version)
# Getting this exactly right isn't crucial, but excluding camera and radar makes it almost
# impossible to get 3 matching versions, even if two models with shared parts are released at the same
# time and only one is in our database.
exclude_types = [Ecu.fwdCamera, Ecu.fwdRadar, Ecu.eps]
# Build lookup table from (addr, subaddr, fw) to list of candidate cars
all_fw_versions = defaultdict(list)
for candidate, fw_by_addr in FW_VERSIONS.items():
if candidate == exclude:
continue
for addr, fws in fw_by_addr.items():
if addr[0] in exclude_types:
continue
for f in fws:
all_fw_versions[(addr[1], addr[2], f)].append(candidate)
match_count = 0
candidate = None
for addr, version in fw_versions_dict.items():
# All cars that have this FW response on the specified address
candidates = all_fw_versions[(addr[0], addr[1], version)]
if len(candidates) == 1:
match_count += 1
if candidate is None:
candidate = candidates[0]
# We uniquely matched two different cars. No fuzzy match possible
elif candidate != candidates[0]:
return set()
if match_count >= 2:
if log:
cloudlog.error(f"Fingerprinted {candidate} using fuzzy match. {match_count} matching ECUs")
return set([candidate])
else:
return set()
def match_fw_to_car_exact(fw_versions_dict):
"""Do an exact FW match. Returns all cars that match the given
FW versions for a list of "essential" ECUs. If an ECU is not considered
essential the FW version can be missing to get a fingerprint, but if it's present it
needs to match the database."""
invalid = []
candidates = FW_VERSIONS
for candidate, fws in candidates.items(): for candidate, fws in candidates.items():
for ecu, expected_versions in fws.items(): for ecu, expected_versions in fws.items():
@ -155,11 +208,11 @@ def match_fw_to_car(fw_versions):
if ecu_type == Ecu.esp and candidate in [TOYOTA.RAV4, TOYOTA.COROLLA, TOYOTA.HIGHLANDER] and found_version is None: if ecu_type == Ecu.esp and candidate in [TOYOTA.RAV4, TOYOTA.COROLLA, TOYOTA.HIGHLANDER] and found_version is None:
continue continue
# TODO: on some toyota, the engine can show on two different addresses # On some Toyota models, the engine can show on two different addresses
if ecu_type == Ecu.engine and candidate in [TOYOTA.COROLLA_TSS2, TOYOTA.CHR, TOYOTA.LEXUS_IS, TOYOTA.AVALON] and found_version is None: if ecu_type == Ecu.engine and candidate in [TOYOTA.COROLLA_TSS2, TOYOTA.CHR, TOYOTA.LEXUS_IS, TOYOTA.AVALON] and found_version is None:
continue continue
# ignore non essential ecus # Ignore non essential ecus
if ecu_type not in ESSENTIAL_ECUS and found_version is None: if ecu_type not in ESSENTIAL_ECUS and found_version is None:
continue continue
@ -170,6 +223,21 @@ def match_fw_to_car(fw_versions):
return set(candidates.keys()) - set(invalid) return set(candidates.keys()) - set(invalid)
def match_fw_to_car(fw_versions, allow_fuzzy=True):
fw_versions_dict = build_fw_dict(fw_versions)
matches = match_fw_to_car_exact(fw_versions_dict)
exact_match = True
if allow_fuzzy and len(matches) == 0:
matches = match_fw_to_car_fuzzy(fw_versions_dict)
# Fuzzy match found
if len(matches) == 1:
exact_match = False
return exact_match, matches
def get_fw_versions(logcan, sendcan, bus, extra=None, timeout=0.1, debug=False, progress=False): def get_fw_versions(logcan, sendcan, bus, extra=None, timeout=0.1, debug=False, progress=False):
ecu_types = {} ecu_types = {}
@ -264,7 +332,7 @@ if __name__ == "__main__":
t = time.time() t = time.time()
fw_vers = get_fw_versions(logcan, sendcan, 1, extra=extra, debug=args.debug, progress=True) fw_vers = get_fw_versions(logcan, sendcan, 1, extra=extra, debug=args.debug, progress=True)
candidates = match_fw_to_car(fw_vers) _, candidates = match_fw_to_car(fw_vers)
print() print()
print("Found FW versions") print("Found FW versions")

@ -28,7 +28,8 @@ class TestFwFingerprint(unittest.TestCase):
fw.append({"ecu": ecu_name, "fwVersion": random.choice(fw_versions), fw.append({"ecu": ecu_name, "fwVersion": random.choice(fw_versions),
"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
self.assertFingerprints(match_fw_to_car(CP.carFw), car_model) _, matches = match_fw_to_car(CP.carFw)
self.assertFingerprints(matches, car_model)
def test_no_duplicate_fw_versions(self): def test_no_duplicate_fw_versions(self):
passed = True passed = True

@ -84,9 +84,12 @@ class Controls:
sounds_available = HARDWARE.get_sound_card_online() sounds_available = HARDWARE.get_sound_card_online()
car_recognized = self.CP.carName != 'mock' car_recognized = self.CP.carName != 'mock'
fuzzy_fingerprint = self.CP.fuzzyFingerprint
# If stock camera is disconnected, we loaded car controls and it's not dashcam mode # If stock camera is disconnected, we loaded car controls and it's not dashcam mode
controller_available = self.CP.enableCamera and self.CI.CC is not None and not passive and not self.CP.dashcamOnly controller_available = self.CP.enableCamera and self.CI.CC is not None and not passive and not self.CP.dashcamOnly
community_feature_disallowed = self.CP.communityFeature and not community_feature_toggle community_feature = self.CP.communityFeature or fuzzy_fingerprint
community_feature_disallowed = community_feature and (not community_feature_toggle)
self.read_only = not car_recognized or not controller_available or \ self.read_only = not car_recognized or not controller_available or \
self.CP.dashcamOnly or community_feature_disallowed self.CP.dashcamOnly or community_feature_disallowed
if self.read_only: if self.read_only:
@ -137,7 +140,7 @@ class Controls:
self.sm['driverMonitoringState'].faceDetected = False self.sm['driverMonitoringState'].faceDetected = False
self.sm['liveParameters'].valid = True self.sm['liveParameters'].valid = True
self.startup_event = get_startup_event(car_recognized, controller_available) self.startup_event = get_startup_event(car_recognized, controller_available, fuzzy_fingerprint)
if not sounds_available: if not sounds_available:
self.events.add(EventName.soundsUnavailable, static=True) self.events.add(EventName.soundsUnavailable, static=True)

@ -208,6 +208,13 @@ def wrong_car_mode_alert(CP: car.CarParams, sm: messaging.SubMaster, metric: boo
text = "Main Switch Off" text = "Main Switch Off"
return NoEntryAlert(text, duration_hud_alert=0.) return NoEntryAlert(text, duration_hud_alert=0.)
def startup_fuzzy_fingerprint_alert(CP: car.CarParams, sm: messaging.SubMaster, metric: bool) -> Alert:
return Alert(
"WARNING: No Exact Match on Car Model",
f"Closest Match: {CP.carFingerprint.title()[:40]}",
AlertStatus.userPrompt, AlertSize.mid,
Priority.LOWER, VisualAlert.none, AudibleAlert.none, 0., 0., 15.)
EVENTS: Dict[int, Dict[str, Union[Alert, Callable[[Any, messaging.SubMaster, bool], Alert]]]] = { EVENTS: Dict[int, Dict[str, Union[Alert, Callable[[Any, messaging.SubMaster, bool], Alert]]]] = {
# ********** events with no alerts ********** # ********** events with no alerts **********
@ -253,6 +260,10 @@ EVENTS: Dict[int, Dict[str, Union[Alert, Callable[[Any, messaging.SubMaster, boo
Priority.LOWER, VisualAlert.none, AudibleAlert.none, 0., 0., 15.), Priority.LOWER, VisualAlert.none, AudibleAlert.none, 0., 0., 15.),
}, },
EventName.startupFuzzyFingerprint: {
ET.PERMANENT: startup_fuzzy_fingerprint_alert,
},
EventName.dashcamMode: { EventName.dashcamMode: {
ET.PERMANENT: Alert( ET.PERMANENT: Alert(
"Dashcam Mode", "Dashcam Mode",

@ -9,11 +9,23 @@ from common.params import Params
from selfdrive.boardd.boardd_api_impl import can_list_to_can_capnp # pylint: disable=no-name-in-module,import-error from selfdrive.boardd.boardd_api_impl import can_list_to_can_capnp # pylint: disable=no-name-in-module,import-error
from selfdrive.car.fingerprints import _FINGERPRINTS from selfdrive.car.fingerprints import _FINGERPRINTS
from selfdrive.car.hyundai.values import CAR as HYUNDAI from selfdrive.car.hyundai.values import CAR as HYUNDAI
from selfdrive.car.toyota.values import CAR as TOYOTA
from selfdrive.car.mazda.values import CAR as MAZDA from selfdrive.car.mazda.values import CAR as MAZDA
from selfdrive.controls.lib.events import EVENT_NAME from selfdrive.controls.lib.events import EVENT_NAME
from selfdrive.test.helpers import with_processes from selfdrive.test.helpers import with_processes
EventName = car.CarEvent.EventName EventName = car.CarEvent.EventName
Ecu = car.CarParams.Ecu
COROLLA_TSS2_FW_VERSIONS = [
(Ecu.engine, 0x700, None, b'\x01896630ZG5000\x00\x00\x00\x00'),
(Ecu.eps, 0x7a1, None, b'\x018965B1255000\x00\x00\x00\x00'),
(Ecu.esp, 0x7b0, None, b'\x01F152602280\x00\x00\x00\x00\x00\x00'),
(Ecu.fwdRadar, 0x750, 0xf, b'\x018821F3301100\x00\x00\x00\x00'),
(Ecu.fwdCamera, 0x750, 0x6d, b'\x028646F12010D0\x00\x00\x00\x008646G26011A0\x00\x00\x00\x00'),
]
COROLLA_TSS2_FW_VERSIONS_FUZZY = COROLLA_TSS2_FW_VERSIONS[:-1] + [(Ecu.fwdCamera, 0x750, 0x6d, b'xxxxxx')]
class TestStartup(unittest.TestCase): class TestStartup(unittest.TestCase):
@ -21,23 +33,30 @@ class TestStartup(unittest.TestCase):
# TODO: test EventName.startup for release branches # TODO: test EventName.startup for release branches
# officially supported car # officially supported car
(EventName.startupMaster, HYUNDAI.SONATA, False), (EventName.startupMaster, HYUNDAI.SONATA, False, None),
(EventName.startupMaster, HYUNDAI.SONATA, True), (EventName.startupMaster, HYUNDAI.SONATA, True, None),
# offically supported car, FW query
(EventName.startupMaster, TOYOTA.COROLLA_TSS2, False, COROLLA_TSS2_FW_VERSIONS),
# community supported car # community supported car
(EventName.startupMaster, HYUNDAI.KIA_STINGER, True), (EventName.startupMaster, HYUNDAI.KIA_STINGER, True, None),
(EventName.communityFeatureDisallowed, HYUNDAI.KIA_STINGER, False), (EventName.communityFeatureDisallowed, HYUNDAI.KIA_STINGER, False, None),
# dashcamOnly car # dashcamOnly car
(EventName.startupNoControl, MAZDA.CX5, True), (EventName.startupNoControl, MAZDA.CX5, True, None),
(EventName.startupNoControl, MAZDA.CX5, False), (EventName.startupNoControl, MAZDA.CX5, False, None),
# unrecognized car # unrecognized car
(EventName.startupNoCar, None, True), (EventName.startupNoCar, None, True, None),
(EventName.startupNoCar, None, False), (EventName.startupNoCar, None, False, None),
# fuzzy match
(EventName.startupFuzzyFingerprint, TOYOTA.COROLLA_TSS2, True, COROLLA_TSS2_FW_VERSIONS_FUZZY),
(EventName.communityFeatureDisallowed, TOYOTA.COROLLA_TSS2, False, COROLLA_TSS2_FW_VERSIONS_FUZZY),
]) ])
@with_processes(['controlsd']) @with_processes(['controlsd'])
def test_startup_alert(self, expected_event, car, toggle_enabled): def test_startup_alert(self, expected_event, car_model, toggle_enabled, fw_versions):
# TODO: this should be done without any real sockets # TODO: this should be done without any real sockets
controls_sock = messaging.sub_sock("controlsState") controls_sock = messaging.sub_sock("controlsState")
@ -49,6 +68,24 @@ class TestStartup(unittest.TestCase):
params.put_bool("OpenpilotEnabledToggle", True) params.put_bool("OpenpilotEnabledToggle", True)
params.put_bool("CommunityFeaturesToggle", toggle_enabled) params.put_bool("CommunityFeaturesToggle", toggle_enabled)
# Build capnn version of FW array
if fw_versions is not None:
car_fw = []
cp = car.CarParams.new_message()
for ecu, addr, subaddress, version in fw_versions:
f = car.CarParams.CarFw.new_message()
f.ecu = ecu
f.address = addr
f.fwVersion = version
if subaddress is not None:
f.subAddress = subaddress
car_fw.append(f)
cp.carVin = "1" * 17
cp.carFw = car_fw
params.put("CarParamsCache", cp.to_bytes())
time.sleep(2) # wait for controlsd to be ready time.sleep(2) # wait for controlsd to be ready
msg = messaging.new_message('pandaState') msg = messaging.new_message('pandaState')
@ -56,10 +93,10 @@ class TestStartup(unittest.TestCase):
pm.send('pandaState', msg) pm.send('pandaState', msg)
# fingerprint # fingerprint
if car is None: if (car_model is None) or (fw_versions is not None):
finger = {addr: 1 for addr in range(1, 100)} finger = {addr: 1 for addr in range(1, 100)}
else: else:
finger = _FINGERPRINTS[car][0] finger = _FINGERPRINTS[car_model][0]
for _ in range(500): for _ in range(500):
msgs = [[addr, 0, b'\x00'*length, 0] for addr, length in finger.items()] msgs = [[addr, 0, b'\x00'*length, 0] for addr, length in finger.items()]
@ -70,10 +107,10 @@ class TestStartup(unittest.TestCase):
if len(msgs): if len(msgs):
event_name = msgs[0].controlsState.alertType.split("/")[0] event_name = msgs[0].controlsState.alertType.split("/")[0]
self.assertEqual(EVENT_NAME[expected_event], event_name, self.assertEqual(EVENT_NAME[expected_event], event_name,
f"expected {EVENT_NAME[expected_event]} for '{car}', got {event_name}") f"expected {EVENT_NAME[expected_event]} for '{car_model}', got {event_name}")
break break
else: else:
self.fail(f"failed to fingerprint {car}") self.fail(f"failed to fingerprint {car_model}")
if __name__ == "__main__": if __name__ == "__main__":
unittest.main() unittest.main()

@ -0,0 +1,52 @@
#!/usr/bin/env python3
# type: ignore
import random
from collections import defaultdict
from tqdm import tqdm
from selfdrive.car.fw_versions import match_fw_to_car_fuzzy
from selfdrive.car.toyota.values import FW_VERSIONS as TOYOTA_FW_VERSIONS
from selfdrive.car.honda.values import FW_VERSIONS as HONDA_FW_VERSIONS
from selfdrive.car.hyundai.values import FW_VERSIONS as HYUNDAI_FW_VERSIONS
from selfdrive.car.volkswagen.values import FW_VERSIONS as VW_FW_VERSIONS
FWS = {}
FWS.update(TOYOTA_FW_VERSIONS)
FWS.update(HONDA_FW_VERSIONS)
FWS.update(HYUNDAI_FW_VERSIONS)
FWS.update(VW_FW_VERSIONS)
if __name__ == "__main__":
total = 0
match = 0
wrong_match = 0
confusions = defaultdict(set)
for _ in tqdm(range(1000)):
for candidate, fws in FWS.items():
fw_dict = {}
for (tp, addr, subaddr), fw_list in fws.items():
fw_dict[(addr, subaddr)] = random.choice(fw_list)
matches = match_fw_to_car_fuzzy(fw_dict, log=False, exclude=candidate)
total += 1
if len(matches) == 1:
if list(matches)[0] == candidate:
match += 1
else:
confusions[candidate] |= matches
wrong_match += 1
print()
for candidate, wrong_matches in sorted(confusions.items()):
print(candidate, wrong_matches)
print()
print(f"Total fuzz cases: {total}")
print(f"Correct matches: {match}")
print(f"Wrong matches: {wrong_match}")

@ -7,7 +7,8 @@ import os
import traceback import traceback
from tqdm import tqdm from tqdm import tqdm
from tools.lib.logreader import LogReader from tools.lib.logreader import LogReader
from selfdrive.car.fw_versions import match_fw_to_car from tools.lib.route import Route
from selfdrive.car.fw_versions import match_fw_to_car_exact, match_fw_to_car_fuzzy, build_fw_dict
from selfdrive.car.toyota.values import FW_VERSIONS as TOYOTA_FW_VERSIONS from selfdrive.car.toyota.values import FW_VERSIONS as TOYOTA_FW_VERSIONS
from selfdrive.car.honda.values import FW_VERSIONS as HONDA_FW_VERSIONS from selfdrive.car.honda.values import FW_VERSIONS as HONDA_FW_VERSIONS
from selfdrive.car.hyundai.values import FW_VERSIONS as HYUNDAI_FW_VERSIONS from selfdrive.car.hyundai.values import FW_VERSIONS as HYUNDAI_FW_VERSIONS
@ -18,6 +19,7 @@ from selfdrive.car.honda.values import FINGERPRINTS as HONDA_FINGERPRINTS
from selfdrive.car.hyundai.values import FINGERPRINTS as HYUNDAI_FINGERPRINTS from selfdrive.car.hyundai.values import FINGERPRINTS as HYUNDAI_FINGERPRINTS
from selfdrive.car.volkswagen.values import FINGERPRINTS as VW_FINGERPRINTS from selfdrive.car.volkswagen.values import FINGERPRINTS as VW_FINGERPRINTS
NO_API = "NO_API" in os.environ
SUPPORTED_CARS = list(TOYOTA_FINGERPRINTS.keys()) + list(HONDA_FINGERPRINTS.keys()) + list(HYUNDAI_FINGERPRINTS.keys())+ list(VW_FINGERPRINTS.keys()) SUPPORTED_CARS = list(TOYOTA_FINGERPRINTS.keys()) + list(HONDA_FINGERPRINTS.keys()) + list(HYUNDAI_FINGERPRINTS.keys())+ list(VW_FINGERPRINTS.keys())
if __name__ == "__main__": if __name__ == "__main__":
@ -33,25 +35,37 @@ if __name__ == "__main__":
mismatches = defaultdict(list) mismatches = defaultdict(list)
wrong = 0 not_fingerprinted = 0
good = 0 solved_by_fuzzy = 0
good_exact = 0
wrong_fuzzy = 0
good_fuzzy = 0
dongles = [] dongles = []
for route in tqdm(routes): for route in tqdm(routes):
route = route.rstrip() route = route.rstrip()
dongle_id, time = route.split('|') dongle_id, time = route.split('|')
qlog_path = f"cd:/{dongle_id}/{time}/0/qlog.bz2"
if dongle_id in dongles: if dongle_id in dongles:
continue continue
if NO_API:
qlog_path = f"cd:/{dongle_id}/{time}/0/qlog.bz2"
else:
route = Route(route)
qlog_path = route.qlog_paths()[0]
if qlog_path is None:
continue
try: try:
lr = LogReader(qlog_path) lr = LogReader(qlog_path)
dongles.append(dongle_id)
for msg in lr: for msg in lr:
if msg.which() == "pandaState": if msg.which() == "pandaState":
if msg.pandaState.pandaType not in ['uno', 'blackPanda']: if msg.pandaState.pandaType not in ['uno', 'blackPanda', 'dos']:
dongles.append(dongle_id)
break break
elif msg.which() == "carParams": elif msg.which() == "carParams":
@ -61,7 +75,6 @@ if __name__ == "__main__":
if len(car_fw) == 0: if len(car_fw) == 0:
break break
dongles.append(dongle_id)
live_fingerprint = msg.carParams.carFingerprint live_fingerprint = msg.carParams.carFingerprint
if args.car is not None: if args.car is not None:
@ -70,15 +83,28 @@ if __name__ == "__main__":
if live_fingerprint not in SUPPORTED_CARS: if live_fingerprint not in SUPPORTED_CARS:
break break
candidates = match_fw_to_car(car_fw) fw_versions_dict = build_fw_dict(car_fw)
if (len(candidates) == 1) and (list(candidates)[0] == live_fingerprint): exact_matches = match_fw_to_car_exact(fw_versions_dict)
good += 1 fuzzy_matches = match_fw_to_car_fuzzy(fw_versions_dict)
print("Correct", live_fingerprint, dongle_id)
if (len(exact_matches) == 1) and (list(exact_matches)[0] == live_fingerprint):
good_exact += 1
print(f"Correct! Live: {live_fingerprint} - Fuzzy: {fuzzy_matches}")
# Check if fuzzy match was correct
if len(fuzzy_matches) == 1:
if list(fuzzy_matches)[0] != live_fingerprint:
wrong_fuzzy += 1
print(f"{dongle_id}|{time}")
print("Fuzzy match wrong! Fuzzy:", fuzzy_matches, "Live:", live_fingerprint)
else:
good_fuzzy += 1
break break
print(f"{dongle_id}|{time}") print(f"{dongle_id}|{time}")
print("Old style:", live_fingerprint, "Vin", msg.carParams.carVin) print("Old style:", live_fingerprint, "Vin", msg.carParams.carVin)
print("New style:", candidates) print("New style (exact):", exact_matches)
print("New style (fuzzy):", fuzzy_matches)
for version in car_fw: for version in car_fw:
subaddr = None if version.subAddress == 0 else hex(version.subAddress) subaddr = None if version.subAddress == 0 else hex(version.subAddress)
@ -114,19 +140,24 @@ if __name__ == "__main__":
mismatches[live_fingerprint].append(mismatch) mismatches[live_fingerprint].append(mismatch)
print() print()
wrong += 1 not_fingerprinted += 1
if len(fuzzy_matches) == 1:
if list(fuzzy_matches)[0] == live_fingerprint:
solved_by_fuzzy += 1
else:
wrong_fuzzy += 1
print("Fuzzy match wrong! Fuzzy:", fuzzy_matches, "Live:", live_fingerprint)
break break
except Exception: except Exception:
traceback.print_exc() traceback.print_exc()
except KeyboardInterrupt: except KeyboardInterrupt:
break break
print(f"Fingerprinted: {good} - Not fingerprinted: {wrong}")
print(f"Number of dongle ids checked: {len(dongles)}")
print() print()
# Print FW versions that need to be added seperated out by car and address # Print FW versions that need to be added seperated out by car and address
for car, m in mismatches.items(): for car, m in sorted(mismatches.items()):
print(car) print(car)
addrs = defaultdict(list) addrs = defaultdict(list)
for (addr, sub_addr, version) in m: for (addr, sub_addr, version) in m:
@ -138,3 +169,15 @@ if __name__ == "__main__":
print(f" {v},") print(f" {v},")
print(" ]") print(" ]")
print() print()
print()
print(f"Number of dongle ids checked: {len(dongles)}")
print(f"Fingerprinted: {good_exact}")
print(f"Not fingerprinted: {not_fingerprinted}")
print(f" of which had a fuzzy match: {solved_by_fuzzy}")
print()
print(f"Correct fuzzy matches: {good_fuzzy}")
print(f"Wrong fuzzy matches: {wrong_fuzzy}")
print()

Loading…
Cancel
Save