From 2123be2eca809b27e1ce0759536942708940b052 Mon Sep 17 00:00:00 2001 From: firestar5683 <168790843+firestar5683@users.noreply.github.com> Date: Tue, 30 Sep 2025 10:43:37 -0500 Subject: [PATCH] Torque --- panda/board/obj/bootstub.panda.bin | Bin 14876 -> 14876 bytes panda/board/obj/bootstub.panda_h7.bin | Bin 16944 -> 16944 bytes panda/board/obj/panda.bin.signed | Bin 60612 -> 61444 bytes panda/board/obj/panda_h7.bin.signed | Bin 69440 -> 70264 bytes panda/board/safety/safety_gm.h | 46 ++++++++++++---- selfdrive/car/gm/carcontroller.py | 72 +++++++++++++++++++++----- selfdrive/car/gm/carstate.py | 23 +++++--- selfdrive/car/gm/gmcan.py | 31 ++++++----- selfdrive/car/gm/interface.py | 21 +++++--- selfdrive/car/gm/values.py | 43 ++++++++++----- 10 files changed, 166 insertions(+), 70 deletions(-) diff --git a/panda/board/obj/bootstub.panda.bin b/panda/board/obj/bootstub.panda.bin index 021ae9daf05903a4fd4a49838c67d2b8dca916d1..ce085622835b36c2e6362c8c2972429e0136f168 100755 GIT binary patch delta 20 bcmbPJGN)w2En{|5BU3Yz#H7s+jjh=LS-c0f delta 20 bcmbPJGN)w2En{{I^Aw{rv&790jjh=LTqXza diff --git a/panda/board/obj/bootstub.panda_h7.bin b/panda/board/obj/bootstub.panda_h7.bin index c2066c76b0006ef4d9590eb5b973c765d15c597e..89611da9382109ea9843d6c0b6e055c4ad74b7de 100755 GIT binary patch delta 22 ccmdnc!ngs5X4$Zt8kw4zBqnWMXw%OM0ADu;< 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 S3OhBk;xFU5d%-#E$^QozrBUSo delta 13516 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 oa@WKcKkgebZ*TrNPx# diff --git a/panda/board/obj/panda_h7.bin.signed b/panda/board/obj/panda_h7.bin.signed index 65706ef0c9ccb23bf73e24a52c839c48e9ebed73..7fb190380df9be1ada3fd8a448576ce4c1f63ae6 100644 GIT binary patch delta 15016 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 L>d!tYCX4?M3Y5gm delta 14651 zcmb8W30PA{7dL+IO#%sPSX6dy5|&6ngJ{LA#(-f73hpRYLC~Vog4Qa8YNEI=tva|= zYqc(|)m9CNH7bIlw!YRjap|kIwzRdT;;W4c@4bX9|1$}+w%_~zp6BCve#|~|=A1KU z&dl7f2f583a<$EVB(Z8Q#W(#A5dZk=se`k7;-M0X9}S2F$N z04M^y4tN9bmW0yst01BUtOBfwq4)@}3GhB(1LSQ1;KBAlcmVJz;5eWGa29X@@Fn0n z;3nWM;3vRufX9Gm00w}GwnNB)egy!3K(83>Uo#Jf3=tp+kOCM27zr2+Py@yRrT|_8 z^vwP!#Bs+j{}n^tTtGSCZ9p|(IY1Bi0?-I(0l4~@`4Zc&c4x--6uwl}Gaw$QC;rdy zWg>d&p;5;pwEwH(Kl6IhJq>$8od0sXr{4cVIK?&9|JBL=%KQK8crS3}&`X>-aRq%t zZ6$+I5l2E&XcZ@o%Kr%ci!&JqV(yz%=|^ZQcZFBUeu|&!NzniK2u1PQdgp%xDp1ME z4j0^<9+Ix8s>mRkph1+!`LN)F6#s-bL7zTE@$t(xD}>=pGnXG7ScaSZE4`Dh8s!og%^bI`?pDpkjh_tXDMG# zXcJyB#%ITm=~Z~)MROo&;q!Jah8Xqz;s6UpMKm>7S3MsuAf;i8r6x*0sXoB6+25y$ zj3eTrmRxwD=pB|zN@pxtZc%tZ*P<&gbiJp(wT4YEiZC_kgVp}mR{joCRaqJXELMjX4@oC3)DZE@eK0r z6LcAHhXux6f^a9`7T^J(9l-qrEfahd=#x$K4Q?Vugh&XJMN3Re&~&dDzu-?OehgqB zApH~cuGb2XWPPCbG7?L4tB(c;e!fzYMuU9^@kF$arO^UkiQhzMPy$#4SosOs?>jJ2 zEaa-Ve@;r+=;m-ORN$!bpgq$SHXi&0QGS^rlK@e%7gq z49vBKr##fT7NU@sh1fWHZ6PVDVdD`mU>NRE8IVBjnup#D2%~n+LhA!miIsXv>Tlw! zoQ%kpZ}3#_)JpaGjutIerI03q<~LHb&81kK=BMI1lZyY0C0Vo~l^(TWTX_Ohp4v<)>PB%0DMqUJIpU3T{F64h{ILg$!C$ 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 diff --git a/panda/board/safety/safety_gm.h b/panda/board/safety/safety_gm.h index dfa936e69..8c51bdf51 100644 --- a/panda/board/safety/safety_gm.h +++ b/panda/board/safety/safety_gm.h @@ -10,16 +10,30 @@ const SteeringLimits GM_STEERING_LIMITS = { }; const LongitudinalLimits GM_ASCM_LONG_LIMITS = { - .max_gas = 3072, - .min_gas = 1404, - .inactive_gas = 1404, + .max_gas = 7168, + .min_gas = 5500, + .inactive_gas = 5500, + .max_brake = 400, +}; + +const LongitudinalLimits GM_ASCM_LONG_LIMITS_SPORT = { + .max_gas = 8191, + .min_gas = 5500, + .inactive_gas = 5500, .max_brake = 400, }; const LongitudinalLimits GM_CAM_LONG_LIMITS = { - .max_gas = 3400, - .min_gas = 1514, - .inactive_gas = 1554, + .max_gas = 7496, + .min_gas = 5610, + .inactive_gas = 5650, + .max_brake = 400, +}; + +const LongitudinalLimits GM_CAM_LONG_LIMITS_SPORT = { + .max_gas = 8848, + .min_gas = 5610, + .inactive_gas = 5650, .max_brake = 400, }; @@ -143,7 +157,7 @@ static void gm_rx_hook(const CANPacket_t *to_push) { } if ((addr == 0xC9) && ((gm_hw == GM_CAM) || (gm_hw == GM_SDGM))) { - brake_pressed = GET_BIT(to_push, 40U); + brake_pressed = GET_BIT(to_push, 40U) != 0U; } if (addr == 0xC9) { @@ -242,10 +256,10 @@ static bool gm_tx_hook(const CANPacket_t *to_send) { // GAS/REGEN: safety check if (addr == 0x2CB) { bool apply = GET_BIT(to_send, 0U); - int gas_regen = ((GET_BYTE(to_send, 2) & 0x7FU) << 5) + ((GET_BYTE(to_send, 3) & 0xF8U) >> 3); + int gas_regen = ((GET_BYTE(to_send, 1) & 0x1U) << 13) + ((GET_BYTE(to_send, 2) & 0xFFU) << 5) + ((GET_BYTE(to_send, 3) & 0xF8U) >> 3); bool violation = false; - // Allow apply bit in pre-enabled and overriding states, except for inactive gas // Allow apply bit in pre-enabled and overriding states + // Allow apply bit in pre-enabled and overriding states violation |= !controls_allowed && apply; violation |= longitudinal_gas_checks(gas_regen, *gm_long_limits); @@ -324,6 +338,8 @@ static int gm_fwd_hook(int bus_num, int addr) { } static safety_config gm_init(uint16_t param) { + sport_mode = alternative_experience & ALT_EXP_RAISE_LONGITUDINAL_LIMITS_TO_ISO_MAX; + if GET_FLAG(param, GM_PARAM_HW_CAM) { gm_hw = GM_CAM; } else if GET_FLAG(param, GM_PARAM_HW_SDGM) { @@ -335,9 +351,17 @@ static safety_config gm_init(uint16_t param) { gm_force_ascm = GET_FLAG(param, GM_PARAM_HW_ASCM_LONG); if (gm_hw == GM_ASCM || gm_force_ascm) { - gm_long_limits = &GM_ASCM_LONG_LIMITS; + if (sport_mode) { + gm_long_limits = &GM_ASCM_LONG_LIMITS_SPORT; + } else { + gm_long_limits = &GM_ASCM_LONG_LIMITS; + } } else if ((gm_hw == GM_CAM) || (gm_hw == GM_SDGM)) { - gm_long_limits = &GM_CAM_LONG_LIMITS; + if (sport_mode) { + gm_long_limits = &GM_CAM_LONG_LIMITS_SPORT; + } else { + gm_long_limits = &GM_CAM_LONG_LIMITS; + } } else { } diff --git a/selfdrive/car/gm/carcontroller.py b/selfdrive/car/gm/carcontroller.py index ef9a32789..8e91a7d1e 100644 --- a/selfdrive/car/gm/carcontroller.py +++ b/selfdrive/car/gm/carcontroller.py @@ -1,5 +1,7 @@ from typing import Tuple import time +import math +from openpilot.common.swaglog import cloudlog from cereal import car from openpilot.common.conversions import Conversions as CV from openpilot.common.filter_simple import FirstOrderFilter @@ -64,13 +66,20 @@ class CarController(CarControllerBase): self.params = CarControllerParams(self.CP) self.params_ = Params() + self.mass = CP.mass + self.tireRadius = 0.075 * CP.wheelbase + 0.1453 + self.frontalArea = 1.05 * CP.wheelbase + 0.0679 + self.coeffDrag = 0.30 + self.airDensity = 1.225 + + + self.packer_pt = CANPacker(DBC[self.CP.carFingerprint]['pt']) self.packer_obj = CANPacker(DBC[self.CP.carFingerprint]['radar']) self.packer_ch = CANPacker(DBC[self.CP.carFingerprint]['chassis']) # FrogPilot variables self.accel_g = 0.0 - self.pitch = FirstOrderFilter(0., 0.09 * 4, DT_CTRL * 4) # runs at 25 Hz self.accel_g = 0.0 self.regen_paddle_pressed = False @@ -330,17 +339,19 @@ class CarController(CarControllerBase): if self.frame % 4 == 0: stopping = actuators.longControlState == LongCtrlState.stopping - # Pitch compensated acceleration; - # TODO: include future pitch (sm['modelDataV2'].orientation.y) to account for long actuator delay - if frogpilot_toggles.long_pitch and len(CC.orientationNED) > 1: - self.pitch.update(CC.orientationNED[1]) - self.accel_g = ACCELERATION_DUE_TO_GRAVITY * apply_deadzone(self.pitch.x, PITCH_DEADZONE) # driving uphill is positive pitch - accel += self.accel_g - brake_accel = actuators.accel + self.accel_g * interp(CS.out.vEgo, BRAKE_PITCH_FACTOR_BP, BRAKE_PITCH_FACTOR_V) - at_full_stop = CC.longActive and CS.out.standstill near_stop = CC.longActive and (CS.out.vEgo < self.params.NEAR_STOP_BRAKE_PHASE) interceptor_gas_cmd = 0 + # --- Regen scaling for ACC-only cars --- + # For ACC-only cars, simulate maximum paddle hold regen + if not self.CP.enableGasInterceptor and self.CP.carFingerprint in CC_REGEN_PADDLE_CAR: + # use regen table gain to compute effective accel + _, press_regen_paddle = self.calc_pedal_command(actuators.accel, True, CS.out.vEgo) + # if simulated paddle pressed, treat as stronger regen + use_regen = press_regen_paddle + else: + use_regen = False + # --- End regen scaling insert --- if not CC.longActive: # ASCM sends max regen when not enabled self.apply_gas = self.params.INACTIVE_REGEN @@ -349,9 +360,38 @@ class CarController(CarControllerBase): self.apply_gas = self.params.INACTIVE_REGEN self.apply_brake = int(min(-100 * frogpilot_toggles.stopAccel, self.params.MAX_BRAKE)) else: - # Normal operation - self.apply_gas = int(round(interp(accel, self.params.GAS_LOOKUP_BP, self.params.GAS_LOOKUP_V))) + if len(CC.orientationNED) == 3 and CS.out.vEgo > self.CP.vEgoStopping: + accel_due_to_pitch = math.sin(CC.orientationNED[1]) * ACCELERATION_DUE_TO_GRAVITY + else: + accel_due_to_pitch = 0.0 + + if frogpilot_toggles.sport_plus: + gas_max = self.params.MAX_GAS_PLUS + accel_max = self.params.ACCEL_MAX_PLUS + else: + gas_max = self.params.MAX_GAS + accel_max = self.params.ACCEL_MAX + + accel = clip(actuators.accel + accel_due_to_pitch, self.params.ACCEL_MIN, accel_max) + torque = self.tireRadius * ((self.mass*accel) + (0.5*self.coeffDrag*self.frontalArea*self.airDensity*CS.out.vEgo**2)) + + scaled_torque = torque + self.params.ZERO_GAS + # --- Regen torque scaling for ACC-only cars --- + # apply full paddle regen curve when simulated regen is active + if use_regen: + min_regen = self.params.GAS_LOOKUP_V[0] # max regen from lookup + else: + min_regen = self.params.MAX_ACC_REGEN + apply_gas_torque = clip(scaled_torque, min_regen, gas_max) + # --- End regen torque scaling --- + apply_gas_torque = clip(scaled_torque, self.params.MAX_ACC_REGEN, gas_max) + BRAKE_SWITCH = int(round(interp(CS.out.vEgo, self.params.BRAKE_SWITCH_LOOKUP_BP, self.params.BRAKE_SWITCH_LOOKUP_V))) + brake_accel = min((scaled_torque - BRAKE_SWITCH)/(self.tireRadius*self.mass), 0) + self.apply_gas = int(round(apply_gas_torque)) self.apply_brake = int(round(interp(brake_accel, self.params.BRAKE_LOOKUP_BP, self.params.BRAKE_LOOKUP_V))) + if self.apply_brake > 0: + self.apply_gas = self.params.INACTIVE_REGEN + # Don't allow any gas above inactive regen while stopping # FIXME: brakes aren't applied immediately when enabling at a stop if stopping: @@ -389,7 +429,7 @@ class CarController(CarControllerBase): if CC.cruiseControl.resume and CS.pcm_acc_status == AccState.STANDSTILL and frogpilot_toggles.volt_sng: acc_engaged = False else: - acc_engaged = CC.enabled + acc_engaged = CC.enabled and not (self.CP.carFingerprint == CAR.CHEVROLET_BOLT_EUV and self.CP.enableGasInterceptor) # GasRegenCmdActive needs to be 1 to avoid cruise faults. It describes the ACC state, not actuation can_sends.append(gmcan.create_gas_regen_command(self.packer_pt, CanBus.POWERTRAIN, self.apply_gas, idx, acc_engaged, at_full_stop)) @@ -422,11 +462,15 @@ class CarController(CarControllerBase): # TODO: integrate this with the code block below? if ( (self.CP.flags & GMFlags.PEDAL_LONG.value) # Always cancel stock CC when using pedal interceptor - or (self.CP.flags & GMFlags.CC_LONG.value and not CC.enabled) # Cancel stock CC if OP is not active + or (self.CP.flags & GMFlags.CC_LONG.value) # Match ACC behavior for non-ACC cars ) and CS.out.cruiseState.enabled: if (self.frame - self.last_button_frame) * DT_CTRL > 0.04: self.last_button_frame = self.frame - can_sends.append(gmcan.create_buttons(self.packer_pt, CanBus.POWERTRAIN, (CS.buttons_counter + 1) % 4, CruiseButtons.CANCEL)) + # Send cancel to appropriate bus based on car type (match stock longitudinal logic) + if self.CP.carFingerprint in SDGM_CAR: + can_sends.append(gmcan.create_buttons(self.packer_pt, CanBus.POWERTRAIN, (CS.buttons_counter + 1) % 4, CruiseButtons.CANCEL)) + else: + can_sends.append(gmcan.create_buttons(self.packer_pt, CanBus.CAMERA, (CS.buttons_counter + 1) % 4, CruiseButtons.CANCEL)) else: # While car is braking, cancel button causes ECM to enter a soft disable state with a fault status. diff --git a/selfdrive/car/gm/carstate.py b/selfdrive/car/gm/carstate.py index 68015ac06..79d86ac81 100644 --- a/selfdrive/car/gm/carstate.py +++ b/selfdrive/car/gm/carstate.py @@ -5,7 +5,7 @@ from openpilot.common.numpy_fast import mean from opendbc.can.can_define import CANDefine from opendbc.can.parser import CANParser from openpilot.selfdrive.car.interfaces import CarStateBase -from openpilot.selfdrive.car.gm.values import DBC, AccState, CanBus, STEER_THRESHOLD, GMFlags, CC_ONLY_CAR, CAMERA_ACC_CAR, SDGM_CAR, CC_REGEN_PADDLE_CAR +from openpilot.selfdrive.car.gm.values import DBC, AccState, CanBus, STEER_THRESHOLD, GMFlags, CC_ONLY_CAR, CAMERA_ACC_CAR, SDGM_CAR, CC_REGEN_PADDLE_CAR, CAR TransmissionType = car.CarParams.TransmissionType NetworkLocation = car.CarParams.NetworkLocation @@ -53,9 +53,6 @@ class CarState(CarStateBase): self.moving_backward = (pt_cp.vl["EBCMWheelSpdRear"]["RLWheelDir"] == 2) or (pt_cp.vl["EBCMWheelSpdRear"]["RRWheelDir"] == 2) # Variables used for avoiding LKAS faults - self.loopback_lka_steering_cmd_updated = len(loopback_cp.vl_all["ASCMLKASteeringCmd"]["RollingCounter"]) > 0 - if self.loopback_lka_steering_cmd_updated: - self.loopback_lka_steering_cmd_ts_nanos = loopback_cp.ts_nanos["ASCMLKASteeringCmd"]["RollingCounter"] # Track timestamps for OEM PRNDL2 and Regen Paddle messages (used to sync spoofing timing) self.prndl2_ts_nanos = pt_cp.ts_nanos["ECMPRDNL2"]["PRNDL2"] @@ -63,6 +60,9 @@ class CarState(CarStateBase): self.regen_paddle_ts_nanos = pt_cp.ts_nanos["EBCMRegenPaddle"]["RegenPaddle"] else: self.regen_paddle_ts_nanos = 0 + self.loopback_lka_steering_cmd_updated = len(loopback_cp.vl_all["ASCMLKASteeringCmd"]["RollingCounter"]) > 0 + if self.loopback_lka_steering_cmd_updated: + self.loopback_lka_steering_cmd_ts_nanos = loopback_cp.ts_nanos["ASCMLKASteeringCmd"]["RollingCounter"] if self.CP.networkLocation == NetworkLocation.fwdCamera and not self.CP.flags & GMFlags.NO_CAMERA.value: self.pt_lka_steering_cmd_counter = pt_cp.vl["ASCMLKASteeringCmd"]["RollingCounter"] self.cam_lka_steering_cmd_counter = cam_cp.vl["ASCMLKASteeringCmd"]["RollingCounter"] @@ -96,7 +96,7 @@ class CarState(CarStateBase): # Regen braking is braking if self.CP.transmissionType == TransmissionType.direct: ret.regenBraking = pt_cp.vl["EBCMRegenPaddle"]["RegenPaddle"] != 0 - self.single_pedal_mode = ret.gearShifter == GearShifter.low or pt_cp.vl["EVDriveMode"]["SinglePedalModeActive"] == 1 or (ret.regenBraking and GearShifter.manumatic) + self.single_pedal_mode = ret.gearShifter == GearShifter.low or pt_cp.vl["EVDriveMode"]["SinglePedalModeActive"] == 1 or (ret.regenBraking and GearShifter.manumatic) or (self.CP.carFingerprint in [CAR.CHEVROLET_BOLT_EUV, CAR.CHEVROLET_BOLT_CC] and self.CP.enableGasInterceptor) if self.CP.enableGasInterceptor: ret.gas = (pt_cp.vl["GAS_SENSOR"]["INTERCEPTOR_GAS"] + pt_cp.vl["GAS_SENSOR"]["INTERCEPTOR_GAS2"]) / 2. @@ -150,6 +150,9 @@ class CarState(CarStateBase): ret.cruiseState.enabled = pt_cp.vl["AcceleratorPedal2"]["CruiseState"] != AccState.OFF ret.cruiseState.standstill = pt_cp.vl["AcceleratorPedal2"]["CruiseState"] == AccState.STANDSTILL + if self.CP.carFingerprint == CAR.CHEVROLET_BOLT_EUV and self.CP.enableGasInterceptor: + ret.cruiseState.enabled = cam_cp.vl["ASCMActiveCruiseControlStatus"]["ACCCmdActive"] != 0 + ret.cruiseState.standstill = cam_cp.vl["ASCMActiveCruiseControlStatus"]["ACCCruiseState"] == 4 if self.CP.networkLocation == NetworkLocation.fwdCamera and not self.CP.flags & GMFlags.NO_CAMERA.value: if self.CP.carFingerprint not in CC_ONLY_CAR: ret.cruiseState.speed = cam_cp.vl["ASCMActiveCruiseControlStatus"]["ACCSpeedSetpoint"] * CV.KPH_TO_MS @@ -163,7 +166,11 @@ class CarState(CarStateBase): if self.CP.carFingerprint in CC_ONLY_CAR: ret.accFaulted = False ret.cruiseState.speed = pt_cp.vl["ECMCruiseControl"]["CruiseSetSpeed"] * CV.KPH_TO_MS - ret.cruiseState.enabled = pt_cp.vl["ECMCruiseControl"]["CruiseActive"] != 0 + # Try ASCM first for cars that might have it (like misfingerprinted Bolts), fall back to ECM + try: + ret.cruiseState.enabled = cam_cp.vl["ASCMActiveCruiseControlStatus"]["ACCCmdActive"] != 0 + except: + ret.cruiseState.enabled = pt_cp.vl["ECMCruiseControl"]["CruiseActive"] != 0 if self.CP.enableBsm: if self.CP.carFingerprint not in SDGM_CAR: @@ -173,7 +180,7 @@ class CarState(CarStateBase): ret.leftBlindspot = cam_cp.vl["BCMBlindSpotMonitor"]["LeftBSM"] == 1 ret.rightBlindspot = cam_cp.vl["BCMBlindSpotMonitor"]["RightBSM"] == 1 - + # FrogPilot CarState functions self.lkas_previously_enabled = self.lkas_enabled if self.CP.carFingerprint in SDGM_CAR: self.lkas_enabled = cam_cp.vl["ASCMSteeringButton"]["LKAButton"] @@ -206,7 +213,7 @@ class CarState(CarStateBase): messages += [ ("AEBCmd", 10), ] - if CP.carFingerprint not in CC_ONLY_CAR: + # Include ASCMActiveCruiseControlStatus for all non-SDGM fwdCamera cars messages += [ ("ASCMActiveCruiseControlStatus", 25), ] diff --git a/selfdrive/car/gm/gmcan.py b/selfdrive/car/gm/gmcan.py index 3bbf2969b..0c0516d03 100644 --- a/selfdrive/car/gm/gmcan.py +++ b/selfdrive/car/gm/gmcan.py @@ -66,7 +66,6 @@ def create_gas_regen_command(packer, bus, throttle, idx, enabled, at_full_stop): "GasRegenFullStopActive": at_full_stop, "GasRegenAlwaysOne": 1, "GasRegenAlwaysOne2": 1, - "GasRegenAlwaysOne3": 1, } dat = packer.make_can_msg("ASCMGasRegenCmd", bus, values)[2] @@ -177,21 +176,6 @@ def create_lka_icon_command(bus, active, critical, steer): dat = b"\x00\x00\x00" return make_can_msg(0x104c006c, dat, bus) -def create_prndl2_command(packer, bus, press_regen_paddle): - prndl2_value = 7 if press_regen_paddle else 6 - manual_mode = 1 if press_regen_paddle else 0 - values = { - "Byte0": 0x0C, - "Byte1": 0x0C, - "Byte2": 0x00, - "PRNDL2": prndl2_value, - "Byte4": 0x00, - "ManualMode": manual_mode, - "TransmissionState": 1, - "Byte7": 0x00 - } - return packer.make_can_msg("ECMPRDNL2", bus, values) - def create_regen_paddle_command(packer, bus, press_regen_paddle): regen_paddle_value = 2 if press_regen_paddle else 0 values = { @@ -205,6 +189,21 @@ def create_regen_paddle_command(packer, bus, press_regen_paddle): } return packer.make_can_msg("EBCMRegenPaddle", bus, values) +def create_prndl2_command(packer, bus, press_regen_paddle): + prndl2_value = 5 if press_regen_paddle else 6 + manual_mode = 1 if press_regen_paddle else 0 + values = { + "Byte0": 0x0C, + "Byte1": 0x0C, + "Byte2": 0x00, + "PRNDL2": prndl2_value, + "Byte4": 0x00, + "ManualMode": manual_mode, + "TransmissionState": 1, + "Byte7": 0x00 + } + return packer.make_can_msg("ECMPRDNL2", bus, values) + def create_gm_cc_spam_command(packer, controller, CS, actuators): if controller.params_.get_bool("IsMetric"): _CV = CV.MS_TO_KPH diff --git a/selfdrive/car/gm/interface.py b/selfdrive/car/gm/interface.py index 632c3dfa7..030a8e12c 100644 --- a/selfdrive/car/gm/interface.py +++ b/selfdrive/car/gm/interface.py @@ -27,8 +27,8 @@ CAM_MSG = 0x320 # AEBCmd ACCELERATOR_POS_MSG = 0xbe NON_LINEAR_TORQUE_PARAMS = { - CAR.CHEVROLET_BOLT_EUV: [1.8, 1.1, 0.280, -0.045], - CAR.CHEVROLET_BOLT_CC: [1.8, 1.1, 0.280, -0.045], + CAR.CHEVROLET_BOLT_EUV: [2.6531724862969748, 1.0, 0.1919764879840985, 0.009054123646805178], + CAR.CHEVROLET_BOLT_CC: [2.6531724862969748, 1.0, 0.1919764879840985, 0.009054123646805178], CAR.GMC_ACADIA: [4.78003305, 1.0, 0.3122, 0.05591772], CAR.CHEVROLET_SILVERADO: [3.29974374, 1.0, 0.25571356, 0.0465122] } @@ -111,11 +111,14 @@ class CarInterface(CarInterfaceBase): ret.longitudinalTuning.kiBP = [5., 35., 60.] if candidate in CAMERA_ACC_CAR: - ret.experimentalLongitudinalAvailable = candidate not in CC_ONLY_CAR + # For ACC models with pedal interceptor, behave like CC_ONLY_CAR + ret.experimentalLongitudinalAvailable = (candidate not in CC_ONLY_CAR) and not ret.enableGasInterceptor ret.networkLocation = NetworkLocation.fwdCamera ret.radarUnavailable = True # no radar - ret.pcmCruise = True + # Only use pcmCruise if no pedal interceptor (bolt_cc style behavior) + ret.pcmCruise = not ret.enableGasInterceptor ret.safetyConfigs[0].safetyParam |= Panda.FLAG_GM_HW_CAM + # Use default minEnableSpeed for ACC models (will be overridden by pedal interceptor section if present) ret.minEnableSpeed = 5 * CV.KPH_TO_MS ret.minSteerSpeed = 10 * CV.KPH_TO_MS @@ -207,9 +210,11 @@ class CarInterface(CarInterfaceBase): CarInterfaceBase.configure_torque_tune(candidate, ret.lateralTuning) ret.lateralTuning.torque.kp = 1.0 - if ret.enableGasInterceptor: - # ACC Bolts use pedal for full longitudinal control, not just sng - ret.flags |= GMFlags.PEDAL_LONG.value + # Enable pedal interceptor for ACC models when detected + if candidate in CAMERA_ACC_CAR and ret.enableGasInterceptor: + # ACC models with pedal interceptor get full pedal longitudinal control + ret.flags |= GMFlags.PEDAL_LONG.value + ret.safetyConfigs[0].safetyParam |= Panda.FLAG_GM_NO_ACC elif candidate == CAR.CHEVROLET_SILVERADO: # On the Bolt, the ECM and camera independently check that you are either above 5 kph or at a stop @@ -271,7 +276,7 @@ class CarInterface(CarInterfaceBase): ret.stoppingControl = True ret.autoResumeSng = True - if candidate in CC_ONLY_CAR: #pedal interceptor tuning + if candidate in CC_ONLY_CAR or (candidate in CAMERA_ACC_CAR and ret.enableGasInterceptor): #pedal interceptor tuning ret.flags |= GMFlags.PEDAL_LONG.value ret.safetyConfigs[0].safetyParam |= Panda.FLAG_GM_PEDAL_LONG # Note: Low speed, stop and go not tested. Should be fairly smooth on highway diff --git a/selfdrive/car/gm/values.py b/selfdrive/car/gm/values.py index 52ab348dd..892329d06 100644 --- a/selfdrive/car/gm/values.py +++ b/selfdrive/car/gm/values.py @@ -38,10 +38,10 @@ class CarControllerParams: def __init__(self, CP): # Gas/brake lookups - self.ZERO_GAS = 6144 # Coasting + self.ZERO_GAS = 6150 # Coasting self.MAX_BRAKE = 400 # ~ -4.0 m/s^2 with regen - if CP.carFingerprint in CAMERA_ACC_CAR and CP.carFingerprint not in CC_ONLY_CAR and CP.carFingerprint != CAR.CHEVROLET_BOLT_EUV: + if CP.carFingerprint in CAMERA_ACC_CAR and CP.carFingerprint not in CC_ONLY_CAR: self.MAX_GAS = 7496 self.MAX_GAS_PLUS = 8848 self.MAX_ACC_REGEN = 5610 @@ -49,32 +49,49 @@ class CarControllerParams: # Camera ACC vehicles have no regen while enabled. # Camera transitions to MAX_ACC_REGEN from ZERO_GAS and uses friction brakes instantly self.max_regen_acceleration = 0. + self.BRAKE_SWITCH_MAX = self.MAX_ACC_REGEN if CP.carFingerprint in EV_CAR else self.ZERO_GAS elif CP.carFingerprint in SDGM_CAR: self.MAX_GAS = 7496 self.MAX_GAS_PLUS = 7496 - self.MAX_ACC_REGEN = 7110 + self.MAX_ACC_REGEN = 5610 self.INACTIVE_REGEN = 5650 self.max_regen_acceleration = 0. + self.BRAKE_SWITCH = self.ZERO_GAS else: self.MAX_GAS = 7168 # Safety limit, not ACC max. Stock ACC >8192 from standstill. self.MAX_GAS_PLUS = 8191 # 8292 uses new bit, possible but not tested. Matches Twilsonco tw-main max - self.MAX_ACC_REGEN = 7110 # Increased for stronger regen braking + self.MAX_ACC_REGEN = 7110 # Max ACC regen is slightly less than max paddle regen self.INACTIVE_REGEN = 5500 # ICE has much less engine braking force compared to regen in EVs, # lower threshold removes some braking deadzone - self.max_regen_acceleration = -3. if CP.carFingerprint in EV_CAR else -0.1 # More aggressive regen for EVs + self.max_regen_acceleration = -3. if CP.carFingerprint in EV_CAR else -0.1 + self.BRAKE_SWITCH_MAX = self.MAX_ACC_REGEN if CP.carFingerprint in EV_CAR else self.ZERO_GAS self.GAS_LOOKUP_BP = [self.max_regen_acceleration, 0., self.ACCEL_MAX] self.GAS_LOOKUP_BP_PLUS = [self.max_regen_acceleration, 0., self.ACCEL_MAX_PLUS] self.GAS_LOOKUP_V = [self.MAX_ACC_REGEN, self.ZERO_GAS, self.MAX_GAS] self.GAS_LOOKUP_V_PLUS = [self.MAX_ACC_REGEN, self.ZERO_GAS, self.MAX_GAS_PLUS] - self.BRAKE_LOOKUP_BP = [self.ACCEL_MIN, self.max_regen_acceleration] + self.BRAKE_LOOKUP_BP = [self.ACCEL_MIN, 0.] self.BRAKE_LOOKUP_V = [self.MAX_BRAKE, 0.] + self.BRAKE_SWITCH_LOOKUP_BP = [0.5, 10] + self.BRAKE_SWITCH_LOOKUP_V = [self.ZERO_GAS, self.BRAKE_SWITCH_MAX] + # determined by letting Volt regen to a stop in L gear from 89mph, + # and by letting off gas and allowing car to creep, for determining + # the positive threshold values at very low speed + EV_GAS_BRAKE_THRESHOLD_BP = [1.29, 1.52, 1.55, 1.6, 1.7, 1.8, 2.0, 2.2, 2.5, 5.52, 9.6, 20.5, 23.5, 35.0] # [m/s] + EV_GAS_BRAKE_THRESHOLD_V = [0.0, -0.14, -0.16, -0.18, -0.215, -0.255, -0.32, -0.41, -0.5, -0.72, -0.895, -1.125, -1.145, -1.16] # [m/s^s] + + def update_ev_gas_brake_threshold(self, v_ego): + gas_brake_threshold = interp(v_ego, self.EV_GAS_BRAKE_THRESHOLD_BP, self.EV_GAS_BRAKE_THRESHOLD_V) + self.GAS_LOOKUP_BP_PLUS = [self.max_regen_acceleration, 0., self.ACCEL_MAX_PLUS] + self.EV_GAS_LOOKUP_BP = [gas_brake_threshold, max(0., gas_brake_threshold), self.ACCEL_MAX] + self.EV_GAS_LOOKUP_BP_PLUS = [gas_brake_threshold, max(0., gas_brake_threshold), self.ACCEL_MAX_PLUS] + self.EV_BRAKE_LOOKUP_BP = [self.ACCEL_MIN, gas_brake_threshold] @dataclass class GMCarDocs(CarDocs): @@ -158,7 +175,7 @@ class CAR(Platforms): GMCarDocs("Chevrolet Silverado 1500 2020-21", "Safety Package II"), GMCarDocs("GMC Sierra 1500 2020-21", "Driver Alert Package II", video_link="https://youtu.be/5HbNoBLzRwE"), ], - GMCarSpecs(mass=2450, wheelbase=3.75, steerRatio=16.3, tireStiffnessFactor=1.0), + GMCarSpecs(mass=2994, wheelbase=3.75, steerRatio=16.3, tireStiffnessFactor=1.0), ) CHEVROLET_EQUINOX = GMPlatformConfig( [GMCarDocs("Chevrolet Equinox 2019-22")], @@ -194,15 +211,15 @@ class CAR(Platforms): CHEVROLET_SUBURBAN.specs, ) GMC_YUKON_CC = GMPlatformConfig( - [GMCarDocs("GMC Yukon No ACC")], + [GMCarDocs("GMC Yukon - No-ACC")], CarSpecs(mass=2541, wheelbase=2.95, steerRatio=16.3, centerToFrontRatio=0.4), ) CADILLAC_CT6_CC = GMPlatformConfig( - [GMCarDocs("Cadillac CT6 No ACC")], + [GMCarDocs("Cadillac CT6 - No-ACC")], CarSpecs(mass=2358, wheelbase=3.11, steerRatio=17.7, centerToFrontRatio=0.4), ) CHEVROLET_TRAILBLAZER_CC = GMPlatformConfig( - [GMCarDocs("Chevrolet Trailblazer 2021-22")], + [GMCarDocs("Chevrolet Trailblazer 2021-22 - No-ACC")], CHEVROLET_TRAILBLAZER.specs, ) CADILLAC_XT4 = GMPlatformConfig( @@ -210,7 +227,7 @@ class CAR(Platforms): CarSpecs(mass=1660, wheelbase=2.78, steerRatio=14.4, centerToFrontRatio=0.4), ) CADILLAC_XT5_CC = GMPlatformConfig( - [GMCarDocs("Cadillac XT5 No ACC")], + [GMCarDocs("Cadillac XT5 - No-ACC")], CarSpecs(mass=1810, wheelbase=2.86, steerRatio=16.34, centerToFrontRatio=0.5), ) CHEVROLET_TRAVERSE = GMPlatformConfig( @@ -222,7 +239,7 @@ class CAR(Platforms): CarSpecs(mass=2050, wheelbase=2.86, steerRatio=16.0, centerToFrontRatio=0.5), ) CHEVROLET_MALIBU_CC = GMPlatformConfig( - [GMCarDocs("Chevrolet Malibu 2023 No ACC")], + [GMCarDocs("Chevrolet Malibu 2023 - No-ACC")], CarSpecs(mass=1450, wheelbase=2.8, steerRatio=15.8, centerToFrontRatio=0.4), ) CHEVROLET_TRAX = GMPlatformConfig( @@ -311,8 +328,8 @@ FW_QUERY_CONFIG = FwQueryConfig( EV_CAR = {CAR.CHEVROLET_VOLT, CAR.CHEVROLET_BOLT_EUV, CAR.CHEVROLET_VOLT_CC, CAR.CHEVROLET_BOLT_CC} CC_ONLY_CAR = {CAR.CHEVROLET_VOLT_CC, CAR.CHEVROLET_BOLT_CC, CAR.CHEVROLET_EQUINOX_CC, CAR.CHEVROLET_SUBURBAN_CC, CAR.GMC_YUKON_CC, CAR.CADILLAC_CT6_CC, CAR.CHEVROLET_TRAILBLAZER_CC, CAR.CADILLAC_XT5_CC, CAR.CHEVROLET_MALIBU_CC} -CC_REGEN_PADDLE_CAR = {CAR.CHEVROLET_BOLT_CC, CAR.CHEVROLET_BOLT_EUV} # CC_ONLY_CAR = set(c for c in CAR if str(c).endswith('_CC')) +CC_REGEN_PADDLE_CAR = {CAR.CHEVROLET_BOLT_CC, CAR.CHEVROLET_BOLT_EUV} # We're integrated at the Safety Data Gateway Module on these cars SDGM_CAR = {CAR.CADILLAC_XT4, CAR.CHEVROLET_TRAVERSE, CAR.BUICK_BABYENCLAVE}