297 lines
12 KiB
Python
297 lines
12 KiB
Python
import unittest
|
|
|
|
from iqdbc.iqpilot.car.hyundai.values import HyundaiSafetyFlagsIQ
|
|
import iqdbc.safety.tests.common as common
|
|
from iqdbc.safety.tests.libsafety import libsafety_py
|
|
from iqdbc.safety.tests.common import make_msg
|
|
|
|
|
|
class Buttons:
|
|
NONE = 0
|
|
RESUME = 1
|
|
SET = 2
|
|
CANCEL = 4
|
|
|
|
|
|
PREV_BUTTON_SAMPLES = 8
|
|
ENABLE_BUTTONS = (Buttons.RESUME, Buttons.SET, Buttons.CANCEL)
|
|
|
|
|
|
class HyundaiButtonBase:
|
|
BUTTONS_TX_BUS = 0 # tx on this bus, rx on 0
|
|
SCC_BUS = 0 # rx on this bus
|
|
|
|
def test_button_sends(self):
|
|
"""
|
|
RES, SET and CANCEL buttons are allowed
|
|
- RES and SET allowed while controls allowed
|
|
- CANCEL allowed while cruise is enabled
|
|
"""
|
|
self.safety.set_controls_allowed(0)
|
|
self.assertFalse(self._tx(self._button_msg(Buttons.RESUME, bus=self.BUTTONS_TX_BUS)))
|
|
self.assertFalse(self._tx(self._button_msg(Buttons.SET, bus=self.BUTTONS_TX_BUS)))
|
|
|
|
self.safety.set_controls_allowed(1)
|
|
self.assertTrue(self._tx(self._button_msg(Buttons.RESUME, bus=self.BUTTONS_TX_BUS)))
|
|
self.assertTrue(self._tx(self._button_msg(Buttons.SET, bus=self.BUTTONS_TX_BUS)))
|
|
|
|
for enabled in (True, False):
|
|
self._rx(self._pcm_status_msg(enabled))
|
|
self.assertEqual(enabled, self._tx(self._button_msg(Buttons.CANCEL, bus=self.BUTTONS_TX_BUS)))
|
|
|
|
def test_enable_control_allowed_from_cruise(self):
|
|
"""
|
|
Hyundai non-longitudinal only enables on PCM rising edge and recent button press. Tests PCM enabling with:
|
|
- disallowed: No buttons
|
|
- disallowed: Buttons that don't enable cruise
|
|
- allowed: Buttons that do enable cruise
|
|
- allowed: Main button with all above combinations
|
|
"""
|
|
for main_button in (0, 1):
|
|
for btn in range(8):
|
|
for _ in range(PREV_BUTTON_SAMPLES): # reset
|
|
self._rx(self._button_msg(Buttons.NONE))
|
|
|
|
self._rx(self._pcm_status_msg(False))
|
|
self.assertFalse(self.safety.get_controls_allowed())
|
|
self._rx(self._button_msg(btn, main_button=main_button))
|
|
self._rx(self._pcm_status_msg(True))
|
|
controls_allowed = btn in ENABLE_BUTTONS or main_button
|
|
self.assertEqual(controls_allowed, self.safety.get_controls_allowed())
|
|
|
|
def test_sampling_cruise_buttons(self):
|
|
"""
|
|
Test that we allow controls on recent button press, but not as button leaves sliding window
|
|
"""
|
|
self._rx(self._button_msg(Buttons.SET))
|
|
for i in range(2 * PREV_BUTTON_SAMPLES):
|
|
self._rx(self._pcm_status_msg(False))
|
|
self.assertFalse(self.safety.get_controls_allowed())
|
|
self._rx(self._pcm_status_msg(True))
|
|
controls_allowed = i < PREV_BUTTON_SAMPLES
|
|
self.assertEqual(controls_allowed, self.safety.get_controls_allowed())
|
|
self._rx(self._button_msg(Buttons.NONE))
|
|
|
|
|
|
class HyundaiLongitudinalBase(common.LongitudinalAccelSafetyTest):
|
|
|
|
DISABLED_ECU_UDS_MSG: tuple[int, int]
|
|
DISABLED_ECU_ACTUATION_MSG: tuple[int, int]
|
|
|
|
@classmethod
|
|
def setUpClass(cls):
|
|
if cls.__name__ == "HyundaiLongitudinalBase":
|
|
cls.safety = None
|
|
raise unittest.SkipTest
|
|
|
|
# override these tests from CarSafetyTest, hyundai longitudinal uses button enable
|
|
def test_disable_control_allowed_from_cruise(self):
|
|
pass
|
|
|
|
def test_enable_control_allowed_from_cruise(self):
|
|
pass
|
|
|
|
def test_sampling_cruise_buttons(self):
|
|
pass
|
|
|
|
def test_cruise_engaged_prev(self):
|
|
pass
|
|
|
|
def test_button_sends(self):
|
|
pass
|
|
|
|
def _pcm_status_msg(self, enable):
|
|
raise Exception
|
|
|
|
def _accel_msg(self, accel, aeb_req=False, aeb_decel=0):
|
|
raise NotImplementedError
|
|
|
|
def _acc_state_msg(self, enable):
|
|
raise NotImplementedError
|
|
|
|
def _tx_acc_state_msg(self, enable):
|
|
raise NotImplementedError
|
|
|
|
def test_pcm_main_cruise_state_availability(self):
|
|
pass
|
|
|
|
def test_set_resume_buttons(self):
|
|
"""
|
|
SET and RESUME enter controls allowed on their falling edge.
|
|
"""
|
|
for btn_prev in range(8):
|
|
for btn_cur in range(8):
|
|
self._rx(self._button_msg(Buttons.NONE))
|
|
self.safety.set_controls_allowed(0)
|
|
for _ in range(10):
|
|
self._rx(self._button_msg(btn_prev))
|
|
self.assertFalse(self.safety.get_controls_allowed())
|
|
|
|
# should enter controls allowed on falling edge and not transitioning to cancel
|
|
should_enable = btn_cur != btn_prev and \
|
|
btn_cur != Buttons.CANCEL and \
|
|
btn_prev in (Buttons.RESUME, Buttons.SET)
|
|
|
|
self._rx(self._button_msg(btn_cur))
|
|
self.assertEqual(should_enable, self.safety.get_controls_allowed())
|
|
|
|
def test_cancel_button(self):
|
|
self.safety.set_controls_allowed(1)
|
|
self._rx(self._button_msg(Buttons.CANCEL))
|
|
self.assertFalse(self.safety.get_controls_allowed())
|
|
|
|
def test_main_cruise_button(self):
|
|
"""Test that main cruise button correctly toggles acc_main_on state"""
|
|
default_safety_mode = self.safety.get_current_safety_mode()
|
|
default_safety_param = self.safety.get_current_safety_param()
|
|
default_safety_param_iq = self.safety.get_current_safety_param_iq()
|
|
|
|
for enable_aol in (True, False):
|
|
with self.subTest("enable_aol", aol_enabled=enable_aol):
|
|
for main_cruise_toggleable in (True, False):
|
|
with self.subTest("main_cruise_toggleable", main_cruise_toggleable=main_cruise_toggleable):
|
|
main_cruise_toggleable_flag = HyundaiSafetyFlagsIQ.MAIN_BTN_LONG_TOGGLE if main_cruise_toggleable else 0
|
|
self.safety.set_current_safety_param_iq(default_safety_param_iq | main_cruise_toggleable_flag)
|
|
self.safety.set_safety_hooks(default_safety_mode, default_safety_param)
|
|
|
|
# Test initial state
|
|
self.safety.set_aol_params(enable_aol, False, False)
|
|
|
|
self.assertFalse(self.safety.get_acc_main_on())
|
|
|
|
self._rx(self._main_cruise_button_msg(0))
|
|
self._rx(self._main_cruise_button_msg(1))
|
|
self.assertEqual(enable_aol and main_cruise_toggleable, self.safety.get_controls_allowed_lat())
|
|
|
|
self._rx(self._main_cruise_button_msg(0))
|
|
self.assertEqual(enable_aol and main_cruise_toggleable, self.safety.get_controls_allowed_lat())
|
|
|
|
self._rx(self._main_cruise_button_msg(1))
|
|
self.assertFalse(self.safety.get_controls_allowed_lat())
|
|
|
|
for _ in range(10):
|
|
self._rx(self._main_cruise_button_msg(1))
|
|
self.assertFalse(self.safety.get_controls_allowed_lat())
|
|
self.safety.set_current_safety_param_iq(default_safety_param_iq)
|
|
|
|
def test_acc_main_sync_mismatches_reset(self):
|
|
"""Test that acc_main_on_mismatches resets properly on rising edge of main button"""
|
|
default_safety_mode = self.safety.get_current_safety_mode()
|
|
default_safety_param = self.safety.get_current_safety_param()
|
|
default_safety_param_iq = self.safety.get_current_safety_param_iq()
|
|
|
|
for enable_aol in (True, False):
|
|
with self.subTest("enable_aol", aol_enabled=enable_aol):
|
|
main_cruise_toggleable_flag = HyundaiSafetyFlagsIQ.MAIN_BTN_LONG_TOGGLE
|
|
self.safety.set_current_safety_param_iq(default_safety_param_iq | main_cruise_toggleable_flag)
|
|
self.safety.set_safety_hooks(default_safety_mode, default_safety_param)
|
|
|
|
self.safety.set_aol_params(enable_aol, False, False)
|
|
|
|
# Initial state
|
|
self._rx(self._main_cruise_button_msg(0))
|
|
self.assertFalse(self.safety.get_acc_main_on())
|
|
|
|
# Set up mismatch condition
|
|
self._rx(self._main_cruise_button_msg(1)) # Press button - acc_main_on = True
|
|
self._rx(self._main_cruise_button_msg(0)) # Release button
|
|
self._tx(self._tx_acc_state_msg(False)) # acc_main_on_tx = False
|
|
self.assertTrue(self.safety.get_acc_main_on())
|
|
self.assertEqual(1, self.safety.get_acc_main_on_mismatches())
|
|
|
|
# Rising edge of acc_main_on should reset
|
|
self._rx(self._main_cruise_button_msg(1)) # Press button again
|
|
self._rx(self._main_cruise_button_msg(0)) # Release button
|
|
self._tx(self._tx_acc_state_msg(False)) # acc_main_on_tx = False
|
|
self.assertFalse(self.safety.get_acc_main_on())
|
|
self.assertEqual(0, self.safety.get_acc_main_on_mismatches())
|
|
self.safety.set_current_safety_param_iq(default_safety_param_iq)
|
|
|
|
def test_acc_main_sync_mismatch_counter(self):
|
|
"""Test mismatch counter behavior and disengagement"""
|
|
default_safety_mode = self.safety.get_current_safety_mode()
|
|
default_safety_param = self.safety.get_current_safety_param()
|
|
default_safety_param_iq = self.safety.get_current_safety_param_iq()
|
|
|
|
for enable_aol in (True, False):
|
|
with self.subTest("enable_aol", aol_enabled=enable_aol):
|
|
main_cruise_toggleable_flag = HyundaiSafetyFlagsIQ.MAIN_BTN_LONG_TOGGLE
|
|
self.safety.set_current_safety_param_iq(default_safety_param_iq | main_cruise_toggleable_flag)
|
|
self.safety.set_safety_hooks(default_safety_mode, default_safety_param)
|
|
|
|
self.safety.set_aol_params(enable_aol, False, False)
|
|
self.safety.set_controls_allowed_lat(True)
|
|
|
|
# Start with matched states
|
|
self._rx(self._main_cruise_button_msg(0))
|
|
self._tx(self._tx_acc_state_msg(False))
|
|
self.assertEqual(0, self.safety.get_acc_main_on_mismatches())
|
|
|
|
# Create mismatch by enabling acc_main_on
|
|
self._rx(self._main_cruise_button_msg(1)) # Press button
|
|
self._rx(self._main_cruise_button_msg(0)) # Release button
|
|
self._tx(self._tx_acc_state_msg(False)) # acc_main_on_tx stays false
|
|
self.assertTrue(self.safety.get_acc_main_on())
|
|
self.assertEqual(1, self.safety.get_acc_main_on_mismatches())
|
|
|
|
# Second mismatch
|
|
self._tx(self._tx_acc_state_msg(False))
|
|
self.assertTrue(self.safety.get_acc_main_on())
|
|
self.assertEqual(2, self.safety.get_acc_main_on_mismatches())
|
|
|
|
# Third mismatch should trigger disengagement
|
|
self._tx(self._tx_acc_state_msg(False))
|
|
self.assertFalse(self.safety.get_acc_main_on())
|
|
self.assertFalse(self.safety.get_controls_allowed_lat())
|
|
# Counter should reset after disengagement
|
|
self._tx(self._tx_acc_state_msg(False))
|
|
self.assertEqual(0, self.safety.get_acc_main_on_mismatches())
|
|
self.safety.set_current_safety_param_iq(default_safety_param_iq)
|
|
|
|
def test_acc_main_sync_mismatch_recovery(self):
|
|
default_safety_mode = self.safety.get_current_safety_mode()
|
|
default_safety_param = self.safety.get_current_safety_param()
|
|
default_safety_param_iq = self.safety.get_current_safety_param_iq()
|
|
|
|
"""Test that mismatch counter resets when states resync"""
|
|
for enable_aol in (True, False):
|
|
with self.subTest("enable_aol", aol_enabled=enable_aol):
|
|
main_cruise_toggleable_flag = HyundaiSafetyFlagsIQ.MAIN_BTN_LONG_TOGGLE
|
|
self.safety.set_current_safety_param_iq(default_safety_param_iq | main_cruise_toggleable_flag)
|
|
self.safety.set_safety_hooks(default_safety_mode, default_safety_param)
|
|
|
|
self.safety.set_aol_params(enable_aol, False, False)
|
|
|
|
# Create initial mismatch
|
|
self._rx(self._main_cruise_button_msg(1)) # Press button
|
|
self._rx(self._main_cruise_button_msg(0)) # Release button
|
|
self._tx(self._tx_acc_state_msg(False)) # acc_main_on_tx = False
|
|
self.assertEqual(1, self.safety.get_acc_main_on_mismatches())
|
|
|
|
# Sync states
|
|
self._tx(self._tx_acc_state_msg(True)) # Match acc_main_on_tx to acc_main_on
|
|
self.assertEqual(0, self.safety.get_acc_main_on_mismatches())
|
|
self.safety.set_current_safety_param_iq(default_safety_param_iq)
|
|
|
|
def test_tester_present_allowed(self, ecu_disable: bool = True):
|
|
"""
|
|
Ensure tester present diagnostic message is allowed to keep ECU knocked out
|
|
for longitudinal control.
|
|
"""
|
|
|
|
addr, bus = self.DISABLED_ECU_UDS_MSG
|
|
for should_tx, msg in ((True, b"\x02\x3E\x80\x00\x00\x00\x00\x00"),
|
|
(False, b"\x03\xAA\xAA\x00\x00\x00\x00\x00")):
|
|
tester_present = libsafety_py.make_CANPacket(addr, bus, msg)
|
|
self.assertEqual(should_tx and ecu_disable, self._tx(tester_present))
|
|
|
|
def test_disabled_ecu_alive(self):
|
|
"""
|
|
If the ECU knockout failed, make sure the relay malfunction is shown
|
|
"""
|
|
|
|
addr, bus = self.DISABLED_ECU_ACTUATION_MSG
|
|
self.assertFalse(self.safety.get_relay_malfunction())
|
|
self._rx(make_msg(bus, addr, 8))
|
|
self.assertTrue(self.safety.get_relay_malfunction())
|