#pragma once #include "opendbc/safety/safety_declarations.h" // TODO: do checksum and counter checks. Add correct timestep, 0.1s for now. #define GM_COMMON_RX_CHECKS \ {.msg = {{0x184, 0, 8, 10U, .ignore_checksum = true, .ignore_counter = true, .ignore_quality_flag = true}, { 0 }, { 0 }}}, \ {.msg = {{0x34A, 0, 5, 10U, .ignore_checksum = true, .ignore_counter = true, .ignore_quality_flag = true}, { 0 }, { 0 }}}, \ {.msg = {{0x1E1, 0, 7, 10U, .ignore_checksum = true, .ignore_counter = true, .ignore_quality_flag = true}, { 0 }, { 0 }}}, \ {.msg = {{0xBE, 0, 6, 10U, .ignore_checksum = true, .ignore_counter = true, .ignore_quality_flag = true}, /* Volt, Silverado, Acadia Denali */ \ {0xBE, 0, 7, 10U, .ignore_checksum = true, .ignore_counter = true, .ignore_quality_flag = true}, /* Bolt EUV */ \ {0xBE, 0, 8, 10U, .ignore_checksum = true, .ignore_counter = true, .ignore_quality_flag = true}}}, /* Escalade */ \ {.msg = {{0x1C4, 0, 8, 10U, .ignore_checksum = true, .ignore_counter = true, .ignore_quality_flag = true}, { 0 }, { 0 }}}, \ {.msg = {{0xC9, 0, 8, 10U, .ignore_checksum = true, .ignore_counter = true, .ignore_quality_flag = true}, { 0 }, { 0 }}}, \ static const LongitudinalLimits *gm_long_limits; enum { GM_BTN_UNPRESS = 1, GM_BTN_RESUME = 2, GM_BTN_SET = 3, GM_BTN_CANCEL = 6, }; typedef enum { GM_ASCM, GM_CAM } GmHardware; static GmHardware gm_hw = GM_ASCM; static bool gm_pcm_cruise = false; static void gm_rx_hook(const CANPacket_t *msg) { const int GM_STANDSTILL_THRSLD = 10; // 0.311kph if (msg->bus == 0U) { if (msg->addr == 0x184U) { int torque_driver_new = ((msg->data[6] & 0x7U) << 8) | msg->data[7]; torque_driver_new = to_signed(torque_driver_new, 11); // update array of samples update_sample(&torque_driver, torque_driver_new); } // sample rear wheel speeds if (msg->addr == 0x34AU) { int left_rear_speed = (msg->data[0] << 8) | msg->data[1]; int right_rear_speed = (msg->data[2] << 8) | msg->data[3]; vehicle_moving = (left_rear_speed > GM_STANDSTILL_THRSLD) || (right_rear_speed > GM_STANDSTILL_THRSLD); } // ACC steering wheel buttons (GM_CAM is tied to the PCM) if ((msg->addr == 0x1E1U) && !gm_pcm_cruise) { int button = (msg->data[5] & 0x70U) >> 4; // enter controls on falling edge of set or rising edge of resume (avoids fault) bool set = (button != GM_BTN_SET) && (cruise_button_prev == GM_BTN_SET); bool res = (button == GM_BTN_RESUME) && (cruise_button_prev != GM_BTN_RESUME); if (set || res) { controls_allowed = true; } // exit controls on cancel press if (button == GM_BTN_CANCEL) { controls_allowed = false; } cruise_button_prev = button; } // Reference for brake pressed signals: // https://github.com/commaai/openpilot/blob/master/selfdrive/car/gm/carstate.py if ((msg->addr == 0xBEU) && (gm_hw == GM_ASCM)) { brake_pressed = msg->data[1] >= 8U; } if ((msg->addr == 0xC9U) && (gm_hw == GM_CAM)) { brake_pressed = GET_BIT(msg, 40U); } if (msg->addr == 0x1C4U) { gas_pressed = msg->data[5] != 0U; // enter controls on rising edge of ACC, exit controls when ACC off if (gm_pcm_cruise) { bool cruise_engaged = (msg->data[1] >> 5) != 0U; pcm_cruise_check(cruise_engaged); } } if (msg->addr == 0xBDU) { regen_braking = (msg->data[0] >> 4) != 0U; } } } static bool gm_tx_hook(const CANPacket_t *msg) { const TorqueSteeringLimits GM_STEERING_LIMITS = { .max_torque = 300, .max_rate_up = 10, .max_rate_down = 15, .driver_torque_allowance = 65, .driver_torque_multiplier = 4, .max_rt_delta = 128, .type = TorqueDriverLimited, }; bool tx = true; // BRAKE: safety check if (msg->addr == 0x315U) { int brake = ((msg->data[0] & 0xFU) << 8) + msg->data[1]; brake = (0x1000 - brake) & 0xFFF; if (longitudinal_brake_checks(brake, *gm_long_limits)) { tx = false; } } // LKA STEER: safety check if (msg->addr == 0x180U) { int desired_torque = ((msg->data[0] & 0x7U) << 8) + msg->data[1]; desired_torque = to_signed(desired_torque, 11); bool steer_req = GET_BIT(msg, 3U); if (steer_torque_cmd_checks(desired_torque, steer_req, GM_STEERING_LIMITS)) { tx = false; } } // GAS/REGEN: safety check if (msg->addr == 0x2CBU) { bool apply = GET_BIT(msg, 0U); // convert float CAN signal to an int for gas checks: 22534 / 0.125 = 180272 int gas_regen = (((msg->data[1] & 0x7U) << 16) | (msg->data[2] << 8) | msg->data[3]) - 180272U; bool violation = false; // Allow apply bit in pre-enabled and overriding states violation |= !controls_allowed && apply; violation |= longitudinal_gas_checks(gas_regen, *gm_long_limits); if (violation) { tx = false; } } // BUTTONS: used for resume spamming and cruise cancellation with stock longitudinal if ((msg->addr == 0x1E1U) && gm_pcm_cruise) { int button = (msg->data[5] >> 4) & 0x7U; bool allowed_cancel = (button == 6) && cruise_engaged_prev; if (!allowed_cancel) { tx = false; } } return tx; } static safety_config gm_init(uint16_t param) { const uint16_t GM_PARAM_HW_CAM = 1; const uint16_t GM_PARAM_EV = 4; // common safety checks assume unscaled integer values static const int GM_GAS_TO_CAN = 8; // 1 / 0.125 static const LongitudinalLimits GM_ASCM_LONG_LIMITS = { .max_gas = 1018 * GM_GAS_TO_CAN, .min_gas = -650 * GM_GAS_TO_CAN, .inactive_gas = -650 * GM_GAS_TO_CAN, .max_brake = 400, }; static const CanMsg GM_ASCM_TX_MSGS[] = {{0x180, 0, 4, .check_relay = true}, {0x409, 0, 7, .check_relay = false}, {0x40A, 0, 7, .check_relay = false}, {0x2CB, 0, 8, .check_relay = true}, {0x370, 0, 6, .check_relay = false}, // pt bus {0xA1, 1, 7, .check_relay = false}, {0x306, 1, 8, .check_relay = false}, {0x308, 1, 7, .check_relay = false}, {0x310, 1, 2, .check_relay = false}, // obs bus {0x315, 2, 5, .check_relay = false}}; // ch bus static const LongitudinalLimits GM_CAM_LONG_LIMITS = { .max_gas = 1346 * GM_GAS_TO_CAN, .min_gas = -540 * GM_GAS_TO_CAN, .inactive_gas = -500 * GM_GAS_TO_CAN, .max_brake = 400, }; // block PSCMStatus (0x184); forwarded through openpilot to hide an alert from the camera static const CanMsg GM_CAM_LONG_TX_MSGS[] = {{0x180, 0, 4, .check_relay = true}, {0x315, 0, 5, .check_relay = true}, {0x2CB, 0, 8, .check_relay = true}, {0x370, 0, 6, .check_relay = true}, // pt bus {0x184, 2, 8, .check_relay = true}}; // camera bus static RxCheck gm_rx_checks[] = { GM_COMMON_RX_CHECKS }; static RxCheck gm_ev_rx_checks[] = { GM_COMMON_RX_CHECKS {.msg = {{0xBD, 0, 7, 40U, .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}, // pt bus {0x1E1, 2, 7, .check_relay = false}, {0x184, 2, 8, .check_relay = true}}; // camera bus gm_hw = GET_FLAG(param, GM_PARAM_HW_CAM) ? GM_CAM : GM_ASCM; if (gm_hw == GM_ASCM) { gm_long_limits = &GM_ASCM_LONG_LIMITS; } else if (gm_hw == GM_CAM) { gm_long_limits = &GM_CAM_LONG_LIMITS; } else { } bool gm_cam_long = false; #ifdef ALLOW_DEBUG const uint16_t GM_PARAM_HW_CAM_LONG = 2; gm_cam_long = GET_FLAG(param, GM_PARAM_HW_CAM_LONG); #endif gm_pcm_cruise = (gm_hw == GM_CAM) && !gm_cam_long; safety_config ret; if (gm_hw == GM_CAM) { // FIXME: cppcheck thinks that gm_cam_long is always false. This is not true // if ALLOW_DEBUG is defined but cppcheck is run without ALLOW_DEBUG // cppcheck-suppress knownConditionTrueFalse ret = gm_cam_long ? BUILD_SAFETY_CFG(gm_rx_checks, GM_CAM_LONG_TX_MSGS) : BUILD_SAFETY_CFG(gm_rx_checks, GM_CAM_TX_MSGS); } else { ret = BUILD_SAFETY_CFG(gm_rx_checks, GM_ASCM_TX_MSGS); } const bool gm_ev = GET_FLAG(param, GM_PARAM_EV); if (gm_ev) { SET_RX_CHECKS(gm_ev_rx_checks, ret); } // ASCM does not forward any messages if (gm_hw == GM_ASCM) { ret.disable_forwarding = true; } return ret; } const safety_hooks gm_hooks = { .init = gm_init, .rx = gm_rx_hook, .tx = gm_tx_hook, };