From 77764bd3458e3abd791cc64491155e63ac668626 Mon Sep 17 00:00:00 2001 From: firestar5683 <168790843+firestar5683@users.noreply.github.com> Date: Mon, 23 Mar 2026 01:53:53 -0500 Subject: [PATCH] opendbc: fix GM pedal RX checks and restore ASCM F1 safety coverage --- opendbc_repo/opendbc/safety/modes/gm.h | 12 +++- opendbc_repo/opendbc/safety/tests/test_gm.py | 75 ++++++++++++++++---- 2 files changed, 74 insertions(+), 13 deletions(-) diff --git a/opendbc_repo/opendbc/safety/modes/gm.h b/opendbc_repo/opendbc/safety/modes/gm.h index d9f956fa64..7244f39f3d 100644 --- a/opendbc_repo/opendbc/safety/modes/gm.h +++ b/opendbc_repo/opendbc/safety/modes/gm.h @@ -601,6 +601,11 @@ static safety_config gm_init(uint16_t param) { GM_ACC_RX_CHECKS }; + static RxCheck gm_ascm_int_stock_cam_rx_checks[] = { + GM_COMMON_RX_CHECKS + {.msg = {{0xF1, 0, 6, 10U, .ignore_checksum = true, .ignore_counter = true, .ignore_quality_flag = true}, { 0 }, { 0 }}}, + }; + static const CanMsg GM_CAM_TX_MSGS[] = {{0x180, 0, 4, .check_relay = true}, {0x370, 0, 6, .check_relay = false}, {0x3D1, 0, 8, .check_relay = false}, // pt bus {0x1E1, 2, 7, .check_relay = false}, {0x184, 2, 8, .check_relay = true}, // camera bus // OPGM Variables @@ -689,6 +694,7 @@ static safety_config gm_init(uint16_t param) { gm_cam_long = GET_FLAG(param, GM_PARAM_HW_CAM_LONG) && !gm_cc_long; gm_pcm_cruise = (gm_hw == GM_CAM || gm_sdgm) && !gm_cam_long && !gm_force_ascm && !gm_pedal_long; + const bool gm_ascm_int_stock_cam = gm_ascm_int && (gm_hw == GM_CAM) && gm_pcm_cruise && !gm_cam_long && !gm_pedal_long && !gm_cc_long; gm_steer_limits = GET_FLAG(param, GM_PARAM_BOLT_2017) ? &GM_BOLT_2017_STEERING_LIMITS : &GM_STEERING_LIMITS; if (gm_hw == GM_ASCM || gm_ascm_int || gm_force_ascm) { @@ -717,7 +723,7 @@ static safety_config gm_init(uint16_t param) { if (enable_gas_interceptor) { if (gm_has_acc && (gm_hw == GM_CAM || gm_sdgm)) { SET_RX_CHECKS(gm_cam_acc_pedal_rx_checks, ret); - } else if (!(gm_pedal_long && !gm_has_acc)) { + } else { SET_RX_CHECKS(gm_pedal_rx_checks, ret); } } else if (gm_has_acc && (gm_hw == GM_CAM || gm_sdgm)) { @@ -730,6 +736,10 @@ static safety_config gm_init(uint16_t param) { SET_RX_CHECKS(gm_ev_rx_checks, ret); } + if (gm_ascm_int_stock_cam) { + SET_RX_CHECKS(gm_ascm_int_stock_cam_rx_checks, ret); + } + // ASCM does not forward any messages if (gm_hw == GM_ASCM) { ret.disable_forwarding = true; diff --git a/opendbc_repo/opendbc/safety/tests/test_gm.py b/opendbc_repo/opendbc/safety/tests/test_gm.py index c9ef1864e0..9288ca89be 100755 --- a/opendbc_repo/opendbc/safety/tests/test_gm.py +++ b/opendbc_repo/opendbc/safety/tests/test_gm.py @@ -148,6 +148,33 @@ class TestGmEVSafetyBase(TestGmSafetyBase): return self.packer.make_can_msg_safety("EBCMRegenPaddle", 0, values) +class GmCameraAccEVRegenMixin: + # Camera-ACC EV modes don't track 0xBD in their RX checks, so regen paddle + # input should be ignored by safety state in these modes. + def test_prev_user_regen(self): + self.assertFalse(self.safety.get_regen_braking_prev()) + for pressed in (False, True, False): + self._rx(self._user_regen_msg(pressed)) + self.assertFalse(self.safety.get_regen_braking_prev()) + + def test_allow_user_regen_at_zero_speed(self): + self._rx(self._vehicle_moving_msg(0)) + self.safety.set_controls_allowed(True) + self._rx(self._user_regen_msg(True)) + self.assertTrue(self.safety.get_controls_allowed()) + self.assertTrue(self.safety.get_longitudinal_allowed()) + self.assertFalse(self.safety.get_regen_braking_prev()) + + def test_not_allow_user_regen_when_moving(self): + self._rx(self._vehicle_moving_msg(self.STANDSTILL_THRESHOLD + 1)) + self.safety.set_controls_allowed(True) + self._rx(self._user_regen_msg(True)) + self.assertTrue(self.safety.get_controls_allowed()) + self.assertTrue(self.safety.get_longitudinal_allowed()) + self.assertFalse(self.safety.get_regen_braking_prev()) + self._rx(self._vehicle_moving_msg(0)) + + class TestGmAscmSafety(GmLongitudinalBase, TestGmSafetyBase): TX_MSGS = [[0x180, 0], [0x409, 0], [0x40A, 0], [0x2CB, 0], [0x370, 0], [0x200, 0], [0x1E1, 0], [0xBD, 0], [0x1F5, 0], # pt bus [0xA1, 1], [0x306, 1], [0x308, 1], [0x310, 1], # obs bus @@ -207,7 +234,35 @@ class TestGmCameraSafety(TestGmCameraSafetyBase): self.assertEqual(enabled, self._tx(self._button_msg(Buttons.CANCEL))) -class TestGmCameraEVSafety(TestGmCameraSafety, TestGmEVSafetyBase): +def _prime_gm_ascm_int_stock_cam_rx_checks(safety, f1_bus: int) -> None: + # This path should only accept the Volt/Malibu ASCM_INT stock-camera status on bus 0. + for bus, addr, length in ( + (0, 0x184, 8), + (0, 0x34A, 5), + (0, 0x1E1, 7), + (f1_bus, 0xF1, 6), + (0, 0x1C4, 8), + (0, 0xC9, 8), + ): + safety.safety_rx_hook(common.make_msg(bus, addr, length)) + + +def test_gm_ascm_int_stock_cam_f1_rx_pinning(): + safety = libsafety_py.libsafety + safety.set_safety_hooks(CarParams.SafetyModel.gm, GMSafetyFlags.HW_CAM | GMSafetyFlags.HW_ASCM_INT) + safety.init_tests() + + _prime_gm_ascm_int_stock_cam_rx_checks(safety, 0) + assert safety.safety_config_valid() + + safety.set_safety_hooks(CarParams.SafetyModel.gm, GMSafetyFlags.HW_CAM | GMSafetyFlags.HW_ASCM_INT) + safety.init_tests() + + _prime_gm_ascm_int_stock_cam_rx_checks(safety, 2) + assert not safety.safety_config_valid() + + +class TestGmCameraEVSafety(GmCameraAccEVRegenMixin, TestGmCameraSafety, TestGmEVSafetyBase): pass @@ -230,7 +285,7 @@ class TestGmCameraLongitudinalSafety(GmLongitudinalBase, TestGmCameraSafetyBase) self.safety.init_tests() -class TestGmCameraLongitudinalEVSafety(TestGmCameraLongitudinalSafety, TestGmEVSafetyBase): +class TestGmCameraLongitudinalEVSafety(GmCameraAccEVRegenMixin, TestGmCameraLongitudinalSafety, TestGmEVSafetyBase): pass @@ -278,20 +333,14 @@ class TestGmInterceptorSafety(common.GasInterceptorSafetyTest, TestGmCameraSafet self.assertEqual(enable, self.safety.get_controls_allowed()) def test_buttons(self): - # Only CANCEL button is allowed while cruise is enabled + # Pedal-long non-ACC only allows CANCEL while controls are active. self.safety.set_controls_allowed(False) for btn in range(8): self.assertFalse(self._tx(self._button_msg(btn))) self.safety.set_controls_allowed(True) for btn in range(8): - self.assertFalse(self._tx(self._button_msg(btn))) - - self.safety.set_controls_allowed(True) - for enabled in (True, False): - self._rx(self._pcm_status_msg(enabled)) - self.assertEqual(enabled, self._tx(self._button_msg(Buttons.CANCEL))) - self.assertTrue(self.safety.get_controls_allowed()) + self.assertEqual(btn == Buttons.CANCEL, self._tx(self._button_msg(btn))) def test_disable_control_allowed_from_cruise(self): pass @@ -306,8 +355,10 @@ class TestGmInterceptorSafety(common.GasInterceptorSafetyTest, TestGmCameraSafet return interceptor_msg(gas, 0x201) def _pcm_status_msg(self, enable): - values = {"CruiseActive": enable} - return self.packer.make_can_msg_panda("ECMCruiseControl", 0, values) + to_send = common.make_msg(0, 0x3D1, 8) + if enable: + to_send[0].data[4] |= 0x80 + return to_send class TestGmCcLongitudinalSafety(TestGmCameraSafety):