1212
1313from estimation import ukf_utils
1414from util .constants import Constants
15+ from util .LowPassFilter import LowPassFilter
1516
1617class SteerOffsetEstimator (Node ):
1718 """
@@ -95,15 +96,15 @@ def __init__(self):
9596 self .enabled = True # estimator enabled
9697 self .auton_enabled_prev = None # previous auton flag for edge detection
9798
98- # moved to reset_filter()
99- # self.x_hat: np.ndarray = None
100- # self.Sigma: np.ndarray = np.diag([1e-4, 1e-4, 1e-2, 1e-2, 1.2e-3]) # state covariance
101- # self.Q = np.diag([1e-4, 1e-4, 1e-4, 2.4e-1, 1e-6]) # init process covariance values (2.4e-1 for velocity based on 3 x std dev of 0.16)
102- # self.R = np.diag([1e-2, 1e-2]) # init sensor covariance values
103- # self.last_time = None
104-
10599 self .reset_filter () # initialize filter state
106100
101+ self .declare_parameter ("steerOffsetFilterTimeConstant" , 50 )
102+ steerOffsetFilterTimeConstant = self .get_parameter ("steerOffsetFilterTimeConstant" ).value
103+ self .lowPassFilter = LowPassFilter (alpha = 1.0 / steerOffsetFilterTimeConstant )
104+
105+ self .declare_parameter ("steerOffsetRawTopic" , "self/steer_offset/raw" )
106+ self .declare_parameter ("steerOffsetFilteredTopic" , "self/steer_offset/filtered" )
107+
107108 if (self .get_namespace () == "/SC" ):
108109 self .wheelbase = Constants .WHEELBASE_SC
109110 self .create_subscription (SCDebugInfoMsg , "debug/firmware" , self .firmware_debug_callback , 1 )
@@ -112,10 +113,13 @@ def __init__(self):
112113 self .create_subscription (NANDDebugInfoMsg , "debug/firmware" , self .firmware_debug_callback , 1 )
113114 self .create_subscription (Odometry , "self/state" , self .update_measurement , 1 ) # Using EKF output for simplicity
114115 self .create_subscription (StampedFloat64Msg , "input/steering" , self .update_steering , 1 )
115- self .offset_publisher = self .create_publisher (Float64 , "self/steer_offset" , 1 )
116+
117+ self .offset_publisher_raw = self .create_publisher (Float64 , self .get_parameter ("steerOffsetRawTopic" ).value , 1 )
118+ self .offset_publisher_filtered = self .create_publisher (Float64 , self .get_parameter ("steerOffsetFilteredTopic" ).value , 1 )
116119 self .state_publisher = self .create_publisher (Float64MultiArray , "self/offset_estimator/state" , 1 )
117120 self .state_covar_publisher = self .create_publisher (Float64MultiArray , "self/offset_estimator/covariance" , 1 )
118121
122+
119123 self .steering = 0
120124
121125 self .timer = self .create_timer (0.01 , self .loop )
@@ -124,8 +128,10 @@ def reset_filter(self):
124128 """Reset the UKF so next measurement initializes state."""
125129 self .start = False
126130 self .x_hat : np .ndarray = np .zeros ((5 ,)) # state vector
127- self .Sigma : np .ndarray = np .diag ([1e-4 , 1e-4 , 1e-2 , 1e-2 , 1.2e-3 ]) # state covariance
128- self .Q = np .diag ([1e-4 , 1e-4 , 1e-4 , 2.4e-1 , 1e-6 ]) # init process covariance values (2.4e-1 for velocity based on 3 x std dev of 0.16)
131+
132+ # Offset variance extremely high out of caution and proof of convergence
133+ self .Sigma : np .ndarray = np .diag ([1e-4 , 1e-4 , 1e-2 , 1e-2 , 5e-2 ]) # state covariance
134+ self .Q = np .diag ([1e-4 , 1e-4 , 1e-4 , 2.4e-1 , 1e-6 ]) # init process covariance values (2.4e-1 for velocity based on 3 x std dev of 0.16)
129135 self .R = np .diag ([1e-2 , 1e-2 ]) # init sensor covariance values
130136 self .last_time = None
131137
@@ -180,11 +186,11 @@ def update_measurement(self, msg):
180186 msg .pose .pose .orientation .z ,
181187 msg .twist .twist .linear .x ,
182188 0 ])
183- # extract 2x2 position covariance from the 6x6 pose covariance
184- self .R = np .reshape (np .stack ((msg .pose .covariance [:2 ], msg .pose .covariance [6 :8 ]), axis = 0 ), (2 , 2 ))
185189
186190 # measurement vector
187191 y = [msg .pose .pose .position .x , msg .pose .pose .position .y ]
192+ # extract 2x2 position covariance from the 6x6 pose covariance
193+ self .R = np .reshape (np .stack ((msg .pose .covariance [:2 ], msg .pose .covariance [6 :8 ]), axis = 0 ), (2 , 2 ))
188194 # perform measurement update
189195 self .x_hat , self .Sigma , self .debug = ukf_utils .ukf_update (self .x_hat , self .Sigma , y , self .R )
190196
@@ -197,7 +203,10 @@ def loop(self):
197203
198204 - Runs the predict step using the RK4-discretized dynamics.
199205 - Wraps heading and steering offset to keep them in valid ranges.
200- - Publishes steer offset, full state, and covariance at 100 Hz.
206+ - Applies a low-pass filter to the steering offset.
207+ - A low-pass filter filters out high-frequency noise in the estimate at the cost of responsiveness/increased lag;
208+ this is okay since we expect the steering offset to vary slowly over time.
209+ - Publishes steer offset (raw and filtered), full state, and covariance at 100 Hz.
201210 """
202211 if (not self .enabled ) or (not self .start ):
203212 return
@@ -208,10 +217,6 @@ def loop(self):
208217 self .x_hat [4 ] = self .wrap_angle (self .x_hat [4 ], np .pi / 2 ) # wrap steer offset
209218 self .last_time = time .time ()
210219
211- # wrap the steering offset to (-pi/2, pi/2]
212- steer_offset = np .rad2deg (self .wrap_angle (self .x_hat [4 ], np .pi / 2 ))
213- self .offset_publisher .publish (Float64 (data = steer_offset ))
214-
215220 state_msg = Float64MultiArray ()
216221 state_msg .data = self .x_hat .tolist ()
217222 state_msg .data [2 ] = np .rad2deg (state_msg .data [2 ]) # convert heading to deg
@@ -223,6 +228,20 @@ def loop(self):
223228 covar_msg .data = self .Sigma .flatten ().tolist ()
224229 self .state_covar_publisher .publish (covar_msg )
225230
231+ offset_variance = self .Sigma [- 1 , - 1 ]
232+
233+ # Checks the offset variance is reasonable, corresponds to 6 deg std deviation.
234+ if offset_variance < Constants .OFFSET_THRESHOLD :
235+
236+ # wrap the steering offset to (-pi/2, pi/2]
237+ steer_offset = np .rad2deg (self .wrap_angle (self .x_hat [4 ], np .pi / 2 ))
238+ self .offset_publisher_raw .publish (Float64 (data = steer_offset ))
239+
240+ # apply low-pass filter to steering offset
241+ steer_offset_filtered = self .lowPassFilter .update (steer_offset )
242+ self .offset_publisher_filtered .publish (Float64 (data = steer_offset_filtered ))
243+
244+
226245
227246def main (args = None ):
228247 rclpy .init (args = args )
0 commit comments