diff --git a/board/obj/.placeholder b/board/obj/.placeholder deleted file mode 100644 index e69de29bb2d..00000000000 diff --git a/board/safety.h b/board/safety.h index 048d7cca489..2b7ad277fcd 100644 --- a/board/safety.h +++ b/board/safety.h @@ -334,6 +334,7 @@ int set_safety_hooks(uint16_t mode, uint16_t param) { regen_braking = false; regen_braking_prev = false; cruise_engaged_prev = false; + cruise_speed_set = false; vehicle_moving = false; acc_main_on = false; cruise_button_prev = 0; diff --git a/board/safety/safety_hyundai.h b/board/safety/safety_hyundai.h index 5a324bff29f..9df4f3d8a58 100644 --- a/board/safety/safety_hyundai.h +++ b/board/safety/safety_hyundai.h @@ -168,6 +168,11 @@ static void hyundai_rx_hook(const CANPacket_t *to_push) { hyundai_common_cruise_state_check(cruise_engaged); } + //// SCC11 is on bus 2 for camera-based SCC cars, bus 0 on all others + if ((addr == 0x420) && (((bus == 0) && !hyundai_camera_scc) || ((bus == 2) && hyundai_camera_scc))) { + cruise_speed_set = (GET_BYTE(to_push, 1)); + } + if (bus == 0) { if (addr == 0x251) { int torque_driver_new = (GET_BYTES(to_push, 0, 2) & 0x7ffU) - 1024U; @@ -271,7 +276,8 @@ static bool hyundai_tx_hook(const CANPacket_t *to_send) { if ((addr == 0x4F1) && !hyundai_longitudinal) { int button = GET_BYTE(to_send, 0) & 0x7U; - bool allowed_resume = (button == 1) && controls_allowed; + bool allowed_resume = ((button == 1) && controls_allowed && hyundai_pause_resune_btn) || + ((button == 1) && controls_allowed && (!hyundai_pause_resune_btn && cruise_speed_set)); bool allowed_cancel = (button == 4) && cruise_engaged_prev; if (!(allowed_resume || allowed_cancel)) { tx = false; diff --git a/board/safety/safety_hyundai_common.h b/board/safety/safety_hyundai_common.h index 54ea0f024af..cf3c69f64d2 100644 --- a/board/safety/safety_hyundai_common.h +++ b/board/safety/safety_hyundai_common.h @@ -7,6 +7,7 @@ const int HYUNDAI_PARAM_LONGITUDINAL = 4; const int HYUNDAI_PARAM_CAMERA_SCC = 8; const int HYUNDAI_PARAM_CANFD_HDA2 = 16; const int HYUNDAI_PARAM_ALT_LIMITS = 64; // TODO: shift this down with the rest of the common flags +const int HYUNDAI_PARAM_PAUSE_RESUME_BTN = 256; const uint8_t HYUNDAI_PREV_BUTTON_SAMPLES = 8; // roughly 160 ms const uint32_t HYUNDAI_STANDSTILL_THRSLD = 12; // 0.375 kph @@ -25,6 +26,8 @@ bool hyundai_longitudinal = false; bool hyundai_camera_scc = false; bool hyundai_canfd_hda2 = false; bool hyundai_alt_limits = false; +bool hyundai_pause_resune_btn = false; +bool hyundai_main_on_pressed = false; uint8_t hyundai_last_button_interaction; // button messages since the user pressed an enable button uint16_t hyundai_canfd_crc_lut[256]; @@ -35,6 +38,7 @@ void hyundai_common_init(uint16_t param) { hyundai_camera_scc = GET_FLAG(param, HYUNDAI_PARAM_CAMERA_SCC); hyundai_canfd_hda2 = GET_FLAG(param, HYUNDAI_PARAM_CANFD_HDA2); hyundai_alt_limits = GET_FLAG(param, HYUNDAI_PARAM_ALT_LIMITS); + hyundai_pause_resune_btn = GET_FLAG(param, HYUNDAI_PARAM_PAUSE_RESUME_BTN); hyundai_last_button_interaction = HYUNDAI_PREV_BUTTON_SAMPLES; @@ -70,16 +74,25 @@ void hyundai_common_cruise_buttons_check(const int cruise_button, const bool mai } if (hyundai_longitudinal) { - // enter controls on falling edge of resume or set + // enter controls on falling edge of resume or set or on rising edge of main the first time for cars with pause/resume bool set = (cruise_button != HYUNDAI_BTN_SET) && (cruise_button_prev == HYUNDAI_BTN_SET); - bool res = (cruise_button != HYUNDAI_BTN_RESUME) && (cruise_button_prev == HYUNDAI_BTN_RESUME); - if (set || res) { + bool res = (cruise_button != HYUNDAI_BTN_RESUME) && (cruise_button_prev == HYUNDAI_BTN_RESUME) && !hyundai_pause_resune_btn; + bool main = (main_button && !hyundai_main_on_pressed && hyundai_pause_resune_btn); + if (set || res || main) { controls_allowed = true; + if (main) { + hyundai_main_on_pressed = true; + } } - // exit controls on cancel press if (cruise_button == HYUNDAI_BTN_CANCEL) { - controls_allowed = false; + if (hyundai_pause_resune_btn) { + // cancel is a pause/resume button on those cars + controls_allowed = !controls_allowed; + } else { + // exit controls on cancel press on those cars + controls_allowed = false; + } } cruise_button_prev = cruise_button; diff --git a/board/safety_declarations.h b/board/safety_declarations.h index 4140f56e34f..dde8d6b19f8 100644 --- a/board/safety_declarations.h +++ b/board/safety_declarations.h @@ -222,6 +222,7 @@ bool vehicle_moving = false; bool acc_main_on = false; // referred to as "ACC off" in ISO 15622:2018 int cruise_button_prev = 0; bool safety_rx_checks_invalid = false; +bool cruise_speed_set = false; // for safety modes with torque steering control int desired_torque_last = 0; // last desired steer torque diff --git a/python/__init__.py b/python/__init__.py index 8567118f24b..46cb38361cf 100644 --- a/python/__init__.py +++ b/python/__init__.py @@ -205,6 +205,7 @@ class Panda: FLAG_HYUNDAI_CANFD_ALT_BUTTONS = 32 FLAG_HYUNDAI_ALT_LIMITS = 64 FLAG_HYUNDAI_CANFD_HDA2_ALT_STEERING = 128 + FLAG_HYUNDAI_PAUSE_RESUME_BTN = 256 FLAG_TESLA_POWERTRAIN = 1 FLAG_TESLA_LONG_CONTROL = 2 diff --git a/tests/libpanda/safety_helpers.h b/tests/libpanda/safety_helpers.h index 36887c8963d..a6ed4249ef6 100644 --- a/tests/libpanda/safety_helpers.h +++ b/tests/libpanda/safety_helpers.h @@ -19,6 +19,10 @@ bool safety_config_valid() { return true; } +void set_cruise_speed_set(bool s) { + cruise_speed_set = s; +} + void set_controls_allowed(bool c){ controls_allowed = c; } diff --git a/tests/libpanda/safety_helpers.py b/tests/libpanda/safety_helpers.py index ea41264ae08..5dcc4fce743 100644 --- a/tests/libpanda/safety_helpers.py +++ b/tests/libpanda/safety_helpers.py @@ -3,6 +3,7 @@ def setup_safety_helpers(ffi): ffi.cdef(""" + void set_cruise_speed_set(bool s); void set_controls_allowed(bool c); bool get_controls_allowed(void); bool get_longitudinal_allowed(void); @@ -53,6 +54,7 @@ def setup_safety_helpers(ffi): """) class PandaSafety(Protocol): + def set_cruise_speed_set(self, s: bool) -> None: ... def set_controls_allowed(self, c: bool) -> None: ... def get_controls_allowed(self) -> bool: ... def get_longitudinal_allowed(self) -> bool: ... diff --git a/tests/misra/suppressions.txt b/tests/misra/suppressions.txt deleted file mode 100644 index c3e260dfbab..00000000000 --- a/tests/misra/suppressions.txt +++ /dev/null @@ -1,28 +0,0 @@ -# Advisory: casting from void pointer to type pointer is ok. Done by STM libraries as well -misra-c2012-11.4 -# Advisory: casting from void pointer to type pointer is ok. Done by STM libraries as well -misra-c2012-11.5 -# Advisory: as stated in the Misra document, use of goto statements in accordance to 15.2 and 15.3 is ok -misra-c2012-15.1 -# Advisory: union types can be used -misra-c2012-19.2 -# Advisory: The # and ## preprocessor operators should not be used -misra-c2012-20.10 - -# needed since not all of these suppressions are applicable to all builds -unmatchedSuppression - -# All interrupt handlers are defined, including ones we don't use -unusedFunction:*/interrupt_handlers*.h - -# all of the below suppressions are from new checks introduced after updating -# cppcheck from 2.5 -> 2.13. they are listed here to separate the update from -# fixing the violations and all are intended to be removed soon after -misra-c2012-1.2 # this is from the extensions (e.g. __typeof__) used in the MIN, MAX, ABS, and CLAMP macros -misra-c2012-2.5 # unused macros. a few legit, rest aren't common between F4/H7 builds. should we do this in the unusedFunction pass? -misra-c2012-8.7 -misra-c2012-8.4 -misra-c2012-21.15 - -# FIXME: violations are in ST's F4 headers -misra-c2012-12.2 diff --git a/tests/safety/common.py b/tests/safety/common.py index e111ff7efff..bee441e0caf 100644 --- a/tests/safety/common.py +++ b/tests/safety/common.py @@ -802,7 +802,8 @@ def test_tx_hook_on_wrong_safety_mode(self): msg = make_msg(bus, addr) self.safety.set_controls_allowed(1) # TODO: this should be blocked - if current_test in ["TestNissanSafety", "TestNissanSafetyAltEpsBus", "TestNissanLeafSafety"] and [addr, bus] in self.TX_MSGS: + if current_test in ["TestNissanSafety", "TestNissanSafetyAltEpsBus", "TestNissanLeafSafety", \ + "TestHyundaiLongitudinalSafetyPauseResumeBtn", "TestHyundaiLongitudinalSafety"] and [addr, bus] in self.TX_MSGS: continue self.assertFalse(self._tx(msg), f"transmit of {addr=:#x} {bus=} from {test_name} during {current_test} was allowed") diff --git a/tests/safety/hyundai_common.py b/tests/safety/hyundai_common.py index da18671af57..a24091f868b 100644 --- a/tests/safety/hyundai_common.py +++ b/tests/safety/hyundai_common.py @@ -24,16 +24,31 @@ class HyundaiButtonBase: def test_button_sends(self): """ Only RES and CANCEL buttons are allowed - - RES allowed while controls allowed + - RES allowed while controls allowed and cruise speed set for CAN cars without pause/resume button, allowed while controls allowed otherwise - 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.assertFalse(self._tx(self._button_msg(Buttons.SET, bus=self.BUTTONS_TX_BUS))) + if "canfd" not in self.__class__.__name__.lower() and "pauseresume" not in self.__class__.__name__.lower(): + 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.safety.set_cruise_speed_set(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.safety.set_cruise_speed_set(1) + self.assertTrue(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))) + else: + 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.assertFalse(self._tx(self._button_msg(Buttons.SET, bus=self.BUTTONS_TX_BUS))) for enabled in (True, False): self._rx(self._pcm_status_msg(enabled)) @@ -132,6 +147,38 @@ def test_cancel_button(self): self._rx(self._button_msg(Buttons.CANCEL)) self.assertFalse(self.safety.get_controls_allowed()) + def test_set_resume_buttons_pause_resume(self): + """ + SET enters controls allowed on their falling edge. + """ + for btn_prev in range(8): + for btn_cur in range(8): + if btn_cur == Buttons.CANCEL or btn_prev == Buttons.CANCEL: + continue + 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_prev == Buttons.SET + + self._rx(self._button_msg(btn_cur)) + self.assertEqual(should_enable, self.safety.get_controls_allowed()) + + def test_cancel_button_pause_resume(self): + """ + CANCEL is a pause/resume button + """ + self.safety.set_controls_allowed(1) + self._rx(self._button_msg(Buttons.CANCEL)) + self.assertFalse(self.safety.get_controls_allowed()) + self.safety.set_controls_allowed(0) + self._rx(self._button_msg(Buttons.CANCEL)) + self.assertTrue(self.safety.get_controls_allowed()) + def test_tester_present_allowed(self): """ Ensure tester present diagnostic message is allowed to keep ECU knocked out diff --git a/tests/safety/test_hyundai.py b/tests/safety/test_hyundai.py index fbe7d1032f8..48ef6d25f04 100755 --- a/tests/safety/test_hyundai.py +++ b/tests/safety/test_hyundai.py @@ -135,6 +135,12 @@ def setUp(self): self.safety.set_safety_hooks(Panda.SAFETY_HYUNDAI, Panda.FLAG_HYUNDAI_CAMERA_SCC) self.safety.init_tests() +class TestHyundaiPauseResumeBtn(TestHyundaiSafety): + def setUp(self): + self.packer = CANPackerPanda("hyundai_kia_generic") + self.safety = libpanda_py.libpanda + self.safety.set_safety_hooks(Panda.SAFETY_HYUNDAI, Panda.FLAG_HYUNDAI_PAUSE_RESUME_BTN) + self.safety.init_tests() class TestHyundaiLegacySafety(TestHyundaiSafety): def setUp(self): @@ -167,6 +173,7 @@ def _user_gas_msg(self, gas): values = {"CR_Vcu_AccPedDep_Pos": gas} return self.packer.make_can_msg_panda("E_EMS11", 0, values, fix_checksum=checksum) + class TestHyundaiLongitudinalSafety(HyundaiLongitudinalBase, TestHyundaiSafety): TX_MSGS = [[0x340, 0], [0x4F1, 0], [0x485, 0], [0x420, 0], [0x421, 0], [0x50A, 0], [0x389, 0], [0x4A2, 0], [0x38D, 0], [0x483, 0], [0x7D0, 0]] @@ -211,6 +218,24 @@ def test_no_aeb_scc12(self): self.assertFalse(self._tx(self._accel_msg(0, aeb_req=True))) self.assertFalse(self._tx(self._accel_msg(0, aeb_decel=1.0))) + def test_set_resume_buttons_pause_resume(self): + pass + + def test_cancel_button_pause_resume(self): + pass + +class TestHyundaiLongitudinalSafetyPauseResumeBtn(TestHyundaiLongitudinalSafety): + def setUp(self): + self.packer = CANPackerPanda("hyundai_kia_generic") + self.safety = libpanda_py.libpanda + self.safety.set_safety_hooks(Panda.SAFETY_HYUNDAI, Panda.FLAG_HYUNDAI_LONG | Panda.FLAG_HYUNDAI_PAUSE_RESUME_BTN) + self.safety.init_tests() + + def test_set_resume_buttons(self): + pass + + def test_cancel_button(self): + pass if __name__ == "__main__": unittest.main() diff --git a/tests/safety/test_hyundai_canfd.py b/tests/safety/test_hyundai_canfd.py index 7f280b63194..8b83485caa3 100755 --- a/tests/safety/test_hyundai_canfd.py +++ b/tests/safety/test_hyundai_canfd.py @@ -116,7 +116,11 @@ def setUp(self): {"GAS_MSG": ("ACCELERATOR_ALT", "ACCELERATOR_PEDAL"), "SCC_BUS": 2, "SAFETY_PARAM": Panda.FLAG_HYUNDAI_HYBRID_GAS | Panda.FLAG_HYUNDAI_CAMERA_SCC}, ]) class TestHyundaiCanfdHDA1(TestHyundaiCanfdHDA1Base): - pass + def test_set_resume_buttons_pause_resume(self): + pass + + def test_cancel_button_pause_resume(self): + pass @parameterized_class([ @@ -155,6 +159,12 @@ def test_button_sends(self): self.safety.set_controls_allowed(enabled) self.assertFalse(self._tx(self._button_msg(btn))) + def test_set_resume_buttons_pause_resume(self): + pass + + def test_cancel_button_pause_resume(self): + pass + class TestHyundaiCanfdHDA2EV(TestHyundaiCanfdBase): @@ -174,6 +184,12 @@ def setUp(self): self.safety.set_safety_hooks(Panda.SAFETY_HYUNDAI_CANFD, Panda.FLAG_HYUNDAI_CANFD_HDA2 | Panda.FLAG_HYUNDAI_EV_GAS) self.safety.init_tests() + def test_set_resume_buttons_pause_resume(self): + pass + + def test_cancel_button_pause_resume(self): + pass + # TODO: Handle ICE and HEV configurations once we see cars that use the new messages class TestHyundaiCanfdHDA2EVAltSteering(TestHyundaiCanfdBase): @@ -195,6 +211,12 @@ def setUp(self): Panda.FLAG_HYUNDAI_CANFD_HDA2_ALT_STEERING) self.safety.init_tests() + def test_set_resume_buttons_pause_resume(self): + pass + + def test_cancel_button_pause_resume(self): + pass + class TestHyundaiCanfdHDA2LongEV(HyundaiLongitudinalBase, TestHyundaiCanfdHDA2EV): @@ -223,6 +245,27 @@ def _accel_msg(self, accel, aeb_req=False, aeb_decel=0): } return self.packer.make_can_msg_panda("SCC_CONTROL", 1, values) + def test_set_resume_buttons_pause_resume(self): + pass + + def test_cancel_button_pause_resume(self): + pass + +class TestHyundaiCanfdHDA2LongEVPauseResumeBtn(TestHyundaiCanfdHDA2LongEV): + def setUp(self): + self.packer = CANPackerPanda("hyundai_canfd") + self.safety = libpanda_py.libpanda + self.safety.set_safety_hooks(Panda.SAFETY_HYUNDAI_CANFD, Panda.FLAG_HYUNDAI_CANFD_HDA2 | \ + Panda.FLAG_HYUNDAI_LONG | Panda.FLAG_HYUNDAI_EV_GAS | Panda.FLAG_HYUNDAI_PAUSE_RESUME_BTN) + self.safety.init_tests() + + def test_set_resume_buttons(self): + pass + + def test_cancel_button(self): + pass + + # Tests HDA1 longitudinal for ICE, hybrid, EV @parameterized_class([ @@ -267,6 +310,38 @@ def _accel_msg(self, accel, aeb_req=False, aeb_decel=0): def test_tester_present_allowed(self): pass + def test_set_resume_buttons_pause_resume(self): + pass + + def test_cancel_button_pause_resume(self): + pass + +# Tests HDA1 longitudinal for ICE, hybrid, EV +@parameterized_class([ + # Camera SCC is the only supported configuration for HDA1 longitudinal, TODO: allow radar SCC + {"GAS_MSG": ("ACCELERATOR_BRAKE_ALT", "ACCELERATOR_PEDAL_PRESSED"), "SAFETY_PARAM": Panda.FLAG_HYUNDAI_LONG}, + {"GAS_MSG": ("ACCELERATOR", "ACCELERATOR_PEDAL"), "SAFETY_PARAM": Panda.FLAG_HYUNDAI_LONG | Panda.FLAG_HYUNDAI_EV_GAS}, + {"GAS_MSG": ("ACCELERATOR_ALT", "ACCELERATOR_PEDAL"), "SAFETY_PARAM": Panda.FLAG_HYUNDAI_LONG | Panda.FLAG_HYUNDAI_HYBRID_GAS}, +]) +class TestHyundaiCanfdHDA1LongPauseResumeBtn(TestHyundaiCanfdHDA1Long): + @classmethod + def setUpClass(cls): + if cls.__name__ == "TestHyundaiCanfdHDA1LongPauseResumeBtn": + cls.safety = None + raise unittest.SkipTest + + def setUp(self): + self.packer = CANPackerPanda("hyundai_canfd") + self.safety = libpanda_py.libpanda + self.safety.set_safety_hooks(Panda.SAFETY_HYUNDAI_CANFD, Panda.FLAG_HYUNDAI_CAMERA_SCC | Panda.FLAG_HYUNDAI_PAUSE_RESUME_BTN | self.SAFETY_PARAM) + self.safety.init_tests() + + def test_set_resume_buttons(self): + pass + + def test_cancel_button(self): + pass + if __name__ == "__main__": unittest.main()