#!/usr/bin/env python3
from cereal import car
from math import fabs
from panda import Panda
from common . conversions import Conversions as CV
from selfdrive . car import STD_CARGO_KG , create_button_event , scale_rot_inertia , scale_tire_stiffness , gen_empty_fingerprint , get_safety_config
from selfdrive . car . gm . values import CAR , CruiseButtons , CarControllerParams , EV_CAR , CAMERA_ACC_CAR
from selfdrive . car . interfaces import CarInterfaceBase
ButtonType = car . CarState . ButtonEvent . Type
EventName = car . CarEvent . EventName
GearShifter = car . CarState . GearShifter
TransmissionType = car . CarParams . TransmissionType
NetworkLocation = car . CarParams . NetworkLocation
BUTTONS_DICT = { CruiseButtons . RES_ACCEL : ButtonType . accelCruise , CruiseButtons . DECEL_SET : ButtonType . decelCruise ,
CruiseButtons . MAIN : ButtonType . altButton3 , CruiseButtons . CANCEL : ButtonType . cancel }
class CarInterface ( CarInterfaceBase ) :
@staticmethod
def get_pid_accel_limits ( CP , current_speed , cruise_speed ) :
return CarControllerParams . ACCEL_MIN , CarControllerParams . ACCEL_MAX
# Determined by iteratively plotting and minimizing error for f(angle, speed) = steer.
@staticmethod
def get_steer_feedforward_volt ( desired_angle , v_ego ) :
desired_angle * = 0.02904609
sigmoid = desired_angle / ( 1 + fabs ( desired_angle ) )
return 0.10006696 * sigmoid * ( v_ego + 3.12485927 )
@staticmethod
def get_steer_feedforward_acadia ( desired_angle , v_ego ) :
desired_angle * = 0.09760208
sigmoid = desired_angle / ( 1 + fabs ( desired_angle ) )
return 0.04689655 * sigmoid * ( v_ego + 10.028217 )
def get_steer_feedforward_function ( self ) :
if self . CP . carFingerprint == CAR . VOLT :
return self . get_steer_feedforward_volt
elif self . CP . carFingerprint == CAR . ACADIA :
return self . get_steer_feedforward_acadia
else :
return CarInterfaceBase . get_steer_feedforward_default
@staticmethod
def get_params ( candidate , fingerprint = gen_empty_fingerprint ( ) , car_fw = None , experimental_long = False ) :
ret = CarInterfaceBase . get_std_params ( candidate , fingerprint )
ret . carName = " gm "
ret . safetyConfigs = [ get_safety_config ( car . CarParams . SafetyModel . gm ) ]
ret . autoResumeSng = False
if candidate in EV_CAR :
ret . transmissionType = TransmissionType . direct
else :
ret . transmissionType = TransmissionType . automatic
ret . longitudinalTuning . deadzoneBP = [ 0. ]
ret . longitudinalTuning . deadzoneV = [ 0.15 ]
ret . longitudinalTuning . kpBP = [ 5. , 35. ]
ret . longitudinalTuning . kiBP = [ 0. ]
if candidate in CAMERA_ACC_CAR :
ret . experimentalLongitudinalAvailable = True
ret . networkLocation = NetworkLocation . fwdCamera
ret . radarOffCan = True # no radar
ret . pcmCruise = True
ret . safetyConfigs [ 0 ] . safetyParam | = Panda . FLAG_GM_HW_CAM
ret . minEnableSpeed = 5 * CV . KPH_TO_MS
if experimental_long :
ret . pcmCruise = False
ret . openpilotLongitudinalControl = True
ret . safetyConfigs [ 0 ] . safetyParam | = Panda . FLAG_GM_HW_CAM_LONG
# Tuning
ret . longitudinalTuning . kpV = [ 2.0 , 1.5 ]
ret . longitudinalTuning . kiV = [ 0.72 ]
ret . stopAccel = - 2.0
ret . stoppingDecelRate = 2.0 # reach brake quickly after enabling
ret . vEgoStopping = 0.25
ret . vEgoStarting = 0.25
ret . longitudinalActuatorDelayUpperBound = 0.5
else : # ASCM, OBD-II harness
ret . openpilotLongitudinalControl = True
ret . networkLocation = NetworkLocation . gateway
ret . radarOffCan = False
ret . pcmCruise = False # stock non-adaptive cruise control is kept off
# supports stop and go, but initial engage must (conservatively) be above 18mph
ret . minEnableSpeed = 18 * CV . MPH_TO_MS
# Tuning
ret . longitudinalTuning . kpV = [ 2.4 , 1.5 ]
ret . longitudinalTuning . kiV = [ 0.36 ]
# 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 , CAR . BOLT_EV }
# Start with a baseline tuning for all GM vehicles. Override tuning as needed in each model section below.
# Some GMs need some tolerance above 10 kph to avoid a fault
ret . minSteerSpeed = 10.1 * CV . KPH_TO_MS
ret . lateralTuning . pid . kiBP , ret . lateralTuning . pid . kpBP = [ [ 0. ] , [ 0. ] ]
ret . lateralTuning . pid . kpV , ret . lateralTuning . pid . kiV = [ [ 0.2 ] , [ 0.00 ] ]
ret . lateralTuning . pid . kf = 0.00004 # full torque for 20 deg at 80mph means 0.00007818594
ret . steerActuatorDelay = 0.1 # Default delay, not measured yet
tire_stiffness_factor = 0.444 # not optimized yet
ret . steerLimitTimer = 0.4
ret . radarTimeStep = 0.0667 # GM radar runs at 15Hz instead of standard 20Hz
if candidate == CAR . VOLT :
ret . mass = 1607. + STD_CARGO_KG
ret . wheelbase = 2.69
ret . steerRatio = 17.7 # Stock 15.7, LiveParameters
tire_stiffness_factor = 0.469 # Stock Michelin Energy Saver A/S, LiveParameters
ret . centerToFront = ret . wheelbase * 0.45 # Volt Gen 1, TODO corner weigh
ret . lateralTuning . pid . kpBP = [ 0. , 40. ]
ret . lateralTuning . pid . kpV = [ 0. , 0.17 ]
ret . lateralTuning . pid . kiBP = [ 0. ]
ret . lateralTuning . pid . kiV = [ 0. ]
ret . lateralTuning . pid . kf = 1. # get_steer_feedforward_volt()
ret . steerActuatorDelay = 0.2
elif candidate == CAR . MALIBU :
ret . mass = 1496. + STD_CARGO_KG
ret . wheelbase = 2.83
ret . steerRatio = 15.8
ret . centerToFront = ret . wheelbase * 0.4 # wild guess
elif candidate == CAR . HOLDEN_ASTRA :
ret . mass = 1363. + STD_CARGO_KG
ret . wheelbase = 2.662
# Remaining parameters copied from Volt for now
ret . centerToFront = ret . wheelbase * 0.4
ret . steerRatio = 15.7
elif candidate == CAR . ACADIA :
ret . minEnableSpeed = - 1. # engage speed is decided by pcm
ret . mass = 4353. * CV . LB_TO_KG + STD_CARGO_KG
ret . wheelbase = 2.86
ret . steerRatio = 14.4 # end to end is 13.46
ret . centerToFront = ret . wheelbase * 0.4
ret . lateralTuning . pid . kf = 1. # get_steer_feedforward_acadia()
ret . longitudinalActuatorDelayUpperBound = 0.5 # large delay to initially start braking
elif candidate == CAR . BUICK_REGAL :
ret . mass = 3779. * CV . LB_TO_KG + STD_CARGO_KG # (3849+3708)/2
ret . wheelbase = 2.83 # 111.4 inches in meters
ret . steerRatio = 14.4 # guess for tourx
ret . centerToFront = ret . wheelbase * 0.4 # guess for tourx
elif candidate == CAR . CADILLAC_ATS :
ret . mass = 1601. + STD_CARGO_KG
ret . wheelbase = 2.78
ret . steerRatio = 15.3
ret . centerToFront = ret . wheelbase * 0.5
elif candidate == CAR . ESCALADE_ESV :
ret . minEnableSpeed = - 1. # engage speed is decided by pcm
ret . mass = 2739. + STD_CARGO_KG
ret . wheelbase = 3.302
ret . steerRatio = 17.3
ret . centerToFront = ret . wheelbase * 0.5
ret . lateralTuning . pid . kiBP , ret . lateralTuning . pid . kpBP = [ [ 10. , 41.0 ] , [ 10. , 41.0 ] ]
ret . lateralTuning . pid . kpV , ret . lateralTuning . pid . kiV = [ [ 0.13 , 0.24 ] , [ 0.01 , 0.02 ] ]
ret . lateralTuning . pid . kf = 0.000045
tire_stiffness_factor = 1.0
elif candidate in ( CAR . BOLT_EV , CAR . BOLT_EUV ) :
ret . mass = 1669. + STD_CARGO_KG
ret . wheelbase = 2.63779
ret . steerRatio = 16.8
ret . centerToFront = 2.15 # measured
tire_stiffness_factor = 1.0
ret . steerActuatorDelay = 0.2
CarInterfaceBase . configure_torque_tune ( candidate , ret . lateralTuning )
elif candidate == CAR . SILVERADO :
ret . mass = 2200. + STD_CARGO_KG
ret . wheelbase = 3.75
ret . steerRatio = 16.3
ret . centerToFront = ret . wheelbase * 0.5
tire_stiffness_factor = 1.0
CarInterfaceBase . configure_torque_tune ( candidate , ret . lateralTuning )
elif candidate == CAR . EQUINOX :
ret . mass = 3500. * CV . LB_TO_KG + STD_CARGO_KG
ret . wheelbase = 2.72
ret . steerRatio = 14.4
ret . centerToFront = ret . wheelbase * 0.4
CarInterfaceBase . configure_torque_tune ( candidate , ret . lateralTuning )
# TODO: get actual value, for now starting with reasonable value for
# civic and scaling by mass and wheelbase
ret . rotationalInertia = scale_rot_inertia ( ret . mass , ret . wheelbase )
# 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 ,
tire_stiffness_factor = tire_stiffness_factor )
return ret
# returns a car.CarState
def _update ( self , c ) :
ret = self . CS . update ( self . cp , self . cp_cam , self . cp_loopback )
if self . CS . cruise_buttons != self . CS . prev_cruise_buttons and self . CS . prev_cruise_buttons != CruiseButtons . INIT :
be = create_button_event ( self . CS . cruise_buttons , self . CS . prev_cruise_buttons , BUTTONS_DICT , CruiseButtons . UNPRESS )
# Suppress resume button if we're resuming from stop so we don't adjust speed.
if be . type == ButtonType . accelCruise and ( ret . cruiseState . enabled and ret . standstill ) :
be . type = ButtonType . unknown
ret . buttonEvents = [ be ]
events = self . create_common_events ( ret , extra_gears = [ GearShifter . sport , GearShifter . low ,
GearShifter . eco , GearShifter . manumatic ] ,
pcm_enable = self . CP . pcmCruise )
# Enabling at a standstill with brake is allowed
# TODO: verify 17 Volt can enable for the first time at a stop and allow for all GMs
below_min_enable_speed = ret . vEgo < self . CP . minEnableSpeed or self . CS . moving_backward
if below_min_enable_speed and not ( ret . standstill and ret . brake > = 20 and
self . CP . networkLocation == NetworkLocation . fwdCamera ) :
events . add ( EventName . belowEngageSpeed )
if ret . cruiseState . standstill :
events . add ( EventName . resumeRequired )
if ret . vEgo < self . CP . minSteerSpeed :
events . add ( EventName . belowSteerSpeed )
ret . events = events . to_msg ( )
return ret
def apply ( self , c ) :
return self . CC . update ( c , self . CS )