diff --git a/board/safety/safety_gm.h b/board/safety/safety_gm.h index 929f9c3672d..5460a53f050 100644 --- a/board/safety/safety_gm.h +++ b/board/safety/safety_gm.h @@ -15,13 +15,18 @@ const int GM_MAX_RATE_UP = 7; const int GM_MAX_RATE_DOWN = 17; const int GM_DRIVER_TORQUE_ALLOWANCE = 50; const int GM_DRIVER_TORQUE_FACTOR = 4; + const int GM_MAX_GAS = 3072; const int GM_MAX_REGEN = 1404; const int GM_MAX_BRAKE = 350; -const CanMsg GM_TX_MSGS[] = {{384, 0, 4}, {1033, 0, 7}, {1034, 0, 7}, {715, 0, 8}, {880, 0, 6}, // pt bus - {161, 1, 7}, {774, 1, 8}, {776, 1, 7}, {784, 1, 2}, // obs bus - {789, 2, 5}, // ch bus - {0x104c006c, 3, 3}, {0x10400060, 3, 5}}; // gmlan + +const CanMsg GM_ASCM_TX_MSGS[] = {{384, 0, 4}, {1033, 0, 7}, {1034, 0, 7}, {715, 0, 8}, {880, 0, 6}, // pt bus + {161, 1, 7}, {774, 1, 8}, {776, 1, 7}, {784, 1, 2}, // obs bus + {789, 2, 5}, // ch bus + {0x104c006c, 3, 3}, {0x10400060, 3, 5}}; // gmlan + +const CanMsg GM_CAM_TX_MSGS[] = {{384, 0, 4}, // pt bus + {481, 2, 7}}; // camera bus // TODO: do checksum and counter checks. Add correct timestep, 0.1s for now. AddrCheckStruct gm_addr_checks[] = { @@ -34,6 +39,7 @@ AddrCheckStruct gm_addr_checks[] = { #define GM_RX_CHECK_LEN (sizeof(gm_addr_checks) / sizeof(gm_addr_checks[0])) addr_checks gm_rx_checks = {gm_addr_checks, GM_RX_CHECK_LEN}; +const uint16_t GM_PARAM_HW_CAM = 1; enum { GM_BTN_UNPRESS = 1, @@ -42,6 +48,8 @@ enum { GM_BTN_CANCEL = 6, }; +enum {GM_ASCM, GM_CAM} gm_hw = GM_ASCM; + static int gm_rx_hook(CANPacket_t *to_push) { bool valid = addr_safety_check(to_push, &gm_rx_checks, NULL, NULL, NULL); @@ -62,8 +70,8 @@ static int gm_rx_hook(CANPacket_t *to_push) { vehicle_moving = GET_BYTE(to_push, 0) | GET_BYTE(to_push, 1); } - // ACC steering wheel buttons - if (addr == 481) { + // ACC steering wheel buttons (GM_CAM is tied to the PCM) + if ((addr == 481) && (gm_hw == GM_ASCM)) { int button = (GET_BYTE(to_push, 5) & 0x70U) >> 4; // exit controls on cancel press @@ -90,6 +98,12 @@ static int gm_rx_hook(CANPacket_t *to_push) { if (addr == 452) { gas_pressed = GET_BYTE(to_push, 5) != 0U; + + // enter controls on rising edge of ACC, exit controls when ACC off + if (gm_hw == GM_CAM) { + bool cruise_engaged = (GET_BYTE(to_push, 1) >> 5) != 0U; + pcm_cruise_check(cruise_engaged); + } } // exit controls on regen paddle @@ -100,11 +114,13 @@ static int gm_rx_hook(CANPacket_t *to_push) { } } - // Check if ASCM or LKA camera are online - // on powertrain bus. - // 384 = ASCMLKASteeringCmd - // 715 = ASCMGasRegenCmd - generic_rx_checks(((addr == 384) || (addr == 715))); + bool stock_ecu_detected = (addr == 384); // ASCMLKASteeringCmd + + // Only check ASCMGasRegenCmd if ASCM, GM_CAM uses stock longitudinal + if ((gm_hw == GM_ASCM) && (addr == 715)) { + stock_ecu_detected = true; + } + generic_rx_checks(stock_ecu_detected); } return valid; } @@ -120,8 +136,10 @@ static int gm_tx_hook(CANPacket_t *to_send, bool longitudinal_allowed) { int tx = 1; int addr = GET_ADDR(to_send); - if (!msg_allowed(to_send, GM_TX_MSGS, sizeof(GM_TX_MSGS)/sizeof(GM_TX_MSGS[0]))) { - tx = 0; + if (gm_hw == GM_CAM) { + tx = msg_allowed(to_send, GM_CAM_TX_MSGS, sizeof(GM_CAM_TX_MSGS)/sizeof(GM_CAM_TX_MSGS[0])); + } else { + tx = msg_allowed(to_send, GM_ASCM_TX_MSGS, sizeof(GM_ASCM_TX_MSGS)/sizeof(GM_ASCM_TX_MSGS[0])); } // disallow actuator commands if gas or brake (with vehicle moving) are pressed @@ -218,12 +236,44 @@ static int gm_tx_hook(CANPacket_t *to_send, bool longitudinal_allowed) { } } + // BUTTONS: used for resume spamming and cruise cancellation with stock longitudinal + if ((addr == 481) && (gm_hw == GM_CAM)) { + int button = (GET_BYTE(to_send, 5) >> 4) & 0x7U; + + bool allowed_cancel = (button == 6) && cruise_engaged_prev; + if (!allowed_cancel) { + tx = 0; + } + } + // 1 allows the message through return tx; } +static int gm_fwd_hook(int bus_num, CANPacket_t *to_fwd) { + + int bus_fwd = -1; + + if (gm_hw == GM_CAM) { + if (bus_num == 0) { + bus_fwd = 2; + } + + if (bus_num == 2) { + // block lkas message, forward all others + int addr = GET_ADDR(to_fwd); + bool is_lkas_msg = (addr == 384); + if (!is_lkas_msg) { + bus_fwd = 0; + } + } + } + + return bus_fwd; +} + static const addr_checks* gm_init(uint16_t param) { - UNUSED(param); + gm_hw = GET_FLAG(param, GM_PARAM_HW_CAM) ? GM_CAM : GM_ASCM; return &gm_rx_checks; } @@ -232,5 +282,5 @@ const safety_hooks gm_hooks = { .rx = gm_rx_hook, .tx = gm_tx_hook, .tx_lin = nooutput_tx_lin_hook, - .fwd = default_fwd_hook, + .fwd = gm_fwd_hook, }; diff --git a/python/__init__.py b/python/__init__.py index fc1cbb30585..7db16b6df42 100644 --- a/python/__init__.py +++ b/python/__init__.py @@ -205,6 +205,8 @@ class Panda: FLAG_SUBARU_GEN2 = 1 + FLAG_GM_HW_CAM = 1 + def __init__(self, serial: Optional[str] = None, claim: bool = True): self._serial = serial self._handle = None diff --git a/tests/safety/test_gm.py b/tests/safety/test_gm.py index 55967afc17d..d9acb0137ee 100755 --- a/tests/safety/test_gm.py +++ b/tests/safety/test_gm.py @@ -18,16 +18,11 @@ class Buttons: CANCEL = 6 -class TestGmSafety(common.PandaSafetyTest, common.DriverTorqueSteeringSafetyTest): - TX_MSGS = [[384, 0], [1033, 0], [1034, 0], [715, 0], [880, 0], # pt bus - [161, 1], [774, 1], [776, 1], [784, 1], # obs bus - [789, 2], # ch bus - [0x104c006c, 3], [0x10400060, 3]] # gmlan +class TestGmSafetyBase(common.PandaSafetyTest, common.DriverTorqueSteeringSafetyTest): STANDSTILL_THRESHOLD = 0 RELAY_MALFUNCTION_ADDR = 384 RELAY_MALFUNCTION_BUS = 0 - FWD_BLACKLISTED_ADDRS: Dict[int, List[int]] = {} - FWD_BUS_LOOKUP: Dict[int, int] = {} + BUTTONS_BUS = 0 MAX_RATE_UP = 7 MAX_RATE_DOWN = 17 @@ -37,6 +32,13 @@ class TestGmSafety(common.PandaSafetyTest, common.DriverTorqueSteeringSafetyTest DRIVER_TORQUE_ALLOWANCE = 50 DRIVER_TORQUE_FACTOR = 4 + @classmethod + def setUpClass(cls): + if cls.__name__ == "TestGmSafetyBase": + cls.packer = None + cls.safety = None + raise unittest.SkipTest + def setUp(self): self.packer = CANPackerPanda("gm_global_a_powertrain_generated") self.packer_chassis = CANPackerPanda("gm_global_a_chassis") @@ -44,16 +46,6 @@ def setUp(self): self.safety.set_safety_hooks(Panda.SAFETY_GM, 0) self.safety.init_tests() - # override these tests from PandaSafetyTest, GM uses button enable - def test_disable_control_allowed_from_cruise(self): - pass - - def test_enable_control_allowed_from_cruise(self): - pass - - def test_cruise_engaged_prev(self): - pass - def _pcm_status_msg(self, enable): raise NotImplementedError @@ -61,10 +53,6 @@ def _speed_msg(self, speed): values = {"%sWheelSpd" % s: speed for s in ["RL", "RR"]} return self.packer.make_can_msg_panda("EBCMWheelSpdRear", 0, values) - def _button_msg(self, buttons): - values = {"ACCButtons": buttons} - return self.packer.make_can_msg_panda("ASCMSteeringButton", 0, values) - def _user_brake_msg(self, brake): # GM safety has a brake threshold of 10 values = {"BrakePedalPosition": 10 if brake else 0} @@ -90,40 +78,24 @@ def _torque_cmd_msg(self, torque, steer_req=1): values = {"LKASteeringCmd": torque} return self.packer.make_can_msg_panda("ASCMLKASteeringCmd", 0, values) - 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.RES_ACCEL, Buttons.DECEL_SET): - self._rx(self._button_msg(Buttons.UNPRESS)) - 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 _button_msg(self, buttons): + values = {"ACCButtons": buttons} + return self.packer.make_can_msg_panda("ASCMSteeringButton", self.BUTTONS_BUS, values) - def test_brake_safety_check(self): + def test_brake_safety_check(self, stock_longitudinal=False): for enabled in [0, 1]: for b in range(0, 500): self.safety.set_controls_allowed(enabled) - if abs(b) > MAX_BRAKE or (not enabled and b != 0): + if abs(b) > MAX_BRAKE or (not enabled and b != 0) or stock_longitudinal: self.assertFalse(self._tx(self._send_brake_msg(b))) else: self.assertTrue(self._tx(self._send_brake_msg(b))) - def test_gas_safety_check(self): + def test_gas_safety_check(self, stock_longitudinal=False): for enabled in [0, 1]: for g in range(0, 2**12 - 1): self.safety.set_controls_allowed(enabled) - if abs(g) > MAX_GAS or (not enabled and g != MAX_REGEN): + if abs(g) > MAX_GAS or (not enabled and g != MAX_REGEN) or stock_longitudinal: self.assertFalse(self._tx(self._send_gas_msg(g))) else: self.assertTrue(self._tx(self._send_gas_msg(g))) @@ -180,5 +152,98 @@ def test_tx_hook_on_pedal_pressed_on_alternative_gas_experience(self): self._rx(self._user_gas_msg(0)) +class TestGmAscmSafety(TestGmSafetyBase): + TX_MSGS = [[384, 0], [1033, 0], [1034, 0], [715, 0], [880, 0], # pt bus + [161, 1], [774, 1], [776, 1], [784, 1], # obs bus + [789, 2], # ch bus + [0x104c006c, 3], [0x10400060, 3]] # gmlan + FWD_BLACKLISTED_ADDRS: Dict[int, List[int]] = {} + FWD_BUS_LOOKUP: Dict[int, int] = {} + + def setUp(self): + self.packer = CANPackerPanda("gm_global_a_powertrain_generated") + self.packer_chassis = CANPackerPanda("gm_global_a_chassis") + self.safety = libpandasafety_py.libpandasafety + self.safety.set_safety_hooks(Panda.SAFETY_GM, 0) + self.safety.init_tests() + + # override these tests from PandaSafetyTest, ASCM GM uses button enable + def test_disable_control_allowed_from_cruise(self): + pass + + def test_enable_control_allowed_from_cruise(self): + pass + + def test_cruise_engaged_prev(self): + pass + + def _pcm_status_msg(self, enable): + 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.RES_ACCEL, Buttons.DECEL_SET): + self._rx(self._button_msg(Buttons.UNPRESS)) + 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()) + + +class TestGmCameraSafety(TestGmSafetyBase): + TX_MSGS = [[384, 0]] # pt bus + FWD_BLACKLISTED_ADDRS = {2: [384]} # LKAS message, ACC messages are (715, 880, 789) + FWD_BUS_LOOKUP = {0: 2, 2: 0} + BUTTONS_BUS = 2 # tx only + + def setUp(self): + self.packer = CANPackerPanda("gm_global_a_powertrain_generated") + self.packer_chassis = CANPackerPanda("gm_global_a_chassis") + self.safety = libpandasafety_py.libpandasafety + self.safety.set_safety_hooks(Panda.SAFETY_GM, Panda.FLAG_GM_HW_CAM) + self.safety.init_tests() + + def _user_gas_msg(self, gas): + cruise_active = self.safety.get_controls_allowed() + values = {"AcceleratorPedal2": 1 if gas else 0, "CruiseState": cruise_active} + return self.packer.make_can_msg_panda("AcceleratorPedal2", 0, values) + + def _pcm_status_msg(self, enable): + values = {"CruiseState": enable} + return self.packer.make_can_msg_panda("AcceleratorPedal2", 0, values) + + def test_buttons(self): + # Only CANCEL button is allowed while cruise is enabled + self.safety.set_controls_allowed(0) + for btn in range(8): + self.assertFalse(self._tx(self._button_msg(btn))) + + self.safety.set_controls_allowed(1) + for btn in range(8): + self.assertFalse(self._tx(self._button_msg(btn))) + + for enabled in (True, False): + self._rx(self._pcm_status_msg(enabled)) + self.assertEqual(enabled, self._tx(self._button_msg(Buttons.CANCEL))) + + # Uses stock longitudinal, allow no longitudinal actuation + def test_brake_safety_check(self, stock_longitudinal=True): + super().test_brake_safety_check(stock_longitudinal=stock_longitudinal) + + def test_gas_safety_check(self, stock_longitudinal=True): + super().test_gas_safety_check(stock_longitudinal=stock_longitudinal) + + if __name__ == "__main__": unittest.main()