#!/usr/bin/env python3 from parameterized import parameterized_class import unittest from opendbc.car.hyundai.values import HyundaiSafetyFlags from opendbc.car.structs import CarParams from opendbc.safety.tests.libsafety import libsafety_py import opendbc.safety.tests.common as common from opendbc.safety.tests.common import CANPackerPanda from opendbc.safety.tests.hyundai_common import HyundaiButtonBase, HyundaiLongitudinalBase # All combinations of radar/camera-SCC and gas/hybrid/EV cars ALL_GAS_EV_HYBRID_COMBOS = [ # Radar SCC {"GAS_MSG": ("ACCELERATOR_BRAKE_ALT", "ACCELERATOR_PEDAL_PRESSED"), "SCC_BUS": 0, "SAFETY_PARAM": 0}, {"GAS_MSG": ("ACCELERATOR", "ACCELERATOR_PEDAL"), "SCC_BUS": 0, "SAFETY_PARAM": HyundaiSafetyFlags.EV_GAS}, {"GAS_MSG": ("ACCELERATOR_ALT", "ACCELERATOR_PEDAL"), "SCC_BUS": 0, "SAFETY_PARAM": HyundaiSafetyFlags.HYBRID_GAS}, # Camera SCC {"GAS_MSG": ("ACCELERATOR_BRAKE_ALT", "ACCELERATOR_PEDAL_PRESSED"), "SCC_BUS": 2, "SAFETY_PARAM": HyundaiSafetyFlags.CAMERA_SCC}, {"GAS_MSG": ("ACCELERATOR", "ACCELERATOR_PEDAL"), "SCC_BUS": 2, "SAFETY_PARAM": HyundaiSafetyFlags.EV_GAS | HyundaiSafetyFlags.CAMERA_SCC}, {"GAS_MSG": ("ACCELERATOR_ALT", "ACCELERATOR_PEDAL"), "SCC_BUS": 2, "SAFETY_PARAM": HyundaiSafetyFlags.HYBRID_GAS | HyundaiSafetyFlags.CAMERA_SCC}, ] class TestHyundaiCanfdBase(HyundaiButtonBase, common.PandaCarSafetyTest, common.DriverTorqueSteeringSafetyTest, common.SteerRequestCutSafetyTest): TX_MSGS = [[0x50, 0], [0x1CF, 1], [0x2A4, 0]] STANDSTILL_THRESHOLD = 12 # 0.375 kph FWD_BLACKLISTED_ADDRS = {2: [0x50, 0x2a4]} MAX_RATE_UP = 2 MAX_RATE_DOWN = 3 MAX_TORQUE_LOOKUP = [0], [270] MAX_RT_DELTA = 112 DRIVER_TORQUE_ALLOWANCE = 250 DRIVER_TORQUE_FACTOR = 2 # Safety around steering req bit MIN_VALID_STEERING_FRAMES = 89 MAX_INVALID_STEERING_FRAMES = 2 PT_BUS = 0 SCC_BUS = 2 STEER_BUS = 0 STEER_MSG = "" GAS_MSG = ("", "") BUTTONS_TX_BUS = 1 def _torque_driver_msg(self, torque): values = {"STEERING_COL_TORQUE": torque} return self.packer.make_can_msg_panda("MDPS", self.PT_BUS, values) def _torque_cmd_msg(self, torque, steer_req=1): values = {"TORQUE_REQUEST": torque, "STEER_REQ": steer_req} return self.packer.make_can_msg_panda(self.STEER_MSG, self.STEER_BUS, values) def _speed_msg(self, speed): values = {f"WHL_Spd{pos}Val": speed * 0.03125 for pos in ["FL", "FR", "RL", "RR"]} return self.packer.make_can_msg_panda("WHEEL_SPEEDS", self.PT_BUS, values) def _user_brake_msg(self, brake): values = {"DriverBraking": brake} return self.packer.make_can_msg_panda("TCS", self.PT_BUS, values) def _user_gas_msg(self, gas): values = {self.GAS_MSG[1]: gas} return self.packer.make_can_msg_panda(self.GAS_MSG[0], self.PT_BUS, values) def _pcm_status_msg(self, enable): values = {"ACCMode": 1 if enable else 0} return self.packer.make_can_msg_panda("SCC_CONTROL", self.SCC_BUS, values) def _button_msg(self, buttons, main_button=0, bus=None): if bus is None: bus = self.PT_BUS values = { "CRUISE_BUTTONS": buttons, "ADAPTIVE_CRUISE_MAIN_BTN": main_button, } return self.packer.make_can_msg_panda("CRUISE_BUTTONS", bus, values) class TestHyundaiCanfdLFASteeringBase(TestHyundaiCanfdBase): TX_MSGS = [[0x12A, 0], [0x1A0, 1], [0x1CF, 0], [0x1E0, 0]] RELAY_MALFUNCTION_ADDRS = {0: (0x12A, 0x1E0)} # LFA, LFAHDA_CLUSTER FWD_BLACKLISTED_ADDRS = {2: [0x12A, 0x1E0]} STEER_MSG = "LFA" BUTTONS_TX_BUS = 2 SAFETY_PARAM: int @classmethod def setUpClass(cls): super().setUpClass() if cls.__name__ in ("TestHyundaiCanfdLFASteering", "TestHyundaiCanfdLFASteeringAltButtons"): cls.packer = None cls.safety = None raise unittest.SkipTest def setUp(self): self.packer = CANPackerPanda("hyundai_canfd_generated") self.safety = libsafety_py.libsafety self.safety.set_safety_hooks(CarParams.SafetyModel.hyundaiCanfd, self.SAFETY_PARAM) self.safety.init_tests() @parameterized_class(ALL_GAS_EV_HYBRID_COMBOS) class TestHyundaiCanfdLFASteering(TestHyundaiCanfdLFASteeringBase): pass class TestHyundaiCanfdLFASteeringAltButtonsBase(TestHyundaiCanfdLFASteeringBase): SAFETY_PARAM: int def setUp(self): self.packer = CANPackerPanda("hyundai_canfd_generated") self.safety = libsafety_py.libsafety self.safety.set_safety_hooks(CarParams.SafetyModel.hyundaiCanfd, HyundaiSafetyFlags.CANFD_ALT_BUTTONS | self.SAFETY_PARAM) self.safety.init_tests() def _button_msg(self, buttons, main_button=0, bus=1): values = { "CRUISE_BUTTONS": buttons, "ADAPTIVE_CRUISE_MAIN_BTN": main_button, } return self.packer.make_can_msg_panda("CRUISE_BUTTONS_ALT", self.PT_BUS, values) def _acc_cancel_msg(self, cancel, accel=0): values = {"ACCMode": 4 if cancel else 0, "aReqRaw": accel, "aReqValue": accel} return self.packer.make_can_msg_panda("SCC_CONTROL", self.PT_BUS, values) def test_button_sends(self): """ No button send allowed with alt buttons. """ for enabled in (True, False): for btn in range(8): self.safety.set_controls_allowed(enabled) self.assertFalse(self._tx(self._button_msg(btn))) def test_acc_cancel(self): # FIXME: the CANFD_ALT_BUTTONS cars are the only ones that use SCC_CONTROL to cancel, why can't we use buttons? for enabled in (True, False): self.safety.set_controls_allowed(enabled) self.assertTrue(self._tx(self._acc_cancel_msg(True))) self.assertFalse(self._tx(self._acc_cancel_msg(True, accel=1))) self.assertFalse(self._tx(self._acc_cancel_msg(False))) @parameterized_class(ALL_GAS_EV_HYBRID_COMBOS) class TestHyundaiCanfdLFASteeringAltButtons(TestHyundaiCanfdLFASteeringAltButtonsBase): pass class TestHyundaiCanfdLKASteeringEV(TestHyundaiCanfdBase): TX_MSGS = [[0x50, 0], [0x1CF, 1], [0x2A4, 0]] RELAY_MALFUNCTION_ADDRS = {0: (0x50, 0x2a4)} # LKAS, CAM_0x2A4 FWD_BLACKLISTED_ADDRS = {2: [0x50, 0x2a4]} PT_BUS = 1 SCC_BUS = 1 STEER_MSG = "LKAS" GAS_MSG = ("ACCELERATOR", "ACCELERATOR_PEDAL") def setUp(self): self.packer = CANPackerPanda("hyundai_canfd_generated") self.safety = libsafety_py.libsafety self.safety.set_safety_hooks(CarParams.SafetyModel.hyundaiCanfd, HyundaiSafetyFlags.CANFD_LKA_STEERING | HyundaiSafetyFlags.EV_GAS) self.safety.init_tests() # TODO: Handle ICE and HEV configurations once we see cars that use the new messages class TestHyundaiCanfdLKASteeringAltEV(TestHyundaiCanfdBase): TX_MSGS = [[0x110, 0], [0x1CF, 1], [0x362, 0]] RELAY_MALFUNCTION_ADDRS = {0: (0x110, 0x362)} # LKAS_ALT, CAM_0x362 FWD_BLACKLISTED_ADDRS = {2: [0x110, 0x362]} PT_BUS = 1 SCC_BUS = 1 STEER_MSG = "LKAS_ALT" GAS_MSG = ("ACCELERATOR", "ACCELERATOR_PEDAL") def setUp(self): self.packer = CANPackerPanda("hyundai_canfd_generated") self.safety = libsafety_py.libsafety self.safety.set_safety_hooks(CarParams.SafetyModel.hyundaiCanfd, HyundaiSafetyFlags.CANFD_LKA_STEERING | HyundaiSafetyFlags.EV_GAS | HyundaiSafetyFlags.CANFD_LKA_STEERING_ALT) self.safety.init_tests() class TestHyundaiCanfdLKASteeringLongEV(HyundaiLongitudinalBase, TestHyundaiCanfdLKASteeringEV): TX_MSGS = [[0x50, 0], [0x1CF, 1], [0x2A4, 0], [0x51, 0], [0x730, 1], [0x12a, 1], [0x160, 1], [0x1e0, 1], [0x1a0, 1], [0x1ea, 1], [0x200, 1], [0x345, 1], [0x1da, 1]] RELAY_MALFUNCTION_ADDRS = {0: (0x50, 0x2a4), 1: (0x1a0,)} # LKAS, CAM_0x2A4, SCC_CONTROL DISABLED_ECU_UDS_MSG = (0x730, 1) DISABLED_ECU_ACTUATION_MSG = (0x1a0, 1) STEER_MSG = "LFA" GAS_MSG = ("ACCELERATOR", "ACCELERATOR_PEDAL") STEER_BUS = 1 def setUp(self): self.packer = CANPackerPanda("hyundai_canfd_generated") self.safety = libsafety_py.libsafety self.safety.set_safety_hooks(CarParams.SafetyModel.hyundaiCanfd, HyundaiSafetyFlags.CANFD_LKA_STEERING | HyundaiSafetyFlags.LONG | HyundaiSafetyFlags.EV_GAS) self.safety.init_tests() def _accel_msg(self, accel, aeb_req=False, aeb_decel=0): values = { "aReqRaw": accel, "aReqValue": accel, } return self.packer.make_can_msg_panda("SCC_CONTROL", 1, values) # Tests longitudinal for ICE, hybrid, EV cars with LFA steering class TestHyundaiCanfdLFASteeringLongBase(HyundaiLongitudinalBase, TestHyundaiCanfdLFASteeringBase): FWD_BLACKLISTED_ADDRS = {2: [0x12a, 0x1e0, 0x1a0, 0x160]} RELAY_MALFUNCTION_ADDRS = {0: (0x12A, 0x1E0, 0x1a0, 0x160)} # LFA, LFAHDA_CLUSTER, SCC_CONTROL, ADRV_0x160 DISABLED_ECU_UDS_MSG = (0x7D0, 0) DISABLED_ECU_ACTUATION_MSG = (0x1a0, 0) @classmethod def setUpClass(cls): if cls.__name__ == "TestHyundaiCanfdLFASteeringLongBase": cls.safety = None raise unittest.SkipTest def setUp(self): self.packer = CANPackerPanda("hyundai_canfd_generated") self.safety = libsafety_py.libsafety self.safety.set_safety_hooks(CarParams.SafetyModel.hyundaiCanfd, HyundaiSafetyFlags.LONG | self.SAFETY_PARAM) self.safety.init_tests() def _accel_msg(self, accel, aeb_req=False, aeb_decel=0): values = { "aReqRaw": accel, "aReqValue": accel, } return self.packer.make_can_msg_panda("SCC_CONTROL", 0, values) def test_tester_present_allowed(self, ecu_disable: bool = True): super().test_tester_present_allowed(ecu_disable=not self.SAFETY_PARAM & HyundaiSafetyFlags.CAMERA_SCC) @parameterized_class(ALL_GAS_EV_HYBRID_COMBOS) class TestHyundaiCanfdLFASteeringLong(TestHyundaiCanfdLFASteeringLongBase): @classmethod def setUpClass(cls): if cls.__name__ == "TestHyundaiCanfdLFASteeringLong": cls.safety = None raise unittest.SkipTest @parameterized_class(ALL_GAS_EV_HYBRID_COMBOS) class TestHyundaiCanfdLFASteeringLongAltButtons(TestHyundaiCanfdLFASteeringLongBase, TestHyundaiCanfdLFASteeringAltButtonsBase): @classmethod def setUpClass(cls): if cls.__name__ == "TestHyundaiCanfdLFASteeringLongAltButtons": cls.safety = None raise unittest.SkipTest def setUp(self): self.packer = CANPackerPanda("hyundai_canfd_generated") self.safety = libsafety_py.libsafety self.safety.set_safety_hooks(CarParams.SafetyModel.hyundaiCanfd, HyundaiSafetyFlags.LONG | HyundaiSafetyFlags.CANFD_ALT_BUTTONS | self.SAFETY_PARAM) self.safety.init_tests() def test_acc_cancel(self): # Alt buttons does not use SCC_CONTROL to cancel if longitudinal pass if __name__ == "__main__": unittest.main()