diff --git a/selfdrive/car/hyundai/values.py b/selfdrive/car/hyundai/values.py index 0db9850b76..491fb58202 100644 --- a/selfdrive/car/hyundai/values.py +++ b/selfdrive/car/hyundai/values.py @@ -359,16 +359,62 @@ def parse_platform_code(fw_version: bytes) -> Tuple[Optional[bytes], Optional[by def hyundai_fuzzy(fw_versions_dict) -> Set[str]: - all_platform_codes = defaultdict(set) + """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.""" + + # Build lookup table from (addr, sub_addr, fw) to list of candidate cars + all_fw_versions = defaultdict(list) + # Platform codes are brand-specific unique identifiers for each platform, less specific than a FW version + all_platform_codes = 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] not in FW_QUERY_CONFIG.platform_code_ecus: - continue + # 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. + if addr[0] not in FUZZY_EXCLUDE_ECUS: + for f in fws: + all_fw_versions[(addr[1], addr[2], f)].append(candidate) + + # Add platform codes to lookup dict if config specifies a function + if addr[0] in config.platform_code_ecus and config.fuzzy_get_platform_codes is not None: + for platform_code in config.fuzzy_get_platform_codes(fws): + all_platform_codes[(addr[1], addr[2], platform_code)].append(candidate) + + matched_ecus = set() + candidate = None + for addr, versions in fw_versions_dict.items(): + ecu_key = (addr[0], addr[1]) + for version in versions: + # All cars that have this FW response on the specified address + candidates = all_fw_versions[(*ecu_key, version)] + + # If not one candidate for this ECU and version, try platform codes + dates + if len(candidates) != 1 and config.fuzzy_get_platform_codes is not None: + # Returns one or none, all cars that have this platform code + for platform_code in config.fuzzy_get_platform_codes([version]): + candidates = all_platform_codes[(*ecu_key, platform_code)] + + if len(candidates) == 1: + matched_ecus.add(ecu_key) + if candidate is None: + candidate = candidates[0] + # We uniquely matched two different cars. No fuzzy match possible + elif candidate != candidates[0]: + return set() - for fw in fws: - code, date = parse_platform_code(fw) - if code is not None: - all_platform_codes[(addr[1], addr[2], code)].add(candidate) + # Note that it is possible to match to a candidate without all its ECUs being present + # if there are enough matches. FIXME: parameterize this or require all ECUs to exist like exact matching + if len(matched_ecus) >= 2: + if log: + cloudlog.error(f"Fingerprinted {candidate} using fuzzy match. {len(matched_ecus)} matching ECUs") + return {candidate} + else: + return set()