from cereal import log
from common . numpy_fast import clip , interp
from selfdrive . controls . lib . pid import PIController
from selfdrive . controls . lib . drive_helpers import CONTROL_N
from selfdrive . modeld . constants import T_IDXS
LongCtrlState = log . ControlsState . LongControlState
ACCEL_MAX = 2.0
ACCEL_MIN = - 4.0
ACCEL_SCALE = 4.0
STOPPING_EGO_SPEED = 0.5
STOPPING_TARGET_SPEED_OFFSET = 0.01
STARTING_TARGET_SPEED = 0.5
DECEL_THRESHOLD_TO_PID = 0.8
DECEL_STOPPING_TARGET = 2.0 # apply at least this amount of brake to maintain the vehicle stationary
RATE = 100.0
DEFAULT_LONG_LAG = 0.15
# TODO this logic isn't really car independent, does not belong here
def long_control_state_trans ( active , long_control_state , v_ego , v_target , v_pid ,
output_accel , brake_pressed , cruise_standstill , min_speed_can ) :
""" Update longitudinal control state machine """
stopping_target_speed = min_speed_can + STOPPING_TARGET_SPEED_OFFSET
stopping_condition = ( v_ego < 2.0 and cruise_standstill ) or \
( v_ego < STOPPING_EGO_SPEED and
( ( v_pid < stopping_target_speed and v_target < stopping_target_speed ) or
brake_pressed ) )
starting_condition = v_target > STARTING_TARGET_SPEED and not cruise_standstill
if not active :
long_control_state = LongCtrlState . off
else :
if long_control_state == LongCtrlState . off :
if active :
long_control_state = LongCtrlState . pid
elif long_control_state == LongCtrlState . pid :
if stopping_condition :
long_control_state = LongCtrlState . stopping
elif long_control_state == LongCtrlState . stopping :
if starting_condition :
long_control_state = LongCtrlState . starting
elif long_control_state == LongCtrlState . starting :
if stopping_condition :
long_control_state = LongCtrlState . stopping
elif output_accel > = - DECEL_THRESHOLD_TO_PID :
long_control_state = LongCtrlState . pid
return long_control_state
class LongControl ( ) :
def __init__ ( self , CP ) :
self . long_control_state = LongCtrlState . off # initialized to off
self . pid = PIController ( ( CP . longitudinalTuning . kpBP , CP . longitudinalTuning . kpV ) ,
( CP . longitudinalTuning . kiBP , CP . longitudinalTuning . kiV ) ,
rate = RATE ,
sat_limit = 0.8 )
self . pid . pos_limit = ACCEL_MAX
self . pid . neg_limit = ACCEL_MIN
self . v_pid = 0.0
self . last_output_accel = 0.0
def reset ( self , v_pid ) :
""" Reset PID controller and change setpoint """
self . pid . reset ( )
self . v_pid = v_pid
def update ( self , active , CS , CP , long_plan ) :
""" Update longitudinal control. This updates the state machine and runs a PID loop """
# Interp control trajectory
# TODO estimate car specific lag, use .15s for now
if len ( long_plan . speeds ) == CONTROL_N :
v_target = interp ( DEFAULT_LONG_LAG , T_IDXS [ : CONTROL_N ] , long_plan . speeds )
v_target_future = long_plan . speeds [ - 1 ]
a_target = interp ( DEFAULT_LONG_LAG , T_IDXS [ : CONTROL_N ] , long_plan . accels )
else :
v_target = 0.0
v_target_future = 0.0
a_target = 0.0
# Update state machine
output_accel = self . last_output_accel
self . long_control_state = long_control_state_trans ( active , self . long_control_state , CS . vEgo ,
v_target_future , self . v_pid , output_accel ,
CS . brakePressed , CS . cruiseState . standstill , CP . minSpeedCan )
v_ego_pid = max ( CS . vEgo , CP . minSpeedCan ) # Without this we get jumps, CAN bus reports 0 when speed < 0.3
if self . long_control_state == LongCtrlState . off or CS . gasPressed :
self . reset ( v_ego_pid )
output_accel = 0.
# tracking objects and driving
elif self . long_control_state == LongCtrlState . pid :
self . v_pid = v_target
# Toyota starts braking more when it thinks you want to stop
# Freeze the integrator so we don't accelerate to compensate, and don't allow positive acceleration
prevent_overshoot = not CP . stoppingControl and CS . vEgo < 1.5 and v_target_future < 0.7
deadzone = interp ( v_ego_pid , CP . longitudinalTuning . deadzoneBP , CP . longitudinalTuning . deadzoneV )
output_accel = self . pid . update ( self . v_pid , v_ego_pid , speed = v_ego_pid , deadzone = deadzone , feedforward = a_target , freeze_integrator = prevent_overshoot )
if prevent_overshoot :
output_accel = min ( output_accel , 0.0 )
# Intention is to stop, switch to a different brake control until we stop
elif self . long_control_state == LongCtrlState . stopping :
# Keep applying brakes until the car is stopped
if not CS . standstill or output_accel > - DECEL_STOPPING_TARGET :
output_accel - = CP . stoppingDecelRate / RATE
output_accel = clip ( output_accel , ACCEL_MIN , ACCEL_MAX )
self . reset ( CS . vEgo )
# Intention is to move again, release brake fast before handing control to PID
elif self . long_control_state == LongCtrlState . starting :
if output_accel < - DECEL_THRESHOLD_TO_PID :
output_accel + = CP . startingAccelRate / RATE
self . reset ( CS . vEgo )
self . last_output_accel = output_accel
final_accel = clip ( output_accel , ACCEL_MIN , ACCEL_MAX )
return final_accel , v_target , a_target