1
- import zmq
2
1
import math
3
2
import numpy as np
4
- import time
5
- import json
6
3
from selfdrive .controls .lib .pid import PIController
7
4
from selfdrive .controls .lib .drive_helpers import MPC_COST_LAT
8
5
from selfdrive .controls .lib .lateral_mpc import libmpc_py
@@ -24,14 +21,6 @@ def calc_states_after_delay(states, v_ego, steer_angle, curvature_factor, steer_
24
21
def get_steer_max (CP , v_ego ):
25
22
return interp (v_ego , CP .steerMaxBP , CP .steerMaxV )
26
23
27
- def apply_deadzone (angle , deadzone ):
28
- if angle > deadzone :
29
- angle -= deadzone
30
- elif angle < - deadzone :
31
- angle += deadzone
32
- else :
33
- angle = 0.
34
- return angle
35
24
36
25
class LatControl (object ):
37
26
def __init__ (self , CP ):
@@ -40,25 +29,6 @@ def __init__(self, CP):
40
29
k_f = CP .steerKf , pos_limit = 1.0 )
41
30
self .last_cloudlog_t = 0.0
42
31
self .setup_mpc (CP .steerRateCost )
43
- self .smooth_factor = 2.0 * CP .steerActuatorDelay / _DT # Multiplier for inductive component (feed forward)
44
- self .projection_factor = 5.0 * _DT # Mutiplier for reactive component (PI)
45
- self .accel_limit = 5.0 # Desired acceleration limit to prevent "whip steer" (resistive component)
46
- self .ff_angle_factor = 0.5 # Kf multiplier for angle-based feed forward
47
- self .ff_rate_factor = 5.0 # Kf multiplier for rate-based feed forward
48
- self .ratioDelayExp = 2.0 # Exponential coefficient for variable steering rate (delay)
49
- self .ratioDelayScale = 0.0 # Multiplier for variable steering rate (delay)
50
- self .prev_angle_rate = 0
51
- self .feed_forward = 0.0
52
- self .angle_rate_desired = 0.0
53
-
54
- # variables for dashboarding
55
- self .context = zmq .Context ()
56
- self .steerpub = self .context .socket (zmq .PUB )
57
- self .steerpub .bind ("tcp://*:8594" )
58
- self .steerdata = ""
59
- self .frames = 0
60
- self .curvature_factor = 0.0
61
- self .slip_factor = 0.0
62
32
63
33
def setup_mpc (self , steer_rate_cost ):
64
34
self .libmpc = libmpc_py .libmpc
@@ -82,58 +52,38 @@ def setup_mpc(self, steer_rate_cost):
82
52
def reset (self ):
83
53
self .pid .reset ()
84
54
85
- def update (self , active , v_ego , angle_steers , angle_rate , steer_override , d_poly , angle_offset , CP , VM , PL ):
86
- cur_time = sec_since_boot ()
55
+ def update (self , active , v_ego , angle_steers , steer_override , d_poly , angle_offset , CP , VM , PL ):
56
+ cur_time = sec_since_boot ()
87
57
self .mpc_updated = False
88
58
# TODO: this creates issues in replay when rewinding time: mpc won't run
89
59
if self .last_mpc_ts < PL .last_md_ts :
90
60
self .last_mpc_ts = PL .last_md_ts
91
61
self .angle_steers_des_prev = self .angle_steers_des_mpc
92
62
93
- self . curvature_factor = VM .curvature_factor (v_ego )
63
+ curvature_factor = VM .curvature_factor (v_ego )
94
64
95
- # This is currently disabled, but it is used to compensate for variable steering rate
96
- ratioDelayFactor = 1. + self .ratioDelayScale * abs (angle_steers / 100. ) ** self .ratioDelayExp
65
+ l_poly = libmpc_py .ffi .new ("double[4]" , list (PL .PP .l_poly ))
66
+ r_poly = libmpc_py .ffi .new ("double[4]" , list (PL .PP .r_poly ))
67
+ p_poly = libmpc_py .ffi .new ("double[4]" , list (PL .PP .p_poly ))
97
68
98
- # Determine a proper delay time that includes the model's processing time, which is variable
99
- plan_age = _DT_MPC + cur_time - float (PL .last_md_ts / 1000000000.0 )
100
- total_delay = ratioDelayFactor * CP .steerActuatorDelay + plan_age
101
-
102
- # Use steering rate from the last 2 samples to estimate acceleration for a more realistic future steering rate
103
- accelerated_angle_rate = 2.0 * angle_rate - self .prev_angle_rate
104
-
105
- # Project the future steering angle for the actuator delay only (not model delay)
106
- projected_angle_steers = ratioDelayFactor * CP .steerActuatorDelay * accelerated_angle_rate + angle_steers
107
-
108
- self .l_poly = libmpc_py .ffi .new ("double[4]" , list (PL .PP .l_poly ))
109
- self .r_poly = libmpc_py .ffi .new ("double[4]" , list (PL .PP .r_poly ))
110
- self .p_poly = libmpc_py .ffi .new ("double[4]" , list (PL .PP .p_poly ))
111
-
112
- # account for actuation delay and the age of the plan
113
- self .cur_state = calc_states_after_delay (self .cur_state , v_ego , projected_angle_steers , self .curvature_factor , CP .steerRatio , total_delay )
69
+ # account for actuation delay
70
+ self .cur_state = calc_states_after_delay (self .cur_state , v_ego , angle_steers , curvature_factor , CP .steerRatio , CP .steerActuatorDelay )
114
71
115
72
v_ego_mpc = max (v_ego , 5.0 ) # avoid mpc roughness due to low speed
116
73
self .libmpc .run_mpc (self .cur_state , self .mpc_solution ,
117
- self . l_poly , self . r_poly , self . p_poly ,
118
- PL .PP .l_prob , PL .PP .r_prob , PL .PP .p_prob , self . curvature_factor , v_ego_mpc , PL .PP .lane_width )
74
+ l_poly , r_poly , p_poly ,
75
+ PL .PP .l_prob , PL .PP .r_prob , PL .PP .p_prob , curvature_factor , v_ego_mpc , PL .PP .lane_width )
119
76
120
77
# reset to current steer angle if not active or overriding
121
78
if active :
122
- self .isActive = 1
123
79
delta_desired = self .mpc_solution [0 ].delta [1 ]
124
80
else :
125
- self .isActive = 0
126
81
delta_desired = math .radians (angle_steers - angle_offset ) / CP .steerRatio
127
82
128
83
self .cur_state [0 ].delta = delta_desired
129
84
130
85
self .angle_steers_des_mpc = float (math .degrees (delta_desired * CP .steerRatio ) + angle_offset )
131
-
132
- # Use the model's solve time instead of cur_time
133
- self .angle_steers_des_time = float (PL .last_md_ts / 1000000000.0 )
134
-
135
- # Use last 2 desired angles to determine the model's desired steer rate
136
- self .angle_rate_desired = (self .angle_steers_des_mpc - self .angle_steers_des_prev ) / _DT_MPC
86
+ self .angle_steers_des_time = cur_time
137
87
self .mpc_updated = True
138
88
139
89
# Check for infeasable MPC solution
@@ -147,81 +97,24 @@ def update(self, active, v_ego, angle_steers, angle_rate, steer_override, d_poly
147
97
self .last_cloudlog_t = t
148
98
cloudlog .warning ("Lateral mpc - nan: True" )
149
99
150
- elif self .steerdata != "" and self .frames > 10 :
151
- self .steerpub .send (self .steerdata )
152
- self .frames = 0
153
- self .steerdata = ""
154
-
155
100
if v_ego < 0.3 or not active :
156
101
output_steer = 0.0
157
- self .feed_forward = 0.0
158
102
self .pid .reset ()
159
103
else :
160
- # Interpolate desired angle between MPC updates
161
- self .angle_steers_des = self .angle_steers_des_prev + self .angle_rate_desired * (cur_time - self .angle_steers_des_time )
162
-
163
- # Determine the target steer rate for desired angle, but prevent the acceleration limit from being exceeded
164
- # Restricting the steer rate creates the resistive component needed for resonance
165
- restricted_steer_rate = np .clip (self .angle_steers_des - float (angle_steers ) , float (angle_rate ) - self .accel_limit , float (angle_rate ) + self .accel_limit )
166
-
167
- # Determine projected desired angle that is within the acceleration limit (prevent the steering wheel from jerking)
168
- projected_angle_steers_des = self .angle_steers_des + self .projection_factor * restricted_steer_rate
169
-
170
- # Determine project angle steers using current steer rate
171
- projected_angle_steers = float (angle_steers ) + self .projection_factor * float (angle_rate )
172
-
104
+ # TODO: ideally we should interp, but for tuning reasons we keep the mpc solution
105
+ # constant for 0.05s.
106
+ #dt = min(cur_time - self.angle_steers_des_time, _DT_MPC + _DT) + _DT # no greater than dt mpc + dt, to prevent too high extraps
107
+ #self.angle_steers_des = self.angle_steers_des_prev + (dt / _DT_MPC) * (self.angle_steers_des_mpc - self.angle_steers_des_prev)
108
+ self .angle_steers_des = self .angle_steers_des_mpc
173
109
steers_max = get_steer_max (CP , v_ego )
174
110
self .pid .pos_limit = steers_max
175
111
self .pid .neg_limit = - steers_max
176
-
112
+ steer_feedforward = self . angle_steers_des # feedforward desired angle
177
113
if CP .steerControlType == car .CarParams .SteerControlType .torque :
178
- # Decide which feed forward mode should be used (angle or rate). Use more dominant mode, and only if conditions are met
179
- # Spread feed forward out over a period of time to make it more inductive (for resonance)
180
- if abs (self .ff_rate_factor * float (restricted_steer_rate )) > abs (self .ff_angle_factor * float (self .angle_steers_des ) - float (angle_offset )) - 0.5 \
181
- and (abs (float (restricted_steer_rate )) > abs (angle_rate ) or (float (restricted_steer_rate ) < 0 ) != (angle_rate < 0 )) \
182
- and (float (restricted_steer_rate ) < 0 ) == (float (self .angle_steers_des ) - float (angle_offset ) - 0.5 < 0 ):
183
- ff_type = "r"
184
- self .feed_forward = (((self .smooth_factor - 1. ) * self .feed_forward ) + self .ff_rate_factor * v_ego ** 2 * float (restricted_steer_rate )) / self .smooth_factor
185
- elif abs (self .angle_steers_des - float (angle_offset )) > 0.5 :
186
- ff_type = "a"
187
- self .feed_forward = (((self .smooth_factor - 1. ) * self .feed_forward ) + self .ff_angle_factor * v_ego ** 2 * float (apply_deadzone (float (self .angle_steers_des ) - float (angle_offset ), 0.5 ))) / self .smooth_factor
188
- else :
189
- ff_type = "r"
190
- self .feed_forward = (((self .smooth_factor - 1. ) * self .feed_forward ) + 0.0 ) / self .smooth_factor
191
- else :
192
- self .feed_forward = self .angle_steers_des_mpc # feedforward desired angle
114
+ steer_feedforward *= v_ego ** 2 # proportional to realigning tire momentum (~ lateral accel)
193
115
deadzone = 0.0
194
-
195
- # Use projected desired and actual angles instead of "current" values, in order to make PI more reactive (for resonance)
196
- output_steer = self .pid .update (projected_angle_steers_des , projected_angle_steers , check_saturation = (v_ego > 10 ), override = steer_override ,
197
- feedforward = self .feed_forward , speed = v_ego , deadzone = deadzone )
198
-
199
-
200
- # All but the last 3 lines after here are for real-time dashboarding
201
- self .pCost = 0.0
202
- self .lCost = 0.0
203
- self .rCost = 0.0
204
- self .hCost = 0.0
205
- self .srCost = 0.0
206
- self .last_ff_a = 0.0
207
- self .last_ff_r = 0.0
208
- self .center_angle = 0.0
209
- self .steer_zero_crossing = 0.0
210
- self .steer_rate_cost = 0.0
211
- self .avg_angle_rate = 0.0
212
- self .angle_rate_count = 0.0
213
- driver_torque = 0.0
214
- steer_motor = 0.0
215
- self .frames += 1
216
-
217
- self .steerdata += ("%d,%s,%d,%d,%f,%f,%f,%f,%f,%f,%f,%f,%f,%f,%f,%f,%f,%f,%f,%f,%f,%f,%f,%f,%f,%f,%f,%f,%f,%f,%f,%f,%f,%f,%f,%f,%f,%f,%f,%f,%f,%f,%f,%f,%f,%f,%f,%f,%f,%f,%f,%f,%f,%f,%f,%f,%f,%f,%f,%f,%f,%f,%f,%f,%f,%f,%f,%f,%f,%d|" % (self .isActive , \
218
- ff_type , 1 if ff_type == "a" else 0 , 1 if ff_type == "r" else 0 , self .cur_state [0 ].x , self .cur_state [0 ].y , self .cur_state [0 ].psi , self .cur_state [0 ].delta , self .cur_state [0 ].t , self .curvature_factor , self .slip_factor ,self .smooth_factor , self .accel_limit , float (restricted_steer_rate ) ,self .ff_angle_factor , self .ff_rate_factor , self .pCost , self .lCost , self .rCost , self .hCost , self .srCost , steer_motor , float (driver_torque ), \
219
- self .angle_rate_count , self .angle_rate_desired , self .avg_angle_rate , projected_angle_steers , float (angle_rate ), self .steer_zero_crossing , self .center_angle , angle_steers , self .angle_steers_des , angle_offset , \
220
- self .angle_steers_des_mpc , CP .steerRatio , CP .steerKf , CP .steerKpV [0 ], CP .steerKiV [0 ], CP .steerRateCost , PL .PP .l_prob , \
221
- PL .PP .r_prob , PL .PP .c_prob , PL .PP .p_prob , self .l_poly [0 ], self .l_poly [1 ], self .l_poly [2 ], self .l_poly [3 ], self .r_poly [0 ], self .r_poly [1 ], self .r_poly [2 ], self .r_poly [3 ], \
222
- self .p_poly [0 ], self .p_poly [1 ], self .p_poly [2 ], self .p_poly [3 ], PL .PP .c_poly [0 ], PL .PP .c_poly [1 ], PL .PP .c_poly [2 ], PL .PP .c_poly [3 ], PL .PP .d_poly [0 ], PL .PP .d_poly [1 ], \
223
- PL .PP .d_poly [2 ], PL .PP .lane_width , PL .PP .lane_width_estimate , PL .PP .lane_width_certainty , v_ego , self .pid .p , self .pid .i , self .pid .f , int (time .time () * 100 ) * 10000000 ))
116
+ output_steer = self .pid .update (self .angle_steers_des , angle_steers , check_saturation = (v_ego > 10 ), override = steer_override ,
117
+ feedforward = steer_feedforward , speed = v_ego , deadzone = deadzone )
224
118
225
119
self .sat_flag = self .pid .saturated
226
- self .prev_angle_rate = angle_rate
227
120
return output_steer , float (self .angle_steers_des )
0 commit comments