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