#!/usr/bin/env python3
from cereal import car
from math import fabs , exp
from panda import Panda
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 . interfaces import CarInterfaceBase , TorqueFromLateralAccelCallbackType , FRICTION_THRESHOLD
from selfdrive . controls . lib . drive_helpers import get_friction
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 }
NON_LINEAR_TORQUE_PARAMS = {
CAR . BOLT_EUV : [ 2.6531724862969748 , 1.0 , 0.1919764879840985 , 0.009054123646805178 ] ,
CAR . ACADIA : [ 4.78003305 , 1.0 , 0.3122 , 0.05591772 ]
}
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 )
def get_steer_feedforward_function ( self ) :
if self . CP . carFingerprint == CAR . VOLT :
return self . get_steer_feedforward_volt
else :
return CarInterfaceBase . get_steer_feedforward_default
def torque_from_lateral_accel_siglin ( self , lateral_accel_value : float , torque_params : car . CarParams . LateralTorqueTuning ,
lateral_accel_error : float , lateral_accel_deadzone : float , friction_compensation : bool ) - > float :
friction = get_friction ( lateral_accel_error , lateral_accel_deadzone , FRICTION_THRESHOLD , torque_params , friction_compensation )
def sig ( val ) :
return 1 / ( 1 + exp ( - val ) ) - 0.5
# The "lat_accel vs torque" relationship is assumed to be the sum of "sigmoid + linear" curves
# An important thing to consider is that the slope at 0 should be > 0 (ideally >1)
# This has big effect on the stability about 0 (noise when going straight)
# ToDo: To generalize to other GMs, explore tanh function as the nonlinear
non_linear_torque_params = NON_LINEAR_TORQUE_PARAMS . get ( self . CP . carFingerprint )
assert non_linear_torque_params , " The params are not defined "
a , b , c , _ = non_linear_torque_params
steer_torque = ( sig ( lateral_accel_value * a ) * b ) + ( lateral_accel_value * c )
return float ( steer_torque ) + friction
def torque_from_lateral_accel ( self ) - > TorqueFromLateralAccelCallbackType :
if self . CP . carFingerprint in NON_LINEAR_TORQUE_PARAMS :
return self . torque_from_lateral_accel_siglin
else :
return self . torque_from_lateral_accel_linear
@staticmethod
def _get_params ( ret , candidate , fingerprint , car_fw , experimental_long , docs ) :
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 . radarUnavailable = True # no radar
ret . pcmCruise = True
ret . safetyConfigs [ 0 ] . safetyParam | = Panda . FLAG_GM_HW_CAM
ret . minEnableSpeed = 5 * CV . KPH_TO_MS
ret . minSteerSpeed = 10 * CV . KPH_TO_MS
# Tuning for experimental long
ret . longitudinalTuning . kpV = [ 2.0 , 1.5 ]
ret . longitudinalTuning . kiV = [ 0.72 ]
ret . stoppingDecelRate = 2.0 # reach brake quickly after enabling
ret . vEgoStopping = 0.25
ret . vEgoStarting = 0.25
if experimental_long :
ret . pcmCruise = False
ret . openpilotLongitudinalControl = True
ret . safetyConfigs [ 0 ] . safetyParam | = Panda . FLAG_GM_HW_CAM_LONG
else : # ASCM, OBD-II harness
ret . openpilotLongitudinalControl = True
ret . networkLocation = NetworkLocation . gateway
ret . radarUnavailable = RADAR_HEADER_MSG not in fingerprint [ CanBus . OBSTACLE ] and not docs
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
ret . minSteerSpeed = 7 * 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 } or \
( ret . networkLocation == NetworkLocation . gateway and ret . radarUnavailable )
# 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. ] ]
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
ret . longitudinalActuatorDelayUpperBound = 0.5 # large delay to initially start braking
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 . steerActuatorDelay = 0.2
CarInterfaceBase . configure_torque_tune ( candidate , ret . lateralTuning )
GM: Buick LaCrosse 2017-19 support (#27332)
* Added Buick LaCrosse 2017
* Added Buick LaCrosse as candidate
* Added Buick LaCrosse CAR_INFO and Fingerprint
* Added Buick LaCrosse to non_tested_cars
* Added Buick LaCrosse
* Updated number of supported cars to 237
* Added ACC and LKAS description to Buick LaCrosse
* Updated CAR_INFO for Buick LaCrosse
* Added the Escalade which was recently updated
* Update selfdrive/car/gm/values.py
Suggested fingerprint by @sshane
Co-authored-by: Shane Smiskol <shane@smiskol.com>
* Premium is a trim with ACC. Use package name instead
* lacrosse custom FF;
fit info:
describe(steer_offsets) = DescribeResult(nobs=1649402, minmax=(-0.7127894163131714, 5.3997602462768555), mean=3.3090523060153645, variance=0.3130325564084465, skewness=-1.5986155151533736, kurtosis=8.18810418298873)
Samples: 1357787
Regularizing...
Regularized samples: 1140
speed: DescribeResult(nobs=1140, minmax=(8.478170424241286, 35.837870224662446), mean=27.888804767013475, variance=34.16742353763829, skewness=-1.068587303119431, kurtosis=0.6193071765927134)
angle: DescribeResult(nobs=1140, minmax=(-21.057768565637094, 28.516874490999708), mean=-0.4328602593886506, variance=43.19046813273241, skewness=0.001966426701503317, kurtosis=0.20547357649038434)
steer: DescribeResult(nobs=1140, minmax=(-0.8432471203007578, 0.9634959333674695), mean=-0.021981142946747863, variance=0.20152217060233915, skewness=0.03202313890158864, kurtosis=-1.2318826088567174)
Performing fit...
Fit: [5.85397825e-01 3.27650818e-01 4.60531117e-03 1.32307599e+01
1.37194709e-01 1.33099557e-01 6.14782304e-02]
ANGLE_COEF = 0.58539783
ANGLE_COEF2 = 0.32765082
ANGLE_OFFSET = 0.00460531
SPEED_OFFSET = 13.23075991
SIGMOID_COEF_RIGHT = 0.13719471
SIGMOID_COEF_LEFT = 0.13309956
SPEED_COEF = 0.06147823
MAE old 0.2098, new 0.0309
STD old 0.1021, new 0.0273
deg 00-03:457, deg 03-06:258, deg 06-09:218, deg 09-12:132, deg 12-15:62
deg 15-18:6, deg 18-21:4, deg 21-24:1, deg 24-27:0, deg 27-30:2
deg 30-33:0, deg 33-36:0, deg 36-39:0, deg 39-42:0, deg 42-45:0
mph 10-15:0, mph 15-20:1, mph 20-25:18, mph 25-30:12, mph 30-35:30
mph 35-40:26, mph 40-45:52, mph 45-50:54, mph 50-55:73, mph 55-60:86
mph 60-65:204, mph 65-70:228, mph 70-75:179, mph 75-80:176, mph 80-85:1
mph 85-90:0,
* Update routes.py
* remove from non tested routes
* use torque controller
* update docs
* update releases
---------
Co-authored-by: Shane Smiskol <shane@smiskol.com>
Co-authored-by: Tim Wilson <twilsonco@gmail.com>
old-commit-hash: ae423a68686ca258c40fa679b931011e99f5f780
2 years ago
elif candidate == CAR . BUICK_LACROSSE :
ret . mass = 1712. + STD_CARGO_KG
ret . wheelbase = 2.91
ret . steerRatio = 15.8
ret . centerToFront = ret . wheelbase * 0.4 # wild guess
CarInterfaceBase . configure_torque_tune ( candidate , ret . lateralTuning )
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 :
ret . minEnableSpeed = - 1. # engage speed is decided by pcm
ret . mass = 5653. * CV . LB_TO_KG + STD_CARGO_KG # (5552+5815)/2
ret . wheelbase = 2.95 # 116 inches in meters
ret . steerRatio = 17.3
ret . centerToFront = ret . wheelbase * 0.5
CarInterfaceBase . configure_torque_tune ( candidate , ret . lateralTuning )
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 == CAR . BOLT_EUV :
ret . mass = 1669. + STD_CARGO_KG
ret . wheelbase = 2.63779
ret . steerRatio = 16.8
ret . centerToFront = ret . wheelbase * 0.4
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
# On the Bolt, the ECM and camera independently check that you are either above 5 kph or at a stop
# with foot on brake to allow engagement, but this platform only has that check in the camera.
# TODO: check if this is split by EV/ICE with more platforms in the future
if ret . openpilotLongitudinalControl :
ret . minEnableSpeed = - 1.
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 )
elif candidate == CAR . TRAILBLAZER :
ret . mass = 1345. + STD_CARGO_KG
ret . wheelbase = 2.64
ret . steerRatio = 16.8
ret . centerToFront = ret . wheelbase * 0.4
tire_stiffness_factor = 1.0
ret . steerActuatorDelay = 0.2
CarInterfaceBase . configure_torque_tune ( candidate , ret . lateralTuning )
# 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 :
buttonEvents = [ create_button_event ( self . CS . cruise_buttons , self . CS . prev_cruise_buttons , BUTTONS_DICT , CruiseButtons . UNPRESS ) ]
# Handle ACCButtons changing buttons mid-press
if self . CS . cruise_buttons != CruiseButtons . UNPRESS and self . CS . prev_cruise_buttons != CruiseButtons . UNPRESS :
buttonEvents . append ( create_button_event ( CruiseButtons . UNPRESS , self . CS . prev_cruise_buttons , BUTTONS_DICT , CruiseButtons . UNPRESS ) )
ret . buttonEvents = buttonEvents
# The ECM allows enabling on falling edge of set, but only rising edge of resume
events = self . create_common_events ( ret , extra_gears = [ GearShifter . sport , GearShifter . low ,
GearShifter . eco , GearShifter . manumatic ] ,
pcm_enable = self . CP . pcmCruise , enable_buttons = ( ButtonType . decelCruise , ) )
if not self . CP . pcmCruise :
if any ( b . type == ButtonType . accelCruise and b . pressed for b in ret . buttonEvents ) :
events . add ( EventName . buttonEnable )
# 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 , now_nanos ) :
return self . CC . update ( c , self . CS , now_nanos )