diff --git a/Dockerfile.panda b/Dockerfile.panda index 7ba810f9..b1d00950 100644 --- a/Dockerfile.panda +++ b/Dockerfile.panda @@ -48,7 +48,7 @@ ENV PATH="/root/.pyenv/bin:/root/.pyenv/shims:${PATH}" ENV PANDA_PATH=/tmp/openpilot/panda ENV OPENPILOT_REF="96e8d5c9fe1a8084dfa5d97c78d4ea2037272420" -ENV OPENDBC_REF="3270c931c07bd3a47839a1a84c109eb2a7d295a6" +ENV OPENDBC_REF="04cc54d5e662aaf708f72cabb65507c7dbb5136d" COPY requirements.txt /tmp/ RUN pyenv install 3.8.10 && \ diff --git a/board/safety/safety_hyundai_canfd.h b/board/safety/safety_hyundai_canfd.h index 75346b55..8e5d70fa 100644 --- a/board/safety/safety_hyundai_canfd.h +++ b/board/safety/safety_hyundai_canfd.h @@ -17,6 +17,22 @@ const CanMsg HYUNDAI_CANFD_HDA2_TX_MSGS[] = { {0x2A4, 0, 24}, // CAM_0x2A4 }; +const CanMsg HYUNDAI_CANFD_HDA2_LONG_TX_MSGS[] = { + {0x50, 0, 16}, // LKAS + {0x1CF, 1, 8}, // CRUISE_BUTTON + {0x2A4, 0, 24}, // CAM_0x2A4 + {0x51, 0, 32}, // ADRV_0x51 + {0x730, 1, 8}, // tester present for ADAS ECU disable + {0x12A, 1, 16}, // LFA + {0x160, 1, 16}, // ADRV_0x160 + {0x1E0, 1, 16}, // LFAHDA_CLUSTER + {0x1A0, 1, 32}, // CRUISE_INFO + {0x1EA, 1, 32}, // ADRV_0x1ea + {0x200, 1, 8}, // ADRV_0x200 + {0x345, 1, 8}, // ADRV_0x345 + {0x1DA, 1, 32}, // ADRV_0x1da +}; + const CanMsg HYUNDAI_CANFD_HDA1_TX_MSGS[] = { {0x12A, 0, 16}, // LFA {0x1A0, 0, 32}, // CRUISE_INFO @@ -49,6 +65,7 @@ uint16_t hyundai_canfd_crc_lut[256]; const int HYUNDAI_PARAM_CANFD_HDA2 = 1; const int HYUNDAI_PARAM_CANFD_ALT_BUTTONS = 2; +const int HYUNDAI_PARAM_CANFD_LONG = 4; bool hyundai_canfd_hda2 = false; bool hyundai_canfd_alt_buttons = false; @@ -160,7 +177,12 @@ static int hyundai_canfd_rx_hook(CANPacket_t *to_push) { } const int steer_addr = hyundai_canfd_hda2 ? 0x50 : 0x12a; - generic_rx_checks((addr == steer_addr) && (bus == 0)); + bool stock_ecu_detected = (addr == steer_addr) && (bus == 0); + if (hyundai_longitudinal) { + // ensure ADRV ECU is still knocked out + stock_ecu_detected = stock_ecu_detected || ((addr == 0x1a0) && (bus == 1)); + } + generic_rx_checks(stock_ecu_detected); return valid; } @@ -172,15 +194,16 @@ static int hyundai_canfd_tx_hook(CANPacket_t *to_send, bool longitudinal_allowed int addr = GET_ADDR(to_send); int bus = GET_BUS(to_send); - if (hyundai_canfd_hda2) { + if (hyundai_canfd_hda2 && !hyundai_longitudinal) { tx = msg_allowed(to_send, HYUNDAI_CANFD_HDA2_TX_MSGS, sizeof(HYUNDAI_CANFD_HDA2_TX_MSGS)/sizeof(HYUNDAI_CANFD_HDA2_TX_MSGS[0])); + } else if (hyundai_canfd_hda2 && hyundai_longitudinal) { + tx = msg_allowed(to_send, HYUNDAI_CANFD_HDA2_LONG_TX_MSGS, sizeof(HYUNDAI_CANFD_HDA2_LONG_TX_MSGS)/sizeof(HYUNDAI_CANFD_HDA2_LONG_TX_MSGS[0])); } else { tx = msg_allowed(to_send, HYUNDAI_CANFD_HDA1_TX_MSGS, sizeof(HYUNDAI_CANFD_HDA1_TX_MSGS)/sizeof(HYUNDAI_CANFD_HDA1_TX_MSGS[0])); } // steering - const int steer_addr = hyundai_canfd_hda2 ? 0x50 : 0x12a; - if ((addr == steer_addr) && (bus == 0)) { + if ((addr == 0x50) || (addr == 0x12a)) { int desired_torque = ((GET_BYTE(to_send, 6) & 0xFU) << 7U) | (GET_BYTE(to_send, 5) >> 1U); desired_torque -= 1024; @@ -202,11 +225,44 @@ static int hyundai_canfd_tx_hook(CANPacket_t *to_send, bool longitudinal_allowed } } + // UDS: only tester present ("\x02\x3E\x80\x00\x00\x00\x00\x00") allowed on diagnostics address + if (addr == 0x730) { + if ((GET_BYTES_04(to_send) != 0x00803E02U) || (GET_BYTES_48(to_send) != 0x0U)) { + tx = 0; + } + } + + // ACCEL: safety check + if (addr == 0x1a0) { + int desired_accel_raw = (((GET_BYTE(to_send, 17) & 0x7U) << 8) | GET_BYTE(to_send, 16)) - 1023U; + int desired_accel_val = ((GET_BYTE(to_send, 18) << 4) | (GET_BYTE(to_send, 17) >> 4)) - 1023U; + + bool violation = false; + + if (hyundai_longitudinal) { + if (!longitudinal_allowed) { + if ((desired_accel_raw != 0) || (desired_accel_val != 0)) { + violation = true; + } + } + violation |= max_limit_check(desired_accel_raw, HYUNDAI_MAX_ACCEL, HYUNDAI_MIN_ACCEL); + violation |= max_limit_check(desired_accel_val, HYUNDAI_MAX_ACCEL, HYUNDAI_MIN_ACCEL); + } else { + // only used to cancel on here + if ((desired_accel_raw != 0) || (desired_accel_val != 0)) { + violation = true; + } + } + + if (violation) { + tx = 0; + } + } + return tx; } static int hyundai_canfd_fwd_hook(int bus_num, CANPacket_t *to_fwd) { - int bus_fwd = -1; int addr = GET_ADDR(to_fwd); @@ -236,7 +292,11 @@ static const addr_checks* hyundai_canfd_init(uint16_t param) { hyundai_canfd_hda2 = GET_FLAG(param, HYUNDAI_PARAM_CANFD_HDA2); hyundai_canfd_alt_buttons = GET_FLAG(param, HYUNDAI_PARAM_CANFD_ALT_BUTTONS); +#ifdef ALLOW_DEBUG + hyundai_longitudinal = GET_FLAG(param, HYUNDAI_PARAM_CANFD_LONG) && hyundai_canfd_hda2; +#else hyundai_longitudinal = false; +#endif return &hyundai_canfd_rx_checks; } diff --git a/python/__init__.py b/python/__init__.py index 1c85b04c..991f69ea 100644 --- a/python/__init__.py +++ b/python/__init__.py @@ -216,6 +216,7 @@ class Panda: FLAG_HYUNDAI_CANFD_HDA2 = 1 FLAG_HYUNDAI_CANFD_ALT_BUTTONS = 2 + FLAG_HYUNDAI_CANFD_LONG = 4 FLAG_TESLA_POWERTRAIN = 1 FLAG_TESLA_LONG_CONTROL = 2 diff --git a/tests/safety/common.py b/tests/safety/common.py index 3c55af49..89e736f4 100644 --- a/tests/safety/common.py +++ b/tests/safety/common.py @@ -278,7 +278,7 @@ class TorqueSteeringSafetyTestBase(PandaSafetyTestBase): # Normally, sending MIN_VALID_STEERING_FRAMES valid frames should always allow self.safety.set_timer(max(rt_us, 0)) should_tx = rt_us >= self.MIN_VALID_STEERING_RT_INTERVAL - for idx in range(self.MAX_INVALID_STEERING_FRAMES): + for _ in range(self.MAX_INVALID_STEERING_FRAMES): self.assertEqual(should_tx, self._tx(self._torque_cmd_msg(self.MAX_TORQUE, steer_req=0))) # Keep blocking after one steer_req mismatch @@ -712,11 +712,16 @@ class PandaSafetyTest(PandaSafetyTestBase): continue if attr.startswith('TestHyundaiCanfd') and current_test.startswith('TestHyundaiCanfd'): continue + + # overlapping TX addrs, but they're not actuating messages for either car + if attr == 'TestHyundaiCanfdHDA2Long' and current_test.startswith('TestToyota'): + tx = list(filter(lambda m: m[0] not in [0x160, ], tx)) + # TODO: Temporary, should be fixed in panda firmware, safety_honda.h if attr.startswith('TestHonda'): # exceptions for common msgs across different hondas tx = list(filter(lambda m: m[0] not in [0x1FA, 0x30C, 0x33D], tx)) - all_tx.append(list([m[0], m[1], attr[4:]] for m in tx)) + all_tx.append(list([m[0], m[1], attr] for m in tx)) # make sure we got all the msgs self.assertTrue(len(all_tx) >= len(test_files)-1) diff --git a/tests/safety/hyundai_common.py b/tests/safety/hyundai_common.py index 5b7ec943..d380b44b 100644 --- a/tests/safety/hyundai_common.py +++ b/tests/safety/hyundai_common.py @@ -1,14 +1,24 @@ +import numpy as np +from typing import Tuple + +import panda.tests.safety.common as common +from panda.tests.safety.common import make_msg + + class Buttons: NONE = 0 RESUME = 1 SET = 2 CANCEL = 4 + +MAX_ACCEL = 2.0 +MIN_ACCEL = -3.5 PREV_BUTTON_SAMPLES = 8 ENABLE_BUTTONS = (Buttons.RESUME, Buttons.SET, Buttons.CANCEL) -class HyundaiButtonBase: #(common.PandaSafetyTest): +class HyundaiButtonBase: # pylint: disable=no-member,abstract-method BUTTONS_BUS = 0 # tx on this bus, rx on 0. added to all `self._tx(self._button_msg(...))` SCC_BUS = 0 # rx on this bus @@ -62,4 +72,85 @@ class HyundaiButtonBase: #(common.PandaSafetyTest): 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)) \ No newline at end of file + self._rx(self._button_msg(Buttons.NONE)) + + +class HyundaiLongitudinalBase: + # pylint: disable=no-member,abstract-method + + DISABLED_ECU_UDS_MSG: Tuple[int, int] + DISABLED_ECU_ACTUATION_MSG: Tuple[int, int] + + # override these tests from PandaSafetyTest, 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 test_set_resume_buttons(self): + """ + SET and RESUME enter controls allowed on their falling edge. + """ + for btn in range(8): + self.safety.set_controls_allowed(0) + for _ in range(10): + self._rx(self._button_msg(btn)) + self.assertFalse(self.safety.get_controls_allowed()) + + # should enter controls allowed on falling edge + if btn in (Buttons.RESUME, Buttons.SET): + self._rx(self._button_msg(Buttons.NONE)) + self.assertTrue(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_accel_safety_check(self): + for controls_allowed in [True, False]: + for accel in np.arange(MIN_ACCEL - 1, MAX_ACCEL + 1, 0.01): + accel = round(accel, 2) # floats might not hit exact boundary conditions without rounding + self.safety.set_controls_allowed(controls_allowed) + send = MIN_ACCEL <= accel <= MAX_ACCEL if controls_allowed else accel == 0 + self.assertEqual(send, self._tx(self._accel_msg(accel)), (controls_allowed, accel)) + + def test_tester_present_allowed(self): + """ + Ensure tester present diagnostic message is allowed to keep ECU knocked out + for longitudinal control. + """ + + addr, bus = self.DISABLED_ECU_UDS_MSG + tester_present = common.package_can_msg((addr, 0, b"\x02\x3E\x80\x00\x00\x00\x00\x00", bus)) + self.assertTrue(self.safety.safety_tx_hook(tester_present)) + + not_tester_present = common.package_can_msg((addr, 0, b"\x03\xAA\xAA\x00\x00\x00\x00\x00", bus)) + self.assertFalse(self.safety.safety_tx_hook(not_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()) + diff --git a/tests/safety/test_hyundai.py b/tests/safety/test_hyundai.py index 74dd35f0..f47b61f8 100755 --- a/tests/safety/test_hyundai.py +++ b/tests/safety/test_hyundai.py @@ -1,25 +1,10 @@ #!/usr/bin/env python3 import unittest -import numpy as np from panda import Panda from panda.tests.safety import libpandasafety_py import panda.tests.safety.common as common -from panda.tests.safety.common import CANPackerPanda, make_msg -from panda.tests.safety.hyundai_common import HyundaiButtonBase - -MAX_ACCEL = 2.0 -MIN_ACCEL = -3.5 - - -class Buttons: - NONE = 0 - RESUME = 1 - SET = 2 - CANCEL = 4 - - -PREV_BUTTON_SAMPLES = 8 -ENABLE_BUTTONS = (Buttons.RESUME, Buttons.SET, Buttons.CANCEL) +from panda.tests.safety.common import CANPackerPanda +from panda.tests.safety.hyundai_common import HyundaiButtonBase, HyundaiLongitudinalBase # 4 bit checkusm used in some hyundai messages @@ -57,9 +42,6 @@ def checksum(msg): return addr, t, ret, bus - - - class TestHyundaiSafety(HyundaiButtonBase, common.PandaSafetyTest, common.DriverTorqueSteeringSafetyTest): TX_MSGS = [[832, 0], [1265, 0], [1157, 0]] STANDSTILL_THRESHOLD = 30 # ~1kph @@ -173,35 +155,20 @@ class TestHyundaiLegacySafetyHEV(TestHyundaiSafety): values = {"CR_Vcu_AccPedDep_Pos": gas} return self.packer.make_can_msg_panda("E_EMS11", 0, values, fix_checksum=checksum) -class TestHyundaiLongitudinalSafety(TestHyundaiSafety): +class TestHyundaiLongitudinalSafety(HyundaiLongitudinalBase, TestHyundaiSafety): TX_MSGS = [[832, 0], [1265, 0], [1157, 0], [1056, 0], [1057, 0], [1290, 0], [905, 0], [1186, 0], [909, 0], [1155, 0], [2000, 0]] + + DISABLED_ECU_UDS_MSG = (2000, 0) + DISABLED_ECU_ACTUATION_MSG = (1057, 0) + def setUp(self): self.packer = CANPackerPanda("hyundai_kia_generic") self.safety = libpandasafety_py.libpandasafety self.safety.set_safety_hooks(Panda.SAFETY_HYUNDAI, Panda.FLAG_HYUNDAI_LONG) self.safety.init_tests() - # override these tests from PandaSafetyTest, 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 NotImplementedError - - def _send_accel_msg(self, accel, aeb_req=False, aeb_decel=0): + def _accel_msg(self, accel, aeb_req=False, aeb_decel=0): values = { "aReqRaw": accel, "aReqValue": accel, @@ -210,7 +177,7 @@ class TestHyundaiLongitudinalSafety(TestHyundaiSafety): } return self.packer.make_can_msg_panda("SCC12", self.SCC_BUS, values) - def _send_fca11_msg(self, idx=0, vsm_aeb_req=False, fca_aeb_req=False, aeb_decel=0): + def _fca11_msg(self, idx=0, vsm_aeb_req=False, fca_aeb_req=False, aeb_decel=0): values = { "CR_FCA_Alive": ((-((idx % 0xF) + 2) % 4) << 2) + 1, "Supplemental_Counter": idx % 0xF, @@ -222,56 +189,15 @@ class TestHyundaiLongitudinalSafety(TestHyundaiSafety): return self.packer.make_can_msg_panda("FCA11", 0, values) def test_no_aeb_fca11(self): - self.assertTrue(self._tx(self._send_fca11_msg())) - self.assertFalse(self._tx(self._send_fca11_msg(vsm_aeb_req=True))) - self.assertFalse(self._tx(self._send_fca11_msg(fca_aeb_req=True))) - self.assertFalse(self._tx(self._send_fca11_msg(aeb_decel=1.0))) + self.assertTrue(self._tx(self._fca11_msg())) + self.assertFalse(self._tx(self._fca11_msg(vsm_aeb_req=True))) + self.assertFalse(self._tx(self._fca11_msg(fca_aeb_req=True))) + self.assertFalse(self._tx(self._fca11_msg(aeb_decel=1.0))) def test_no_aeb_scc12(self): - self.assertTrue(self._tx(self._send_accel_msg(0))) - self.assertFalse(self._tx(self._send_accel_msg(0, aeb_req=True))) - self.assertFalse(self._tx(self._send_accel_msg(0, aeb_decel=1.0))) - - def test_set_resume_buttons(self): - """ - SET and RESUME enter controls allowed on their falling edge. - """ - for btn in range(8): - self.safety.set_controls_allowed(0) - for _ in range(10): - self._rx(self._button_msg(btn)) - self.assertFalse(self.safety.get_controls_allowed()) - - # should enter controls allowed on falling edge - if btn in (Buttons.RESUME, Buttons.SET): - self._rx(self._button_msg(Buttons.NONE)) - self.assertTrue(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_accel_safety_check(self): - for controls_allowed in [True, False]: - for accel in np.arange(MIN_ACCEL - 1, MAX_ACCEL + 1, 0.01): - accel = round(accel, 2) # floats might not hit exact boundary conditions without rounding - self.safety.set_controls_allowed(controls_allowed) - send = MIN_ACCEL <= accel <= MAX_ACCEL if controls_allowed else accel == 0 - self.assertEqual(send, self._tx(self._send_accel_msg(accel)), (controls_allowed, accel)) - - def test_diagnostics(self): - tester_present = common.package_can_msg((0x7d0, 0, b"\x02\x3E\x80\x00\x00\x00\x00\x00", 0)) - self.assertTrue(self.safety.safety_tx_hook(tester_present)) - - not_tester_present = common.package_can_msg((0x7d0, 0, b"\x03\xAA\xAA\x00\x00\x00\x00\x00", 0)) - self.assertFalse(self.safety.safety_tx_hook(not_tester_present)) - - def test_radar_alive(self): - # If the radar knockout failed, make sure the relay malfunction is shown - self.assertFalse(self.safety.get_relay_malfunction()) - self._rx(make_msg(0, 1057, 8)) - self.assertTrue(self.safety.get_relay_malfunction()) + self.assertTrue(self._tx(self._accel_msg(0))) + self.assertFalse(self._tx(self._accel_msg(0, aeb_req=True))) + self.assertFalse(self._tx(self._accel_msg(0, aeb_decel=1.0))) if __name__ == "__main__": diff --git a/tests/safety/test_hyundai_canfd.py b/tests/safety/test_hyundai_canfd.py index e45b9bd1..d153bc71 100755 --- a/tests/safety/test_hyundai_canfd.py +++ b/tests/safety/test_hyundai_canfd.py @@ -4,7 +4,7 @@ from panda import Panda from panda.tests.safety import libpandasafety_py import panda.tests.safety.common as common from panda.tests.safety.common import CANPackerPanda -from panda.tests.safety.hyundai_common import HyundaiButtonBase +from panda.tests.safety.hyundai_common import HyundaiButtonBase, HyundaiLongitudinalBase class TestHyundaiCanfdBase(HyundaiButtonBase, common.PandaSafetyTest, common.DriverTorqueSteeringSafetyTest): @@ -26,6 +26,7 @@ class TestHyundaiCanfdBase(HyundaiButtonBase, common.PandaSafetyTest, common.Dri DRIVER_TORQUE_FACTOR = 2 PT_BUS = 0 + STEER_BUS = 0 STEER_MSG = "" @classmethod @@ -41,7 +42,7 @@ class TestHyundaiCanfdBase(HyundaiButtonBase, common.PandaSafetyTest, common.Dri def _torque_cmd_msg(self, torque, steer_req=1): values = {"TORQUE_REQUEST": torque} - return self.packer.make_can_msg_panda(self.STEER_MSG, 0, values) + return self.packer.make_can_msg_panda(self.STEER_MSG, self.STEER_BUS, values) def _speed_msg(self, speed): values = {f"WHEEL_SPEED_{i}": speed * 0.03125 for i in range(1, 5)} @@ -130,6 +131,29 @@ class TestHyundaiCanfdHDA2(TestHyundaiCanfdBase): self.safety.init_tests() +class TestHyundaiCanfdHDA2Long(HyundaiLongitudinalBase, TestHyundaiCanfdHDA2): + + 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]] + + DISABLED_ECU_UDS_MSG = (0x730, 1) + DISABLED_ECU_ACTUATION_MSG = (0x1a0, 1) + + STEER_MSG = "LFA" + STEER_BUS = 1 + + def setUp(self): + self.packer = CANPackerPanda("hyundai_canfd") + self.safety = libpandasafety_py.libpandasafety + self.safety.set_safety_hooks(Panda.SAFETY_HYUNDAI_CANFD, Panda.FLAG_HYUNDAI_CANFD_HDA2 | Panda.FLAG_HYUNDAI_CANFD_LONG) + self.safety.init_tests() + + def _accel_msg(self, accel, aeb_req=False, aeb_decel=0): + values = { + "ACCEL_REQ": accel, + "ACCEL_REQ2": accel, + } + return self.packer.make_can_msg_panda("CRUISE_INFO", 1, values) if __name__ == "__main__":