Skip to content

Commit

Permalink
Squash build-volt
Browse files Browse the repository at this point in the history
  • Loading branch information
OPGM CI Automated committed Apr 5, 2023
1 parent ff89184 commit 4e0b632
Show file tree
Hide file tree
Showing 9 changed files with 260 additions and 45 deletions.
2 changes: 1 addition & 1 deletion panda
123 changes: 100 additions & 23 deletions selfdrive/car/gm/carcontroller.py
Original file line number Diff line number Diff line change
@@ -1,22 +1,58 @@
import math

from cereal import car
from common.conversions import Conversions as CV
from common.numpy_fast import interp
from common.numpy_fast import interp, clip
from common.realtime import DT_CTRL
from opendbc.can.packer import CANPacker
from selfdrive.car import apply_driver_steer_torque_limits
from selfdrive.car import apply_driver_steer_torque_limits, create_gas_interceptor_command
from selfdrive.car.gm import gmcan
from selfdrive.car.gm.values import DBC, CanBus, CarControllerParams, CruiseButtons
from selfdrive.car.gm.values import DBC, CanBus, CarControllerParams, CruiseButtons, CC_ONLY_CAR

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
# Enforce a minimum interval between steering messages to avoid a fault
MIN_STEER_MSG_INTERVAL_MS = 15


def actuator_hystereses(final_pedal, pedal_steady):
# hyst params... TODO: move these to VehicleParams
pedal_hyst_gap = 0.01 # don't change pedal command for small oscillations within this value

# for small pedal oscillations within pedal_hyst_gap, don't change the pedal command
if math.isclose(final_pedal, 0.0):
pedal_steady = 0.
elif final_pedal > pedal_steady + pedal_hyst_gap:
pedal_steady = final_pedal - pedal_hyst_gap
elif final_pedal < pedal_steady - pedal_hyst_gap:
pedal_steady = final_pedal + pedal_hyst_gap
final_pedal = pedal_steady

return final_pedal, pedal_steady


def actuator_hystereses(final_pedal, pedal_steady):
# hyst params... TODO: move these to VehicleParams
pedal_hyst_gap = 0.01 # don't change pedal command for small oscillations within this value

# for small pedal oscillations within pedal_hyst_gap, don't change the pedal command
if math.isclose(final_pedal, 0.0):
pedal_steady = 0.
elif final_pedal > pedal_steady + pedal_hyst_gap:
pedal_steady = final_pedal - pedal_hyst_gap
elif final_pedal < pedal_steady - pedal_hyst_gap:
pedal_steady = final_pedal + pedal_hyst_gap
final_pedal = pedal_steady

return final_pedal, pedal_steady


class CarController:
def __init__(self, dbc_name, CP, VM):
self.CP = CP
Expand All @@ -28,6 +64,7 @@ def __init__(self, dbc_name, CP, VM):
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)
Expand Down Expand Up @@ -93,25 +130,65 @@ def update(self, CC, CS, now_nanos):
self.apply_gas = int(round(interp(actuators.accel, self.params.GAS_LOOKUP_BP, self.params.GAS_LOOKUP_V)))
self.apply_brake = int(round(interp(actuators.accel, self.params.BRAKE_LOOKUP_BP, self.params.BRAKE_LOOKUP_V)))

idx = (self.frame // 4) % 4

at_full_stop = CC.longActive and CS.out.standstill
near_stop = CC.longActive and (CS.out.vEgo < self.params.NEAR_STOP_BRAKE_PHASE)
friction_brake_bus = CanBus.CHASSIS
# GM Camera exceptions
# TODO: can we always check the longControlState?
if self.CP.networkLocation == NetworkLocation.fwdCamera:
at_full_stop = at_full_stop and actuators.longControlState == LongCtrlState.stopping
friction_brake_bus = CanBus.POWERTRAIN

# 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, CC.enabled, at_full_stop))
can_sends.append(gmcan.create_friction_brake_command(self.packer_ch, friction_brake_bus, self.apply_brake, idx, CC.enabled, near_stop, at_full_stop, self.CP))

# Send dashboard UI commands (ACC status)
send_fcw = hud_alert == VisualAlert.fcw
can_sends.append(gmcan.create_acc_dashboard_command(self.packer_pt, CanBus.POWERTRAIN, CC.enabled,
hud_v_cruise * CV.MS_TO_KPH, hud_control.leadVisible, send_fcw))
# BEGIN INTERCEPTOR ############################
if CS.CP.enableGasInterceptor:
# TODO: JJS Detect saturated battery?
if CS.single_pedal_mode:
# In L Mode, Pedal applies regen at a fixed coast-point (TODO: max regen in L mode may be different per car)
# This will apply to EVs in L mode.
# accel values below zero down to a cutoff point
# that approximates the percentage of braking regen can handle should be scaled between 0 and the coast-point
# accell values below this point will need to be add-on future hijacked AEB
# TODO: Determine (or guess) at regen percentage

# From Felger's Bolt Fort
# It seems in L mode, accel / decel point is around 1/5
# -1-------AEB------0----regen---0.15-------accel----------+1
# Shrink gas request to 0.85, have it start at 0.2
# Shrink brake request to 0.85, first 0.15 gives regen, rest gives AEB

zero = 0.15625 # 40/256

if actuators.accel > 0.:
# Scales the accel from 0-1 to 0.156-1
pedal_gas = clip(((1 - zero) * actuators.accel + zero), 0., 1.)
else:
# if accel is negative, -0.1 -> 0.015625
pedal_gas = clip(zero + actuators.accel, 0., zero) # Make brake the same size as gas, but clip to regen
# aeb = actuators.brake*(1-zero)-regen # For use later, braking more than regen
else:
pedal_gas = clip(actuators.accel, 0., 1.)

# apply pedal hysteresis and clip the final output to valid values.
pedal_final, self.pedal_steady = actuator_hystereses(pedal_gas, self.pedal_steady)
pedal_gas = clip(pedal_final, 0., 1.)

if not CC.longActive:
pedal_gas = 0.0 # May not be needed with the enable param

idx = (self.frame // 4) % 4
can_sends.append(create_gas_interceptor_command(self.packer_pt, pedal_gas, idx))
# END INTERCEPTOR ############################
else:
idx = (self.frame // 4) % 4

at_full_stop = CC.longActive and CS.out.standstill
near_stop = CC.longActive and (CS.out.vEgo < self.params.NEAR_STOP_BRAKE_PHASE)
friction_brake_bus = CanBus.CHASSIS
# GM Camera exceptions
# TODO: can we always check the longControlState?
if self.CP.networkLocation == NetworkLocation.fwdCamera and self.CP.carFingerprint not in CC_ONLY_CAR:
at_full_stop = at_full_stop and actuators.longControlState == LongCtrlState.stopping
friction_brake_bus = CanBus.POWERTRAIN

# 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, CC.enabled, at_full_stop))
can_sends.append(gmcan.create_friction_brake_command(self.packer_ch, friction_brake_bus, self.apply_brake, idx, CC.enabled, near_stop, at_full_stop, self.CP))

# Send dashboard UI commands (ACC status)
send_fcw = hud_alert == VisualAlert.fcw
can_sends.append(gmcan.create_acc_dashboard_command(self.packer_pt, CanBus.POWERTRAIN, CC.enabled,
hud_v_cruise * CV.MS_TO_KPH, hud_control.leadVisible, send_fcw))

# Radar needs to know current speed and yaw rate (50hz),
# and that ADAS is alive (10hz)
Expand Down Expand Up @@ -143,7 +220,7 @@ def update(self, CC, CS, now_nanos):
self.last_button_frame = self.frame
can_sends.append(gmcan.create_buttons(self.packer_pt, CanBus.CAMERA, CS.buttons_counter, CruiseButtons.CANCEL))

if self.CP.networkLocation == NetworkLocation.fwdCamera:
if self.CP.networkLocation == NetworkLocation.fwdCamera and self.CP.carFingerprint not in CC_ONLY_CAR:
# Silence "Take Steering" alert sent by camera, forward PSCMStatus with HandsOffSWlDetectionStatus=1
if self.frame % 10 == 0:
can_sends.append(gmcan.create_pscm_status(self.packer_pt, CanBus.CAMERA, CS.pscm_status))
Expand Down
50 changes: 42 additions & 8 deletions selfdrive/car/gm/carstate.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,10 +5,11 @@
from opendbc.can.can_define import CANDefine
from opendbc.can.parser import CANParser
from selfdrive.car.interfaces import CarStateBase
from selfdrive.car.gm.values import DBC, AccState, CanBus, STEER_THRESHOLD
from selfdrive.car.gm.values import DBC, AccState, CanBus, STEER_THRESHOLD, CC_ONLY_CAR

TransmissionType = car.CarParams.TransmissionType
NetworkLocation = car.CarParams.NetworkLocation
GearShifter = car.CarState.GearShifter
STANDSTILL_THRESHOLD = 10 * 0.0311 * CV.KPH_TO_MS


Expand All @@ -25,6 +26,7 @@ def __init__(self, CP):
self.pt_lka_steering_cmd_counter = 0
self.cam_lka_steering_cmd_counter = 0
self.buttons_counter = 0
self.single_pedal_mode = False

def update(self, pt_cp, cam_cp, loopback_cp):
ret = car.CarState.new_message()
Expand Down Expand Up @@ -59,10 +61,14 @@ def update(self, pt_cp, cam_cp, loopback_cp):
else:
ret.gearShifter = self.parse_gear_shifter(self.shifter_values.get(pt_cp.vl["ECMPRDNL2"]["PRNDL2"], None))

ret.brake = pt_cp.vl["ECMAcceleratorPos"]["BrakePedalPos"]
if self.CP.networkLocation == NetworkLocation.fwdCamera:
if self.CP.carFingerprint in CC_ONLY_CAR:
ret.brake = pt_cp.vl["EBCMBrakePedalPosition"]["BrakePedalPosition"] / 0xd0
else:
ret.brake = pt_cp.vl["ECMAcceleratorPos"]["BrakePedalPos"]
ret.brakePressed = pt_cp.vl["ECMEngineStatus"]["BrakePressed"] != 0
else:
ret.brake = pt_cp.vl["ECMAcceleratorPos"]["BrakePedalPos"]
# Some Volt 2016-17 have loose brake pedal push rod retainers which causes the ECM to believe
# that the brake is being intermittently pressed without user interaction.
# To avoid a cruise fault we need to use a conservative brake position threshold
Expand All @@ -72,9 +78,14 @@ def update(self, pt_cp, cam_cp, loopback_cp):
# 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

ret.gas = pt_cp.vl["AcceleratorPedal2"]["AcceleratorPedal2"] / 254.
ret.gasPressed = ret.gas > 1e-5
if self.CP.enableGasInterceptor:
ret.gas = (pt_cp.vl["GAS_SENSOR"]["INTERCEPTOR_GAS"] + pt_cp.vl["GAS_SENSOR"]["INTERCEPTOR_GAS2"]) / 2.
ret.gasPressed = ret.gas > 15
else:
ret.gas = pt_cp.vl["AcceleratorPedal2"]["AcceleratorPedal2"] / 254.
ret.gasPressed = ret.gas > 1e-5

ret.steeringAngleDeg = pt_cp.vl["PSCMSteeringAngle"]["SteeringWheelAngle"]
ret.steeringRateDeg = pt_cp.vl["PSCMSteeringAngle"]["SteeringWheelRate"]
Expand Down Expand Up @@ -103,10 +114,15 @@ def update(self, pt_cp, cam_cp, loopback_cp):
ret.espDisabled = pt_cp.vl["ESPStatus"]["TractionControlOn"] != 1
ret.accFaulted = (pt_cp.vl["AcceleratorPedal2"]["CruiseState"] == AccState.FAULTED or
pt_cp.vl["EBCMFrictionBrakeStatus"]["FrictionBrakeUnavailable"] == 1)
if self.CP.carFingerprint in CC_ONLY_CAR:
ret.accFaulted = False
if self.CP.enableGasInterceptor: # Flip CC main logic when pedal is being used for long TODO: switch to cancel cc
ret.cruiseState.available = (not ret.cruiseState.available)
ret.accFaulted = False

ret.cruiseState.enabled = pt_cp.vl["AcceleratorPedal2"]["CruiseState"] != AccState.OFF
ret.cruiseState.standstill = pt_cp.vl["AcceleratorPedal2"]["CruiseState"] == AccState.STANDSTILL
if self.CP.networkLocation == NetworkLocation.fwdCamera:
if self.CP.networkLocation == NetworkLocation.fwdCamera and self.CP.carFingerprint not in CC_ONLY_CAR:
ret.cruiseState.speed = cam_cp.vl["ASCMActiveCruiseControlStatus"]["ACCSpeedSetpoint"] * CV.KPH_TO_MS
ret.stockAeb = cam_cp.vl["AEBCmd"]["AEBCmdActive"] != 0
# openpilot controls nonAdaptive when not pcmCruise
Expand All @@ -123,14 +139,19 @@ def get_cam_can_parser(CP):
signals += [
("AEBCmdActive", "AEBCmd"),
("RollingCounter", "ASCMLKASteeringCmd"),
("ACCSpeedSetpoint", "ASCMActiveCruiseControlStatus"),
("ACCCruiseState", "ASCMActiveCruiseControlStatus"),
]
checks += [
("AEBCmd", 10),
("ASCMLKASteeringCmd", 10),
("ASCMActiveCruiseControlStatus", 25),
]
if CP.carFingerprint not in CC_ONLY_CAR:
signals += [
("ACCSpeedSetpoint", "ASCMActiveCruiseControlStatus"),
("ACCCruiseState", "ASCMActiveCruiseControlStatus"),
]
checks += [
("ASCMActiveCruiseControlStatus", 25),
]

return CANParser(DBC[CP.carFingerprint]["pt"], signals, checks, CanBus.CAMERA)

Expand Down Expand Up @@ -195,15 +216,28 @@ def get_can_parser(CP):
if CP.networkLocation == NetworkLocation.fwdCamera:
signals += [
("RollingCounter", "ASCMLKASteeringCmd"),
("SinglePedalModeActive", "EVDriveMode"),
]
checks += [
("ASCMLKASteeringCmd", 0),
("EVDriveMode", 0),
]

if CP.transmissionType == TransmissionType.direct:
signals.append(("RegenPaddle", "EBCMRegenPaddle"))
checks.append(("EBCMRegenPaddle", 50))

if CP.carFingerprint in CC_ONLY_CAR:
signals.remove(("BrakePedalPos", "ECMAcceleratorPos"))
signals.append(("BrakePedalPosition", "EBCMBrakePedalPosition"))
checks.remove(("ECMAcceleratorPos", 80))
checks.append(("EBCMBrakePedalPosition", 100))

if CP.enableGasInterceptor:
signals.append(("INTERCEPTOR_GAS", "GAS_SENSOR"))
signals.append(("INTERCEPTOR_GAS2", "GAS_SENSOR"))
checks.append(("GAS_SENSOR", 50))

return CANParser(DBC[CP.carFingerprint]["pt"], signals, checks, CanBus.POWERTRAIN)

@staticmethod
Expand Down
34 changes: 27 additions & 7 deletions selfdrive/car/gm/interface.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@
from common.conversions import Conversions as CV
from selfdrive.car import STD_CARGO_KG, create_button_event, scale_tire_stiffness, get_safety_config
from selfdrive.car.gm.radar_interface import RADAR_HEADER_MSG
from selfdrive.car.gm.values import CAR, CruiseButtons, CarControllerParams, EV_CAR, CAMERA_ACC_CAR, CanBus
from selfdrive.car.gm.values import CAR, CruiseButtons, CarControllerParams, EV_CAR, CAMERA_ACC_CAR, CanBus, CC_ONLY_CAR
from selfdrive.car.interfaces import CarInterfaceBase, TorqueFromLateralAccelCallbackType, FRICTION_THRESHOLD
from selfdrive.controls.lib.drive_helpers import get_friction

Expand Down Expand Up @@ -38,7 +38,7 @@ def get_steer_feedforward_acadia(desired_angle, v_ego):
return 0.04689655 * sigmoid * (v_ego + 10.028217)

def get_steer_feedforward_function(self):
if self.CP.carFingerprint == CAR.VOLT:
if self.CP.carFingerprint in (CAR.VOLT, CAR.VOLT_CC):
return self.get_steer_feedforward_volt
elif self.CP.carFingerprint == CAR.ACADIA:
return self.get_steer_feedforward_acadia
Expand Down Expand Up @@ -75,6 +75,8 @@ def _get_params(ret, candidate, fingerprint, car_fw, experimental_long):
ret.autoResumeSng = False
use_off_car_defaults = len(fingerprint[0]) == 0 # Pick sensible carParams during offline doc generation/CI jobs

ret.enableGasInterceptor = 0x201 in fingerprint[0]

if candidate in EV_CAR:
ret.transmissionType = TransmissionType.direct
else:
Expand All @@ -87,7 +89,7 @@ def _get_params(ret, candidate, fingerprint, car_fw, experimental_long):
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
Expand Down Expand Up @@ -123,8 +125,7 @@ def _get_params(ret, candidate, fingerprint, car_fw, experimental_long):
# These cars have been put into dashcam only due to both a lack of users and test coverage.
# These cars likely still work fine. Once a user confirms each car works and a test route is
# added to selfdrive/car/tests/routes.py, we can remove it from this list.
ret.dashcamOnly = candidate in {CAR.CADILLAC_ATS, CAR.HOLDEN_ASTRA, CAR.MALIBU, CAR.BUICK_REGAL, CAR.EQUINOX} or \
(ret.networkLocation == NetworkLocation.gateway and ret.radarUnavailable)
ret.dashcamOnly = candidate in {CAR.CADILLAC_ATS, CAR.HOLDEN_ASTRA, CAR.MALIBU, CAR.BUICK_REGAL, CAR.EQUINOX}

# Start with a baseline tuning for all GM vehicles. Override tuning as needed in each model section below.
ret.lateralTuning.pid.kiBP, ret.lateralTuning.pid.kpBP = [[0.], [0.]]
Expand All @@ -137,7 +138,7 @@ def _get_params(ret, candidate, fingerprint, car_fw, experimental_long):
ret.radarTimeStep = 0.0667 # GM radar runs at 15Hz instead of standard 20Hz
ret.longitudinalActuatorDelayUpperBound = 0.5 # large delay to initially start braking

if candidate == CAR.VOLT:
if candidate in (CAR.VOLT, CAR.VOLT_CC):
ret.mass = 1607. + STD_CARGO_KG
ret.wheelbase = 2.69
ret.steerRatio = 17.7 # Stock 15.7, LiveParameters
Expand Down Expand Up @@ -210,7 +211,7 @@ def _get_params(ret, candidate, fingerprint, car_fw, experimental_long):
ret.lateralTuning.pid.kf = 0.000045
tire_stiffness_factor = 1.0

elif candidate == CAR.BOLT_EUV:
elif candidate in (CAR.BOLT_EUV, CAR.BOLT_CC):
ret.mass = 1669. + STD_CARGO_KG
ret.wheelbase = 2.63779
ret.steerRatio = 16.8
Expand All @@ -234,6 +235,23 @@ def _get_params(ret, candidate, fingerprint, car_fw, experimental_long):
ret.centerToFront = ret.wheelbase * 0.4
CarInterfaceBase.configure_torque_tune(candidate, ret.lateralTuning)

if ret.enableGasInterceptor:
ret.minEnableSpeed = -1
ret.pcmCruise = False
ret.openpilotLongitudinalControl = True
# Note: Low speed, stop and go not tested. Should be fairly smooth on highway
ret.longitudinalTuning.kpV = [0.4, 0.06]
ret.longitudinalTuning.kiBP = [0., 35.0]
ret.longitudinalTuning.kiV = [0.0, 0.04]
ret.longitudinalTuning.kiV = [0.0, 0.04]
ret.longitudinalTuning.kf = 0.3
ret.stoppingDecelRate = 0.8 # reach stopping target smoothly, brake_travel/s while trying to stop
ret.stopAccel = 0. # Required acceleration to keep vehicle stationary
ret.vEgoStopping = 0.5 # Speed at which the car goes into stopping state, when car starts requesting stopping accel
ret.vEgoStarting = 0.5 # Speed at which the car goes into starting state, when car starts requesting starting accel,
# vEgoStarting needs to be > or == vEgoStopping to avoid state transition oscillation
ret.stoppingControl = True

# TODO: start from empirically derived lateral slip stiffness for the civic and scale by
# mass and CG position, so all cars will have approximately similar dyn behaviors
ret.tireStiffnessFront, ret.tireStiffnessRear = scale_tire_stiffness(ret.mass, ret.wheelbase, ret.centerToFront,
Expand Down Expand Up @@ -271,6 +289,8 @@ def _update(self, c):
events.add(EventName.resumeRequired)
if ret.vEgo < self.CP.minSteerSpeed:
events.add(EventName.belowSteerSpeed)
if self.CP.enableGasInterceptor and self.CP.transmissionType == TransmissionType.direct and not self.CS.single_pedal_mode:
events.add(EventName.brakeUnavailable)

ret.events = events.to_msg()

Expand Down
Loading

0 comments on commit 4e0b632

Please sign in to comment.