1
0
forked from IQ.Lvbs/IQ.Pilot
Files
IQ.Pilot/iqdbc_repo/iqdbc/car/hyundai/tests/test_ev_mode.py
2026-08-22 23:42:42 -05:00

195 lines
8.5 KiB
Python

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