forked from IQ.Lvbs/IQ.Pilot
IQ.Pilot Release Commit @ 5bc9cd3
This commit is contained in:
194
iqdbc_repo/iqdbc/car/hyundai/tests/test_ev_mode.py
Normal file
194
iqdbc_repo/iqdbc/car/hyundai/tests/test_ev_mode.py
Normal file
@@ -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 openpilot.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
|
||||
Reference in New Issue
Block a user