blob: d1eaf5c88e174d48c74b6d7ef4782165b36bc53f [file] [log] [blame]
James Kuszmaul29c59522022-02-12 16:44:26 -08001#ifndef Y2022_CONTROL_LOOPS_LOCALIZER_LOCALIZER_H_
2#define Y2022_CONTROL_LOOPS_LOCALIZER_LOCALIZER_H_
3
4#include "Eigen/Dense"
5#include "Eigen/Geometry"
6
7#include "aos/events/event_loop.h"
8#include "aos/containers/ring_buffer.h"
9#include "aos/time/time.h"
10#include "y2020/vision/sift/sift_generated.h"
11#include "y2022/control_loops/localizer/localizer_status_generated.h"
12#include "y2022/control_loops/localizer/localizer_output_generated.h"
13#include "frc971/control_loops/drivetrain/improved_down_estimator.h"
James Kuszmaul29c59522022-02-12 16:44:26 -080014#include "frc971/control_loops/drivetrain/drivetrain_output_generated.h"
15#include "frc971/control_loops/drivetrain/localizer_generated.h"
16#include "frc971/zeroing/imu_zeroer.h"
James Kuszmaul5ed29dd2022-02-13 18:32:06 -080017#include "frc971/zeroing/wrap.h"
James Kuszmaul29c59522022-02-12 16:44:26 -080018
19namespace frc971::controls {
20
21namespace testing {
22class LocalizerTest;
23}
24
25// Localizer implementation that makes use of a 6-axis IMU, encoder readings,
26// drivetrain voltages, and camera returns to localize the robot. Meant to
27// be run on a raspberry pi.
28//
29// This operates on the principle that the drivetrain can be in one of two
30// modes:
31// 1) A "normal" mode where it is obeying the regular drivetrain model, with
32// minimal lateral motion and no major external disturbances. This is
33// referred to as the "model" mode in the code/variable names.
34// 2) An non-holonomic mode where the robot is just flying around on a 2-D
35// plane with no meaningful constraints (referred to as an "accel" model
36// in the code, because we rely primarily on the accelerometer readings).
37//
38// In order to determine which mode to be in, we attempt to track whether the
39// two models are diverging substantially. In order to do this, we maintain a
40// 1-second long queue of "branches". A branch is generated every X iterations
41// and contains a model state and an accel state. When the branch starts, the
42// two will have identical states. For the remaining 1 second, the model state
43// will evolve purely according to the drivetrian model, and the accel state
44// will evolve purely using IMU readings.
45//
46// When the branch reaches 1 second in age, we calculate a residual associated
47// with how much the accel and model based states diverged. If they have
48// diverged substantially, that implies that the model is a poor match for
49// whatever has been happening to the robot in the past second, so if we were
50// previously relying on the model, we will override the current "actual"
51// state with the branched accel state, and then continue to update the accel
52// state based on IMU readings.
53// If we are currently in the accel state, we will continue generating branches
54// until the branches stop diverging--this will indicate that the model
55// matches the accelerometer readings again, and so we will swap back to
56// the model-based state.
57//
58// TODO:
59// * Implement paying attention to camera readings.
60// * Tune for ADIS16505/real robot.
James Kuszmaul29c59522022-02-12 16:44:26 -080061class ModelBasedLocalizer {
62 public:
63 static constexpr size_t kX = 0;
64 static constexpr size_t kY = 1;
65 static constexpr size_t kTheta = 2;
66
67 static constexpr size_t kVelocityX = 3;
68 static constexpr size_t kVelocityY = 4;
69 static constexpr size_t kNAccelStates = 5;
70
71 static constexpr size_t kLeftEncoder = 3;
72 static constexpr size_t kLeftVelocity = 4;
73 static constexpr size_t kLeftVoltageError = 5;
74 static constexpr size_t kRightEncoder = 6;
75 static constexpr size_t kRightVelocity = 7;
76 static constexpr size_t kRightVoltageError = 8;
77 static constexpr size_t kNModelStates = 9;
78
79 static constexpr size_t kNModelOutputs = 3;
80
81 static constexpr size_t kNAccelOuputs = 1;
82
83 static constexpr size_t kAccelX = 0;
84 static constexpr size_t kAccelY = 1;
85 static constexpr size_t kThetaRate = 2;
86 static constexpr size_t kNAccelInputs = 3;
87
88 static constexpr size_t kLeftVoltage = 0;
89 static constexpr size_t kRightVoltage = 1;
90 static constexpr size_t kNModelInputs = 2;
91
James Kuszmaul93825a02022-02-13 16:50:33 -080092 // Branching period, in cycles.
James Kuszmaul5ed29dd2022-02-13 18:32:06 -080093 // Needs 10 to even stay alive, and still at ~96% CPU.
94 // ~20 gives ~55-60% CPU.
95 static constexpr int kBranchPeriod = 20;
James Kuszmaul93825a02022-02-13 16:50:33 -080096
James Kuszmaul29c59522022-02-12 16:44:26 -080097 typedef Eigen::Matrix<double, kNModelStates, 1> ModelState;
98 typedef Eigen::Matrix<double, kNAccelStates, 1> AccelState;
99 typedef Eigen::Matrix<double, kNModelInputs, 1> ModelInput;
100 typedef Eigen::Matrix<double, kNAccelInputs, 1> AccelInput;
101
102 ModelBasedLocalizer(
103 const control_loops::drivetrain::DrivetrainConfig<double> &dt_config);
104 void HandleImu(aos::monotonic_clock::time_point t,
105 const Eigen::Vector3d &gyro, const Eigen::Vector3d &accel,
106 const Eigen::Vector2d encoders, const Eigen::Vector2d voltage);
107 void HandleImageMatch(aos::monotonic_clock::time_point,
108 const vision::sift::ImageMatchResult *) {
109 LOG(FATAL) << "Unimplemented.";
110 }
111 void HandleReset(aos::monotonic_clock::time_point,
112 const Eigen::Vector3d &xytheta);
113
114 flatbuffers::Offset<ModelBasedStatus> PopulateStatus(
115 flatbuffers::FlatBufferBuilder *fbb);
116
117 Eigen::Vector3d xytheta() const {
118 if (using_model_) {
119 return current_state_.model_state.block<3, 1>(0, 0);
120 } else {
121 return current_state_.accel_state.block<3, 1>(0, 0);
122 }
123 }
124
James Kuszmaul10d3fd42022-02-25 21:57:36 -0800125 Eigen::Quaterniond orientation() const { return last_orientation_; }
126
James Kuszmaul29c59522022-02-12 16:44:26 -0800127 AccelState accel_state() const { return current_state_.accel_state; };
128
James Kuszmaul5ed29dd2022-02-13 18:32:06 -0800129 void set_longitudinal_offset(double offset) { long_offset_ = offset; }
130
James Kuszmaul29c59522022-02-12 16:44:26 -0800131 private:
132 struct CombinedState {
133 AccelState accel_state;
134 ModelState model_state;
135 aos::monotonic_clock::time_point branch_time;
136 double accumulated_divergence;
137 };
138
139 static flatbuffers::Offset<AccelBasedState> BuildAccelState(
140 flatbuffers::FlatBufferBuilder *fbb, const AccelState &state);
141
142 static flatbuffers::Offset<ModelBasedState> BuildModelState(
143 flatbuffers::FlatBufferBuilder *fbb, const ModelState &state);
144
145 Eigen::Matrix<double, kNModelStates, kNModelStates> AModel(
146 const ModelState &state) const;
147 Eigen::Matrix<double, kNAccelStates, kNAccelStates> AAccel() const;
148 ModelState DiffModel(const ModelState &state, const ModelInput &U) const;
149 AccelState DiffAccel(const AccelState &state, const AccelInput &U) const;
150
151 ModelState UpdateModel(const ModelState &model, const ModelInput &input,
152 aos::monotonic_clock::duration dt) const;
153 AccelState UpdateAccel(const AccelState &accel, const AccelInput &input,
154 aos::monotonic_clock::duration dt) const;
155
156 AccelState AccelStateForModelState(const ModelState &state) const;
157 ModelState ModelStateForAccelState(const AccelState &state,
158 const Eigen::Vector2d &encoders,
159 const double yaw_rate) const;
160 double ModelDivergence(const CombinedState &state,
161 const AccelInput &accel_inputs,
162 const Eigen::Vector2d &filtered_accel,
163 const ModelInput &model_inputs);
James Kuszmaul5ed29dd2022-02-13 18:32:06 -0800164 void UpdateState(
165 CombinedState *state,
166 const Eigen::Matrix<double, kNModelStates, kNModelOutputs> &K,
167 const Eigen::Matrix<double, kNModelOutputs, 1> &Z,
168 const Eigen::Matrix<double, kNModelOutputs, kNModelStates> &H,
169 const AccelInput &accel_input, const ModelInput &model_input,
170 aos::monotonic_clock::duration dt);
James Kuszmaul29c59522022-02-12 16:44:26 -0800171
172 const control_loops::drivetrain::DrivetrainConfig<double> dt_config_;
173 const StateFeedbackHybridPlantCoefficients<2, 2, 2, double>
174 velocity_drivetrain_coefficients_;
175 Eigen::Matrix<double, kNModelStates, kNModelStates> A_continuous_model_;
176 Eigen::Matrix<double, kNAccelStates, kNAccelStates> A_continuous_accel_;
177 Eigen::Matrix<double, kNModelStates, kNModelInputs> B_continuous_model_;
178 Eigen::Matrix<double, kNAccelStates, kNAccelInputs> B_continuous_accel_;
179
180 Eigen::Matrix<double, kNModelStates, kNModelStates> Q_continuous_model_;
181
182 Eigen::Matrix<double, kNModelStates, kNModelStates> P_model_;
183 // When we are following the model, we will, on each iteration:
184 // 1) Perform a model-based update of a single state.
185 // 2) Add a hypothetical non-model-based entry based on the current state.
186 // 3) Evict too-old non-model-based entries.
187 control_loops::drivetrain::DrivetrainUkf down_estimator_;
188
189 // Buffer of old branches these are all created by initializing a new
190 // model-based state based on the current state, and then initializing a new
191 // accel-based state on top of that new model-based state (to eliminate the
192 // impact of any lateral motion).
193 // We then integrate up all of these states and observe how much the model and
194 // accel based states of each branch compare to one another.
James Kuszmaul93825a02022-02-13 16:50:33 -0800195 aos::RingBuffer<CombinedState, 2000 / kBranchPeriod> branches_;
196 int branch_counter_ = 0;
James Kuszmaul29c59522022-02-12 16:44:26 -0800197
198 CombinedState current_state_;
199 aos::monotonic_clock::time_point t_ = aos::monotonic_clock::min_time;
200 bool using_model_;
201
James Kuszmaul5ed29dd2022-02-13 18:32:06 -0800202 // X position of the IMU, in meters. 0 = center of robot, positive = ahead of
203 // center, negative = behind center.
204 double long_offset_ = -0.15;
205
James Kuszmaul29c59522022-02-12 16:44:26 -0800206 double last_residual_ = 0.0;
207 double filtered_residual_ = 0.0;
208 Eigen::Vector2d filtered_residual_accel_ = Eigen::Vector2d::Zero();
209 Eigen::Vector3d abs_accel_ = Eigen::Vector3d::Zero();
210 double velocity_residual_ = 0.0;
211 double accel_residual_ = 0.0;
212 double theta_rate_residual_ = 0.0;
213 int hysteresis_count_ = 0;
James Kuszmaul10d3fd42022-02-25 21:57:36 -0800214 Eigen::Quaterniond last_orientation_ = Eigen::Quaterniond::Identity();
James Kuszmaul29c59522022-02-12 16:44:26 -0800215
216 int clock_resets_ = 0;
217
218 friend class testing::LocalizerTest;
219};
220
221class EventLoopLocalizer {
222 public:
223 EventLoopLocalizer(
224 aos::EventLoop *event_loop,
225 const control_loops::drivetrain::DrivetrainConfig<double> &dt_config);
226
James Kuszmaul5ed29dd2022-02-13 18:32:06 -0800227 ModelBasedLocalizer *localizer() { return &model_based_; }
228
James Kuszmaul29c59522022-02-12 16:44:26 -0800229 private:
230 aos::EventLoop *event_loop_;
231 ModelBasedLocalizer model_based_;
232 aos::Sender<LocalizerStatus> status_sender_;
233 aos::Sender<LocalizerOutput> output_sender_;
James Kuszmaul29c59522022-02-12 16:44:26 -0800234 aos::Fetcher<frc971::control_loops::drivetrain::Output> output_fetcher_;
235 zeroing::ImuZeroer zeroer_;
236 aos::monotonic_clock::time_point last_output_send_ =
237 aos::monotonic_clock::min_time;
James Kuszmaul5ed29dd2022-02-13 18:32:06 -0800238 std::optional<aos::monotonic_clock::time_point> last_pico_timestamp_;
239 aos::monotonic_clock::duration pico_offset_error_;
240 // t = pico_offset_ + pico_timestamp.
241 // Note that this can drift over sufficiently long time periods!
242 std::optional<std::chrono::nanoseconds> pico_offset_;
243
244 zeroing::UnwrapSensor left_encoder_;
245 zeroing::UnwrapSensor right_encoder_;
James Kuszmaul29c59522022-02-12 16:44:26 -0800246};
247} // namespace frc971::controls
248#endif // Y2022_CONTROL_LOOPS_LOCALIZER_LOCALIZER_H_