130 lines
5.2 KiB
Python
130 lines
5.2 KiB
Python
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<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]| )')
|
|
|
|
|
|
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)
|