blob: fb56436bcf62e5bf72e10a5688408d57bd487100 [file] [log] [blame]
James Kuszmaul51fa1ae2022-02-26 00:49:57 -08001#ifndef Y2022_LOCALIZER_LOCALIZER_H_
2#define Y2022_LOCALIZER_LOCALIZER_H_
James Kuszmaul29c59522022-02-12 16:44:26 -08003
4#include "Eigen/Dense"
5#include "Eigen/Geometry"
Philipp Schrader790cb542023-07-05 21:06:52 -07006
James Kuszmaul29c59522022-02-12 16:44:26 -08007#include "aos/containers/ring_buffer.h"
James Kuszmaul0dedb5e2022-03-05 16:02:20 -08008#include "aos/containers/sized_array.h"
James Kuszmaul8c4f6592022-02-26 15:49:30 -08009#include "aos/events/event_loop.h"
10#include "aos/network/message_bridge_server_generated.h"
James Kuszmaul29c59522022-02-12 16:44:26 -080011#include "aos/time/time.h"
James Kuszmaul29c59522022-02-12 16:44:26 -080012#include "frc971/control_loops/drivetrain/drivetrain_output_generated.h"
James Kuszmaul8c4f6592022-02-26 15:49:30 -080013#include "frc971/control_loops/drivetrain/improved_down_estimator.h"
James Kuszmaule3df1ed2023-02-20 16:21:17 -080014#include "frc971/control_loops/drivetrain/localization/localizer_output_generated.h"
15#include "frc971/control_loops/drivetrain/localization/utils.h"
James Kuszmaul29c59522022-02-12 16:44:26 -080016#include "frc971/control_loops/drivetrain/localizer_generated.h"
James Kuszmaule3df1ed2023-02-20 16:21:17 -080017#include "frc971/imu_reader/imu_watcher.h"
18#include "frc971/input/joystick_state_generated.h"
James Kuszmaul29c59522022-02-12 16:44:26 -080019#include "frc971/zeroing/imu_zeroer.h"
James Kuszmaul5ed29dd2022-02-13 18:32:06 -080020#include "frc971/zeroing/wrap.h"
James Kuszmaul8c4f6592022-02-26 15:49:30 -080021#include "y2022/control_loops/superstructure/superstructure_status_generated.h"
James Kuszmaul8c4f6592022-02-26 15:49:30 -080022#include "y2022/localizer/localizer_status_generated.h"
James Kuszmaul0dedb5e2022-03-05 16:02:20 -080023#include "y2022/localizer/localizer_visualization_generated.h"
James Kuszmaul8c4f6592022-02-26 15:49:30 -080024#include "y2022/vision/target_estimate_generated.h"
James Kuszmaul29c59522022-02-12 16:44:26 -080025
26namespace frc971::controls {
27
28namespace testing {
29class LocalizerTest;
30}
31
32// Localizer implementation that makes use of a 6-axis IMU, encoder readings,
33// drivetrain voltages, and camera returns to localize the robot. Meant to
34// be run on a raspberry pi.
35//
36// This operates on the principle that the drivetrain can be in one of two
37// modes:
38// 1) A "normal" mode where it is obeying the regular drivetrain model, with
39// minimal lateral motion and no major external disturbances. This is
40// referred to as the "model" mode in the code/variable names.
41// 2) An non-holonomic mode where the robot is just flying around on a 2-D
42// plane with no meaningful constraints (referred to as an "accel" model
43// in the code, because we rely primarily on the accelerometer readings).
44//
45// In order to determine which mode to be in, we attempt to track whether the
46// two models are diverging substantially. In order to do this, we maintain a
47// 1-second long queue of "branches". A branch is generated every X iterations
48// and contains a model state and an accel state. When the branch starts, the
49// two will have identical states. For the remaining 1 second, the model state
50// will evolve purely according to the drivetrian model, and the accel state
51// will evolve purely using IMU readings.
52//
53// When the branch reaches 1 second in age, we calculate a residual associated
54// with how much the accel and model based states diverged. If they have
55// diverged substantially, that implies that the model is a poor match for
56// whatever has been happening to the robot in the past second, so if we were
57// previously relying on the model, we will override the current "actual"
58// state with the branched accel state, and then continue to update the accel
59// state based on IMU readings.
60// If we are currently in the accel state, we will continue generating branches
61// until the branches stop diverging--this will indicate that the model
62// matches the accelerometer readings again, and so we will swap back to
63// the model-based state.
James Kuszmaul29c59522022-02-12 16:44:26 -080064class ModelBasedLocalizer {
65 public:
Milind Upadhyayd67e9cf2022-03-13 13:56:57 -070066 static constexpr size_t kNumPis = 4;
67
James Kuszmaul29c59522022-02-12 16:44:26 -080068 static constexpr size_t kX = 0;
69 static constexpr size_t kY = 1;
70 static constexpr size_t kTheta = 2;
71
72 static constexpr size_t kVelocityX = 3;
73 static constexpr size_t kVelocityY = 4;
74 static constexpr size_t kNAccelStates = 5;
75
76 static constexpr size_t kLeftEncoder = 3;
77 static constexpr size_t kLeftVelocity = 4;
78 static constexpr size_t kLeftVoltageError = 5;
79 static constexpr size_t kRightEncoder = 6;
80 static constexpr size_t kRightVelocity = 7;
81 static constexpr size_t kRightVoltageError = 8;
82 static constexpr size_t kNModelStates = 9;
83
84 static constexpr size_t kNModelOutputs = 3;
85
86 static constexpr size_t kNAccelOuputs = 1;
87
88 static constexpr size_t kAccelX = 0;
89 static constexpr size_t kAccelY = 1;
90 static constexpr size_t kThetaRate = 2;
91 static constexpr size_t kNAccelInputs = 3;
92
93 static constexpr size_t kLeftVoltage = 0;
94 static constexpr size_t kRightVoltage = 1;
95 static constexpr size_t kNModelInputs = 2;
96
James Kuszmaul93825a02022-02-13 16:50:33 -080097 // Branching period, in cycles.
James Kuszmaul5ed29dd2022-02-13 18:32:06 -080098 // Needs 10 to even stay alive, and still at ~96% CPU.
99 // ~20 gives ~55-60% CPU.
James Kuszmaul896b4ec2022-02-26 22:56:29 -0800100 static constexpr int kBranchPeriod = 100;
James Kuszmaul93825a02022-02-13 16:50:33 -0800101
James Kuszmaul0dedb5e2022-03-05 16:02:20 -0800102 static constexpr size_t kDebugBufferSize = 10;
103
James Kuszmaul29c59522022-02-12 16:44:26 -0800104 typedef Eigen::Matrix<double, kNModelStates, 1> ModelState;
105 typedef Eigen::Matrix<double, kNAccelStates, 1> AccelState;
106 typedef Eigen::Matrix<double, kNModelInputs, 1> ModelInput;
107 typedef Eigen::Matrix<double, kNAccelInputs, 1> AccelInput;
108
109 ModelBasedLocalizer(
110 const control_loops::drivetrain::DrivetrainConfig<double> &dt_config);
111 void HandleImu(aos::monotonic_clock::time_point t,
112 const Eigen::Vector3d &gyro, const Eigen::Vector3d &accel,
James Kuszmaul5a5a7832022-03-16 23:15:08 -0700113 const std::optional<Eigen::Vector2d> encoders,
114 const Eigen::Vector2d voltage);
James Kuszmaul8c4f6592022-02-26 15:49:30 -0800115 void HandleTurret(aos::monotonic_clock::time_point sample_time,
116 double turret_position, double turret_velocity);
117 void HandleImageMatch(aos::monotonic_clock::time_point sample_time,
James Kuszmaul0dedb5e2022-03-05 16:02:20 -0800118 const y2022::vision::TargetEstimate *target,
119 int camera_index);
James Kuszmaul29c59522022-02-12 16:44:26 -0800120 void HandleReset(aos::monotonic_clock::time_point,
121 const Eigen::Vector3d &xytheta);
122
123 flatbuffers::Offset<ModelBasedStatus> PopulateStatus(
124 flatbuffers::FlatBufferBuilder *fbb);
125
126 Eigen::Vector3d xytheta() const {
127 if (using_model_) {
128 return current_state_.model_state.block<3, 1>(0, 0);
129 } else {
130 return current_state_.accel_state.block<3, 1>(0, 0);
131 }
132 }
133
James Kuszmaul10d3fd42022-02-25 21:57:36 -0800134 Eigen::Quaterniond orientation() const { return last_orientation_; }
135
James Kuszmaul29c59522022-02-12 16:44:26 -0800136 AccelState accel_state() const { return current_state_.accel_state; };
137
James Kuszmaul5ed29dd2022-02-13 18:32:06 -0800138 void set_longitudinal_offset(double offset) { long_offset_ = offset; }
James Kuszmaul20014542022-04-06 21:35:44 -0700139 void set_use_aggressive_image_corrections(bool aggressive) {
140 aggressive_corrections_ = aggressive;
141 }
James Kuszmaul5ed29dd2022-02-13 18:32:06 -0800142
James Kuszmaul0dedb5e2022-03-05 16:02:20 -0800143 void TallyRejection(const RejectionReason reason);
144
145 flatbuffers::Offset<LocalizerVisualization> PopulateVisualization(
146 flatbuffers::FlatBufferBuilder *fbb);
147
148 size_t NumQueuedImageDebugs() const { return image_debugs_.size(); }
James Kuszmaul8c4f6592022-02-26 15:49:30 -0800149
Milind Upadhyayd67e9cf2022-03-13 13:56:57 -0700150 std::array<LedOutput, kNumPis> led_outputs() const { return led_outputs_; }
151
Austin Schuh3806ffb2022-04-13 19:44:10 -0700152 int total_accepted() const { return statistics_.total_accepted; }
153
James Kuszmaul29c59522022-02-12 16:44:26 -0800154 private:
155 struct CombinedState {
James Kuszmaul8c4f6592022-02-26 15:49:30 -0800156 AccelState accel_state = AccelState::Zero();
157 ModelState model_state = ModelState::Zero();
158 aos::monotonic_clock::time_point branch_time =
159 aos::monotonic_clock::min_time;
160 double accumulated_divergence = 0.0;
161 };
162
163 // Struct to store state data needed to perform a camera correction, since
164 // camera corrections require looking back in time.
165 struct OldPosition {
166 aos::monotonic_clock::time_point sample_time =
167 aos::monotonic_clock::min_time;
168 Eigen::Vector3d xytheta = Eigen::Vector3d::Zero();
169 double turret_position = 0.0;
170 double turret_velocity = 0.0;
James Kuszmaul29c59522022-02-12 16:44:26 -0800171 };
172
173 static flatbuffers::Offset<AccelBasedState> BuildAccelState(
174 flatbuffers::FlatBufferBuilder *fbb, const AccelState &state);
175
176 static flatbuffers::Offset<ModelBasedState> BuildModelState(
177 flatbuffers::FlatBufferBuilder *fbb, const ModelState &state);
178
179 Eigen::Matrix<double, kNModelStates, kNModelStates> AModel(
180 const ModelState &state) const;
181 Eigen::Matrix<double, kNAccelStates, kNAccelStates> AAccel() const;
182 ModelState DiffModel(const ModelState &state, const ModelInput &U) const;
183 AccelState DiffAccel(const AccelState &state, const AccelInput &U) const;
184
185 ModelState UpdateModel(const ModelState &model, const ModelInput &input,
186 aos::monotonic_clock::duration dt) const;
187 AccelState UpdateAccel(const AccelState &accel, const AccelInput &input,
188 aos::monotonic_clock::duration dt) const;
189
190 AccelState AccelStateForModelState(const ModelState &state) const;
191 ModelState ModelStateForAccelState(const AccelState &state,
192 const Eigen::Vector2d &encoders,
193 const double yaw_rate) const;
194 double ModelDivergence(const CombinedState &state,
195 const AccelInput &accel_inputs,
196 const Eigen::Vector2d &filtered_accel,
197 const ModelInput &model_inputs);
James Kuszmaul5ed29dd2022-02-13 18:32:06 -0800198 void UpdateState(
199 CombinedState *state,
200 const Eigen::Matrix<double, kNModelStates, kNModelOutputs> &K,
201 const Eigen::Matrix<double, kNModelOutputs, 1> &Z,
202 const Eigen::Matrix<double, kNModelOutputs, kNModelStates> &H,
203 const AccelInput &accel_input, const ModelInput &model_input,
204 aos::monotonic_clock::duration dt);
James Kuszmaul29c59522022-02-12 16:44:26 -0800205
James Kuszmaul8c4f6592022-02-26 15:49:30 -0800206 const OldPosition GetStateForTime(aos::monotonic_clock::time_point time);
207
208 // Returns the transformation to get from the camera frame to the robot frame
209 // for the specified state.
210 const Eigen::Matrix<double, 4, 4> CameraTransform(
211 const OldPosition &state,
212 const frc971::vision::calibration::CameraCalibration *calibration,
213 std::optional<RejectionReason> *rejection_reason) const;
214
215 // Returns the robot x/y position implied by the specified camera data and
216 // estimated state from that time.
James Kuszmaul0dedb5e2022-03-05 16:02:20 -0800217 // H_field_camera is passed in so that it can be used as a debugging output.
James Kuszmaul8c4f6592022-02-26 15:49:30 -0800218 const std::optional<Eigen::Vector2d> CameraMeasuredRobotPosition(
219 const OldPosition &state, const y2022::vision::TargetEstimate *target,
James Kuszmaul0dedb5e2022-03-05 16:02:20 -0800220 std::optional<RejectionReason> *rejection_reason,
221 Eigen::Matrix<double, 4, 4> *H_field_camera_measured) const;
222
223 flatbuffers::Offset<TargetEstimateDebug> PackTargetEstimateDebug(
224 const TargetEstimateDebugT &debug, flatbuffers::FlatBufferBuilder *fbb);
225
226 flatbuffers::Offset<CumulativeStatistics> PopulateStatistics(
227 flatbuffers::FlatBufferBuilder *fbb);
James Kuszmaul8c4f6592022-02-26 15:49:30 -0800228
James Kuszmaul29c59522022-02-12 16:44:26 -0800229 const control_loops::drivetrain::DrivetrainConfig<double> dt_config_;
230 const StateFeedbackHybridPlantCoefficients<2, 2, 2, double>
231 velocity_drivetrain_coefficients_;
232 Eigen::Matrix<double, kNModelStates, kNModelStates> A_continuous_model_;
233 Eigen::Matrix<double, kNAccelStates, kNAccelStates> A_continuous_accel_;
James Kuszmaul8c4f6592022-02-26 15:49:30 -0800234 Eigen::Matrix<double, kNAccelStates, kNAccelStates> A_discrete_accel_;
James Kuszmaul29c59522022-02-12 16:44:26 -0800235 Eigen::Matrix<double, kNModelStates, kNModelInputs> B_continuous_model_;
236 Eigen::Matrix<double, kNAccelStates, kNAccelInputs> B_continuous_accel_;
237
238 Eigen::Matrix<double, kNModelStates, kNModelStates> Q_continuous_model_;
239
240 Eigen::Matrix<double, kNModelStates, kNModelStates> P_model_;
James Kuszmaul8c4f6592022-02-26 15:49:30 -0800241
242 Eigen::Matrix<double, kNAccelStates, kNAccelStates> Q_continuous_accel_;
243 Eigen::Matrix<double, kNAccelStates, kNAccelStates> Q_discrete_accel_;
244
245 Eigen::Matrix<double, kNAccelStates, kNAccelStates> P_accel_;
246
247 control_loops::drivetrain::DrivetrainUkf down_estimator_;
248
James Kuszmaul29c59522022-02-12 16:44:26 -0800249 // When we are following the model, we will, on each iteration:
250 // 1) Perform a model-based update of a single state.
251 // 2) Add a hypothetical non-model-based entry based on the current state.
252 // 3) Evict too-old non-model-based entries.
James Kuszmaul29c59522022-02-12 16:44:26 -0800253
254 // Buffer of old branches these are all created by initializing a new
255 // model-based state based on the current state, and then initializing a new
256 // accel-based state on top of that new model-based state (to eliminate the
257 // impact of any lateral motion).
258 // We then integrate up all of these states and observe how much the model and
259 // accel based states of each branch compare to one another.
James Kuszmaul93825a02022-02-13 16:50:33 -0800260 aos::RingBuffer<CombinedState, 2000 / kBranchPeriod> branches_;
261 int branch_counter_ = 0;
James Kuszmaul29c59522022-02-12 16:44:26 -0800262
James Kuszmaul8c4f6592022-02-26 15:49:30 -0800263 // Buffer of old x/y/theta positions. This is used so that we can have a
264 // reference for exactly where we thought a camera was when it took an image.
265 aos::RingBuffer<OldPosition, 500 / kBranchPeriod> old_positions_;
266
James Kuszmaul29c59522022-02-12 16:44:26 -0800267 CombinedState current_state_;
268 aos::monotonic_clock::time_point t_ = aos::monotonic_clock::min_time;
269 bool using_model_;
270
James Kuszmaul5ed29dd2022-02-13 18:32:06 -0800271 // X position of the IMU, in meters. 0 = center of robot, positive = ahead of
272 // center, negative = behind center.
273 double long_offset_ = -0.15;
274
James Kuszmaul20014542022-04-06 21:35:44 -0700275 // Whether to use more aggressive corrections on the localizer. Only do this
276 // in teleop, since it can make spline control really jumpy.
277 bool aggressive_corrections_ = false;
278
James Kuszmaul29c59522022-02-12 16:44:26 -0800279 double last_residual_ = 0.0;
280 double filtered_residual_ = 0.0;
281 Eigen::Vector2d filtered_residual_accel_ = Eigen::Vector2d::Zero();
282 Eigen::Vector3d abs_accel_ = Eigen::Vector3d::Zero();
283 double velocity_residual_ = 0.0;
284 double accel_residual_ = 0.0;
285 double theta_rate_residual_ = 0.0;
286 int hysteresis_count_ = 0;
James Kuszmaul10d3fd42022-02-25 21:57:36 -0800287 Eigen::Quaterniond last_orientation_ = Eigen::Quaterniond::Identity();
James Kuszmaul29c59522022-02-12 16:44:26 -0800288
289 int clock_resets_ = 0;
290
James Kuszmaul8c4f6592022-02-26 15:49:30 -0800291 std::optional<aos::monotonic_clock::time_point> last_turret_update_;
292 double latest_turret_position_ = 0.0;
293 double latest_turret_velocity_ = 0.0;
294
295 // Stuff to track faults.
296 static constexpr size_t kNumRejectionReasons =
297 static_cast<int>(RejectionReason::MAX) -
298 static_cast<int>(RejectionReason::MIN) + 1;
299
300 struct Statistics {
301 int total_accepted = 0;
302 int total_candidates = 0;
303 static_assert(0 == static_cast<int>(RejectionReason::MIN));
304 static_assert(
305 kNumRejectionReasons ==
306 sizeof(
307 std::invoke_result<decltype(EnumValuesRejectionReason)>::type) /
308 sizeof(RejectionReason),
309 "RejectionReason has non-contiguous error values.");
310 std::array<int, kNumRejectionReasons> rejection_counts;
311 };
312 Statistics statistics_;
313
James Kuszmaul0dedb5e2022-03-05 16:02:20 -0800314 aos::SizedArray<TargetEstimateDebugT, kDebugBufferSize> image_debugs_;
Milind Upadhyayd67e9cf2022-03-13 13:56:57 -0700315 std::array<LedOutput, kNumPis> led_outputs_;
James Kuszmaul0dedb5e2022-03-05 16:02:20 -0800316
James Kuszmaul29c59522022-02-12 16:44:26 -0800317 friend class testing::LocalizerTest;
318};
319
320class EventLoopLocalizer {
321 public:
James Kuszmaul0dedb5e2022-03-05 16:02:20 -0800322 static constexpr std::chrono::milliseconds kMinVisualizationPeriod{100};
323
James Kuszmaul29c59522022-02-12 16:44:26 -0800324 EventLoopLocalizer(
325 aos::EventLoop *event_loop,
326 const control_loops::drivetrain::DrivetrainConfig<double> &dt_config);
327
James Kuszmaul5ed29dd2022-02-13 18:32:06 -0800328 ModelBasedLocalizer *localizer() { return &model_based_; }
329
James Kuszmaul29c59522022-02-12 16:44:26 -0800330 private:
James Kuszmaul9f2f53c2023-02-19 14:08:18 -0800331 void HandleImu(aos::monotonic_clock::time_point sample_time_pico,
332 aos::monotonic_clock::time_point sample_time_pi,
333 std::optional<Eigen::Vector2d> encoders, Eigen::Vector3d gyro,
334 Eigen::Vector3d accel);
James Kuszmaul29c59522022-02-12 16:44:26 -0800335 aos::EventLoop *event_loop_;
336 ModelBasedLocalizer model_based_;
337 aos::Sender<LocalizerStatus> status_sender_;
338 aos::Sender<LocalizerOutput> output_sender_;
James Kuszmaul0dedb5e2022-03-05 16:02:20 -0800339 aos::Sender<LocalizerVisualization> visualization_sender_;
Milind Upadhyayd67e9cf2022-03-13 13:56:57 -0700340 std::array<aos::Fetcher<y2022::vision::TargetEstimate>,
341 ModelBasedLocalizer::kNumPis>
James Kuszmaul2b2f8772022-03-12 15:25:35 -0800342 target_estimate_fetchers_;
343 aos::Fetcher<y2022::control_loops::superstructure::Status>
344 superstructure_fetcher_;
James Kuszmaul29c59522022-02-12 16:44:26 -0800345 aos::monotonic_clock::time_point last_output_send_ =
346 aos::monotonic_clock::min_time;
James Kuszmaul0dedb5e2022-03-05 16:02:20 -0800347 aos::monotonic_clock::time_point last_visualization_send_ =
348 aos::monotonic_clock::min_time;
James Kuszmaul5ed29dd2022-02-13 18:32:06 -0800349
James Kuszmaul9f2f53c2023-02-19 14:08:18 -0800350 ImuWatcher imu_watcher_;
James Kuszmaul9a1733a2023-02-19 16:51:47 -0800351 control_loops::drivetrain::LocalizationUtils utils_;
James Kuszmaul29c59522022-02-12 16:44:26 -0800352};
353} // namespace frc971::controls
James Kuszmaul51fa1ae2022-02-26 00:49:57 -0800354#endif // Y2022_LOCALIZER_LOCALIZER_H_