diff --git a/opendbc/car/hyundai/carstate.py b/opendbc/car/hyundai/carstate.py index edcfcddb7de..61ca0161687 100644 --- a/opendbc/car/hyundai/carstate.py +++ b/opendbc/car/hyundai/carstate.py @@ -55,6 +55,14 @@ def __init__(self, CP): self.is_metric = False self.buttons_counter = 0 + # With openpilot longitudinal, stock SCC no longer publishes its main/set state. + # Mirror the steering-wheel state so button behavior remains stock-like. + self.main_enabled = False + self.main_enable_used = False + self.cruise_speed_set = False + self.cruise_available = False + self.pause_resume_is_resume = False + self.cruise_info = {} # On some cars, CLU15->CF_Clu_VehicleSpeed can oscillate faster than the dash updates. Sample at 5 Hz @@ -63,6 +71,51 @@ def __init__(self, CP): self.params = CarControllerParams(CP) + @property + def has_pause_resume_button(self) -> bool: + return bool(self.CP.flags & HyundaiFlags.PAUSE_RESUME) + + def update_cruise_button_state(self, ret: structs.CarState) -> None: + if self.CP.pcmCruise: + return + + for event in ret.buttonEvents: + if event.type == ButtonType.mainCruise and event.pressed: + self.main_enabled = not self.main_enabled + + if self.has_pause_resume_button and event.type == ButtonType.cancel: + # Latch the meaning for the whole press. Otherwise the release could be + # mistaken for resume after the press has already disengaged controls. + if event.pressed: + self.pause_resume_is_resume = not self.out.cruiseState.enabled + if self.pause_resume_is_resume: + event.type = ButtonType.accelCruise + if not event.pressed: + self.pause_resume_is_resume = False + + ret.cruiseState.available = ret.cruiseState.available and self.main_enabled + self.cruise_available = ret.cruiseState.available + + def update_button_enable(self, button_events: list[structs.CarState.ButtonEvent]) -> bool: + if self.CP.pcmCruise: + return False + + for event in button_events: + if event.type == ButtonType.mainCruise and event.pressed: + if self.has_pause_resume_button and self.cruise_available and not self.main_enable_used: + self.main_enable_used = True + self.cruise_speed_set = True + return True + elif event.type == ButtonType.decelCruise and not event.pressed: + if self.cruise_available: + self.cruise_speed_set = True + return True + elif event.type == ButtonType.accelCruise and not event.pressed: + if self.cruise_available and self.cruise_speed_set: + return True + + return False + def recent_button_interaction(self) -> bool: # On some newer model years, the CANCEL button acts as a pause/resume button based on the PCM state # To avoid re-engaging when openpilot cancels, check user engagement intention via buttons @@ -191,6 +244,8 @@ def update(self, can_parsers) -> structs.CarState: *create_button_events(self.main_buttons[-1], prev_main_buttons, {1: ButtonType.mainCruise}), *create_button_events(self.lda_button, prev_lda_button, {1: ButtonType.lkas})] + self.update_cruise_button_state(ret) + ret.blockPcmEnable = not self.recent_button_interaction() # low speed steer alert hysteresis logic (only for cars with steer cut off above 10 m/s) @@ -289,6 +344,8 @@ def update_canfd(self, can_parsers) -> structs.CarState: *create_button_events(self.main_buttons[-1], prev_main_buttons, {1: ButtonType.mainCruise}), *create_button_events(self.lda_button, prev_lda_button, {1: ButtonType.lkas})] + self.update_cruise_button_state(ret) + ret.blockPcmEnable = not self.recent_button_interaction() return ret diff --git a/opendbc/car/hyundai/interface.py b/opendbc/car/hyundai/interface.py index 08a426f07c1..8f267f195ad 100644 --- a/opendbc/car/hyundai/interface.py +++ b/opendbc/car/hyundai/interface.py @@ -124,6 +124,9 @@ def _get_params(ret: structs.CarParams, candidate, fingerprint, car_fw, alpha_lo # see https://github.com/commaai/opendbc/pull/1137/ ret.dashcamOnly = True + if ret.flags & HyundaiFlags.PAUSE_RESUME: + ret.safetyConfigs[-1].safetyParam |= HyundaiSafetyFlags.PAUSE_RESUME.value + # Common longitudinal control setup ret.radarUnavailable = RADAR_START_ADDR not in fingerprint[1] or Bus.radar not in DBC[ret.carFingerprint] diff --git a/opendbc/car/hyundai/tests/test_hyundai.py b/opendbc/car/hyundai/tests/test_hyundai.py index 7b1241fc767..d57ee396b43 100644 --- a/opendbc/car/hyundai/tests/test_hyundai.py +++ b/opendbc/car/hyundai/tests/test_hyundai.py @@ -1,9 +1,10 @@ import unittest -from opendbc.car import gen_empty_fingerprint +from opendbc.car import gen_empty_fingerprint, structs from opendbc.car.structs import CarParams from opendbc.car.fw_versions import build_fw_dict from opendbc.car.hyundai.interface import CarInterface +from opendbc.car.hyundai.carstate import CarState from opendbc.car.hyundai.hyundaicanfd import CanBus from opendbc.car.hyundai.radar_interface import RADAR_START_ADDR from opendbc.car.hyundai.values import CAR, DATE_FW_ECUS, FW_QUERY_CONFIG, CANFD_FUZZY_WHITELIST, \ @@ -13,6 +14,7 @@ from opendbc.testing import fuzzy_test Ecu = CarParams.Ecu +ButtonType = structs.CarState.ButtonEvent.Type # Some platforms have date codes in a different format we don't yet parse (or are missing). # For now, assert list of expected missing date cars @@ -256,3 +258,84 @@ def test_fuzzy_excluded_platforms(self): platforms_with_shared_codes.add(platform) assert platforms_with_shared_codes == excluded_platforms + + +class TestHyundaiButtonLogic(unittest.TestCase): + @staticmethod + def make_state(candidate): + CP = CarInterface.get_params(candidate, gen_empty_fingerprint(), [], True, False, False) + return CP, CarState(CP) + + @staticmethod + def update_buttons(CS, events, cruise_enabled=False): + CS.out.cruiseState.enabled = cruise_enabled + ret = structs.CarState() + ret.cruiseState.available = True + ret.buttonEvents = events + CS.update_cruise_button_state(ret) + return ret, CS.update_button_enable(ret.buttonEvents) + + @staticmethod + def event(button_type, pressed): + return structs.CarState.ButtonEvent(type=button_type, pressed=pressed) + + def test_main_gates_cruise_without_clearing_set_speed(self): + _, CS = self.make_state(CAR.HYUNDAI_SONATA) + + ret, enable = self.update_buttons(CS, [self.event(ButtonType.decelCruise, False)]) + self.assertFalse(ret.cruiseState.available) + self.assertFalse(enable) + + ret, enable = self.update_buttons(CS, [self.event(ButtonType.mainCruise, True)]) + self.assertTrue(ret.cruiseState.available) + self.assertFalse(enable) + _, enable = self.update_buttons(CS, [self.event(ButtonType.accelCruise, False)]) + self.assertFalse(enable) + + _, enable = self.update_buttons(CS, [self.event(ButtonType.decelCruise, False)]) + self.assertTrue(enable) + self.update_buttons(CS, [self.event(ButtonType.mainCruise, True)]) + self.update_buttons(CS, [self.event(ButtonType.mainCruise, False)]) + ret, enable = self.update_buttons(CS, [self.event(ButtonType.mainCruise, True)]) + self.assertTrue(ret.cruiseState.available) + self.assertFalse(enable) + _, enable = self.update_buttons(CS, [self.event(ButtonType.accelCruise, False)]) + self.assertTrue(enable) + + def test_pause_resume_latches_press_meaning(self): + _, CS = self.make_state(CAR.HYUNDAI_KONA_EV_2022) + + _, enable = self.update_buttons(CS, [self.event(ButtonType.mainCruise, True)]) + self.assertTrue(enable) + + ret, enable = self.update_buttons(CS, [self.event(ButtonType.cancel, True)], cruise_enabled=True) + self.assertEqual(ret.buttonEvents[0].type, ButtonType.cancel) + self.assertFalse(enable) + ret, enable = self.update_buttons(CS, [self.event(ButtonType.cancel, False)], cruise_enabled=False) + self.assertEqual(ret.buttonEvents[0].type, ButtonType.cancel) + self.assertFalse(enable) + + ret, enable = self.update_buttons(CS, [self.event(ButtonType.cancel, True)], cruise_enabled=False) + self.assertEqual(ret.buttonEvents[0].type, ButtonType.accelCruise) + self.assertFalse(enable) + ret, enable = self.update_buttons(CS, [self.event(ButtonType.cancel, False)], cruise_enabled=False) + self.assertEqual(ret.buttonEvents[0].type, ButtonType.accelCruise) + self.assertTrue(enable) + + def test_pause_resume_platform_flags(self): + expected = { + CAR.HYUNDAI_KONA_EV_2022, + CAR.HYUNDAI_SANTA_FE_2022, + CAR.HYUNDAI_SANTA_FE_HEV_2022, + CAR.HYUNDAI_SANTA_FE_PHEV_2022, + CAR.HYUNDAI_IONIQ_5, + CAR.HYUNDAI_SANTA_CRUZ_1ST_GEN, + CAR.KIA_EV6, + CAR.GENESIS_GV70_1ST_GEN, + } + self.assertEqual(cars_with(HyundaiFlags.PAUSE_RESUME), expected) + + for candidate in expected: + CP, CS = self.make_state(candidate) + self.assertTrue(CS.has_pause_resume_button) + self.assertTrue(CP.safetyConfigs[-1].safetyParam & HyundaiSafetyFlags.PAUSE_RESUME) diff --git a/opendbc/car/hyundai/values.py b/opendbc/car/hyundai/values.py index 2d764fbf4eb..5321621c0f2 100644 --- a/opendbc/car/hyundai/values.py +++ b/opendbc/car/hyundai/values.py @@ -66,6 +66,7 @@ class HyundaiSafetyFlags(IntFlag): CANFD_LKA_STEER_MSG_ALT = 128 FCEV_GAS = 256 ALT_LIMITS_2 = 512 + PAUSE_RESUME = 1024 # Hyundai/Kia/Genesis SCC (Smart Cruise Control) and steering architecture: @@ -147,6 +148,9 @@ class HyundaiFlags(IntFlag): ALT_LIMITS_2 = 2 ** 26 + # The button encoded as CANCEL is labeled pause/resume and toggles cruise. + PAUSE_RESUME = 2 ** 27 + @dataclass class HyundaiCarDocs(CarDocs): @@ -273,7 +277,7 @@ class CAR(Platforms): HYUNDAI_KONA_EV_2022 = HyundaiPlatformConfig( [HyundaiCarDocs("Hyundai Kona Electric 2022-23", car_parts=CarParts.common([CarHarness.hyundai_o]))], CarSpecs(mass=1743, wheelbase=2.6, steerRatio=13.42, tireStiffnessFactor=0.385), - flags=HyundaiFlags.CAMERA_SCC | HyundaiFlags.EV | HyundaiFlags.ALT_LIMITS, + flags=HyundaiFlags.CAMERA_SCC | HyundaiFlags.EV | HyundaiFlags.ALT_LIMITS | HyundaiFlags.PAUSE_RESUME, ) HYUNDAI_KONA_EV_2ND_GEN = HyundaiCanFDPlatformConfig( [HyundaiCarDocs("Hyundai Kona Electric (with HDA II, Korea only) 2023", video="https://www.youtube.com/watch?v=U2fOCmcQ8hw", @@ -301,17 +305,17 @@ class CAR(Platforms): [HyundaiCarDocs("Hyundai Santa Fe 2021-23", "All", video="https://youtu.be/VnHzSTygTS4", car_parts=CarParts.common([CarHarness.hyundai_l]))], HYUNDAI_SANTA_FE.specs, - flags=HyundaiFlags.CHECKSUM_CRC8, + flags=HyundaiFlags.CHECKSUM_CRC8 | HyundaiFlags.PAUSE_RESUME, ) HYUNDAI_SANTA_FE_HEV_2022 = HyundaiPlatformConfig( [HyundaiCarDocs("Hyundai Santa Fe Hybrid 2022-23", "All", car_parts=CarParts.common([CarHarness.hyundai_l]))], HYUNDAI_SANTA_FE.specs, - flags=HyundaiFlags.CHECKSUM_CRC8 | HyundaiFlags.HYBRID, + flags=HyundaiFlags.CHECKSUM_CRC8 | HyundaiFlags.HYBRID | HyundaiFlags.PAUSE_RESUME, ) HYUNDAI_SANTA_FE_PHEV_2022 = HyundaiPlatformConfig( [HyundaiCarDocs("Hyundai Santa Fe Plug-in Hybrid 2022-23", "All", car_parts=CarParts.common([CarHarness.hyundai_l]))], HYUNDAI_SANTA_FE.specs, - flags=HyundaiFlags.CHECKSUM_CRC8 | HyundaiFlags.HYBRID, + flags=HyundaiFlags.CHECKSUM_CRC8 | HyundaiFlags.HYBRID | HyundaiFlags.PAUSE_RESUME, ) HYUNDAI_SONATA = HyundaiPlatformConfig( [HyundaiCarDocs("Hyundai Sonata 2020-23", "All", video="https://www.youtube.com/watch?v=ix63r9kE3Fw", @@ -361,7 +365,7 @@ class CAR(Platforms): HyundaiCarDocs("Hyundai Ioniq 5 (with HDA II) 2022-24", "Highway Driving Assist II", car_parts=CarParts.common([CarHarness.hyundai_q])), ], CarSpecs(mass=1948, wheelbase=2.97, steerRatio=14.26, tireStiffnessFactor=0.65), - flags=HyundaiFlags.EV, + flags=HyundaiFlags.EV | HyundaiFlags.PAUSE_RESUME, ) HYUNDAI_IONIQ_6 = HyundaiCanFDPlatformConfig( [ @@ -384,6 +388,7 @@ class CAR(Platforms): [HyundaiCarDocs("Hyundai Santa Cruz 2022-24", car_parts=CarParts.common([CarHarness.hyundai_n]))], # weight from Limited trim - the only supported trim, steering ratio according to Hyundai News https://www.hyundainews.com/assets/documents/original/48035-2022SantaCruzProductGuideSpecsv2081521.pdf CarSpecs(mass=1870, wheelbase=3, steerRatio=14.2), + flags=HyundaiFlags.PAUSE_RESUME, ) HYUNDAI_CUSTIN_1ST_GEN = HyundaiPlatformConfig( [HyundaiCarDocs("Hyundai Custin 2023", "All", car_parts=CarParts.common([CarHarness.hyundai_k]))], @@ -543,7 +548,7 @@ class CAR(Platforms): HyundaiCarDocs("Kia EV6 (with HDA II) 2022-24", "Highway Driving Assist II", car_parts=CarParts.common([CarHarness.hyundai_p])) ], CarSpecs(mass=2055, wheelbase=2.9, steerRatio=16, tireStiffnessFactor=0.65), - flags=HyundaiFlags.EV, + flags=HyundaiFlags.EV | HyundaiFlags.PAUSE_RESUME, ) KIA_CARNIVAL_4TH_GEN = HyundaiCanFDPlatformConfig( [ @@ -585,7 +590,7 @@ class CAR(Platforms): HyundaiCarDocs("Genesis GV70 (3.5T Trim, without HDA II) 2022-23", "All", car_parts=CarParts.common([CarHarness.hyundai_m])), ], CarSpecs(mass=1950, wheelbase=2.87, steerRatio=14.6), - flags=HyundaiFlags.CANFD_RADAR_SCC, + flags=HyundaiFlags.CANFD_RADAR_SCC | HyundaiFlags.PAUSE_RESUME, ) GENESIS_GV70_ELECTRIFIED_1ST_GEN = HyundaiCanFDPlatformConfig( [ diff --git a/opendbc/safety/modes/hyundai_common.h b/opendbc/safety/modes/hyundai_common.h index 6797ae74c44..ed6c2d1e8dd 100644 --- a/opendbc/safety/modes/hyundai_common.h +++ b/opendbc/safety/modes/hyundai_common.h @@ -42,6 +42,14 @@ bool hyundai_fcev_gas_signal = false; extern bool hyundai_alt_limits_2; bool hyundai_alt_limits_2 = false; +extern bool hyundai_pause_resume; +bool hyundai_pause_resume = false; + +static bool hyundai_main_button_prev; +static bool hyundai_main_enabled; +static bool hyundai_main_enable_used; +static bool hyundai_cruise_speed_set; + static uint8_t hyundai_last_button_interaction; // button messages since the user pressed an enable button void hyundai_common_init(uint16_t param) { @@ -52,6 +60,7 @@ void hyundai_common_init(uint16_t param) { const uint16_t HYUNDAI_PARAM_ALT_LIMITS = 64; // TODO: shift this down with the rest of the common flags const uint16_t HYUNDAI_PARAM_FCEV_GAS = 256; const uint16_t HYUNDAI_PARAM_ALT_LIMITS_2 = 512; + const uint16_t HYUNDAI_PARAM_PAUSE_RESUME = 1024; hyundai_ev_gas_signal = GET_FLAG(param, HYUNDAI_PARAM_EV_GAS); hyundai_hybrid_gas_signal = !hyundai_ev_gas_signal && GET_FLAG(param, HYUNDAI_PARAM_HYBRID_GAS); @@ -60,8 +69,13 @@ void hyundai_common_init(uint16_t param) { hyundai_alt_limits = GET_FLAG(param, HYUNDAI_PARAM_ALT_LIMITS); hyundai_fcev_gas_signal = GET_FLAG(param, HYUNDAI_PARAM_FCEV_GAS); hyundai_alt_limits_2 = GET_FLAG(param, HYUNDAI_PARAM_ALT_LIMITS_2); + hyundai_pause_resume = GET_FLAG(param, HYUNDAI_PARAM_PAUSE_RESUME); hyundai_last_button_interaction = HYUNDAI_PREV_BUTTON_SAMPLES; + hyundai_main_button_prev = false; + hyundai_main_enabled = false; + hyundai_main_enable_used = false; + hyundai_cruise_speed_set = false; #ifdef ALLOW_DEBUG const uint16_t HYUNDAI_PARAM_LONGITUDINAL = 4; @@ -96,19 +110,39 @@ 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 - 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) { + const bool main_rising = main_button && !hyundai_main_button_prev; + const bool set_falling = (cruise_button != HYUNDAI_BTN_SET) && (cruise_button_prev == HYUNDAI_BTN_SET); + const bool resume_falling = (cruise_button != HYUNDAI_BTN_RESUME) && (cruise_button_prev == HYUNDAI_BTN_RESUME); + const bool pause_resume_rising = (cruise_button == HYUNDAI_BTN_CANCEL) && (cruise_button_prev != HYUNDAI_BTN_CANCEL); + + if (main_rising) { + hyundai_main_enabled = !hyundai_main_enabled; + if (!hyundai_main_enabled) { + controls_allowed = false; + } else if (hyundai_pause_resume && !hyundai_main_enable_used) { + controls_allowed = true; + hyundai_cruise_speed_set = true; + hyundai_main_enable_used = true; + } + } + + if (set_falling && hyundai_main_enabled) { + hyundai_cruise_speed_set = true; + controls_allowed = true; + } + + if (resume_falling && !hyundai_pause_resume && hyundai_main_enabled && hyundai_cruise_speed_set) { controls_allowed = true; } - // exit controls on cancel press - if (cruise_button == HYUNDAI_BTN_CANCEL) { + if (pause_resume_rising && hyundai_pause_resume && hyundai_main_enabled && hyundai_cruise_speed_set) { + controls_allowed = !controls_allowed; + } else if ((cruise_button == HYUNDAI_BTN_CANCEL) && !hyundai_pause_resume) { controls_allowed = false; } cruise_button_prev = cruise_button; + hyundai_main_button_prev = main_button; } } diff --git a/opendbc/safety/tests/common.py b/opendbc/safety/tests/common.py index 090a7fb325c..1c5646f005a 100644 --- a/opendbc/safety/tests/common.py +++ b/opendbc/safety/tests/common.py @@ -1016,7 +1016,8 @@ def test_tx_hook_on_wrong_safety_mode(self): continue if attr.startswith('TestHyundaiCanfd') and current_test.startswith('TestHyundaiCanfd'): continue - if {attr, current_test}.issubset({'TestHyundaiLongitudinalSafety', 'TestHyundaiLongitudinalSafetyCameraSCC', 'TestHyundaiSafetyFCEVLong'}): + if {attr, current_test}.issubset({'TestHyundaiLongitudinalSafety', 'TestHyundaiLongitudinalSafetyCameraSCC', + 'TestHyundaiLongitudinalSafetyPauseResume', 'TestHyundaiSafetyFCEVLong'}): continue volkswagen_shared = ('TestVolkswagenMqb', 'TestVolkswagenMlb', 'TestVolkswagenMeb') if attr.startswith(volkswagen_shared) and current_test.startswith(volkswagen_shared): diff --git a/opendbc/safety/tests/hyundai_common.py b/opendbc/safety/tests/hyundai_common.py index 5d79155aaf1..d065795b5ae 100644 --- a/opendbc/safety/tests/hyundai_common.py +++ b/opendbc/safety/tests/hyundai_common.py @@ -107,25 +107,46 @@ def _accel_msg(self, accel, aeb_req=False, aeb_decel=0): def test_set_resume_buttons(self): """ - SET and RESUME enter controls allowed on their falling edge. + MAIN gates cruise, SET stores a speed, and RESUME only works once a + speed has been stored. MAIN toggles do not clear that speed. """ - 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()) + self._rx(self._button_msg(Buttons.SET)) + self._rx(self._button_msg(Buttons.NONE)) + self.assertFalse(self.safety.get_controls_allowed()) + + self._rx(self._button_msg(Buttons.NONE, main_button=1)) + self._rx(self._button_msg(Buttons.NONE)) + self.assertFalse(self.safety.get_controls_allowed()) + + self._rx(self._button_msg(Buttons.RESUME)) + self._rx(self._button_msg(Buttons.NONE)) + self.assertFalse(self.safety.get_controls_allowed()) + + self._rx(self._button_msg(Buttons.SET)) + self._rx(self._button_msg(Buttons.SET)) + self.assertFalse(self.safety.get_controls_allowed()) + self._rx(self._button_msg(Buttons.NONE)) + self.assertTrue(self.safety.get_controls_allowed()) + + self.safety.set_controls_allowed(0) + self._rx(self._button_msg(Buttons.RESUME)) + self._rx(self._button_msg(Buttons.NONE)) + self.assertTrue(self.safety.get_controls_allowed()) + + # Toggling MAIN off and back on disables controls but preserves set speed. + self._rx(self._button_msg(Buttons.NONE, main_button=1)) + self._rx(self._button_msg(Buttons.NONE)) + self.assertFalse(self.safety.get_controls_allowed()) + self._rx(self._button_msg(Buttons.NONE, main_button=1)) + self._rx(self._button_msg(Buttons.NONE)) + self.assertFalse(self.safety.get_controls_allowed()) + self._rx(self._button_msg(Buttons.RESUME)) + self._rx(self._button_msg(Buttons.NONE)) + self.assertTrue(self.safety.get_controls_allowed()) def test_cancel_button(self): + self._rx(self._button_msg(Buttons.NONE, main_button=1)) + self._rx(self._button_msg(Buttons.NONE)) self.safety.set_controls_allowed(1) self._rx(self._button_msg(Buttons.CANCEL)) self.assertFalse(self.safety.get_controls_allowed()) @@ -151,3 +172,36 @@ def test_disabled_ecu_alive(self): self.assertFalse(self.safety.get_relay_malfunction()) self._rx(make_msg(bus, addr, 8)) self.assertTrue(self.safety.get_relay_malfunction()) + + +class HyundaiLongitudinalPauseResumeBase: + def test_set_resume_buttons(self): + # First MAIN rising edge enables and seeds a set speed. + self.assertFalse(self.safety.get_controls_allowed()) + self._rx(self._button_msg(Buttons.NONE, main_button=1)) + self.assertTrue(self.safety.get_controls_allowed()) + self._rx(self._button_msg(Buttons.NONE)) + + # Holding pause/resume must toggle only once. + self._rx(self._button_msg(Buttons.CANCEL)) + self.assertFalse(self.safety.get_controls_allowed()) + self._rx(self._button_msg(Buttons.CANCEL)) + self.assertFalse(self.safety.get_controls_allowed()) + self._rx(self._button_msg(Buttons.NONE)) + self._rx(self._button_msg(Buttons.CANCEL)) + self.assertTrue(self.safety.get_controls_allowed()) + self._rx(self._button_msg(Buttons.NONE)) + + # Later MAIN toggles never auto-enable, but do not clear the set speed. + self._rx(self._button_msg(Buttons.NONE, main_button=1)) + self._rx(self._button_msg(Buttons.NONE)) + self.assertFalse(self.safety.get_controls_allowed()) + self._rx(self._button_msg(Buttons.NONE, main_button=1)) + self._rx(self._button_msg(Buttons.NONE)) + self.assertFalse(self.safety.get_controls_allowed()) + self._rx(self._button_msg(Buttons.CANCEL)) + self.assertTrue(self.safety.get_controls_allowed()) + + def test_cancel_button(self): + # Covered as the pause/resume transition above. + pass diff --git a/opendbc/safety/tests/test_hyundai.py b/opendbc/safety/tests/test_hyundai.py index e53a448f37b..777e8d74291 100755 --- a/opendbc/safety/tests/test_hyundai.py +++ b/opendbc/safety/tests/test_hyundai.py @@ -7,7 +7,7 @@ from opendbc.safety.tests.libsafety import libsafety_py import opendbc.safety.tests.common as common from opendbc.safety.tests.common import CANPackerSafety -from opendbc.safety.tests.hyundai_common import HyundaiButtonBase, HyundaiLongitudinalBase +from opendbc.safety.tests.hyundai_common import HyundaiButtonBase, HyundaiLongitudinalBase, HyundaiLongitudinalPauseResumeBase # 4 bit checkusm used in some hyundai messages @@ -238,6 +238,14 @@ def test_no_aeb_scc12(self): self.assertFalse(self._tx(self._accel_msg(0, aeb_decel=1.0))) +class TestHyundaiLongitudinalSafetyPauseResume(HyundaiLongitudinalPauseResumeBase, TestHyundaiLongitudinalSafety): + def setUp(self): + self.packer = CANPackerSafety("hyundai_can_generated") + self.safety = libsafety_py.libsafety + self.safety.set_safety_hooks(CarParams.SafetyModel.hyundai, HyundaiSafetyFlags.LONG | HyundaiSafetyFlags.PAUSE_RESUME) + self.safety.init_tests() + + class TestHyundaiLongitudinalSafetyCameraSCC(HyundaiLongitudinalBase, TestHyundaiSafety): TX_MSGS = [[0x340, 0], [0x4F1, 2], [0x485, 0], [0x420, 0], [0x421, 0], [0x50A, 0], [0x389, 0], [0x4A2, 0]] diff --git a/opendbc/safety/tests/test_hyundai_canfd.py b/opendbc/safety/tests/test_hyundai_canfd.py index 6cec7376ece..9613086c031 100755 --- a/opendbc/safety/tests/test_hyundai_canfd.py +++ b/opendbc/safety/tests/test_hyundai_canfd.py @@ -7,7 +7,7 @@ from opendbc.safety.tests.libsafety import libsafety_py import opendbc.safety.tests.common as common from opendbc.safety.tests.common import CANPackerSafety -from opendbc.safety.tests.hyundai_common import HyundaiButtonBase, HyundaiLongitudinalBase +from opendbc.safety.tests.hyundai_common import HyundaiButtonBase, HyundaiLongitudinalBase, HyundaiLongitudinalPauseResumeBase # All combinations of radar/camera-SCC and gas/hybrid/EV cars ALL_GAS_EV_HYBRID_COMBOS = [ @@ -223,6 +223,15 @@ def _accel_msg(self, accel, aeb_req=False, aeb_decel=0): return self.packer.make_can_msg_safety("SCC_CONTROL", 1, values) +class TestHyundaiCanfdLKASteeringLongEVPauseResume(HyundaiLongitudinalPauseResumeBase, TestHyundaiCanfdLKASteeringLongEV): + def setUp(self): + self.packer = CANPackerSafety("hyundai_canfd_generated") + self.safety = libsafety_py.libsafety + self.safety.set_safety_hooks(CarParams.SafetyModel.hyundaiCanfd, HyundaiSafetyFlags.CANFD_LKA_STEER_MSG | + HyundaiSafetyFlags.LONG | HyundaiSafetyFlags.EV_GAS | HyundaiSafetyFlags.PAUSE_RESUME) + self.safety.init_tests() + + # Tests longitudinal for ICE, hybrid, EV cars with LFA steering class TestHyundaiCanfdLFASteeringLongBase(HyundaiLongitudinalBase, TestHyundaiCanfdLFASteeringBase):