From c7de259c8329ed923530b5760c8307224817064e Mon Sep 17 00:00:00 2001 From: FrogAi <91348155+FrogAi@users.noreply.github.com> Date: Tue, 27 Feb 2024 16:34:47 -0700 Subject: [PATCH] CC longitudinal control Co-Authored-By: Eric Brown <13560103+nworb-cire@users.noreply.github.com> --- panda/board/safety/safety_gm.h | 47 +++++++++++++++++++----------- panda/python/__init__.py | 1 + selfdrive/car/gm/carcontroller.py | 9 ++++++ selfdrive/car/gm/gmcan.py | 48 ++++++++++++++++++++++++++++++- selfdrive/car/gm/interface.py | 25 +++++++++++++++- selfdrive/car/gm/values.py | 1 + 6 files changed, 112 insertions(+), 19 deletions(-) diff --git a/panda/board/safety/safety_gm.h b/panda/board/safety/safety_gm.h index 241e09f..309cbd3 100644 --- a/panda/board/safety/safety_gm.h +++ b/panda/board/safety/safety_gm.h @@ -46,26 +46,26 @@ const CanMsg GM_ASCM_TX_MSGS[] = {{0x180, 0, 4}, {0x409, 0, 7}, {0x40A, 0, 7}, { {0x315, 2, 5}, // ch bus {0x104c006c, 3, 3}, {0x10400060, 3, 5}}; // gmlan -const CanMsg GM_CAM_TX_MSGS[] = {{0x180, 0, 4}, // pt bus +const CanMsg GM_CAM_TX_MSGS[] = {{0x180, 0, 4}, {0x1E1, 0, 7}, // pt bus {0x1E1, 2, 7}, {0x184, 2, 8}}; // camera bus +const CanMsg GM_CAM_LONG_TX_MSGS[] = {{0x180, 0, 4}, {0x315, 0, 5}, {0x2CB, 0, 8}, {0x370, 0, 6}, // pt bus + {0x1E1, 2, 7}, {0x184, 2, 8}}; // camera bus + const CanMsg GM_SDGM_TX_MSGS[] = {{0x180, 0, 4}, {0x1E1, 0, 7}, // pt bus {0x184, 2, 8}}; // camera bus -const CanMsg GM_CAM_LONG_TX_MSGS[] = {{0x180, 0, 4}, {0x315, 0, 5}, {0x2CB, 0, 8}, {0x370, 0, 6}, // pt bus - {0x184, 2, 8}}; // camera bus +const CanMsg GM_CC_LONG_TX_MSGS[] = {{0x180, 0, 4}, {0x1E1, 0, 7}, // pt bus + {0x184, 2, 8}, {0x1E1, 2, 7}}; // camera bus // TODO: do checksum and counter checks. Add correct timestep, 0.1s for now. RxCheck gm_rx_checks[] = { {.msg = {{0x184, 0, 8, .frequency = 10U}, { 0 }, { 0 }}}, {.msg = {{0x34A, 0, 5, .frequency = 10U}, { 0 }, { 0 }}}, {.msg = {{0x1E1, 0, 7, .frequency = 10U}, // Non-SDGM Car - {0x1E1, 2, 7, .frequency = 10U}}}, // SDGM Car + {0x1E1, 2, 7, .frequency = 100000U}}}, // SDGM Car {.msg = {{0xF1, 0, 6, .frequency = 10U}, // Non-SDGM Car - {0xF1, 2, 6, .frequency = 10U}}}, // SDGM Car - {.msg = {{0xBE, 0, 6, .frequency = 10U}, // Volt, Silverado, Acadia Denali - {0xBE, 0, 7, .frequency = 10U}, // Bolt EUV - {0xBE, 0, 8, .frequency = 10U}}}, // Escalade + {0xF1, 2, 6, .frequency = 100000U}}}, // SDGM Car {.msg = {{0x1C4, 0, 8, .frequency = 10U}, { 0 }, { 0 }}}, {.msg = {{0xC9, 0, 8, .frequency = 10U}, { 0 }, { 0 }}}, }; @@ -73,6 +73,7 @@ RxCheck gm_rx_checks[] = { const uint16_t GM_PARAM_HW_CAM = 1; const uint16_t GM_PARAM_HW_CAM_LONG = 2; const uint16_t GM_PARAM_HW_SDGM = 4; +const uint16_t GM_PARAM_CC_LONG = 8; enum { GM_BTN_UNPRESS = 1, @@ -84,6 +85,7 @@ enum { enum {GM_ASCM, GM_CAM, GM_SDGM} gm_hw = GM_ASCM; bool gm_cam_long = false; bool gm_pcm_cruise = false; +bool gm_cc_long = false; static void handle_gm_wheel_buttons(const CANPacket_t *to_push) { int button = (GET_BYTE(to_push, 5) & 0x70U) >> 4; @@ -127,7 +129,7 @@ static void gm_rx_hook(const CANPacket_t *to_push) { } // ACC steering wheel buttons (GM_CAM and GM_SDGM are tied to the PCM) - if ((addr == 0x1E1) && (!gm_pcm_cruise || gas_interceptor_detected || gm_cc_long) && (gm_hw != GM_SDGM)) { + if ((addr == 0x1E1) && (!gm_pcm_cruise || gm_cc_long) && (gm_hw != GM_SDGM)) { handle_gm_wheel_buttons(to_push); } @@ -207,11 +209,16 @@ static bool gm_tx_hook(const CANPacket_t *to_send) { } // BUTTONS: used for resume spamming and cruise cancellation with stock longitudinal - if ((addr == 0x1E1) && gm_pcm_cruise) { + if ((addr == 0x1E1) && (gm_pcm_cruise || gm_cc_long)) { int button = (GET_BYTE(to_send, 5) >> 4) & 0x7U; - bool allowed_cancel = (button == 6) && cruise_engaged_prev; - if (!allowed_cancel) { + bool allowed_btn = (button == GM_BTN_CANCEL) && cruise_engaged_prev; + // For standard CC, allow spamming of SET / RESUME + if (gm_cc_long) { + allowed_btn |= cruise_engaged_prev && (button == GM_BTN_SET || button == GM_BTN_RESUME || button == GM_BTN_UNPRESS); + } + + if (!allowed_btn) { tx = false; } } @@ -271,16 +278,22 @@ static safety_config gm_init(uint16_t param) { } else { } -#ifdef ALLOW_DEBUG - gm_cam_long = GET_FLAG(param, GM_PARAM_HW_CAM_LONG); -#endif - gm_pcm_cruise = (gm_hw == GM_CAM) && !gm_cam_long || (gm_hw == GM_SDGM); + gm_cc_long = GET_FLAG(param, GM_PARAM_CC_LONG); + gm_cam_long = GET_FLAG(param, GM_PARAM_HW_CAM_LONG) && !gm_cc_long; + gm_pcm_cruise = ((gm_hw == GM_CAM) && (!gm_cam_long || gm_cc_long) && !gm_force_ascm && !gm_pedal_long) || (gm_hw == GM_SDGM); safety_config ret = BUILD_SAFETY_CFG(gm_rx_checks, GM_ASCM_TX_MSGS); if (gm_hw == GM_CAM) { - 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); + if (gm_cc_long) { + ret = BUILD_SAFETY_CFG(gm_rx_checks, GM_CC_LONG_TX_MSGS); + } else if (gm_cam_long) { + ret = BUILD_SAFETY_CFG(gm_rx_checks, GM_CAM_LONG_TX_MSGS); + } else { + ret = BUILD_SAFETY_CFG(gm_rx_checks, GM_CAM_TX_MSGS); + } } else if (gm_hw == GM_SDGM) { ret = BUILD_SAFETY_CFG(gm_rx_checks, GM_SDGM_TX_MSGS); + } return ret; } diff --git a/panda/python/__init__.py b/panda/python/__init__.py index 871f536..1e60947 100644 --- a/panda/python/__init__.py +++ b/panda/python/__init__.py @@ -232,6 +232,7 @@ class Panda: FLAG_GM_HW_CAM = 1 FLAG_GM_HW_CAM_LONG = 2 FLAG_GM_HW_SDGM = 4 + FLAG_GM_CC_LONG = 8 FLAG_GM_HW_ASCM_LONG = 16 FLAG_FORD_LONG_CONTROL = 1 diff --git a/selfdrive/car/gm/carcontroller.py b/selfdrive/car/gm/carcontroller.py index 26f2de4..e5abab1 100644 --- a/selfdrive/car/gm/carcontroller.py +++ b/selfdrive/car/gm/carcontroller.py @@ -10,6 +10,8 @@ from openpilot.selfdrive.car.gm.values import DBC, CanBus, CarControllerParams, VisualAlert = car.CarControl.HUDControl.VisualAlert NetworkLocation = car.CarParams.NetworkLocation LongCtrlState = car.CarControl.Actuators.LongControlState +GearShifter = car.CarState.GearShifter +TransmissionType = car.CarParams.TransmissionType # Camera cancels up to 0.1s after brake is pressed, ECM allows 0.5s CAMERA_CANCEL_DELAY_FRAMES = 10 @@ -24,10 +26,12 @@ class CarController: self.apply_steer_last = 0 self.apply_gas = 0 self.apply_brake = 0 + self.apply_speed = 0 self.frame = 0 self.last_steer_frame = 0 self.last_button_frame = 0 self.cancel_counter = 0 + self.pedal_steady = 0. self.lka_steering_cmd_counter = 0 self.lka_icon_status_last = (False, False) @@ -107,6 +111,10 @@ class CarController: at_full_stop = CC.longActive and CS.out.standstill near_stop = CC.longActive and (CS.out.vEgo < self.params.NEAR_STOP_BRAKE_PHASE) + if self.CP.flags & GMFlags.CC_LONG.value: + if CC.longActive and CS.out.vEgo > self.CP.minEnableSpeed: + # Using extend instead of append since the message is only sent intermittently + can_sends.extend(gmcan.create_gm_cc_spam_command(self.packer_pt, self, CS, actuators)) if self.CP.carFingerprint not in CC_ONLY_CAR: friction_brake_bus = CanBus.CHASSIS # GM Camera exceptions @@ -168,6 +176,7 @@ class CarController: new_actuators.steerOutputCan = self.apply_steer_last new_actuators.gas = self.apply_gas new_actuators.brake = self.apply_brake + new_actuators.speed = self.apply_speed self.frame += 1 return new_actuators, can_sends diff --git a/selfdrive/car/gm/gmcan.py b/selfdrive/car/gm/gmcan.py index bd1e29c..8ebb3fd 100644 --- a/selfdrive/car/gm/gmcan.py +++ b/selfdrive/car/gm/gmcan.py @@ -1,5 +1,9 @@ +import math + +from openpilot.common.conversions import Conversions as CV +from openpilot.common.realtime import DT_CTRL from openpilot.selfdrive.car import make_can_msg -from openpilot.selfdrive.car.gm.values import CAR +from openpilot.selfdrive.car.gm.values import CAR, CruiseButtons, CanBus def create_buttons(packer, bus, idx, button): @@ -171,3 +175,45 @@ def create_lka_icon_command(bus, active, critical, steer): else: dat = b"\x00\x00\x00" return make_can_msg(0x104c006c, dat, bus) + + +def create_gm_cc_spam_command(packer, controller, CS, actuators): + # TODO: Cleanup the timing - normal is every 30ms... + + cruiseBtn = CruiseButtons.INIT + + # if controller.params_.get_bool("IsMetric"): + # accel = actuators.accel * CV.MS_TO_KPH # m/s/s to km/h/s + # else: + # accel = actuators.accel * CV.MS_TO_MPH # m/s/s to mph/s + accel = actuators.accel * CV.MS_TO_MPH # m/s/s to mph/s + speedSetPoint = int(round(CS.out.cruiseState.speed * CV.MS_TO_MPH)) + + RATE_UP_MAX = 0.2 # may be lower on new/euro cars + RATE_DOWN_MAX = 0.2 # may be lower on new/euro cars + + if speedSetPoint == CS.CP.minEnableSpeed and accel < -1: + cruiseBtn = CruiseButtons.CANCEL + controller.apply_speed = 0 + rate = 0.04 + elif accel < 0: + cruiseBtn = CruiseButtons.DECEL_SET + rate = max(-1 / accel, RATE_DOWN_MAX) + controller.apply_speed = speedSetPoint - 1 + elif accel > 0: + cruiseBtn = CruiseButtons.RES_ACCEL + rate = max(1 / accel, RATE_UP_MAX) + controller.apply_speed = speedSetPoint + 1 + else: + controller.apply_speed = speedSetPoint + rate = float('inf') + + # Check rlogs closely - our message shouldn't show up on the pt bus for us + # Or bus 2, since we're forwarding... but I think it does + # TODO: Cleanup the timing - normal is every 30ms... + if (cruiseBtn != CruiseButtons.INIT) and ((controller.frame - controller.last_button_frame) * DT_CTRL > rate): + controller.last_button_frame = controller.frame + idx = (CS.buttons_counter + 1) % 4 # Need to predict the next idx for '22-23 EUV + return [create_buttons(packer, CanBus.POWERTRAIN, idx, cruiseBtn)] + else: + return [] diff --git a/selfdrive/car/gm/interface.py b/selfdrive/car/gm/interface.py index ac54fff..a6f895d 100755 --- a/selfdrive/car/gm/interface.py +++ b/selfdrive/car/gm/interface.py @@ -107,7 +107,7 @@ class CarInterface(CarInterfaceBase): ret.longitudinalTuning.kiBP = [0.] if candidate in CAMERA_ACC_CAR: - ret.experimentalLongitudinalAvailable = True + ret.experimentalLongitudinalAvailable = candidate not in CC_ONLY_CAR ret.networkLocation = NetworkLocation.fwdCamera ret.radarUnavailable = True # no radar ret.pcmCruise = True @@ -316,6 +316,26 @@ class CarInterface(CarInterfaceBase): ret.steerRatio = 17.7 CarInterfaceBase.configure_torque_tune(candidate, ret.lateralTuning) + elif candidate in CC_ONLY_CAR: + ret.flags |= GMFlags.CC_LONG.value + ret.safetyConfigs[0].safetyParam |= Panda.FLAG_GM_CC_LONG + ret.radarUnavailable = True + ret.experimentalLongitudinalAvailable = False + ret.minEnableSpeed = 24 * CV.MPH_TO_MS + ret.openpilotLongitudinalControl = True + ret.pcmCruise = False + + ret.longitudinalTuning.deadzoneBP = [0.] + ret.longitudinalTuning.deadzoneV = [0.56] # == 2 km/h/s, 1.25 mph/s + ret.stoppingDecelRate = 11.18 # == 25 mph/s (.04 rate) + ret.longitudinalActuatorDelayLowerBound = 1. # TODO: measure this + ret.longitudinalActuatorDelayUpperBound = 2. + + ret.longitudinalTuning.kpBP = [10.7, 10.8, 28.] # 10.7 m/s == 24 mph + ret.longitudinalTuning.kpV = [0., 20., 20.] # set lower end to 0 since we can't drive below that speed + ret.longitudinalTuning.kiBP = [0.] + ret.longitudinalTuning.kiV = [0.1] + if candidate in CC_ONLY_CAR: ret.safetyConfigs[0].safetyParam |= Panda.FLAG_GM_NO_ACC @@ -352,6 +372,9 @@ class CarInterface(CarInterfaceBase): if ret.vEgo < self.CP.minSteerSpeed: events.add(EventName.belowSteerSpeed) + if (self.CP.flags & GMFlags.CC_LONG.value) and ret.vEgo < self.CP.minEnableSpeed and ret.cruiseState.enabled: + events.add(EventName.speedTooLow) + ret.events = events.to_msg() return ret diff --git a/selfdrive/car/gm/values.py b/selfdrive/car/gm/values.py index 0092ac6..2e1d2c6 100644 --- a/selfdrive/car/gm/values.py +++ b/selfdrive/car/gm/values.py @@ -179,6 +179,7 @@ class CanBus: DROPPED = 192 class GMFlags(IntFlag): + CC_LONG = 2 NO_ACCELERATOR_POS_MSG = 8