IQ.Pilot Prebuilt Release @ 27f668a
This commit is contained in:
@@ -0,0 +1,21 @@
|
||||
#!/usr/bin/env python3
|
||||
from iqdbc.car.structs import CarParams
|
||||
from iqdbc.car.hyundai.values import PLATFORM_CODE_ECUS, get_platform_codes
|
||||
from iqdbc.car.hyundai.fingerprints import FW_VERSIONS
|
||||
|
||||
Ecu = CarParams.Ecu
|
||||
|
||||
if __name__ == "__main__":
|
||||
for car_model, ecus in FW_VERSIONS.items():
|
||||
print()
|
||||
print(car_model)
|
||||
for ecu in sorted(ecus):
|
||||
if ecu[0] not in PLATFORM_CODE_ECUS:
|
||||
continue
|
||||
|
||||
platform_codes = get_platform_codes(ecus[ecu])
|
||||
codes = {code for code, _ in platform_codes}
|
||||
dates = {date for _, date in platform_codes if date is not None}
|
||||
print(f' (Ecu.{ecu[0]}, {hex(ecu[1])}, {ecu[2]}):')
|
||||
print(f' Codes: {codes}')
|
||||
print(f' Dates: {dates}')
|
||||
@@ -0,0 +1,194 @@
|
||||
import math
|
||||
|
||||
import pytest
|
||||
|
||||
from iqdbc.can import CANPacker, CANParser
|
||||
from iqdbc.car import Bus, gen_empty_fingerprint, structs
|
||||
from iqdbc.car.hyundai.carstate import CarState, EV_MODE_STATUS_TIMEOUT_NS, _get_ev_mode_state
|
||||
from iqdbc.car.hyundai.interface import CarInterface
|
||||
from iqdbc.car.hyundai.values import CANFD_HYBRID_STATUS_ADDR, CANFD_HYBRID_STATUS_DLC, CAR, DBC, EV_MODE_ACTIVE_VALUES, \
|
||||
EV_MODE_STATUS_ADDR, EV_MODE_STATUS_DLC, EV_MODE_STATUS_MSG, EV_MODE_STATUS_SIGNAL, \
|
||||
HyundaiExtFlags, HyundaiFlags
|
||||
from iqpilot.common.params import Params
|
||||
|
||||
|
||||
def get_params(candidate, *, hybrid=True, hybrid_bus=0, hybrid_status=None, hybrid_status_bus=0,
|
||||
hybrid_status_dlc=CANFD_HYBRID_STATUS_DLC, status_bus=0, status_dlc=EV_MODE_STATUS_DLC):
|
||||
Params().put_int("HyundaiCameraSCC", 1)
|
||||
Params().put_int("CanfdHDA2", 1)
|
||||
|
||||
fingerprint = gen_empty_fingerprint()
|
||||
if hybrid:
|
||||
fingerprint[hybrid_bus][0x105] = 32
|
||||
if hybrid_status is None:
|
||||
hybrid_status = hybrid
|
||||
if hybrid_status:
|
||||
fingerprint[hybrid_status_bus][CANFD_HYBRID_STATUS_ADDR] = hybrid_status_dlc
|
||||
if status_bus is not None:
|
||||
fingerprint[status_bus][EV_MODE_STATUS_ADDR] = status_dlc
|
||||
return CarInterface.get_params(candidate, fingerprint, [], False, False, False)
|
||||
|
||||
|
||||
@pytest.mark.parametrize(("candidate", "hybrid", "status_bus", "status_dlc", "expected"), (
|
||||
(CAR.HYUNDAI_SANTAFE_MX5_HEV, True, 0, 32, True),
|
||||
(CAR.HYUNDAI_SANTAFE_MX5_HEV, False, 0, 32, False),
|
||||
(CAR.HYUNDAI_SANTAFE_MX5_HEV, True, None, 32, False),
|
||||
(CAR.HYUNDAI_SANTAFE_MX5_HEV, True, 1, 32, False),
|
||||
(CAR.HYUNDAI_SANTAFE_MX5_HEV, True, 0, 16, False),
|
||||
(CAR.KIA_SORENTO_4TH_GEN, False, None, 32, False),
|
||||
# Shared ICE/HEV/PHEV candidates rely on the observed ECAN capability frames, not their model name.
|
||||
(CAR.HYUNDAI_TUCSON_4TH_GEN, True, 0, 32, True),
|
||||
(CAR.HYUNDAI_TUCSON_4TH_GEN, False, 0, 32, False),
|
||||
(CAR.KIA_SORENTO_HEV_4TH_GEN, True, 0, 32, True),
|
||||
(CAR.HYUNDAI_KONA_HEV_2ND_GEN, True, 0, 32, True),
|
||||
(CAR.HYUNDAI_KONA_HEV_2ND_GEN, False, 0, 32, False),
|
||||
(CAR.HYUNDAI_ELANTRA_HEV_2021, True, 0, 32, False),
|
||||
))
|
||||
def test_ev_mode_capability(candidate, hybrid, status_bus, status_dlc, expected):
|
||||
CP = get_params(candidate, hybrid=hybrid, status_bus=status_bus, status_dlc=status_dlc)
|
||||
assert bool(CP.extFlags & HyundaiExtFlags.EV_MODE_STATUS_230) == expected
|
||||
|
||||
|
||||
@pytest.mark.parametrize(("hybrid_status_bus", "hybrid_status_dlc"), (
|
||||
(1, CANFD_HYBRID_STATUS_DLC),
|
||||
(0, 16),
|
||||
))
|
||||
def test_ev_mode_capability_requires_ecan_hybrid_status_dlc32(hybrid_status_bus, hybrid_status_dlc):
|
||||
CP = get_params(CAR.HYUNDAI_SANTAFE_MX5_HEV, hybrid_status_bus=hybrid_status_bus,
|
||||
hybrid_status_dlc=hybrid_status_dlc)
|
||||
|
||||
assert not bool(CP.extFlags & HyundaiExtFlags.EV_MODE_STATUS_230)
|
||||
|
||||
|
||||
def test_0x105_and_ev_status_without_0xfa_do_not_enable_ev_mode():
|
||||
CP = get_params(CAR.HYUNDAI_TUCSON_4TH_GEN, hybrid=True, hybrid_status=False)
|
||||
|
||||
assert bool(CP.flags & HyundaiFlags.HYBRID)
|
||||
assert not bool(CP.extFlags & HyundaiExtFlags.EV_MODE_STATUS_230)
|
||||
|
||||
|
||||
def test_ev_mode_display_capability_does_not_change_hybrid_safety_classification():
|
||||
CP = get_params(CAR.HYUNDAI_TUCSON_4TH_GEN, hybrid=False, hybrid_status=True)
|
||||
|
||||
assert not bool(CP.flags & HyundaiFlags.HYBRID)
|
||||
assert bool(CP.extFlags & HyundaiExtFlags.EV_MODE_STATUS_230)
|
||||
|
||||
|
||||
def test_ev_mode_capability_uses_the_detected_ecan_offset():
|
||||
CP = get_params(CAR.KIA_SORENTO_HEV_4TH_GEN, hybrid_bus=4, hybrid_status_bus=4, status_bus=4)
|
||||
|
||||
assert bool(CP.extFlags & HyundaiExtFlags.EV_MODE_STATUS_230)
|
||||
|
||||
|
||||
def test_ev_mode_parser_registration_is_capability_gated():
|
||||
supported = get_params(CAR.HYUNDAI_SANTAFE_MX5_HEV)
|
||||
unsupported = get_params(CAR.KIA_SORENTO_4TH_GEN, hybrid=False, status_bus=1, status_dlc=16)
|
||||
|
||||
supported_parser = CarState.get_can_parsers_canfd(None, supported)[Bus.pt]
|
||||
unsupported_parser = CarState.get_can_parsers_canfd(None, unsupported)[Bus.pt]
|
||||
|
||||
assert EV_MODE_STATUS_ADDR in supported_parser.addresses
|
||||
assert supported_parser.message_states[EV_MODE_STATUS_ADDR].ignore_alive
|
||||
assert supported_parser.message_states[EV_MODE_STATUS_ADDR].ignore_counter
|
||||
assert EV_MODE_STATUS_ADDR not in unsupported_parser.addresses
|
||||
|
||||
|
||||
def test_sorento_ice_corner_radar_status_does_not_enable_ev_mode():
|
||||
Params().put_int("HyundaiCameraSCC", 1)
|
||||
Params().put_int("CanfdHDA2", 1)
|
||||
|
||||
fingerprint = gen_empty_fingerprint()
|
||||
fingerprint[1][0x230] = 16 # Actual Sorento ICE ACAN corner-radar status frame.
|
||||
CP = CarInterface.get_params(CAR.KIA_SORENTO_4TH_GEN, fingerprint, [], False, False, False)
|
||||
parser = CarState.get_can_parsers_canfd(None, CP)[Bus.pt]
|
||||
|
||||
assert not bool(CP.extFlags & HyundaiExtFlags.EV_MODE_STATUS_230)
|
||||
assert EV_MODE_STATUS_ADDR not in parser.addresses
|
||||
|
||||
|
||||
def test_ev_mode_state_requires_a_fresh_dlc32_frame():
|
||||
CP = get_params(CAR.HYUNDAI_SANTAFE_MX5_HEV)
|
||||
parser = CarState.get_can_parsers_canfd(None, CP)[Bus.pt]
|
||||
packer = CANPacker(DBC[CP.carFingerprint][Bus.pt])
|
||||
|
||||
assert _get_ev_mode_state(parser) == (False, False)
|
||||
|
||||
timestamp = 1_000_000_000
|
||||
wrong_bus_short_status = (EV_MODE_STATUS_ADDR, b"\x00" * 16, 1)
|
||||
parser.update([timestamp, [wrong_bus_short_status]])
|
||||
assert _get_ev_mode_state(parser) == (False, False)
|
||||
|
||||
ev_active = packer.make_can_msg(EV_MODE_STATUS_MSG, parser.bus, {"COUNTER": 1, EV_MODE_STATUS_SIGNAL: 6})
|
||||
parser.update([timestamp + 100_000_000, [ev_active]])
|
||||
assert _get_ev_mode_state(parser) == (True, True)
|
||||
|
||||
ev_inactive = packer.make_can_msg(EV_MODE_STATUS_MSG, parser.bus, {"COUNTER": 2, EV_MODE_STATUS_SIGNAL: 3})
|
||||
parser.update([timestamp + 200_000_000, [ev_inactive]])
|
||||
assert _get_ev_mode_state(parser) == (False, True)
|
||||
|
||||
parser.dat[EV_MODE_STATUS_ADDR] = b"\x00" * 16
|
||||
assert _get_ev_mode_state(parser) == (False, False)
|
||||
|
||||
parser.dat[EV_MODE_STATUS_ADDR] = ev_inactive[1]
|
||||
parser.update([timestamp + 200_000_000 + EV_MODE_STATUS_TIMEOUT_NS + 1, []])
|
||||
assert not parser.bus_timeout
|
||||
assert _get_ev_mode_state(parser) == (False, False)
|
||||
|
||||
|
||||
@pytest.mark.parametrize("mode", range(16))
|
||||
def test_ev_mode_enum_mapping(mode):
|
||||
CP = get_params(CAR.HYUNDAI_SANTAFE_MX5_HEV)
|
||||
parser = CarState.get_can_parsers_canfd(None, CP)[Bus.pt]
|
||||
packer = CANPacker(DBC[CP.carFingerprint][Bus.pt])
|
||||
msg = packer.make_can_msg(EV_MODE_STATUS_MSG, parser.bus, {"COUNTER": mode, EV_MODE_STATUS_SIGNAL: mode})
|
||||
|
||||
parser.update([1_000_000_000, [msg]])
|
||||
|
||||
assert int(parser.vl[EV_MODE_STATUS_MSG][EV_MODE_STATUS_SIGNAL]) == mode
|
||||
assert _get_ev_mode_state(parser) == (mode in EV_MODE_ACTIVE_VALUES, True)
|
||||
|
||||
|
||||
@pytest.mark.parametrize(("payload", "expected_mode", "expected_active"), (
|
||||
("758139048402000000000000000000000000d3009805001c24000000c05dc80f", 1, True),
|
||||
("4d466f048402000000000000000000000000bf007408001c24000000c05dc80f", 2, True),
|
||||
("32206e048402000000000000000000000000c9007418001c24000000c05dc80f", 6, True),
|
||||
# Route 455 mode 3 was a false positive with the old single-bit 0x230 interpretation.
|
||||
("4f935e0484020000000000000000000000004400640d001c10000000c05dc40f", 3, False),
|
||||
("066558048402000000000000000000000000c4003020001c24000000c05dc80f", 8, False),
|
||||
("059683048402000000000000000000000000a1008425001c24000000c05dc80f", 9, False),
|
||||
("011305048402000000000000000000000000d600a829001c24000000c05dc80f", 10, False),
|
||||
))
|
||||
def test_ev_mode_dbc_decodes_real_mx5_frames(payload, expected_mode, expected_active):
|
||||
parser = CANParser("hyundai_canfd_generated", [(EV_MODE_STATUS_MSG, math.nan)], 0)
|
||||
updated = parser.update([1_000_000_000, [(EV_MODE_STATUS_ADDR, bytes.fromhex(payload), 0)]])
|
||||
|
||||
assert updated == {EV_MODE_STATUS_ADDR}
|
||||
assert int(parser.vl[EV_MODE_STATUS_MSG][EV_MODE_STATUS_SIGNAL]) == expected_mode
|
||||
assert _get_ev_mode_state(parser) == (expected_active, True)
|
||||
|
||||
|
||||
def test_ev_mode_rejects_checksum_corruption():
|
||||
CP = get_params(CAR.HYUNDAI_SANTAFE_MX5_HEV)
|
||||
parser = CarState.get_can_parsers_canfd(None, CP)[Bus.pt]
|
||||
payload = bytearray.fromhex("32206e048402000000000000000000000000c9007418001c24000000c05dc80f")
|
||||
payload[-1] ^= 1
|
||||
|
||||
parser.update([1_000_000_000, [(EV_MODE_STATUS_ADDR, bytes(payload), parser.bus)]])
|
||||
|
||||
assert EV_MODE_STATUS_ADDR not in parser.dat
|
||||
assert _get_ev_mode_state(parser) == (False, False)
|
||||
|
||||
|
||||
def test_ev_mode_fields_default_invalid():
|
||||
state = structs.CarState()
|
||||
assert not state.evModeActive
|
||||
assert not state.evModeValid
|
||||
|
||||
|
||||
def test_ev_mode_parser_is_optional_for_can_validity():
|
||||
CP = get_params(CAR.HYUNDAI_SANTAFE_MX5_HEV)
|
||||
parser = CarState.get_can_parsers_canfd(None, CP)[Bus.pt]
|
||||
state = parser.message_states[EV_MODE_STATUS_ADDR]
|
||||
|
||||
assert state.ignore_alive
|
||||
assert state.ignore_counter
|
||||
@@ -0,0 +1,225 @@
|
||||
from hypothesis import settings, given, strategies as st
|
||||
|
||||
from iqdbc.car import gen_empty_fingerprint
|
||||
from iqdbc.car.structs import CarParams
|
||||
from iqdbc.car.fw_versions import build_fw_dict
|
||||
from iqdbc.car.hyundai.interface import CarInterface
|
||||
from iqdbc.car.hyundai.radar_interface import RADAR_START_ADDR
|
||||
from iqdbc.car.hyundai.values import CANFD_CAR, CAN_GEARS, CAR, CHECKSUM, DATE_FW_ECUS, \
|
||||
HYBRID_CAR, EV_CAR, FW_QUERY_CONFIG, LEGACY_SAFETY_MODE_CAR, \
|
||||
PLATFORM_CODE_ECUS, HYUNDAI_VERSION_REQUEST_LONG, \
|
||||
HyundaiFlags, get_platform_codes, HyundaiSafetyFlags
|
||||
from iqdbc.car.hyundai.fingerprints import FW_VERSIONS
|
||||
|
||||
Ecu = CarParams.Ecu
|
||||
|
||||
# Some platforms have date codes in a different format we don't yet parse (or are missing).
|
||||
# For now, assert list of expected missing date cars
|
||||
NO_DATES_PLATFORMS = {
|
||||
# CAN FD
|
||||
CAR.KIA_SPORTAGE_5TH_GEN,
|
||||
CAR.HYUNDAI_SANTA_CRUZ_1ST_GEN,
|
||||
CAR.HYUNDAI_TUCSON_4TH_GEN,
|
||||
# CAN
|
||||
CAR.HYUNDAI_ELANTRA,
|
||||
CAR.HYUNDAI_ELANTRA_GT_I30,
|
||||
CAR.KIA_CEED,
|
||||
CAR.KIA_FORTE,
|
||||
CAR.KIA_OPTIMA_G4,
|
||||
CAR.KIA_OPTIMA_G4_FL,
|
||||
CAR.KIA_SORENTO,
|
||||
CAR.HYUNDAI_KONA,
|
||||
CAR.HYUNDAI_KONA_EV,
|
||||
CAR.HYUNDAI_KONA_EV_2022,
|
||||
CAR.HYUNDAI_KONA_HEV,
|
||||
CAR.HYUNDAI_SONATA_LF,
|
||||
CAR.HYUNDAI_VELOSTER,
|
||||
CAR.HYUNDAI_KONA_2022,
|
||||
}
|
||||
|
||||
CANFD_EXPECTED_ECUS = {Ecu.fwdCamera, Ecu.fwdRadar}
|
||||
|
||||
|
||||
class TestHyundaiFingerprint:
|
||||
def test_feature_detection(self, monkeypatch, tmp_path):
|
||||
monkeypatch.setenv("PARAMS_ROOT", str(tmp_path))
|
||||
# radar available
|
||||
for radar in (True, False):
|
||||
fingerprint = gen_empty_fingerprint()
|
||||
if radar:
|
||||
fingerprint[1][RADAR_START_ADDR] = 8
|
||||
CP = CarInterface.get_params(CAR.HYUNDAI_SONATA, fingerprint, [], False, False, False)
|
||||
assert CP.radarUnavailable != radar
|
||||
|
||||
def test_alternate_limits(self):
|
||||
# Alternate lateral control limits, for high torque cars, verify Panda safety mode flag is set
|
||||
fingerprint = gen_empty_fingerprint()
|
||||
for car_model in CAR:
|
||||
CP = CarInterface.get_params(car_model, fingerprint, [], False, False, False)
|
||||
assert bool(CP.flags & HyundaiFlags.ALT_LIMITS) == bool(CP.safetyConfigs[-1].safetyParam & HyundaiSafetyFlags.ALT_LIMITS)
|
||||
|
||||
def test_can_features(self):
|
||||
# Test no EV/HEV in any gear lists (should all use ELECT_GEAR)
|
||||
assert set.union(*CAN_GEARS.values()) & (HYBRID_CAR | EV_CAR) == set()
|
||||
|
||||
# Test CAN FD cars are not classified with classic-CAN-only parsing or safety modes.
|
||||
can_specific_feature_list = set.union(*CAN_GEARS.values(), *CHECKSUM.values(), LEGACY_SAFETY_MODE_CAR)
|
||||
for car_model in CANFD_CAR:
|
||||
assert car_model not in can_specific_feature_list, "CAN FD car unexpectedly found in a CAN feature list"
|
||||
|
||||
def test_hybrid_ev_sets(self):
|
||||
assert HYBRID_CAR & EV_CAR == set(), "Shared cars between hybrid and EV"
|
||||
assert HYBRID_CAR <= set(CAR)
|
||||
assert EV_CAR <= set(CAR)
|
||||
|
||||
def test_canfd_ecu_whitelist(self):
|
||||
# Asserts only expected Ecus can exist in database for CAN-FD cars
|
||||
for car_model in CANFD_CAR:
|
||||
ecus = {fw[0] for fw in FW_VERSIONS.get(car_model, {}).keys()}
|
||||
ecus_not_in_whitelist = ecus - CANFD_EXPECTED_ECUS
|
||||
ecu_strings = ", ".join([f"Ecu.{ecu}" for ecu in ecus_not_in_whitelist])
|
||||
assert len(ecus_not_in_whitelist) == 0, \
|
||||
f"{car_model}: Car model has unexpected ECUs: {ecu_strings}"
|
||||
|
||||
def test_blacklisted_parts(self, subtests):
|
||||
# Asserts no ECUs known to be shared across platforms exist in the database.
|
||||
# Tucson having Santa Cruz camera and EPS for example
|
||||
for car_model, ecus in FW_VERSIONS.items():
|
||||
if car_model == CAR.HYUNDAI_SANTA_CRUZ_1ST_GEN:
|
||||
continue
|
||||
with subtests.test(car_model=car_model.value):
|
||||
for code, _ in get_platform_codes(ecus[(Ecu.fwdCamera, 0x7c4, None)]):
|
||||
if b"-" not in code:
|
||||
continue
|
||||
part = code.split(b"-")[1]
|
||||
assert not part.startswith(b'CW'), "Car has bad part number"
|
||||
|
||||
def test_correct_ecu_response_database(self, subtests):
|
||||
"""
|
||||
Assert standard responses for certain ECUs, since they can
|
||||
respond to multiple queries with different data
|
||||
"""
|
||||
expected_fw_prefix = HYUNDAI_VERSION_REQUEST_LONG[1:]
|
||||
for car_model, ecus in FW_VERSIONS.items():
|
||||
with subtests.test(car_model=car_model.value):
|
||||
for ecu, fws in ecus.items():
|
||||
assert all(fw.startswith(expected_fw_prefix) for fw in fws), \
|
||||
f"FW from unexpected request in database: {(ecu, fws)}"
|
||||
|
||||
@settings(max_examples=100)
|
||||
@given(data=st.data())
|
||||
def test_platform_codes_fuzzy_fw(self, data):
|
||||
"""Ensure function doesn't raise an exception"""
|
||||
fw_strategy = st.lists(st.binary())
|
||||
fws = data.draw(fw_strategy)
|
||||
get_platform_codes(fws)
|
||||
|
||||
def test_expected_platform_codes(self, subtests):
|
||||
# Ensures we don't accidentally add multiple platform codes for a car unless it is intentional
|
||||
for car_model, ecus in FW_VERSIONS.items():
|
||||
with subtests.test(car_model=car_model.value):
|
||||
for ecu, fws in ecus.items():
|
||||
if ecu[0] not in PLATFORM_CODE_ECUS:
|
||||
continue
|
||||
|
||||
# Third and fourth character are usually EV/hybrid identifiers
|
||||
codes = {code.split(b"-")[0][:2] for code, _ in get_platform_codes(fws)}
|
||||
if car_model == CAR.HYUNDAI_PALISADE:
|
||||
assert codes == {b"LX", b"ON"}, f"Car has unexpected platform codes: {car_model} {codes}"
|
||||
elif car_model == CAR.HYUNDAI_KONA_EV and ecu[0] == Ecu.fwdCamera:
|
||||
assert codes == {b"OE", b"OS"}, f"Car has unexpected platform codes: {car_model} {codes}"
|
||||
else:
|
||||
assert len(codes) == 1, f"Car has multiple platform codes: {car_model} {codes}"
|
||||
|
||||
# Tests for platform codes, part numbers, and FW dates which Hyundai will use to fuzzy
|
||||
# fingerprint in the absence of full FW matches:
|
||||
def test_platform_code_ecus_available(self, subtests):
|
||||
# TODO: add queries for these non-CAN FD cars to get EPS
|
||||
no_eps_platforms = CANFD_CAR | {CAR.KIA_SORENTO, CAR.KIA_OPTIMA_G4, CAR.KIA_OPTIMA_G4_FL, CAR.KIA_OPTIMA_H,
|
||||
CAR.KIA_OPTIMA_H_G4_FL, CAR.HYUNDAI_SONATA_LF, CAR.HYUNDAI_TUCSON, CAR.GENESIS_G90, CAR.GENESIS_G80, CAR.HYUNDAI_ELANTRA}
|
||||
|
||||
# Asserts ECU keys essential for fuzzy fingerprinting are available on all platforms
|
||||
for car_model, ecus in FW_VERSIONS.items():
|
||||
with subtests.test(car_model=car_model.value):
|
||||
for platform_code_ecu in PLATFORM_CODE_ECUS:
|
||||
if platform_code_ecu in (Ecu.fwdRadar, Ecu.eps) and car_model == CAR.HYUNDAI_GENESIS:
|
||||
continue
|
||||
if platform_code_ecu == Ecu.eps and car_model in no_eps_platforms:
|
||||
continue
|
||||
assert platform_code_ecu in [e[0] for e in ecus]
|
||||
|
||||
def test_fw_format(self, subtests):
|
||||
# Asserts:
|
||||
# - every supported ECU FW version returns one platform code
|
||||
# - every supported ECU FW version has a part number
|
||||
# - expected parsing of ECU FW dates
|
||||
|
||||
for car_model, ecus in FW_VERSIONS.items():
|
||||
with subtests.test(car_model=car_model.value):
|
||||
for ecu, fws in ecus.items():
|
||||
if ecu[0] not in PLATFORM_CODE_ECUS:
|
||||
continue
|
||||
|
||||
codes = set()
|
||||
for fw in fws:
|
||||
result = get_platform_codes([fw])
|
||||
assert 1 == len(result), f"Unable to parse FW: {fw}"
|
||||
codes |= result
|
||||
|
||||
if ecu[0] not in DATE_FW_ECUS or car_model in NO_DATES_PLATFORMS:
|
||||
assert all(date is None for _, date in codes)
|
||||
else:
|
||||
assert all(date is not None for _, date in codes)
|
||||
|
||||
if car_model != CAR.HYUNDAI_GENESIS:
|
||||
assert all(b"-" in code for code, _ in codes), \
|
||||
f"FW does not have part number: {fw}"
|
||||
|
||||
def test_platform_codes_spot_check(self):
|
||||
# Asserts basic platform code parsing behavior for a few cases
|
||||
results = get_platform_codes([b"\xf1\x00DH LKAS 1.1 -150210"])
|
||||
assert results == {(b"DH", b"150210")}
|
||||
|
||||
# Some cameras and all radars do not have dates
|
||||
results = get_platform_codes([b"\xf1\x00AEhe SCC H-CUP 1.01 1.01 96400-G2000 "])
|
||||
assert results == {(b"AEhe-G2000", None)}
|
||||
|
||||
results = get_platform_codes([b"\xf1\x00CV1_ RDR ----- 1.00 1.01 99110-CV000 "])
|
||||
assert results == {(b"CV1-CV000", None)}
|
||||
|
||||
results = get_platform_codes([
|
||||
b"\xf1\x00DH LKAS 1.1 -150210",
|
||||
b"\xf1\x00AEhe SCC H-CUP 1.01 1.01 96400-G2000 ",
|
||||
b"\xf1\x00CV1_ RDR ----- 1.00 1.01 99110-CV000 ",
|
||||
])
|
||||
assert results == {(b"DH", b"150210"), (b"AEhe-G2000", None), (b"CV1-CV000", None)}
|
||||
|
||||
results = get_platform_codes([
|
||||
b"\xf1\x00LX2 MFC AT USA LHD 1.00 1.07 99211-S8100 220222",
|
||||
b"\xf1\x00LX2 MFC AT USA LHD 1.00 1.08 99211-S8100 211103",
|
||||
b"\xf1\x00ON MFC AT USA LHD 1.00 1.01 99211-S9100 190405",
|
||||
b"\xf1\x00ON MFC AT USA LHD 1.00 1.03 99211-S9100 190720",
|
||||
])
|
||||
assert results == {(b"LX2-S8100", b"220222"), (b"LX2-S8100", b"211103"),
|
||||
(b"ON-S9100", b"190405"), (b"ON-S9100", b"190720")}
|
||||
|
||||
def test_fuzzy_excluded_platforms(self):
|
||||
platforms_with_shared_codes = set()
|
||||
for platform, fw_by_addr in FW_VERSIONS.items():
|
||||
car_fw = []
|
||||
for ecu, fw_versions in fw_by_addr.items():
|
||||
ecu_name, addr, sub_addr = ecu
|
||||
for fw in fw_versions:
|
||||
car_fw.append(CarParams.CarFw(ecu=ecu_name, fwVersion=fw, address=addr,
|
||||
subAddress=0 if sub_addr is None else sub_addr))
|
||||
|
||||
CP = CarParams(carFw=car_fw)
|
||||
matches = FW_QUERY_CONFIG.match_fw_to_car_fuzzy(build_fw_dict(CP.carFw), CP.carVin, FW_VERSIONS)
|
||||
if len(matches) == 1:
|
||||
assert list(matches)[0] == platform
|
||||
else:
|
||||
platforms_with_shared_codes.add(platform)
|
||||
|
||||
# A unique fuzzy match must always resolve back to the platform that supplied the firmware.
|
||||
# Ambiguity is expected for shared platform codes and for platforms without parseable dates.
|
||||
assert {CAR.GENESIS_G70, CAR.GENESIS_G70_2020} <= platforms_with_shared_codes
|
||||
@@ -0,0 +1,53 @@
|
||||
import pytest
|
||||
|
||||
from iqdbc.car import gen_empty_fingerprint, structs
|
||||
from iqdbc.car.hyundai.interface import CarInterface
|
||||
from iqdbc.car.hyundai.values import CAR, HyundaiFlagsIQ, HyundaiSafetyFlagsIQ
|
||||
|
||||
|
||||
@pytest.mark.parametrize("candidate", list(CAR), ids=lambda candidate: candidate.value)
|
||||
@pytest.mark.parametrize("alpha_long", (False, True), ids=("stock_long", "openpilot_long"))
|
||||
def test_all_platform_state_controller_and_radar(candidate, alpha_long, monkeypatch, tmp_path):
|
||||
"""Every declared HKG platform must initialize and execute one complete interface cycle."""
|
||||
monkeypatch.setenv("PARAMS_ROOT", str(tmp_path / candidate.value))
|
||||
fingerprint = gen_empty_fingerprint()
|
||||
|
||||
cp = CarInterface.get_params(candidate, fingerprint, [], alpha_long, False, False)
|
||||
cp_iq = CarInterface.get_params_iq(cp, candidate, fingerprint, [], alpha_long, False, False)
|
||||
interface = CarInterface(cp, cp_iq)
|
||||
|
||||
state, state_iq = interface.update([])
|
||||
actuators, can_sends = interface.apply(structs.CarControl().as_reader(), structs.IQCarControl())
|
||||
radar = interface.RadarInterface(cp, cp_iq)
|
||||
radar_result = radar.update([])
|
||||
|
||||
assert state.vEgo == 0.0
|
||||
assert state_iq is not None
|
||||
assert actuators is not None
|
||||
assert isinstance(can_sends, list)
|
||||
assert radar_result is None
|
||||
|
||||
|
||||
def test_parameter_defaults_do_not_require_persisted_fingerprint(monkeypatch, tmp_path):
|
||||
"""A clean installation must not crash when carrotpilot-specific Params have not been written yet."""
|
||||
monkeypatch.setenv("PARAMS_ROOT", str(tmp_path))
|
||||
fingerprint = gen_empty_fingerprint()
|
||||
|
||||
cp = CarInterface.get_params(CAR.HYUNDAI_SONATA, fingerprint, [], False, False, False)
|
||||
cp_iq = CarInterface.get_params_iq(cp, CAR.HYUNDAI_SONATA, fingerprint, [], False, False, False)
|
||||
interface = CarInterface(cp, cp_iq)
|
||||
|
||||
state, _ = interface.update([])
|
||||
assert state.vEgo == 0.0
|
||||
|
||||
|
||||
def test_classic_lfa_button_capability_survives_iq_module_removal(monkeypatch, tmp_path):
|
||||
monkeypatch.setenv("PARAMS_ROOT", str(tmp_path))
|
||||
fingerprint = gen_empty_fingerprint()
|
||||
fingerprint[0][0x391] = 8
|
||||
|
||||
cp = CarInterface.get_params(CAR.HYUNDAI_SONATA, fingerprint, [], False, False, False)
|
||||
cp_iq = CarInterface.get_params_iq(cp, CAR.HYUNDAI_SONATA, fingerprint, [], False, False, False)
|
||||
|
||||
assert cp_iq.flags & HyundaiFlagsIQ.HAS_LFA_BUTTON
|
||||
assert cp_iq.iqSafetyFlags & HyundaiSafetyFlagsIQ.HAS_LDA_BUTTON
|
||||
326
artifacts/package_runtime/iqdbc/car/hyundai/tests/test_radar.py
Normal file
326
artifacts/package_runtime/iqdbc/car/hyundai/tests/test_radar.py
Normal file
@@ -0,0 +1,326 @@
|
||||
import math
|
||||
|
||||
import pytest
|
||||
|
||||
from iqdbc.can import CANParser
|
||||
from iqdbc.car import Bus, structs
|
||||
import iqdbc.car.hyundai.hyundaicanfd as hyundaicanfd
|
||||
import iqdbc.car.hyundai.radar_interface as radar_interface_module
|
||||
from iqdbc.car.hyundai.radar_interface import (
|
||||
CORNER_OBJECT_STABLE_TRACK_ID_START,
|
||||
RADAR_MSG_COUNT3,
|
||||
RADAR_MSG_COUNT4,
|
||||
RADAR_START_ADDR_CANFD3,
|
||||
CornerObjectTrackIdManager,
|
||||
RadarInterface,
|
||||
corner_object_position_valid,
|
||||
)
|
||||
from iqdbc.car.hyundai.values import CAR, HyundaiExtFlags, HyundaiFlags
|
||||
|
||||
|
||||
class TestDensoRadar:
|
||||
@staticmethod
|
||||
def parse(addr, dat):
|
||||
name = f"RADAR_TRACK_{addr:x}"
|
||||
parser = CANParser("hyundai_kia_denso_front_radar_generated", [(name, 20)], 1)
|
||||
parser.update([0, [(addr, bytes.fromhex(dat), 1)]])
|
||||
return parser.vl[name]
|
||||
|
||||
def test_active_track_signals(self):
|
||||
# Person walking toward the parked car, left of the camera center.
|
||||
track = self.parse(0x503, "bc047efcc1fe8b00")
|
||||
|
||||
assert track["LONG_DIST"] == pytest.approx(7.1875)
|
||||
assert track["LAT_DIST"] == pytest.approx(-1.625)
|
||||
assert track["REL_SPEED"] == pytest.approx(-0.734375)
|
||||
assert track["OBJECT_STATE"] == 3
|
||||
|
||||
def test_empty_track(self):
|
||||
track = self.parse(0x507, "53fff80000000081")
|
||||
|
||||
assert track["LONG_DIST"] == pytest.approx(409.55)
|
||||
assert track["LAT_DIST"] == 0
|
||||
assert track["REL_SPEED"] == 0
|
||||
assert track["OBJECT_STATE"] == 0
|
||||
|
||||
def test_long_range_lateral_distance(self):
|
||||
# Real driving sample: treating the signed field as -12 degrees would put
|
||||
# this target about 34 m sideways at 161 m. It is instead -3.0 m lateral.
|
||||
track = self.parse(0x506, "b664eafa00cd230b")
|
||||
|
||||
assert track["LONG_DIST"] == pytest.approx(161.4625)
|
||||
assert track["LAT_DIST"] == pytest.approx(-3.0)
|
||||
assert track["OBJECT_STATE"] == 3
|
||||
|
||||
def test_parser_selection_and_point_conversion(self, monkeypatch):
|
||||
class FakeParams:
|
||||
def get_int(self, key):
|
||||
return 1 if key == "EnableRadarTracks" else 0
|
||||
|
||||
monkeypatch.setattr(radar_interface_module, "Params", FakeParams)
|
||||
cp = structs.CarParams()
|
||||
cp.carFingerprint = CAR.KIA_SORENTO
|
||||
cp.flags = 0
|
||||
cp.extFlags = HyundaiExtFlags.RADAR_GROUP4.value
|
||||
cp.radarUnavailable = False
|
||||
cp.safetyConfigs = [structs.CarParams.SafetyConfig()]
|
||||
|
||||
radar_interface = RadarInterface(cp)
|
||||
|
||||
assert radar_interface.radar_group4
|
||||
assert RADAR_MSG_COUNT4 == 8
|
||||
assert radar_interface.radar_msg_count == RADAR_MSG_COUNT4
|
||||
assert radar_interface.trigger_msg_tracks == 0x507
|
||||
|
||||
active_dat = bytes.fromhex("bc047efcc1fe8b00")
|
||||
empty_dat = bytes.fromhex("bcfff80000000081")
|
||||
packets = [(addr, active_dat if addr == 0x503 else empty_dat, 1) for addr in range(0x500, 0x508)]
|
||||
radar_data = radar_interface.update([0, packets])
|
||||
point = next(point for point in radar_data.points if point.trackId == 35)
|
||||
|
||||
assert point.measured
|
||||
assert point.dRel == pytest.approx(7.1875)
|
||||
assert point.yRel == pytest.approx(1.625)
|
||||
assert point.vRel == pytest.approx(-0.734375)
|
||||
assert math.isnan(point.aRel)
|
||||
|
||||
# EN: Confirm that the long-range sample survives the filter and converts
|
||||
# radar-left-negative to openpilot-left-positive coordinates.
|
||||
# KO: 장거리 샘플의 필터 통과와 레이더 좌측 음수 좌표가 openpilot 좌측
|
||||
# 양수 좌표로 변환되는지 확인함.
|
||||
long_range_dat = bytes.fromhex("b664eafa00cd230b")
|
||||
packets = [(addr, long_range_dat if addr == 0x506 else empty_dat, 1) for addr in range(0x500, 0x508)]
|
||||
radar_data = radar_interface.update([0, packets])
|
||||
point = next(point for point in radar_data.points if point.trackId == 38)
|
||||
|
||||
assert point.dRel == pytest.approx(161.4625)
|
||||
assert point.yRel == pytest.approx(3.0)
|
||||
|
||||
# EN: A state-0 raw detection must not enter a stable tracked-object slot.
|
||||
# KO: 상태 0인 raw detection이 안정적인 추적 객체 슬롯에 들어오지 않음을 확인함.
|
||||
raw_detection = bytes.fromhex("d702f4fc200000e4")
|
||||
packets = [(addr, raw_detection if addr == 0x503 else empty_dat, 1) for addr in range(0x500, 0x508)]
|
||||
radar_data = radar_interface.update([0, packets])
|
||||
assert not radar_data.points
|
||||
|
||||
# EN: A real confirmed track beyond the former 205 m limit remains valid.
|
||||
# KO: 기존 205m 상한을 넘는 실제 확정 트랙도 유효하게 유지됨.
|
||||
confirmed_213m_track = bytes.fromhex("35854c0780f163e0")
|
||||
packets = [(addr, confirmed_213m_track if addr == 0x503 else empty_dat, 1) for addr in range(0x500, 0x508)]
|
||||
radar_data = radar_interface.update([0, packets])
|
||||
point = next(point for point in radar_data.points if point.trackId == 35)
|
||||
assert point.dRel == pytest.approx(213.275)
|
||||
assert point.yRel == pytest.approx(-3.75)
|
||||
|
||||
# EN: The 325 m boundary is rejected, leaving ample separation from the
|
||||
# 409.55 m empty-slot sentinel.
|
||||
# KO: 325m 경계값을 제외해 409.55m 빈 슬롯 값과 충분한 간격을 확보함.
|
||||
boundary_track = bytes.fromhex("bccb200000000300")
|
||||
packets = [(addr, boundary_track if addr == 0x503 else empty_dat, 1) for addr in range(0x500, 0x508)]
|
||||
radar_data = radar_interface.update([0, packets])
|
||||
assert not radar_data.points
|
||||
|
||||
# EN: The wider profile keeps a real stable track at 4.875 m, covering more
|
||||
# of the outer adjacent lane than the conservative 4.5 m profile.
|
||||
# KO: 넓어진 필터에서 4.875m의 실제 안정 트랙을 유지해 보수적인 4.5m
|
||||
# 설정보다 바깥쪽 인접 차선을 더 넓게 포함함.
|
||||
outer_lane_track = bytes.fromhex("d80b66f640000300")
|
||||
packets = [(addr, outer_lane_track if addr == 0x503 else empty_dat, 1) for addr in range(0x500, 0x508)]
|
||||
radar_data = radar_interface.update([0, packets])
|
||||
point = next(point for point in radar_data.points if point.trackId == 35)
|
||||
assert point.yRel == pytest.approx(4.875)
|
||||
|
||||
# EN: Tracks beyond the widened envelope are rejected as roadside clutter;
|
||||
# this payload differs only in lateral distance (-7.0 m).
|
||||
# KO: 넓어진 범위를 벗어난 트랙은 도로변 잡음으로 제외함. 이 payload는
|
||||
# 횡방향 거리(-7.0m)만 다름.
|
||||
far_side_reflection = bytes.fromhex("d80b66f200000300")
|
||||
packets = [(addr, far_side_reflection if addr == 0x503 else empty_dat, 1) for addr in range(0x500, 0x508)]
|
||||
radar_data = radar_interface.update([0, packets])
|
||||
assert not radar_data.points
|
||||
|
||||
|
||||
class TestRadarGroup3:
|
||||
@staticmethod
|
||||
def parse(addr, dat):
|
||||
name = f"RADAR_TRACK_{addr:x}"
|
||||
parser = CANParser("hyundai_canfd_radar_generated", [(name, 20)], 1)
|
||||
parser.update([0, [(addr, bytes.fromhex(dat), 1)]])
|
||||
return parser.vl[name]
|
||||
|
||||
def test_group3_active_track(self):
|
||||
track = self.parse(0x406, "e1043b0f02590e692a227e16f80fe00f28fcc753a20a0000")
|
||||
|
||||
assert track["OBJECT_LENGTH"] == pytest.approx(4.4)
|
||||
assert track["LONG_DIST"] == pytest.approx(55.4)
|
||||
assert track["LAT_DIST"] == pytest.approx(-3.0)
|
||||
assert track["REL_SPEED"] == pytest.approx(4.4)
|
||||
|
||||
def test_group3_empty_track(self):
|
||||
track = self.parse(0x407, "c03d3b0000000000ff0700000000000000d0020000000000")
|
||||
|
||||
assert track["OBJECT_LENGTH"] == 0
|
||||
assert track["LONG_DIST"] == pytest.approx(204.7)
|
||||
assert track["LAT_DIST"] == 0
|
||||
assert track["REL_SPEED"] == 0
|
||||
|
||||
def test_group3_parser_selection(self, monkeypatch):
|
||||
class FakeParams:
|
||||
def get_int(self, key):
|
||||
return 1 if key == "EnableRadarTracks" else 0
|
||||
|
||||
monkeypatch.setattr(radar_interface_module, "Params", FakeParams)
|
||||
monkeypatch.setattr(hyundaicanfd, "Params", FakeParams)
|
||||
cp = structs.CarParams()
|
||||
cp.carFingerprint = next(car for car, dbc in radar_interface_module.DBC.items() if "hyundai_canfd" in dbc[Bus.pt])
|
||||
cp.flags = HyundaiFlags.CANFD.value
|
||||
cp.extFlags = HyundaiExtFlags.RADAR_GROUP3.value
|
||||
cp.radarUnavailable = False
|
||||
cp.safetyConfigs = [structs.CarParams.SafetyConfig()]
|
||||
|
||||
radar_interface = RadarInterface(cp)
|
||||
|
||||
assert radar_interface.radar_group3
|
||||
assert radar_interface.radar_start_addr == RADAR_START_ADDR_CANFD3
|
||||
assert radar_interface.radar_msg_count == RADAR_MSG_COUNT3
|
||||
assert radar_interface.trigger_msg_tracks == 0x41D
|
||||
|
||||
active_dat = bytes.fromhex("e1043b0f02590e692a227e16f80fe00f28fcc753a20a0000")
|
||||
empty_dat = bytes.fromhex("c03d3b0000000000ff0700000000000000d0020000000000")
|
||||
packets = [(addr, active_dat if addr == 0x406 else empty_dat, 1) for addr in range(0x400, 0x41E)]
|
||||
radar_data = radar_interface.update([0, packets])
|
||||
point = next(point for point in radar_data.points if point.trackId == 38)
|
||||
|
||||
assert point.measured
|
||||
assert point.dRel == pytest.approx(53.1)
|
||||
assert point.yRel == pytest.approx(-3.0)
|
||||
assert point.vRel == pytest.approx(4.4)
|
||||
|
||||
|
||||
class TestCornerRadarObjectIdentity:
|
||||
@staticmethod
|
||||
def set_bits(data, start, size, value):
|
||||
for offset in range(size):
|
||||
bit = start + offset
|
||||
data[bit // 8] |= ((value >> offset) & 1) << (bit % 8)
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
"dbc,msg_name,addr,age_signal,id_signal,age_start,id_start",
|
||||
(
|
||||
("hyundai_canfd_corner_radar_180_generated", "CORNER_RADAR_180_OBJECTS_180", 0x180,
|
||||
"SLOT1_AGE", "SLOT1_OBJECT_ID", 32, 44),
|
||||
("hyundai_canfd_corner_radar_235_generated", "CORNER_RADAR_235_OBJECTS_235", 0x235,
|
||||
"OBJ_AGE", "OBJ_OBJECT_ID", 32, 44),
|
||||
),
|
||||
)
|
||||
def test_object_identity_signals(self, dbc, msg_name, addr, age_signal, id_signal, age_start, id_start):
|
||||
data = bytearray(32)
|
||||
self.set_bits(data, age_start, 8, 23)
|
||||
self.set_bits(data, id_start, 7, 46)
|
||||
parser = CANParser(dbc, [(msg_name, 33)], 1)
|
||||
parser.update([0, [(addr, bytes(data), 1)]])
|
||||
|
||||
assert parser.vl[msg_name][age_signal] == 23
|
||||
assert parser.vl[msg_name][id_signal] == 46
|
||||
|
||||
def test_track_id_survives_slot_move_and_resets_with_age(self):
|
||||
manager = CornerObjectTrackIdManager()
|
||||
first_id = manager.get_track_id("corner180", object_id=108, age=240)
|
||||
|
||||
assert first_id == CORNER_OBJECT_STABLE_TRACK_ID_START
|
||||
assert manager.get_track_id("corner180", object_id=108, age=241) == first_id
|
||||
assert manager.get_track_id("corner235", object_id=108, age=241) != first_id
|
||||
assert manager.get_track_id("corner180", object_id=108, age=2) != first_id
|
||||
|
||||
def test_clipped_side_object_position_is_valid(self):
|
||||
assert corner_object_position_valid(0.0, 2.8)
|
||||
assert corner_object_position_valid(25.0, 0.2)
|
||||
assert not corner_object_position_valid(0.0, 0.0)
|
||||
assert not corner_object_position_valid(0.0, 5.0)
|
||||
|
||||
|
||||
class TestCornerRadar430CandidateFilter:
|
||||
@staticmethod
|
||||
def slot_word(distance_raw, meta13=0, b2=10, b3=2):
|
||||
return distance_raw | (meta13 << 13) | (b2 << 16) | (b3 << 24)
|
||||
|
||||
@classmethod
|
||||
def message(cls, slots):
|
||||
words = [0x010d1f40] * 7
|
||||
for slot, word in slots.items():
|
||||
words[slot - 1] = word
|
||||
|
||||
dat = bytearray(32)
|
||||
for idx, word in enumerate(words):
|
||||
dat[4 + idx * 4:8 + idx * 4] = int(word).to_bytes(4, "little")
|
||||
return bytes(dat)
|
||||
|
||||
@staticmethod
|
||||
def build_interface(monkeypatch):
|
||||
class FakeParams:
|
||||
def get_int(self, key):
|
||||
return 1 if key == "EnableCornerRadar" else 0
|
||||
|
||||
monkeypatch.setattr(radar_interface_module, "Params", FakeParams)
|
||||
monkeypatch.setattr(hyundaicanfd, "Params", FakeParams)
|
||||
cp = structs.CarParams()
|
||||
cp.carFingerprint = next(car for car, dbc in radar_interface_module.DBC.items() if "hyundai_canfd" in dbc[Bus.pt])
|
||||
cp.flags = HyundaiFlags.CANFD.value
|
||||
cp.extFlags = HyundaiExtFlags.CORNER_RADAR_OBJECTS_430.value
|
||||
cp.radarUnavailable = True
|
||||
cp.safetyConfigs = [structs.CarParams.SafetyConfig()]
|
||||
return RadarInterface(cp)
|
||||
|
||||
@staticmethod
|
||||
def update_frames(radar_interface, packets, frames=5):
|
||||
radar_data = None
|
||||
for _ in range(frames):
|
||||
radar_data = radar_interface.update([0, packets])
|
||||
return radar_data
|
||||
|
||||
def test_430_promotes_supported_neighbor_bins(self, monkeypatch):
|
||||
radar_interface = self.build_interface(monkeypatch)
|
||||
empty = self.message({})
|
||||
supported_bins = self.message({
|
||||
6: self.slot_word(1000),
|
||||
7: self.slot_word(1004),
|
||||
})
|
||||
packets = [(addr, supported_bins if addr == 0x436 else empty, 1) for addr in range(0x430, 0x438)]
|
||||
packets += [(addr, empty, 1) for addr in range(0x440, 0x448)]
|
||||
|
||||
radar_data = self.update_frames(radar_interface, packets)
|
||||
points = {point.trackId: point for point in radar_data.points}
|
||||
|
||||
assert points[300].measured
|
||||
assert points[300].dRel == pytest.approx(50.1)
|
||||
assert points[300].yRel == pytest.approx(2.0)
|
||||
assert points[300].yvRel == 0.0
|
||||
|
||||
def test_430_expires_noncenter_inward_yvrel(self, monkeypatch):
|
||||
radar_interface = self.build_interface(monkeypatch)
|
||||
empty = self.message({})
|
||||
frame_defs = (
|
||||
(0x431, 4, 5),
|
||||
(0x433, 3, 4),
|
||||
(0x435, 2, 3),
|
||||
(0x430, 5, 6),
|
||||
(0x432, 4, 5),
|
||||
(0x434, 3, 4),
|
||||
(0x436, 2, 3),
|
||||
)
|
||||
|
||||
radar_data = None
|
||||
for addr, first_slot, second_slot in frame_defs:
|
||||
msg = self.message({
|
||||
first_slot: self.slot_word(1000),
|
||||
second_slot: self.slot_word(1004),
|
||||
})
|
||||
packets = [(a, msg if a == addr else empty, 1) for a in range(0x430, 0x438)]
|
||||
packets += [(a, empty, 1) for a in range(0x440, 0x448)]
|
||||
radar_data = radar_interface.update([0, packets])
|
||||
radar_data = self.update_frames(radar_interface, packets, frames=3)
|
||||
points = {point.trackId: point for point in radar_data.points}
|
||||
|
||||
assert points[300].measured
|
||||
assert points[300].yRel == pytest.approx(2.0)
|
||||
assert points[300].yvRel == 0.0
|
||||
Reference in New Issue
Block a user