1
+ import zmq
1
2
import math
2
3
import numpy as np
4
+ import time
5
+ import json
3
6
from selfdrive .controls .lib .pid import PIController
4
7
from selfdrive .controls .lib .drive_helpers import MPC_COST_LAT
5
8
from selfdrive .controls .lib .lateral_mpc import libmpc_py
@@ -21,6 +24,14 @@ def calc_states_after_delay(states, v_ego, steer_angle, curvature_factor, steer_
21
24
def get_steer_max (CP , v_ego ):
22
25
return interp (v_ego , CP .steerMaxBP , CP .steerMaxV )
23
26
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
24
35
25
36
class LatControl (object ):
26
37
def __init__ (self , CP ):
@@ -29,6 +40,25 @@ def __init__(self, CP):
29
40
k_f = CP .steerKf , pos_limit = 1.0 )
30
41
self .last_cloudlog_t = 0.0
31
42
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
32
62
33
63
def setup_mpc (self , steer_rate_cost ):
34
64
self .libmpc = libmpc_py .libmpc
@@ -52,38 +82,58 @@ def setup_mpc(self, steer_rate_cost):
52
82
def reset (self ):
53
83
self .pid .reset ()
54
84
55
- def update (self , active , v_ego , angle_steers , steer_override , d_poly , angle_offset , CP , VM , PL ):
56
- cur_time = sec_since_boot ()
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 ()
57
87
self .mpc_updated = False
58
88
# TODO: this creates issues in replay when rewinding time: mpc won't run
59
89
if self .last_mpc_ts < PL .last_md_ts :
60
90
self .last_mpc_ts = PL .last_md_ts
61
91
self .angle_steers_des_prev = self .angle_steers_des_mpc
62
92
63
- curvature_factor = VM .curvature_factor (v_ego )
93
+ self . curvature_factor = VM .curvature_factor (v_ego )
64
94
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 ))
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
68
97
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 )
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 )
71
114
72
115
v_ego_mpc = max (v_ego , 5.0 ) # avoid mpc roughness due to low speed
73
116
self .libmpc .run_mpc (self .cur_state , self .mpc_solution ,
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 )
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 )
76
119
77
120
# reset to current steer angle if not active or overriding
78
121
if active :
122
+ self .isActive = 1
79
123
delta_desired = self .mpc_solution [0 ].delta [1 ]
80
124
else :
125
+ self .isActive = 0
81
126
delta_desired = math .radians (angle_steers - angle_offset ) / CP .steerRatio
82
127
83
128
self .cur_state [0 ].delta = delta_desired
84
129
85
130
self .angle_steers_des_mpc = float (math .degrees (delta_desired * CP .steerRatio ) + angle_offset )
86
- self .angle_steers_des_time = cur_time
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
87
137
self .mpc_updated = True
88
138
89
139
# Check for infeasable MPC solution
@@ -97,24 +147,81 @@ def update(self, active, v_ego, angle_steers, steer_override, d_poly, angle_offs
97
147
self .last_cloudlog_t = t
98
148
cloudlog .warning ("Lateral mpc - nan: True" )
99
149
150
+ elif self .steerdata != "" and self .frames > 10 :
151
+ self .steerpub .send (self .steerdata )
152
+ self .frames = 0
153
+ self .steerdata = ""
154
+
100
155
if v_ego < 0.3 or not active :
101
156
output_steer = 0.0
157
+ self .feed_forward = 0.0
102
158
self .pid .reset ()
103
159
else :
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
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
+
109
173
steers_max = get_steer_max (CP , v_ego )
110
174
self .pid .pos_limit = steers_max
111
175
self .pid .neg_limit = - steers_max
112
- steer_feedforward = self . angle_steers_des # feedforward desired angle
176
+
113
177
if CP .steerControlType == car .CarParams .SteerControlType .torque :
114
- steer_feedforward *= v_ego ** 2 # proportional to realigning tire momentum (~ lateral accel)
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
115
193
deadzone = 0.0
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 )
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 ))
118
224
119
225
self .sat_flag = self .pid .saturated
226
+ self .prev_angle_rate = angle_rate
120
227
return output_steer , float (self .angle_steers_des )
0 commit comments