import random import re from types import SimpleNamespace import pytest from opendbc.car import structs from opendbc.car.volkswagen.carstate import CarState from opendbc.car.structs import CarParams from opendbc.car.volkswagen.values import CAR, FW_QUERY_CONFIG, WMI from opendbc.car.volkswagen.fingerprints import FW_VERSIONS Ecu = 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]| )') class TestVolkswagenPlatformConfigs: def test_spare_part_fw_pattern(self, subtests): # Relied on for determining if a FW is likely VW for platform, ecus in FW_VERSIONS.items(): with subtests.test(platform=platform.value): for fws in ecus.values(): for fw in fws: assert SPARE_PART_FW_PATTERN.match(fw) is not None, f"Bad FW: {fw}" def test_chassis_codes(self, subtests): for platform in CAR: with subtests.test(platform=platform.value): assert len(platform.config.wmis) > 0, "WMIs not set" assert len(platform.config.chassis_codes) > 0, "Chassis codes not set" assert 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 shared_chassis_codes = platform.config.chassis_codes & comp.config.chassis_codes if len(shared_chassis_codes) == 0: continue # A shared chassis code is unambiguous when the VIN WMI separates the candidates. if platform.config.wmis.isdisjoint(comp.config.wmis): continue platform_model_years = getattr(platform.config, "model_years", set()) comp_model_years = getattr(comp.config, "model_years", set()) if platform_model_years and comp_model_years and platform_model_years.isdisjoint(comp_model_years): continue assert set() == shared_chassis_codes, f"Shared chassis codes: {comp}" def test_custom_fuzzy_fingerprinting(self, subtests): all_radar_fw = list({fw for ecus in FW_VERSIONS.values() for fw in ecus.get((Ecu.fwdRadar, 0x757, None), [])}) for platform in CAR: with subtests.test(platform=platform.name): # Fuzzy matching keys off the radar ECU (CHECK_FUZZY_ECUS), so a platform that # declares no radar ECU can never be VIN-matched and should never be expected to match. platform_has_radar = (Ecu.fwdRadar, 0x757, None) in FW_VERSIONS.get(platform, {}) for wmi in WMI: for chassis_code in platform.config.chassis_codes | {"00"}: platform_model_years = getattr(platform.config, "model_years", set()) model_years = platform_model_years if platform_model_years else {"0"} for model_year in model_years | {"0"}: vin = ["0"] * 17 vin[0:3] = wmi vin[6:8] = chassis_code vin[9] = model_year vin = "".join(vin) # Check a few FW cases - expected, unexpected for radar_fw in random.sample(all_radar_fw, 5) + [b'\xf1\x875Q0907572G \xf1\x890571', b'\xf1\x877H9907572AA\xf1\x890396']: model_year_match = len(platform_model_years) == 0 or model_year in platform_model_years should_match = ((wmi in platform.config.wmis and chassis_code in platform.config.chassis_codes and model_year_match) and radar_fw in all_radar_fw and platform_has_radar) 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() assert expected_matches == matches, "Bad match" @pytest.mark.parametrize("button_type", ( structs.CarState.ButtonEvent.Type.setCruise, structs.CarState.ButtonEvent.Type.resumeCruise, )) def test_button_enable_is_blocked_while_cruise_fault_lateral_mode_is_still_faulted(button_type): state = object.__new__(CarState) state.CP = SimpleNamespace(pcmCruise=False) state.cruise_fault_lateral_active = True state.cruise_faulted = True button_events = [structs.CarState.ButtonEvent(pressed=False, type=button_type)] assert not state.update_button_enable(button_events) def test_button_enable_recovers_once_cruise_fault_clears(): state = object.__new__(CarState) state.CP = SimpleNamespace(pcmCruise=False) state.cruise_fault_lateral_active = True state.cruise_faulted = False button_events = [structs.CarState.ButtonEvent( pressed=False, type=structs.CarState.ButtonEvent.Type.setCruise, )] assert state.update_button_enable(button_events) def test_pq_hca_ready_does_not_complete_eps_initialization(): state = object.__new__(CarState) state.eps_init_complete = False state.frame = 0 assert state.update_hca_state("READY", ready_confirms_init=False) == (True, False) state.frame = 317 assert state.update_hca_state("FAULT", ready_confirms_init=False) == (True, False) state.frame = 1001 assert state.update_hca_state("FAULT", ready_confirms_init=False) == (False, True)