blob: c58f664f885560bd52e5a32bde5e0c942fe0103b [file] [log] [blame]
James Kuszmaul29c59522022-02-12 16:44:26 -08001#include "y2022/control_loops/localizer/localizer.h"
2
3#include "aos/events/simulated_event_loop.h"
4#include "gtest/gtest.h"
5#include "frc971/control_loops/drivetrain/drivetrain_test_lib.h"
6
7namespace frc971::controls::testing {
8typedef ModelBasedLocalizer::ModelState ModelState;
9typedef ModelBasedLocalizer::AccelState AccelState;
10typedef ModelBasedLocalizer::ModelInput ModelInput;
11typedef ModelBasedLocalizer::AccelInput AccelInput;
12namespace {
13constexpr size_t kX = ModelBasedLocalizer::kX;
14constexpr size_t kY = ModelBasedLocalizer::kY;
15constexpr size_t kTheta = ModelBasedLocalizer::kTheta;
16constexpr size_t kVelocityX = ModelBasedLocalizer::kVelocityX;
17constexpr size_t kVelocityY = ModelBasedLocalizer::kVelocityY;
18constexpr size_t kAccelX = ModelBasedLocalizer::kAccelX;
19constexpr size_t kAccelY = ModelBasedLocalizer::kAccelY;
20constexpr size_t kThetaRate = ModelBasedLocalizer::kThetaRate;
21constexpr size_t kLeftEncoder = ModelBasedLocalizer::kLeftEncoder;
22constexpr size_t kLeftVelocity = ModelBasedLocalizer::kLeftVelocity;
23constexpr size_t kLeftVoltageError = ModelBasedLocalizer::kLeftVoltageError;
24constexpr size_t kRightEncoder = ModelBasedLocalizer::kRightEncoder;
25constexpr size_t kRightVelocity = ModelBasedLocalizer::kRightVelocity;
26constexpr size_t kRightVoltageError = ModelBasedLocalizer::kRightVoltageError;
27constexpr size_t kLeftVoltage = ModelBasedLocalizer::kLeftVoltage;
28constexpr size_t kRightVoltage = ModelBasedLocalizer::kRightVoltage;
29}
30
31class LocalizerTest : public ::testing::Test {
32 protected:
33 LocalizerTest()
34 : dt_config_(
35 control_loops::drivetrain::testing::GetTestDrivetrainConfig()),
James Kuszmaul5ed29dd2022-02-13 18:32:06 -080036 localizer_(dt_config_) {
37 localizer_.set_longitudinal_offset(0.0);
38 }
James Kuszmaul29c59522022-02-12 16:44:26 -080039 ModelState CallDiffModel(const ModelState &state, const ModelInput &U) {
40 return localizer_.DiffModel(state, U);
41 }
42
43 AccelState CallDiffAccel(const AccelState &state, const AccelInput &U) {
44 return localizer_.DiffAccel(state, U);
45 }
46
47 const control_loops::drivetrain::DrivetrainConfig<double> dt_config_;
48 ModelBasedLocalizer localizer_;
49
50};
51
52TEST_F(LocalizerTest, AccelIntegrationTest) {
53 AccelState state;
54 state.setZero();
55 AccelInput input;
56 input.setZero();
57
58 EXPECT_EQ(0.0, CallDiffAccel(state, input).norm());
59 // Non-zero x/y/theta states should still result in a zero derivative.
60 state(kX) = 1.0;
61 state(kY) = 1.0;
62 state(kTheta) = 1.0;
63 EXPECT_EQ(0.0, CallDiffAccel(state, input).norm());
64
65 state.setZero();
66 state(kVelocityX) = 1.0;
67 state(kVelocityY) = 2.0;
68 EXPECT_EQ((AccelState() << 1.0, 2.0, 0.0, 0.0, 0.0).finished(),
69 CallDiffAccel(state, input));
70 // Derivatives should be independent of theta.
71 state(kTheta) = M_PI / 2.0;
72 EXPECT_EQ((AccelState() << 1.0, 2.0, 0.0, 0.0, 0.0).finished(),
73 CallDiffAccel(state, input));
74
75 state.setZero();
76 input(kAccelX) = 1.0;
77 input(kAccelY) = 2.0;
78 input(kThetaRate) = 3.0;
79 EXPECT_EQ((AccelState() << 0.0, 0.0, 3.0, 1.0, 2.0).finished(),
80 CallDiffAccel(state, input));
81 state(kTheta) = M_PI / 2.0;
82 EXPECT_EQ((AccelState() << 0.0, 0.0, 3.0, 1.0, 2.0).finished(),
83 CallDiffAccel(state, input));
84}
85
86TEST_F(LocalizerTest, ModelIntegrationTest) {
87 ModelState state;
88 state.setZero();
89 ModelInput input;
90 input.setZero();
91 ModelState diff;
92
93 EXPECT_EQ(0.0, CallDiffModel(state, input).norm());
94 // Non-zero x/y/theta/encoder states should still result in a zero derivative.
95 state(kX) = 1.0;
96 state(kY) = 1.0;
97 state(kTheta) = 1.0;
98 state(kLeftEncoder) = 1.0;
99 state(kRightEncoder) = 1.0;
100 EXPECT_EQ(0.0, CallDiffModel(state, input).norm());
101
102 state.setZero();
103 state(kLeftVelocity) = 1.0;
104 state(kRightVelocity) = 1.0;
105 diff = CallDiffModel(state, input);
106 const ModelState mask_velocities =
107 (ModelState() << 1.0, 1.0, 1.0, 1.0, 0.0, 1.0, 1.0, 0.0, 1.0).finished();
108 EXPECT_EQ(
109 (ModelState() << 1.0, 0.0, 0.0, 1.0, 0.0, 0.0, 1.0, 0.0, 0.0).finished(),
110 diff.cwiseProduct(mask_velocities));
111 EXPECT_EQ(diff(kLeftVelocity), diff(kRightVelocity));
112 EXPECT_GT(0.0, diff(kLeftVelocity));
113 state(kTheta) = M_PI / 2.0;
114 diff = CallDiffModel(state, input);
115 EXPECT_NEAR(0.0,
116 ((ModelState() << 0.0, 1.0, 0.0, 1.0, 0.0, 0.0, 1.0, 0.0, 0.0)
117 .finished() -
118 diff.cwiseProduct(mask_velocities))
119 .norm(),
120 1e-12);
121 EXPECT_EQ(diff(kLeftVelocity), diff(kRightVelocity));
122 EXPECT_GT(0.0, diff(kLeftVelocity));
123
124 state.setZero();
125 state(kLeftVelocity) = -1.0;
126 state(kRightVelocity) = 1.0;
127 diff = CallDiffModel(state, input);
128 EXPECT_EQ((ModelState() << 0.0, 0.0, 1.0 / dt_config_.robot_radius, -1.0, 0.0,
129 0.0, 1.0, 0.0, 0.0)
130 .finished(),
131 diff.cwiseProduct(mask_velocities));
132 EXPECT_EQ(-diff(kLeftVelocity), diff(kRightVelocity));
133 EXPECT_LT(0.0, diff(kLeftVelocity));
134
135 state.setZero();
136 input(kLeftVoltage) = 5.0;
137 input(kRightVoltage) = 6.0;
138 diff = CallDiffModel(state, input);
139 EXPECT_EQ(0, diff(kX));
140 EXPECT_EQ(0, diff(kY));
141 EXPECT_EQ(0, diff(kTheta));
142 EXPECT_EQ(0, diff(kLeftEncoder));
143 EXPECT_EQ(0, diff(kRightEncoder));
144 EXPECT_EQ(0, diff(kLeftVoltageError));
145 EXPECT_EQ(0, diff(kRightVoltageError));
146 EXPECT_LT(0, diff(kLeftVelocity));
147 EXPECT_LT(0, diff(kRightVelocity));
148 EXPECT_LT(diff(kLeftVelocity), diff(kRightVelocity));
149
150 state.setZero();
151 state(kLeftVoltageError) = -1.0;
152 state(kRightVoltageError) = -2.0;
153 input(kLeftVoltage) = 1.0;
154 input(kRightVoltage) = 2.0;
155 EXPECT_EQ(ModelState::Zero(), CallDiffModel(state, input));
156}
157
158// Test that the HandleReset does indeed reset the state of the localizer.
159TEST_F(LocalizerTest, LocalizerReset) {
160 aos::monotonic_clock::time_point t = aos::monotonic_clock::epoch();
161 localizer_.HandleReset(t, {1.0, 2.0, 3.0});
162 EXPECT_EQ((Eigen::Vector3d{1.0, 2.0, 3.0}), localizer_.xytheta());
163 localizer_.HandleReset(t, {4.0, 5.0, 6.0});
164 EXPECT_EQ((Eigen::Vector3d{4.0, 5.0, 6.0}), localizer_.xytheta());
165}
166
167// Test that if we are moving only by accelerometer readings (and just assuming
168// zero voltage/encoders) that we initially don't believe it but then latch into
169// following the accelerometer.
170// Note: this test is somewhat sensitive to the exact tuning values used for the
171// filter.
172TEST_F(LocalizerTest, AccelOnly) {
173 const aos::monotonic_clock::time_point start = aos::monotonic_clock::epoch();
174 const std::chrono::microseconds kDt{500};
175 aos::monotonic_clock::time_point t = start - std::chrono::milliseconds(1000);
176 Eigen::Vector3d gyro{0.0, 0.0, 0.0};
177 const Eigen::Vector2d encoders{0.0, 0.0};
178 const Eigen::Vector2d voltages{0.0, 0.0};
James Kuszmaul5ed29dd2022-02-13 18:32:06 -0800179 Eigen::Vector3d accel{5.0, 2.0, 9.80665};
James Kuszmaul29c59522022-02-12 16:44:26 -0800180 Eigen::Vector3d accel_gs = accel / 9.80665;
181 while (t < start) {
182 // Spin to fill up the buffer.
183 localizer_.HandleImu(t, gyro, Eigen::Vector3d::UnitZ(), encoders, voltages);
184 t += kDt;
185 }
186 while (t < start + std::chrono::milliseconds(100)) {
187 localizer_.HandleImu(t, gyro, accel_gs, encoders, voltages);
188 EXPECT_EQ(Eigen::Vector3d::Zero(), localizer_.xytheta());
189 t += kDt;
190 }
191 while (t < start + std::chrono::milliseconds(500)) {
192 // Too lazy to hard-code when the transition happens.
193 localizer_.HandleImu(t, gyro, accel_gs, encoders, voltages);
194 t += kDt;
195 }
196 while (t < start + std::chrono::milliseconds(1000)) {
197 SCOPED_TRACE(t);
198 localizer_.HandleImu(t, gyro, accel_gs, encoders, voltages);
199 const Eigen::Vector3d xytheta = localizer_.xytheta();
200 t += kDt;
201 EXPECT_NEAR(
202 0.5 * accel(0) * std::pow(aos::time::DurationInSeconds(t - start), 2),
203 xytheta(0), 1e-4);
204 EXPECT_NEAR(
205 0.5 * accel(1) * std::pow(aos::time::DurationInSeconds(t - start), 2),
206 xytheta(1), 1e-4);
207 EXPECT_EQ(0.0, xytheta(2));
208 }
209
James Kuszmaul5ed29dd2022-02-13 18:32:06 -0800210 ASSERT_NEAR(accel(0), localizer_.accel_state()(kVelocityX), 1e-10);
211 ASSERT_NEAR(accel(1), localizer_.accel_state()(kVelocityY), 1e-10);
James Kuszmaul29c59522022-02-12 16:44:26 -0800212
213 // Start going in a cirlce, and confirm that we
214 // handle things correctly. We rotate the accelerometer readings by 90 degrees
215 // and then leave them constant, which should make it look like we are going
216 // around in a circle.
217 accel = Eigen::Vector3d{-accel(1), accel(0), 9.80665};
218 accel_gs = accel / 9.80665;
219 // v^2 / r = a
220 // w * r = v
221 // v^2 / v * w = a
222 // w = a / v
223 const double omega = accel.topRows<2>().norm() /
224 std::hypot(localizer_.accel_state()(kVelocityX),
225 localizer_.accel_state()(kVelocityY));
226 gyro << 0.0, 0.0, omega;
227 // Due to the magic of math, omega works out to be 1.0 after having run at the
228 // acceleration for one second.
229 ASSERT_NEAR(1.0, omega, 1e-10);
230 // Yes, we could save some operations here, but let's be at least somewhat
231 // clear about what we're doing...
232 const double radius = accel.topRows<2>().norm() / (omega * omega);
233 const Eigen::Vector2d center = localizer_.xytheta().topRows<2>() +
234 accel.topRows<2>().normalized() * radius;
235 const double initial_theta = std::atan2(-accel(1), -accel(0));
236
237 std::chrono::microseconds one_revolution_time(
238 static_cast<int>(2 * M_PI / omega * 1e6));
239
240 aos::monotonic_clock::time_point circle_start = t;
241
242 while (t < circle_start + one_revolution_time) {
243 SCOPED_TRACE(t);
244 localizer_.HandleImu(t, gyro, accel_gs, encoders, voltages);
245 t += kDt;
246 const double t_circle = aos::time::DurationInSeconds(t - circle_start);
247 ASSERT_NEAR(t_circle * omega, localizer_.xytheta()(2), 1e-5);
248 const double theta_circle = t_circle * omega + initial_theta;
249 const Eigen::Vector2d offset =
250 radius *
251 Eigen::Vector2d{std::cos(theta_circle), std::sin(theta_circle)};
252 const Eigen::Vector2d expected = center + offset;
253 const Eigen::Vector2d estimated = localizer_.xytheta().topRows<2>();
254 const Eigen::Vector2d implied_offset = estimated - center;
255 const double implied_theta =
256 std::atan2(implied_offset.y(), implied_offset.x());
257 VLOG(1) << "center: " << center.transpose() << " radius " << radius
258 << "\nlocalizer " << localizer_.xytheta().transpose()
259 << " t_circle " << t_circle << " omega " << omega << " theta "
260 << theta_circle << "\noffset " << offset.transpose()
261 << "\nexpected " << expected.transpose() << "\nimplied offset "
262 << implied_offset << " implied_theta " << implied_theta << "\nvel "
263 << localizer_.accel_state()(kVelocityX) << ", "
264 << localizer_.accel_state()(kVelocityY);
265 ASSERT_NEAR(0.0, (expected - localizer_.xytheta().topRows<2>()).norm(),
266 1e-2);
267 }
268
269 // Set accelerometer back to zero and confirm that we recover (the
270 // implementation decays the accelerometer speeds to zero when still, so
271 // should recover).
272 while (t <
273 circle_start + one_revolution_time + std::chrono::milliseconds(3000)) {
274 localizer_.HandleImu(t, Eigen::Vector3d::Zero(), Eigen::Vector3d::UnitZ(),
275 encoders, voltages);
276 t += kDt;
277 }
278 const Eigen::Vector3d final_pos = localizer_.xytheta();
279 localizer_.HandleImu(t, Eigen::Vector3d::Zero(), Eigen::Vector3d::UnitZ(),
280 encoders, voltages);
281 ASSERT_NEAR(0.0, (final_pos - localizer_.xytheta()).norm(), 1e-10);
282}
283
284using control_loops::drivetrain::Output;
285
286class EventLoopLocalizerTest : public ::testing::Test {
287 protected:
288 EventLoopLocalizerTest()
Austin Schuhc5fa6d92022-02-25 14:36:28 -0800289 : configuration_(aos::configuration::ReadConfig("y2022/aos_config.json")),
James Kuszmaul29c59522022-02-12 16:44:26 -0800290 event_loop_factory_(&configuration_.message()),
291 roborio_node_(
292 aos::configuration::GetNode(&configuration_.message(), "roborio")),
293 imu_node_(
294 aos::configuration::GetNode(&configuration_.message(), "imu")),
295 dt_config_(
296 control_loops::drivetrain::testing::GetTestDrivetrainConfig()),
297 localizer_event_loop_(
298 event_loop_factory_.MakeEventLoop("localizer", imu_node_)),
299 localizer_(localizer_event_loop_.get(), dt_config_),
300 drivetrain_plant_event_loop_(event_loop_factory_.MakeEventLoop(
301 "drivetrain_plant", roborio_node_)),
302 drivetrain_plant_imu_event_loop_(
303 event_loop_factory_.MakeEventLoop("drivetrain_plant", imu_node_)),
304 drivetrain_plant_(drivetrain_plant_event_loop_.get(),
305 drivetrain_plant_imu_event_loop_.get(), dt_config_,
306 std::chrono::microseconds(500)),
307 roborio_test_event_loop_(
308 event_loop_factory_.MakeEventLoop("test", roborio_node_)),
309 imu_test_event_loop_(
310 event_loop_factory_.MakeEventLoop("test", imu_node_)),
311 logger_test_event_loop_(
312 event_loop_factory_.GetNodeEventLoopFactory("logger")
313 ->MakeEventLoop("test")),
314 output_sender_(
315 roborio_test_event_loop_->MakeSender<Output>("/drivetrain")),
316 output_fetcher_(roborio_test_event_loop_->MakeFetcher<LocalizerOutput>(
317 "/localizer")),
318 status_fetcher_(
319 imu_test_event_loop_->MakeFetcher<LocalizerStatus>("/localizer")) {
James Kuszmaul5ed29dd2022-02-13 18:32:06 -0800320 localizer_.localizer()->set_longitudinal_offset(0.0);
James Kuszmaul29c59522022-02-12 16:44:26 -0800321 aos::TimerHandler *timer = roborio_test_event_loop_->AddTimer([this]() {
322 auto builder = output_sender_.MakeBuilder();
323 auto output_builder = builder.MakeBuilder<Output>();
324 output_builder.add_left_voltage(output_voltages_(0));
325 output_builder.add_right_voltage(output_voltages_(1));
326 builder.CheckOk(builder.Send(output_builder.Finish()));
327 });
328 roborio_test_event_loop_->OnRun([timer, this]() {
329 timer->Setup(roborio_test_event_loop_->monotonic_now(),
330 std::chrono::milliseconds(5));
331 });
332 // Get things zeroed.
333 event_loop_factory_.RunFor(std::chrono::seconds(10));
334 CHECK(status_fetcher_.Fetch());
335 CHECK(status_fetcher_->zeroed());
336 }
337
338 aos::FlatbufferDetachedBuffer<aos::Configuration> configuration_;
339 aos::SimulatedEventLoopFactory event_loop_factory_;
340 const aos::Node *const roborio_node_;
341 const aos::Node *const imu_node_;
342 const control_loops::drivetrain::DrivetrainConfig<double> dt_config_;
343 std::unique_ptr<aos::EventLoop> localizer_event_loop_;
344 EventLoopLocalizer localizer_;
345
346 std::unique_ptr<aos::EventLoop> drivetrain_plant_event_loop_;
347 std::unique_ptr<aos::EventLoop> drivetrain_plant_imu_event_loop_;
348 control_loops::drivetrain::testing::DrivetrainSimulation drivetrain_plant_;
349
350 std::unique_ptr<aos::EventLoop> roborio_test_event_loop_;
351 std::unique_ptr<aos::EventLoop> imu_test_event_loop_;
352 std::unique_ptr<aos::EventLoop> logger_test_event_loop_;
353
354 aos::Sender<Output> output_sender_;
355 aos::Fetcher<LocalizerOutput> output_fetcher_;
356 aos::Fetcher<LocalizerStatus> status_fetcher_;
357
358 Eigen::Vector2d output_voltages_ = Eigen::Vector2d::Zero();
359};
360
361TEST_F(EventLoopLocalizerTest, Nominal) {
362 output_voltages_ << 1.0, 1.0;
363 event_loop_factory_.RunFor(std::chrono::seconds(2));
364 drivetrain_plant_.set_accel_sin_magnitude(0.01);
365 CHECK(output_fetcher_.Fetch());
366 CHECK(status_fetcher_.Fetch());
367 // The two can be different because they may've been sent at different times.
368 ASSERT_NEAR(output_fetcher_->x(), status_fetcher_->model_based()->x(), 1e-6);
369 ASSERT_NEAR(output_fetcher_->y(), status_fetcher_->model_based()->y(), 1e-6);
370 ASSERT_NEAR(output_fetcher_->theta(), status_fetcher_->model_based()->theta(),
371 1e-6);
372 ASSERT_LT(0.1, output_fetcher_->x());
373 ASSERT_NEAR(0.0, output_fetcher_->y(), 1e-10);
374 ASSERT_NEAR(0.0, output_fetcher_->theta(), 1e-10);
375 ASSERT_TRUE(status_fetcher_->has_model_based());
376 ASSERT_TRUE(status_fetcher_->model_based()->using_model());
377 ASSERT_LT(0.1, status_fetcher_->model_based()->accel_state()->velocity_x());
378 ASSERT_NEAR(0.0, status_fetcher_->model_based()->accel_state()->velocity_y(),
379 1e-10);
380 ASSERT_NEAR(
381 0.0, status_fetcher_->model_based()->model_state()->left_voltage_error(),
382 1e-1);
383 ASSERT_NEAR(
384 0.0, status_fetcher_->model_based()->model_state()->right_voltage_error(),
385 1e-1);
386}
387
388TEST_F(EventLoopLocalizerTest, Reverse) {
389 output_voltages_ << -4.0, -4.0;
390 drivetrain_plant_.set_accel_sin_magnitude(0.01);
391 event_loop_factory_.RunFor(std::chrono::seconds(2));
392 CHECK(output_fetcher_.Fetch());
393 CHECK(status_fetcher_.Fetch());
394 // The two can be different because they may've been sent at different times.
395 ASSERT_NEAR(output_fetcher_->x(), status_fetcher_->model_based()->x(), 1e-6);
396 ASSERT_NEAR(output_fetcher_->y(), status_fetcher_->model_based()->y(), 1e-6);
397 ASSERT_NEAR(output_fetcher_->theta(), status_fetcher_->model_based()->theta(),
398 1e-6);
399 ASSERT_GT(-0.1, output_fetcher_->x());
400 ASSERT_NEAR(0.0, output_fetcher_->y(), 1e-10);
401 ASSERT_NEAR(0.0, output_fetcher_->theta(), 1e-10);
402 ASSERT_TRUE(status_fetcher_->has_model_based());
403 ASSERT_TRUE(status_fetcher_->model_based()->using_model());
404 ASSERT_GT(-0.1, status_fetcher_->model_based()->accel_state()->velocity_x());
405 ASSERT_NEAR(0.0, status_fetcher_->model_based()->accel_state()->velocity_y(),
406 1e-10);
407 ASSERT_NEAR(
408 0.0, status_fetcher_->model_based()->model_state()->left_voltage_error(),
409 1e-1);
410 ASSERT_NEAR(
411 0.0, status_fetcher_->model_based()->model_state()->right_voltage_error(),
412 1e-1);
413}
414
415TEST_F(EventLoopLocalizerTest, SpinInPlace) {
416 output_voltages_ << 4.0, -4.0;
417 event_loop_factory_.RunFor(std::chrono::seconds(2));
418 CHECK(output_fetcher_.Fetch());
419 CHECK(status_fetcher_.Fetch());
420 // The two can be different because they may've been sent at different times.
421 ASSERT_NEAR(output_fetcher_->x(), status_fetcher_->model_based()->x(), 1e-6);
422 ASSERT_NEAR(output_fetcher_->y(), status_fetcher_->model_based()->y(), 1e-6);
423 ASSERT_NEAR(output_fetcher_->theta(), status_fetcher_->model_based()->theta(),
424 1e-1);
425 ASSERT_NEAR(0.0, output_fetcher_->x(), 1e-10);
426 ASSERT_NEAR(0.0, output_fetcher_->y(), 1e-10);
427 ASSERT_LT(0.1, std::abs(output_fetcher_->theta()));
428 ASSERT_TRUE(status_fetcher_->has_model_based());
429 ASSERT_TRUE(status_fetcher_->model_based()->using_model());
430 ASSERT_NEAR(0.0, status_fetcher_->model_based()->accel_state()->velocity_x(),
431 1e-10);
432 ASSERT_NEAR(0.0, status_fetcher_->model_based()->accel_state()->velocity_y(),
433 1e-10);
434 ASSERT_NEAR(-status_fetcher_->model_based()->model_state()->left_velocity(),
435 status_fetcher_->model_based()->model_state()->right_velocity(),
436 1e-3);
437 ASSERT_NEAR(
438 0.0, status_fetcher_->model_based()->model_state()->left_voltage_error(),
439 1e-1);
440 ASSERT_NEAR(
441 0.0, status_fetcher_->model_based()->model_state()->right_voltage_error(),
442 1e-1);
443 ASSERT_NEAR(0.0, status_fetcher_->model_based()->residual(), 1e-3);
444}
445
446TEST_F(EventLoopLocalizerTest, Curve) {
447 output_voltages_ << 2.0, 4.0;
448 event_loop_factory_.RunFor(std::chrono::seconds(2));
449 CHECK(output_fetcher_.Fetch());
450 CHECK(status_fetcher_.Fetch());
451 // The two can be different because they may've been sent at different times.
452 ASSERT_NEAR(output_fetcher_->x(), status_fetcher_->model_based()->x(), 1e-2);
453 ASSERT_NEAR(output_fetcher_->y(), status_fetcher_->model_based()->y(), 1e-2);
454 ASSERT_NEAR(output_fetcher_->theta(), status_fetcher_->model_based()->theta(),
455 1e-1);
456 ASSERT_LT(0.1, output_fetcher_->x());
457 ASSERT_LT(0.1, output_fetcher_->y());
458 ASSERT_LT(0.1, std::abs(output_fetcher_->theta()));
459 ASSERT_TRUE(status_fetcher_->has_model_based());
460 ASSERT_TRUE(status_fetcher_->model_based()->using_model());
461 ASSERT_LT(0.0, status_fetcher_->model_based()->accel_state()->velocity_x());
462 ASSERT_LT(0.0, status_fetcher_->model_based()->accel_state()->velocity_y());
463 ASSERT_NEAR(
464 0.0, status_fetcher_->model_based()->model_state()->left_voltage_error(),
465 1e-1);
466 ASSERT_NEAR(
467 0.0, status_fetcher_->model_based()->model_state()->right_voltage_error(),
468 1e-1);
469 ASSERT_NEAR(0.0, status_fetcher_->model_based()->residual(), 1e-1)
470 << aos::FlatbufferToJson(status_fetcher_.get(), {.multi_line = true});
471}
472
473// Tests that small amounts of voltage error are handled by the model-based
474// half of the localizer.
475TEST_F(EventLoopLocalizerTest, VoltageError) {
476 output_voltages_ << 0.0, 0.0;
477 drivetrain_plant_.set_left_voltage_offset(2.0);
478 drivetrain_plant_.set_right_voltage_offset(2.0);
479 drivetrain_plant_.set_accel_sin_magnitude(0.01);
480
481 event_loop_factory_.RunFor(std::chrono::seconds(2));
482 CHECK(output_fetcher_.Fetch());
483 CHECK(status_fetcher_.Fetch());
484 // Should still be using the model, but have a non-trivial residual.
485 ASSERT_TRUE(status_fetcher_->model_based()->using_model());
James Kuszmaul5ed29dd2022-02-13 18:32:06 -0800486 ASSERT_LT(0.02, status_fetcher_->model_based()->residual())
James Kuszmaul29c59522022-02-12 16:44:26 -0800487 << aos::FlatbufferToJson(status_fetcher_.get(), {.multi_line = true});
488
489 // Afer running for a while, voltage error terms should converge and result in
490 // low residuals.
491 event_loop_factory_.RunFor(std::chrono::seconds(10));
492 CHECK(output_fetcher_.Fetch());
493 CHECK(status_fetcher_.Fetch());
494 ASSERT_TRUE(status_fetcher_->model_based()->using_model());
495 ASSERT_NEAR(
496 2.0, status_fetcher_->model_based()->model_state()->left_voltage_error(),
497 0.1)
498 << aos::FlatbufferToJson(status_fetcher_.get(), {.multi_line = true});
499 ASSERT_NEAR(
500 2.0, status_fetcher_->model_based()->model_state()->right_voltage_error(),
501 0.1)
502 << aos::FlatbufferToJson(status_fetcher_.get(), {.multi_line = true});
James Kuszmaul5ed29dd2022-02-13 18:32:06 -0800503 ASSERT_GT(0.02, status_fetcher_->model_based()->residual())
James Kuszmaul29c59522022-02-12 16:44:26 -0800504 << aos::FlatbufferToJson(status_fetcher_.get(), {.multi_line = true});
505}
506
507// Tests that large amounts of voltage error force us into the
508// acceleration-based localizer.
509TEST_F(EventLoopLocalizerTest, HighVoltageError) {
510 output_voltages_ << 0.0, 0.0;
511 drivetrain_plant_.set_left_voltage_offset(200.0);
512 drivetrain_plant_.set_right_voltage_offset(200.0);
513 drivetrain_plant_.set_accel_sin_magnitude(0.01);
514
515 event_loop_factory_.RunFor(std::chrono::seconds(2));
516 CHECK(output_fetcher_.Fetch());
517 CHECK(status_fetcher_.Fetch());
518 // Should still be using the model, but have a non-trivial residual.
519 ASSERT_FALSE(status_fetcher_->model_based()->using_model());
520 ASSERT_LT(0.1, status_fetcher_->model_based()->residual())
521 << aos::FlatbufferToJson(status_fetcher_.get(), {.multi_line = true});
522 ASSERT_NEAR(drivetrain_plant_.state()(0),
523 status_fetcher_->model_based()->x(), 1.0);
524 ASSERT_NEAR(drivetrain_plant_.state()(1),
525 status_fetcher_->model_based()->y(), 1e-6);
526}
527
528} // namespace frc91::controls::testing