import numpy as np
from selfdrive . controls . lib . drive_helpers import get_steer_max
from common . numpy_fast import clip
from common . realtime import DT_CTRL
from cereal import log
class LatControlLQR ( ) :
def __init__ ( self , CP ) :
self . scale = CP . lateralTuning . lqr . scale
self . ki = CP . lateralTuning . lqr . ki
self . A = np . array ( CP . lateralTuning . lqr . a ) . reshape ( ( 2 , 2 ) )
self . B = np . array ( CP . lateralTuning . lqr . b ) . reshape ( ( 2 , 1 ) )
self . C = np . array ( CP . lateralTuning . lqr . c ) . reshape ( ( 1 , 2 ) )
self . K = np . array ( CP . lateralTuning . lqr . k ) . reshape ( ( 1 , 2 ) )
self . L = np . array ( CP . lateralTuning . lqr . l ) . reshape ( ( 2 , 1 ) )
self . dc_gain = CP . lateralTuning . lqr . dcGain
self . x_hat = np . array ( [ [ 0 ] , [ 0 ] ] )
self . i_unwind_rate = 0.3 * DT_CTRL
self . i_rate = 1.0 * DT_CTRL
self . sat_count_rate = 1.0 * DT_CTRL
self . sat_limit = CP . steerLimitTimer
self . reset ( )
def reset ( self ) :
self . i_lqr = 0.0
self . output_steer = 0.0
self . sat_count = 0.0
def _check_saturation ( self , control , check_saturation , limit ) :
saturated = abs ( control ) == limit
if saturated and check_saturation :
self . sat_count + = self . sat_count_rate
else :
self . sat_count - = self . sat_count_rate
self . sat_count = clip ( self . sat_count , 0.0 , 1.0 )
return self . sat_count > self . sat_limit
def update ( self , active , CS , CP , path_plan ) :
lqr_log = log . ControlsState . LateralLQRState . new_message ( )
steers_max = get_steer_max ( CP , CS . vEgo )
torque_scale = ( 0.45 + CS . vEgo / 60.0 ) * * 2 # Scale actuator model with speed
steering_angle = CS . steeringAngle
# Subtract offset. Zero angle should correspond to zero torque
self . angle_steers_des = path_plan . angleSteers - path_plan . angleOffset
steering_angle - = path_plan . angleOffset
# Update Kalman filter
angle_steers_k = float ( self . C . dot ( self . x_hat ) )
e = steering_angle - angle_steers_k
self . x_hat = self . A . dot ( self . x_hat ) + self . B . dot ( CS . steeringTorqueEps / torque_scale ) + self . L . dot ( e )
if CS . vEgo < 0.3 or not active :
lqr_log . active = False
lqr_output = 0.
self . reset ( )
else :
lqr_log . active = True
# LQR
u_lqr = float ( self . angle_steers_des / self . dc_gain - self . K . dot ( self . x_hat ) )
lqr_output = torque_scale * u_lqr / self . scale
# Integrator
if CS . steeringPressed :
self . i_lqr - = self . i_unwind_rate * float ( np . sign ( self . i_lqr ) )
else :
error = self . angle_steers_des - angle_steers_k
i = self . i_lqr + self . ki * self . i_rate * error
control = lqr_output + i
if ( ( error > = 0 and ( control < = steers_max or i < 0.0 ) ) or \
( error < = 0 and ( control > = - steers_max or i > 0.0 ) ) ) :
self . i_lqr = i
self . output_steer = lqr_output + self . i_lqr
self . output_steer = clip ( self . output_steer , - steers_max , steers_max )
check_saturation = ( CS . vEgo > 10 ) and not CS . steeringRateLimited and not CS . steeringPressed
saturated = self . _check_saturation ( self . output_steer , check_saturation , steers_max )
lqr_log . steerAngle = angle_steers_k + path_plan . angleOffset
lqr_log . i = self . i_lqr
lqr_log . output = self . output_steer
lqr_log . lqrOutput = lqr_output
lqr_log . saturated = saturated
return self . output_steer , float ( self . angle_steers_des ) , lqr_log