blob: 608314356542802275d60653688af28511bff237 [file] [log] [blame]
James Kuszmaul51fa1ae2022-02-26 00:49:57 -08001#include "y2022/localizer/localizer.h"
James Kuszmaul29c59522022-02-12 16:44:26 -08002
Philipp Schrader790cb542023-07-05 21:06:52 -07003#include "gtest/gtest.h"
4
James Kuszmaul8c4f6592022-02-26 15:49:30 -08005#include "aos/events/logging/log_writer.h"
James Kuszmaul29c59522022-02-12 16:44:26 -08006#include "aos/events/simulated_event_loop.h"
James Kuszmaul29c59522022-02-12 16:44:26 -08007#include "frc971/control_loops/drivetrain/drivetrain_test_lib.h"
James Kuszmaul8c4f6592022-02-26 15:49:30 -08008#include "frc971/control_loops/pose.h"
James Kuszmaul8c4f6592022-02-26 15:49:30 -08009#include "y2022/control_loops/drivetrain/drivetrain_base.h"
Milind Upadhyayd67e9cf2022-03-13 13:56:57 -070010#include "y2022/control_loops/superstructure/superstructure_status_generated.h"
11#include "y2022/vision/target_estimate_generated.h"
James Kuszmaul8c4f6592022-02-26 15:49:30 -080012
13DEFINE_string(output_folder, "",
14 "If set, logs all channels to the provided logfile.");
James Kuszmaul29c59522022-02-12 16:44:26 -080015
16namespace frc971::controls::testing {
17typedef ModelBasedLocalizer::ModelState ModelState;
18typedef ModelBasedLocalizer::AccelState AccelState;
19typedef ModelBasedLocalizer::ModelInput ModelInput;
20typedef ModelBasedLocalizer::AccelInput AccelInput;
James Kuszmaul8c4f6592022-02-26 15:49:30 -080021
Milind Upadhyayd67e9cf2022-03-13 13:56:57 -070022using frc971::control_loops::Pose;
James Kuszmaul8c4f6592022-02-26 15:49:30 -080023using frc971::control_loops::drivetrain::DrivetrainConfig;
24using frc971::control_loops::drivetrain::LocalizerControl;
Milind Upadhyayd67e9cf2022-03-13 13:56:57 -070025using frc971::vision::calibration::CameraCalibrationT;
26using frc971::vision::calibration::TransformationMatrixT;
James Kuszmaul8c4f6592022-02-26 15:49:30 -080027using y2022::vision::TargetEstimate;
28using y2022::vision::TargetEstimateT;
29
James Kuszmaul29c59522022-02-12 16:44:26 -080030namespace {
31constexpr size_t kX = ModelBasedLocalizer::kX;
32constexpr size_t kY = ModelBasedLocalizer::kY;
33constexpr size_t kTheta = ModelBasedLocalizer::kTheta;
34constexpr size_t kVelocityX = ModelBasedLocalizer::kVelocityX;
35constexpr size_t kVelocityY = ModelBasedLocalizer::kVelocityY;
36constexpr size_t kAccelX = ModelBasedLocalizer::kAccelX;
37constexpr size_t kAccelY = ModelBasedLocalizer::kAccelY;
38constexpr size_t kThetaRate = ModelBasedLocalizer::kThetaRate;
39constexpr size_t kLeftEncoder = ModelBasedLocalizer::kLeftEncoder;
40constexpr size_t kLeftVelocity = ModelBasedLocalizer::kLeftVelocity;
41constexpr size_t kLeftVoltageError = ModelBasedLocalizer::kLeftVoltageError;
42constexpr size_t kRightEncoder = ModelBasedLocalizer::kRightEncoder;
43constexpr size_t kRightVelocity = ModelBasedLocalizer::kRightVelocity;
44constexpr size_t kRightVoltageError = ModelBasedLocalizer::kRightVoltageError;
45constexpr size_t kLeftVoltage = ModelBasedLocalizer::kLeftVoltage;
46constexpr size_t kRightVoltage = ModelBasedLocalizer::kRightVoltage;
James Kuszmaul8c4f6592022-02-26 15:49:30 -080047
48Eigen::Matrix<double, 4, 4> TurretRobotTransformation() {
49 Eigen::Matrix<double, 4, 4> H;
50 H.setIdentity();
51 H.block<3, 1>(0, 3) << 1, 1.1, 0.9;
52 return H;
53}
54
55// Provides the location of the camera on the turret.
56Eigen::Matrix<double, 4, 4> CameraTurretTransformation() {
57 Eigen::Matrix<double, 4, 4> H;
58 H.setIdentity();
59 H.block<3, 1>(0, 3) << 0.1, 0, 0;
60 H.block<3, 3>(0, 0) << 0, 0, 1, -1, 0, 0, 0, -1, 0;
61
62 // Introduce a bit of pitch to make sure that we're exercising all the code.
63 H.block<3, 3>(0, 0) =
64 Eigen::AngleAxis<double>(0.1, Eigen::Vector3d::UnitY()) *
65 H.block<3, 3>(0, 0);
66 return H;
67}
68
69// Copies an Eigen matrix into a row-major vector of the data.
70std::vector<float> MatrixToVector(const Eigen::Matrix<double, 4, 4> &H) {
71 std::vector<float> data;
72 for (int row = 0; row < 4; ++row) {
73 for (int col = 0; col < 4; ++col) {
74 data.push_back(H(row, col));
75 }
76 }
77 return data;
78}
79
80DrivetrainConfig<double> GetTest2022DrivetrainConfig() {
81 DrivetrainConfig<double> config =
82 y2022::control_loops::drivetrain::GetDrivetrainConfig();
83 config.is_simulated = true;
84 return config;
85}
Milind Upadhyayd67e9cf2022-03-13 13:56:57 -070086} // namespace
James Kuszmaul29c59522022-02-12 16:44:26 -080087
88class LocalizerTest : public ::testing::Test {
89 protected:
90 LocalizerTest()
Milind Upadhyayd67e9cf2022-03-13 13:56:57 -070091 : dt_config_(GetTest2022DrivetrainConfig()), localizer_(dt_config_) {
James Kuszmaul5ed29dd2022-02-13 18:32:06 -080092 localizer_.set_longitudinal_offset(0.0);
93 }
James Kuszmaul29c59522022-02-12 16:44:26 -080094 ModelState CallDiffModel(const ModelState &state, const ModelInput &U) {
95 return localizer_.DiffModel(state, U);
96 }
97
98 AccelState CallDiffAccel(const AccelState &state, const AccelInput &U) {
99 return localizer_.DiffAccel(state, U);
100 }
101
102 const control_loops::drivetrain::DrivetrainConfig<double> dt_config_;
103 ModelBasedLocalizer localizer_;
James Kuszmaul29c59522022-02-12 16:44:26 -0800104};
105
106TEST_F(LocalizerTest, AccelIntegrationTest) {
107 AccelState state;
108 state.setZero();
109 AccelInput input;
110 input.setZero();
111
112 EXPECT_EQ(0.0, CallDiffAccel(state, input).norm());
113 // Non-zero x/y/theta states should still result in a zero derivative.
114 state(kX) = 1.0;
115 state(kY) = 1.0;
116 state(kTheta) = 1.0;
117 EXPECT_EQ(0.0, CallDiffAccel(state, input).norm());
118
119 state.setZero();
120 state(kVelocityX) = 1.0;
121 state(kVelocityY) = 2.0;
122 EXPECT_EQ((AccelState() << 1.0, 2.0, 0.0, 0.0, 0.0).finished(),
123 CallDiffAccel(state, input));
124 // Derivatives should be independent of theta.
125 state(kTheta) = M_PI / 2.0;
126 EXPECT_EQ((AccelState() << 1.0, 2.0, 0.0, 0.0, 0.0).finished(),
127 CallDiffAccel(state, input));
128
129 state.setZero();
130 input(kAccelX) = 1.0;
131 input(kAccelY) = 2.0;
132 input(kThetaRate) = 3.0;
133 EXPECT_EQ((AccelState() << 0.0, 0.0, 3.0, 1.0, 2.0).finished(),
134 CallDiffAccel(state, input));
135 state(kTheta) = M_PI / 2.0;
136 EXPECT_EQ((AccelState() << 0.0, 0.0, 3.0, 1.0, 2.0).finished(),
137 CallDiffAccel(state, input));
138}
139
140TEST_F(LocalizerTest, ModelIntegrationTest) {
141 ModelState state;
142 state.setZero();
143 ModelInput input;
144 input.setZero();
145 ModelState diff;
146
147 EXPECT_EQ(0.0, CallDiffModel(state, input).norm());
148 // Non-zero x/y/theta/encoder states should still result in a zero derivative.
149 state(kX) = 1.0;
150 state(kY) = 1.0;
151 state(kTheta) = 1.0;
152 state(kLeftEncoder) = 1.0;
153 state(kRightEncoder) = 1.0;
154 EXPECT_EQ(0.0, CallDiffModel(state, input).norm());
155
156 state.setZero();
157 state(kLeftVelocity) = 1.0;
158 state(kRightVelocity) = 1.0;
159 diff = CallDiffModel(state, input);
160 const ModelState mask_velocities =
161 (ModelState() << 1.0, 1.0, 1.0, 1.0, 0.0, 1.0, 1.0, 0.0, 1.0).finished();
162 EXPECT_EQ(
163 (ModelState() << 1.0, 0.0, 0.0, 1.0, 0.0, 0.0, 1.0, 0.0, 0.0).finished(),
164 diff.cwiseProduct(mask_velocities));
165 EXPECT_EQ(diff(kLeftVelocity), diff(kRightVelocity));
166 EXPECT_GT(0.0, diff(kLeftVelocity));
167 state(kTheta) = M_PI / 2.0;
168 diff = CallDiffModel(state, input);
169 EXPECT_NEAR(0.0,
170 ((ModelState() << 0.0, 1.0, 0.0, 1.0, 0.0, 0.0, 1.0, 0.0, 0.0)
171 .finished() -
172 diff.cwiseProduct(mask_velocities))
173 .norm(),
174 1e-12);
175 EXPECT_EQ(diff(kLeftVelocity), diff(kRightVelocity));
176 EXPECT_GT(0.0, diff(kLeftVelocity));
177
178 state.setZero();
179 state(kLeftVelocity) = -1.0;
180 state(kRightVelocity) = 1.0;
181 diff = CallDiffModel(state, input);
182 EXPECT_EQ((ModelState() << 0.0, 0.0, 1.0 / dt_config_.robot_radius, -1.0, 0.0,
183 0.0, 1.0, 0.0, 0.0)
184 .finished(),
185 diff.cwiseProduct(mask_velocities));
186 EXPECT_EQ(-diff(kLeftVelocity), diff(kRightVelocity));
187 EXPECT_LT(0.0, diff(kLeftVelocity));
188
189 state.setZero();
190 input(kLeftVoltage) = 5.0;
191 input(kRightVoltage) = 6.0;
192 diff = CallDiffModel(state, input);
193 EXPECT_EQ(0, diff(kX));
194 EXPECT_EQ(0, diff(kY));
195 EXPECT_EQ(0, diff(kTheta));
196 EXPECT_EQ(0, diff(kLeftEncoder));
197 EXPECT_EQ(0, diff(kRightEncoder));
198 EXPECT_EQ(0, diff(kLeftVoltageError));
199 EXPECT_EQ(0, diff(kRightVoltageError));
200 EXPECT_LT(0, diff(kLeftVelocity));
201 EXPECT_LT(0, diff(kRightVelocity));
202 EXPECT_LT(diff(kLeftVelocity), diff(kRightVelocity));
203
204 state.setZero();
205 state(kLeftVoltageError) = -1.0;
206 state(kRightVoltageError) = -2.0;
207 input(kLeftVoltage) = 1.0;
208 input(kRightVoltage) = 2.0;
209 EXPECT_EQ(ModelState::Zero(), CallDiffModel(state, input));
210}
211
212// Test that the HandleReset does indeed reset the state of the localizer.
213TEST_F(LocalizerTest, LocalizerReset) {
214 aos::monotonic_clock::time_point t = aos::monotonic_clock::epoch();
215 localizer_.HandleReset(t, {1.0, 2.0, 3.0});
216 EXPECT_EQ((Eigen::Vector3d{1.0, 2.0, 3.0}), localizer_.xytheta());
217 localizer_.HandleReset(t, {4.0, 5.0, 6.0});
218 EXPECT_EQ((Eigen::Vector3d{4.0, 5.0, 6.0}), localizer_.xytheta());
219}
220
221// Test that if we are moving only by accelerometer readings (and just assuming
222// zero voltage/encoders) that we initially don't believe it but then latch into
223// following the accelerometer.
224// Note: this test is somewhat sensitive to the exact tuning values used for the
225// filter.
226TEST_F(LocalizerTest, AccelOnly) {
227 const aos::monotonic_clock::time_point start = aos::monotonic_clock::epoch();
228 const std::chrono::microseconds kDt{500};
229 aos::monotonic_clock::time_point t = start - std::chrono::milliseconds(1000);
230 Eigen::Vector3d gyro{0.0, 0.0, 0.0};
231 const Eigen::Vector2d encoders{0.0, 0.0};
232 const Eigen::Vector2d voltages{0.0, 0.0};
James Kuszmaul5ed29dd2022-02-13 18:32:06 -0800233 Eigen::Vector3d accel{5.0, 2.0, 9.80665};
Milind Upadhyayd67e9cf2022-03-13 13:56:57 -0700234 Eigen::Vector3d accel_gs =
235 dt_config_.imu_transform.inverse() * accel / 9.80665;
James Kuszmaul29c59522022-02-12 16:44:26 -0800236 while (t < start) {
237 // Spin to fill up the buffer.
238 localizer_.HandleImu(t, gyro, Eigen::Vector3d::UnitZ(), encoders, voltages);
239 t += kDt;
240 }
241 while (t < start + std::chrono::milliseconds(100)) {
242 localizer_.HandleImu(t, gyro, accel_gs, encoders, voltages);
243 EXPECT_EQ(Eigen::Vector3d::Zero(), localizer_.xytheta());
244 t += kDt;
245 }
246 while (t < start + std::chrono::milliseconds(500)) {
247 // Too lazy to hard-code when the transition happens.
248 localizer_.HandleImu(t, gyro, accel_gs, encoders, voltages);
249 t += kDt;
250 }
251 while (t < start + std::chrono::milliseconds(1000)) {
252 SCOPED_TRACE(t);
253 localizer_.HandleImu(t, gyro, accel_gs, encoders, voltages);
254 const Eigen::Vector3d xytheta = localizer_.xytheta();
255 t += kDt;
256 EXPECT_NEAR(
257 0.5 * accel(0) * std::pow(aos::time::DurationInSeconds(t - start), 2),
258 xytheta(0), 1e-4);
259 EXPECT_NEAR(
260 0.5 * accel(1) * std::pow(aos::time::DurationInSeconds(t - start), 2),
261 xytheta(1), 1e-4);
262 EXPECT_EQ(0.0, xytheta(2));
263 }
264
James Kuszmaul5ed29dd2022-02-13 18:32:06 -0800265 ASSERT_NEAR(accel(0), localizer_.accel_state()(kVelocityX), 1e-10);
266 ASSERT_NEAR(accel(1), localizer_.accel_state()(kVelocityY), 1e-10);
James Kuszmaul29c59522022-02-12 16:44:26 -0800267
268 // Start going in a cirlce, and confirm that we
269 // handle things correctly. We rotate the accelerometer readings by 90 degrees
270 // and then leave them constant, which should make it look like we are going
271 // around in a circle.
272 accel = Eigen::Vector3d{-accel(1), accel(0), 9.80665};
James Kuszmaul8c4f6592022-02-26 15:49:30 -0800273 accel_gs = dt_config_.imu_transform.inverse() * accel / 9.80665;
James Kuszmaul29c59522022-02-12 16:44:26 -0800274 // v^2 / r = a
275 // w * r = v
276 // v^2 / v * w = a
277 // w = a / v
278 const double omega = accel.topRows<2>().norm() /
279 std::hypot(localizer_.accel_state()(kVelocityX),
280 localizer_.accel_state()(kVelocityY));
281 gyro << 0.0, 0.0, omega;
282 // Due to the magic of math, omega works out to be 1.0 after having run at the
283 // acceleration for one second.
284 ASSERT_NEAR(1.0, omega, 1e-10);
285 // Yes, we could save some operations here, but let's be at least somewhat
286 // clear about what we're doing...
287 const double radius = accel.topRows<2>().norm() / (omega * omega);
288 const Eigen::Vector2d center = localizer_.xytheta().topRows<2>() +
289 accel.topRows<2>().normalized() * radius;
290 const double initial_theta = std::atan2(-accel(1), -accel(0));
291
292 std::chrono::microseconds one_revolution_time(
293 static_cast<int>(2 * M_PI / omega * 1e6));
294
295 aos::monotonic_clock::time_point circle_start = t;
296
297 while (t < circle_start + one_revolution_time) {
298 SCOPED_TRACE(t);
299 localizer_.HandleImu(t, gyro, accel_gs, encoders, voltages);
300 t += kDt;
301 const double t_circle = aos::time::DurationInSeconds(t - circle_start);
302 ASSERT_NEAR(t_circle * omega, localizer_.xytheta()(2), 1e-5);
303 const double theta_circle = t_circle * omega + initial_theta;
304 const Eigen::Vector2d offset =
305 radius *
306 Eigen::Vector2d{std::cos(theta_circle), std::sin(theta_circle)};
307 const Eigen::Vector2d expected = center + offset;
308 const Eigen::Vector2d estimated = localizer_.xytheta().topRows<2>();
309 const Eigen::Vector2d implied_offset = estimated - center;
310 const double implied_theta =
311 std::atan2(implied_offset.y(), implied_offset.x());
312 VLOG(1) << "center: " << center.transpose() << " radius " << radius
313 << "\nlocalizer " << localizer_.xytheta().transpose()
314 << " t_circle " << t_circle << " omega " << omega << " theta "
315 << theta_circle << "\noffset " << offset.transpose()
316 << "\nexpected " << expected.transpose() << "\nimplied offset "
317 << implied_offset << " implied_theta " << implied_theta << "\nvel "
318 << localizer_.accel_state()(kVelocityX) << ", "
319 << localizer_.accel_state()(kVelocityY);
320 ASSERT_NEAR(0.0, (expected - localizer_.xytheta().topRows<2>()).norm(),
321 1e-2);
322 }
323
324 // Set accelerometer back to zero and confirm that we recover (the
325 // implementation decays the accelerometer speeds to zero when still, so
326 // should recover).
327 while (t <
328 circle_start + one_revolution_time + std::chrono::milliseconds(3000)) {
329 localizer_.HandleImu(t, Eigen::Vector3d::Zero(), Eigen::Vector3d::UnitZ(),
330 encoders, voltages);
331 t += kDt;
332 }
333 const Eigen::Vector3d final_pos = localizer_.xytheta();
334 localizer_.HandleImu(t, Eigen::Vector3d::Zero(), Eigen::Vector3d::UnitZ(),
335 encoders, voltages);
336 ASSERT_NEAR(0.0, (final_pos - localizer_.xytheta()).norm(), 1e-10);
337}
338
339using control_loops::drivetrain::Output;
340
341class EventLoopLocalizerTest : public ::testing::Test {
342 protected:
343 EventLoopLocalizerTest()
Austin Schuhc5fa6d92022-02-25 14:36:28 -0800344 : configuration_(aos::configuration::ReadConfig("y2022/aos_config.json")),
James Kuszmaul29c59522022-02-12 16:44:26 -0800345 event_loop_factory_(&configuration_.message()),
346 roborio_node_(
347 aos::configuration::GetNode(&configuration_.message(), "roborio")),
348 imu_node_(
349 aos::configuration::GetNode(&configuration_.message(), "imu")),
James Kuszmaul8c4f6592022-02-26 15:49:30 -0800350 camera_node_(
351 aos::configuration::GetNode(&configuration_.message(), "pi1")),
James Kuszmaul29c59522022-02-12 16:44:26 -0800352 dt_config_(
353 control_loops::drivetrain::testing::GetTestDrivetrainConfig()),
354 localizer_event_loop_(
355 event_loop_factory_.MakeEventLoop("localizer", imu_node_)),
356 localizer_(localizer_event_loop_.get(), dt_config_),
357 drivetrain_plant_event_loop_(event_loop_factory_.MakeEventLoop(
358 "drivetrain_plant", roborio_node_)),
359 drivetrain_plant_imu_event_loop_(
360 event_loop_factory_.MakeEventLoop("drivetrain_plant", imu_node_)),
361 drivetrain_plant_(drivetrain_plant_event_loop_.get(),
362 drivetrain_plant_imu_event_loop_.get(), dt_config_,
363 std::chrono::microseconds(500)),
364 roborio_test_event_loop_(
365 event_loop_factory_.MakeEventLoop("test", roborio_node_)),
366 imu_test_event_loop_(
367 event_loop_factory_.MakeEventLoop("test", imu_node_)),
James Kuszmaul8c4f6592022-02-26 15:49:30 -0800368 camera_test_event_loop_(
369 event_loop_factory_.MakeEventLoop("test", camera_node_)),
James Kuszmaul29c59522022-02-12 16:44:26 -0800370 logger_test_event_loop_(
371 event_loop_factory_.GetNodeEventLoopFactory("logger")
372 ->MakeEventLoop("test")),
373 output_sender_(
374 roborio_test_event_loop_->MakeSender<Output>("/drivetrain")),
James Kuszmaul8c4f6592022-02-26 15:49:30 -0800375 turret_sender_(
376 roborio_test_event_loop_
377 ->MakeSender<y2022::control_loops::superstructure::Status>(
378 "/superstructure")),
379 target_sender_(
380 camera_test_event_loop_->MakeSender<y2022::vision::TargetEstimate>(
381 "/camera")),
382 control_sender_(roborio_test_event_loop_->MakeSender<LocalizerControl>(
383 "/drivetrain")),
James Kuszmaul29c59522022-02-12 16:44:26 -0800384 output_fetcher_(roborio_test_event_loop_->MakeFetcher<LocalizerOutput>(
385 "/localizer")),
386 status_fetcher_(
387 imu_test_event_loop_->MakeFetcher<LocalizerStatus>("/localizer")) {
James Kuszmaul5ed29dd2022-02-13 18:32:06 -0800388 localizer_.localizer()->set_longitudinal_offset(0.0);
James Kuszmaul8c4f6592022-02-26 15:49:30 -0800389 {
390 aos::TimerHandler *timer = roborio_test_event_loop_->AddTimer([this]() {
391 {
392 auto builder = output_sender_.MakeBuilder();
393 auto output_builder = builder.MakeBuilder<Output>();
394 output_builder.add_left_voltage(output_voltages_(0));
395 output_builder.add_right_voltage(output_voltages_(1));
396 builder.CheckOk(builder.Send(output_builder.Finish()));
397 }
398 {
399 auto builder = turret_sender_.MakeBuilder();
James Kuszmaulf3ef9e12022-03-05 17:13:00 -0800400 auto turret_estimator_builder =
401 builder
402 .MakeBuilder<frc971::PotAndAbsoluteEncoderEstimatorState>();
403 turret_estimator_builder.add_position(turret_position_);
404 const flatbuffers::Offset<frc971::PotAndAbsoluteEncoderEstimatorState>
405 turret_estimator_offset = turret_estimator_builder.Finish();
James Kuszmaul8c4f6592022-02-26 15:49:30 -0800406 auto turret_builder =
407 builder
408 .MakeBuilder<frc971::control_loops::
409 PotAndAbsoluteEncoderProfiledJointStatus>();
410 turret_builder.add_position(turret_position_);
411 turret_builder.add_velocity(turret_velocity_);
James Kuszmaulf3ef9e12022-03-05 17:13:00 -0800412 turret_builder.add_zeroed(true);
413 turret_builder.add_estimator_state(turret_estimator_offset);
James Kuszmaul8c4f6592022-02-26 15:49:30 -0800414 const auto turret_offset = turret_builder.Finish();
415 auto status_builder =
416 builder
417 .MakeBuilder<y2022::control_loops::superstructure::Status>();
418 status_builder.add_turret(turret_offset);
419 builder.CheckOk(builder.Send(status_builder.Finish()));
420 }
421 });
422 roborio_test_event_loop_->OnRun([timer, this]() {
423 timer->Setup(roborio_test_event_loop_->monotonic_now(),
424 std::chrono::milliseconds(5));
425 });
426 }
427 {
428 aos::TimerHandler *timer = camera_test_event_loop_->AddTimer([this]() {
429 if (!send_targets_) {
430 return;
431 }
432 const frc971::control_loops::Pose robot_pose(
433 {drivetrain_plant_.GetPosition().x(),
434 drivetrain_plant_.GetPosition().y(), 0.0},
435 drivetrain_plant_.state()(2, 0));
436 const Eigen::Matrix<double, 4, 4> H_turret_camera =
437 frc971::control_loops::TransformationMatrixForYaw(
438 turret_position_) *
439 CameraTurretTransformation();
440
441 const Eigen::Matrix<double, 4, 4> H_field_camera =
442 robot_pose.AsTransformationMatrix() * TurretRobotTransformation() *
443 H_turret_camera;
444 const Eigen::Matrix<double, 4, 4> target_transform =
445 Eigen::Matrix<double, 4, 4>::Identity();
446 const Eigen::Matrix<double, 4, 4> H_camera_target =
447 H_field_camera.inverse() * target_transform;
448 const Eigen::Matrix<double, 4, 4> H_target_camera =
449 H_camera_target.inverse();
450
451 std::unique_ptr<y2022::vision::TargetEstimateT> estimate(
452 new y2022::vision::TargetEstimateT());
453 estimate->distance = H_target_camera.block<2, 1>(0, 3).norm();
454 estimate->angle_to_target =
455 std::atan2(-H_camera_target(0, 3), H_camera_target(2, 3));
456 estimate->camera_calibration.reset(new CameraCalibrationT());
457 {
458 estimate->camera_calibration->fixed_extrinsics.reset(
459 new TransformationMatrixT());
460 TransformationMatrixT *H_robot_turret =
461 estimate->camera_calibration->fixed_extrinsics.get();
462 H_robot_turret->data = MatrixToVector(TurretRobotTransformation());
463 }
464
465 estimate->camera_calibration->turret_extrinsics.reset(
466 new TransformationMatrixT());
467 estimate->camera_calibration->turret_extrinsics->data =
468 MatrixToVector(CameraTurretTransformation());
469
James Kuszmaulaa39d962022-03-06 14:54:28 -0800470 estimate->confidence = 1.0;
471
James Kuszmaul8c4f6592022-02-26 15:49:30 -0800472 auto builder = target_sender_.MakeBuilder();
473 builder.CheckOk(
474 builder.Send(TargetEstimate::Pack(*builder.fbb(), estimate.get())));
475 });
476 camera_test_event_loop_->OnRun([timer, this]() {
477 timer->Setup(camera_test_event_loop_->monotonic_now(),
478 std::chrono::milliseconds(50));
479 });
480 }
481
482 localizer_control_send_timer_ =
483 roborio_test_event_loop_->AddTimer([this]() {
484 auto builder = control_sender_.MakeBuilder();
485 auto control_builder = builder.MakeBuilder<LocalizerControl>();
486 control_builder.add_x(localizer_control_x_);
487 control_builder.add_y(localizer_control_y_);
488 control_builder.add_theta(localizer_control_theta_);
489 control_builder.add_theta_uncertainty(0.01);
490 control_builder.add_keep_current_theta(false);
491 builder.CheckOk(builder.Send(control_builder.Finish()));
492 });
493
James Kuszmaul29c59522022-02-12 16:44:26 -0800494 // Get things zeroed.
495 event_loop_factory_.RunFor(std::chrono::seconds(10));
496 CHECK(status_fetcher_.Fetch());
497 CHECK(status_fetcher_->zeroed());
James Kuszmaul8c4f6592022-02-26 15:49:30 -0800498
499 if (!FLAGS_output_folder.empty()) {
500 logger_event_loop_ =
501 event_loop_factory_.MakeEventLoop("logger", imu_node_);
502 logger_ = std::make_unique<aos::logger::Logger>(logger_event_loop_.get());
503 logger_->StartLoggingOnRun(FLAGS_output_folder);
504 }
505 }
506
507 void SendLocalizerControl(double x, double y, double theta) {
508 localizer_control_x_ = x;
509 localizer_control_y_ = y;
510 localizer_control_theta_ = theta;
511 localizer_control_send_timer_->Setup(
512 roborio_test_event_loop_->monotonic_now());
513 }
514 ::testing::AssertionResult IsNear(double expected, double actual,
515 double epsilon) {
516 if (std::abs(expected - actual) < epsilon) {
517 return ::testing::AssertionSuccess();
518 } else {
519 return ::testing::AssertionFailure()
520 << "Expected " << expected << " but got " << actual
521 << " with a max difference of " << epsilon
522 << " and an actual difference of " << std::abs(expected - actual);
523 }
524 }
525 ::testing::AssertionResult VerifyEstimatorAccurate(double eps) {
526 const Eigen::Matrix<double, 5, 1> true_state = drivetrain_plant_.state();
527 ::testing::AssertionResult result(true);
528 status_fetcher_.Fetch();
529 if (!(result = IsNear(status_fetcher_->model_based()->x(), true_state(0),
530 eps))) {
531 return result;
532 }
533 if (!(result = IsNear(status_fetcher_->model_based()->y(), true_state(1),
534 eps))) {
535 return result;
536 }
537 if (!(result = IsNear(status_fetcher_->model_based()->theta(),
538 true_state(2), eps))) {
539 return result;
540 }
541 return result;
James Kuszmaul29c59522022-02-12 16:44:26 -0800542 }
543
544 aos::FlatbufferDetachedBuffer<aos::Configuration> configuration_;
545 aos::SimulatedEventLoopFactory event_loop_factory_;
546 const aos::Node *const roborio_node_;
547 const aos::Node *const imu_node_;
James Kuszmaul8c4f6592022-02-26 15:49:30 -0800548 const aos::Node *const camera_node_;
James Kuszmaul29c59522022-02-12 16:44:26 -0800549 const control_loops::drivetrain::DrivetrainConfig<double> dt_config_;
550 std::unique_ptr<aos::EventLoop> localizer_event_loop_;
551 EventLoopLocalizer localizer_;
552
553 std::unique_ptr<aos::EventLoop> drivetrain_plant_event_loop_;
554 std::unique_ptr<aos::EventLoop> drivetrain_plant_imu_event_loop_;
555 control_loops::drivetrain::testing::DrivetrainSimulation drivetrain_plant_;
556
557 std::unique_ptr<aos::EventLoop> roborio_test_event_loop_;
558 std::unique_ptr<aos::EventLoop> imu_test_event_loop_;
James Kuszmaul8c4f6592022-02-26 15:49:30 -0800559 std::unique_ptr<aos::EventLoop> camera_test_event_loop_;
James Kuszmaul29c59522022-02-12 16:44:26 -0800560 std::unique_ptr<aos::EventLoop> logger_test_event_loop_;
561
562 aos::Sender<Output> output_sender_;
James Kuszmaul8c4f6592022-02-26 15:49:30 -0800563 aos::Sender<y2022::control_loops::superstructure::Status> turret_sender_;
564 aos::Sender<y2022::vision::TargetEstimate> target_sender_;
565 aos::Sender<LocalizerControl> control_sender_;
James Kuszmaul29c59522022-02-12 16:44:26 -0800566 aos::Fetcher<LocalizerOutput> output_fetcher_;
567 aos::Fetcher<LocalizerStatus> status_fetcher_;
568
569 Eigen::Vector2d output_voltages_ = Eigen::Vector2d::Zero();
James Kuszmaul8c4f6592022-02-26 15:49:30 -0800570
571 aos::TimerHandler *localizer_control_send_timer_;
572
573 bool send_targets_ = false;
574 double turret_position_ = 0.0;
575 double turret_velocity_ = 0.0;
576
577 double localizer_control_x_ = 0.0;
578 double localizer_control_y_ = 0.0;
579 double localizer_control_theta_ = 0.0;
580
581 std::unique_ptr<aos::EventLoop> logger_event_loop_;
582 std::unique_ptr<aos::logger::Logger> logger_;
James Kuszmaul29c59522022-02-12 16:44:26 -0800583};
584
585TEST_F(EventLoopLocalizerTest, Nominal) {
586 output_voltages_ << 1.0, 1.0;
587 event_loop_factory_.RunFor(std::chrono::seconds(2));
588 drivetrain_plant_.set_accel_sin_magnitude(0.01);
589 CHECK(output_fetcher_.Fetch());
590 CHECK(status_fetcher_.Fetch());
591 // The two can be different because they may've been sent at different times.
592 ASSERT_NEAR(output_fetcher_->x(), status_fetcher_->model_based()->x(), 1e-6);
593 ASSERT_NEAR(output_fetcher_->y(), status_fetcher_->model_based()->y(), 1e-6);
594 ASSERT_NEAR(output_fetcher_->theta(), status_fetcher_->model_based()->theta(),
595 1e-6);
596 ASSERT_LT(0.1, output_fetcher_->x());
597 ASSERT_NEAR(0.0, output_fetcher_->y(), 1e-10);
598 ASSERT_NEAR(0.0, output_fetcher_->theta(), 1e-10);
599 ASSERT_TRUE(status_fetcher_->has_model_based());
600 ASSERT_TRUE(status_fetcher_->model_based()->using_model());
601 ASSERT_LT(0.1, status_fetcher_->model_based()->accel_state()->velocity_x());
602 ASSERT_NEAR(0.0, status_fetcher_->model_based()->accel_state()->velocity_y(),
603 1e-10);
604 ASSERT_NEAR(
605 0.0, status_fetcher_->model_based()->model_state()->left_voltage_error(),
606 1e-1);
607 ASSERT_NEAR(
608 0.0, status_fetcher_->model_based()->model_state()->right_voltage_error(),
609 1e-1);
610}
611
612TEST_F(EventLoopLocalizerTest, Reverse) {
613 output_voltages_ << -4.0, -4.0;
614 drivetrain_plant_.set_accel_sin_magnitude(0.01);
615 event_loop_factory_.RunFor(std::chrono::seconds(2));
616 CHECK(output_fetcher_.Fetch());
617 CHECK(status_fetcher_.Fetch());
618 // The two can be different because they may've been sent at different times.
619 ASSERT_NEAR(output_fetcher_->x(), status_fetcher_->model_based()->x(), 1e-6);
620 ASSERT_NEAR(output_fetcher_->y(), status_fetcher_->model_based()->y(), 1e-6);
621 ASSERT_NEAR(output_fetcher_->theta(), status_fetcher_->model_based()->theta(),
622 1e-6);
623 ASSERT_GT(-0.1, output_fetcher_->x());
624 ASSERT_NEAR(0.0, output_fetcher_->y(), 1e-10);
625 ASSERT_NEAR(0.0, output_fetcher_->theta(), 1e-10);
626 ASSERT_TRUE(status_fetcher_->has_model_based());
627 ASSERT_TRUE(status_fetcher_->model_based()->using_model());
628 ASSERT_GT(-0.1, status_fetcher_->model_based()->accel_state()->velocity_x());
629 ASSERT_NEAR(0.0, status_fetcher_->model_based()->accel_state()->velocity_y(),
630 1e-10);
631 ASSERT_NEAR(
632 0.0, status_fetcher_->model_based()->model_state()->left_voltage_error(),
633 1e-1);
634 ASSERT_NEAR(
635 0.0, status_fetcher_->model_based()->model_state()->right_voltage_error(),
636 1e-1);
637}
638
639TEST_F(EventLoopLocalizerTest, SpinInPlace) {
640 output_voltages_ << 4.0, -4.0;
641 event_loop_factory_.RunFor(std::chrono::seconds(2));
642 CHECK(output_fetcher_.Fetch());
643 CHECK(status_fetcher_.Fetch());
644 // The two can be different because they may've been sent at different times.
645 ASSERT_NEAR(output_fetcher_->x(), status_fetcher_->model_based()->x(), 1e-6);
646 ASSERT_NEAR(output_fetcher_->y(), status_fetcher_->model_based()->y(), 1e-6);
647 ASSERT_NEAR(output_fetcher_->theta(), status_fetcher_->model_based()->theta(),
648 1e-1);
649 ASSERT_NEAR(0.0, output_fetcher_->x(), 1e-10);
650 ASSERT_NEAR(0.0, output_fetcher_->y(), 1e-10);
651 ASSERT_LT(0.1, std::abs(output_fetcher_->theta()));
652 ASSERT_TRUE(status_fetcher_->has_model_based());
653 ASSERT_TRUE(status_fetcher_->model_based()->using_model());
654 ASSERT_NEAR(0.0, status_fetcher_->model_based()->accel_state()->velocity_x(),
655 1e-10);
656 ASSERT_NEAR(0.0, status_fetcher_->model_based()->accel_state()->velocity_y(),
657 1e-10);
658 ASSERT_NEAR(-status_fetcher_->model_based()->model_state()->left_velocity(),
659 status_fetcher_->model_based()->model_state()->right_velocity(),
660 1e-3);
661 ASSERT_NEAR(
662 0.0, status_fetcher_->model_based()->model_state()->left_voltage_error(),
663 1e-1);
664 ASSERT_NEAR(
665 0.0, status_fetcher_->model_based()->model_state()->right_voltage_error(),
666 1e-1);
667 ASSERT_NEAR(0.0, status_fetcher_->model_based()->residual(), 1e-3);
668}
669
670TEST_F(EventLoopLocalizerTest, Curve) {
671 output_voltages_ << 2.0, 4.0;
672 event_loop_factory_.RunFor(std::chrono::seconds(2));
673 CHECK(output_fetcher_.Fetch());
674 CHECK(status_fetcher_.Fetch());
675 // The two can be different because they may've been sent at different times.
676 ASSERT_NEAR(output_fetcher_->x(), status_fetcher_->model_based()->x(), 1e-2);
677 ASSERT_NEAR(output_fetcher_->y(), status_fetcher_->model_based()->y(), 1e-2);
678 ASSERT_NEAR(output_fetcher_->theta(), status_fetcher_->model_based()->theta(),
679 1e-1);
680 ASSERT_LT(0.1, output_fetcher_->x());
681 ASSERT_LT(0.1, output_fetcher_->y());
682 ASSERT_LT(0.1, std::abs(output_fetcher_->theta()));
683 ASSERT_TRUE(status_fetcher_->has_model_based());
684 ASSERT_TRUE(status_fetcher_->model_based()->using_model());
685 ASSERT_LT(0.0, status_fetcher_->model_based()->accel_state()->velocity_x());
686 ASSERT_LT(0.0, status_fetcher_->model_based()->accel_state()->velocity_y());
687 ASSERT_NEAR(
688 0.0, status_fetcher_->model_based()->model_state()->left_voltage_error(),
689 1e-1);
690 ASSERT_NEAR(
691 0.0, status_fetcher_->model_based()->model_state()->right_voltage_error(),
692 1e-1);
693 ASSERT_NEAR(0.0, status_fetcher_->model_based()->residual(), 1e-1)
694 << aos::FlatbufferToJson(status_fetcher_.get(), {.multi_line = true});
695}
696
697// Tests that small amounts of voltage error are handled by the model-based
698// half of the localizer.
699TEST_F(EventLoopLocalizerTest, VoltageError) {
700 output_voltages_ << 0.0, 0.0;
701 drivetrain_plant_.set_left_voltage_offset(2.0);
702 drivetrain_plant_.set_right_voltage_offset(2.0);
703 drivetrain_plant_.set_accel_sin_magnitude(0.01);
704
705 event_loop_factory_.RunFor(std::chrono::seconds(2));
706 CHECK(output_fetcher_.Fetch());
707 CHECK(status_fetcher_.Fetch());
708 // Should still be using the model, but have a non-trivial residual.
709 ASSERT_TRUE(status_fetcher_->model_based()->using_model());
James Kuszmaul5ed29dd2022-02-13 18:32:06 -0800710 ASSERT_LT(0.02, status_fetcher_->model_based()->residual())
James Kuszmaul29c59522022-02-12 16:44:26 -0800711 << aos::FlatbufferToJson(status_fetcher_.get(), {.multi_line = true});
712
713 // Afer running for a while, voltage error terms should converge and result in
714 // low residuals.
715 event_loop_factory_.RunFor(std::chrono::seconds(10));
716 CHECK(output_fetcher_.Fetch());
717 CHECK(status_fetcher_.Fetch());
718 ASSERT_TRUE(status_fetcher_->model_based()->using_model());
719 ASSERT_NEAR(
720 2.0, status_fetcher_->model_based()->model_state()->left_voltage_error(),
721 0.1)
722 << aos::FlatbufferToJson(status_fetcher_.get(), {.multi_line = true});
723 ASSERT_NEAR(
724 2.0, status_fetcher_->model_based()->model_state()->right_voltage_error(),
725 0.1)
726 << aos::FlatbufferToJson(status_fetcher_.get(), {.multi_line = true});
James Kuszmaul5ed29dd2022-02-13 18:32:06 -0800727 ASSERT_GT(0.02, status_fetcher_->model_based()->residual())
James Kuszmaul29c59522022-02-12 16:44:26 -0800728 << aos::FlatbufferToJson(status_fetcher_.get(), {.multi_line = true});
729}
730
731// Tests that large amounts of voltage error force us into the
732// acceleration-based localizer.
733TEST_F(EventLoopLocalizerTest, HighVoltageError) {
734 output_voltages_ << 0.0, 0.0;
735 drivetrain_plant_.set_left_voltage_offset(200.0);
736 drivetrain_plant_.set_right_voltage_offset(200.0);
737 drivetrain_plant_.set_accel_sin_magnitude(0.01);
738
739 event_loop_factory_.RunFor(std::chrono::seconds(2));
740 CHECK(output_fetcher_.Fetch());
741 CHECK(status_fetcher_.Fetch());
742 // Should still be using the model, but have a non-trivial residual.
743 ASSERT_FALSE(status_fetcher_->model_based()->using_model());
744 ASSERT_LT(0.1, status_fetcher_->model_based()->residual())
745 << aos::FlatbufferToJson(status_fetcher_.get(), {.multi_line = true});
Milind Upadhyayd67e9cf2022-03-13 13:56:57 -0700746 ASSERT_NEAR(drivetrain_plant_.state()(0), status_fetcher_->model_based()->x(),
747 1.0);
748 ASSERT_NEAR(drivetrain_plant_.state()(1), status_fetcher_->model_based()->y(),
749 1e-6);
James Kuszmaul29c59522022-02-12 16:44:26 -0800750}
751
James Kuszmaul8c4f6592022-02-26 15:49:30 -0800752// Tests that image corrections in the nominal case (no errors) causes no
753// issues.
754TEST_F(EventLoopLocalizerTest, NominalImageCorrections) {
755 output_voltages_ << 3.0, 2.0;
756 drivetrain_plant_.set_accel_sin_magnitude(0.01);
757 send_targets_ = true;
758
759 event_loop_factory_.RunFor(std::chrono::seconds(4));
760 CHECK(status_fetcher_.Fetch());
761 ASSERT_TRUE(status_fetcher_->model_based()->using_model());
762 EXPECT_TRUE(VerifyEstimatorAccurate(1e-1));
763 ASSERT_TRUE(status_fetcher_->model_based()->has_statistics());
764 ASSERT_LT(10,
765 status_fetcher_->model_based()->statistics()->total_candidates());
766 ASSERT_EQ(status_fetcher_->model_based()->statistics()->total_candidates(),
767 status_fetcher_->model_based()->statistics()->total_accepted());
768}
769
770// Tests that image corrections when there is an error at the start results
771// in us actually getting corrected over time.
772TEST_F(EventLoopLocalizerTest, ImageCorrections) {
773 output_voltages_ << 0.0, 0.0;
774 drivetrain_plant_.mutable_state()->x() = 2.0;
775 drivetrain_plant_.mutable_state()->y() = 2.0;
776 SendLocalizerControl(5.0, 3.0, 0.0);
777 event_loop_factory_.RunFor(std::chrono::seconds(4));
778 CHECK(output_fetcher_.Fetch());
779 ASSERT_NEAR(5.0, output_fetcher_->x(), 1e-5);
780 ASSERT_NEAR(3.0, output_fetcher_->y(), 1e-5);
781 ASSERT_NEAR(0.0, output_fetcher_->theta(), 1e-5);
782
783 send_targets_ = true;
784
785 event_loop_factory_.RunFor(std::chrono::seconds(4));
786 CHECK(status_fetcher_.Fetch());
787 ASSERT_TRUE(status_fetcher_->model_based()->using_model());
James Kuszmaul5a5a7832022-03-16 23:15:08 -0700788 EXPECT_TRUE(VerifyEstimatorAccurate(5e-1));
James Kuszmaul8c4f6592022-02-26 15:49:30 -0800789 ASSERT_TRUE(status_fetcher_->model_based()->has_statistics());
790 ASSERT_LT(10,
791 status_fetcher_->model_based()->statistics()->total_candidates());
792 ASSERT_EQ(status_fetcher_->model_based()->statistics()->total_candidates(),
793 status_fetcher_->model_based()->statistics()->total_accepted());
794}
795
James Kuszmaulf3ef9e12022-03-05 17:13:00 -0800796// Tests that image corrections are ignored when the turret moves too fast.
797TEST_F(EventLoopLocalizerTest, ImageCorrectionsTurretTooFast) {
798 output_voltages_ << 0.0, 0.0;
799 drivetrain_plant_.mutable_state()->x() = 2.0;
800 drivetrain_plant_.mutable_state()->y() = 2.0;
801 SendLocalizerControl(5.0, 3.0, 0.0);
802 turret_velocity_ = 10.0;
803 event_loop_factory_.RunFor(std::chrono::seconds(4));
804 CHECK(output_fetcher_.Fetch());
805 ASSERT_NEAR(5.0, output_fetcher_->x(), 1e-5);
806 ASSERT_NEAR(3.0, output_fetcher_->y(), 1e-5);
807 ASSERT_NEAR(0.0, output_fetcher_->theta(), 1e-5);
808
809 send_targets_ = true;
810
811 event_loop_factory_.RunFor(std::chrono::seconds(4));
812 CHECK(status_fetcher_.Fetch());
813 CHECK(output_fetcher_.Fetch());
814 ASSERT_NEAR(5.0, output_fetcher_->x(), 1e-5);
815 ASSERT_NEAR(3.0, output_fetcher_->y(), 1e-5);
816 ASSERT_NEAR(0.0, output_fetcher_->theta(), 1e-5);
817 ASSERT_TRUE(status_fetcher_->model_based()->has_statistics());
818 ASSERT_LT(10,
819 status_fetcher_->model_based()->statistics()->total_candidates());
820 ASSERT_EQ(0, status_fetcher_->model_based()->statistics()->total_accepted());
821 ASSERT_EQ(status_fetcher_->model_based()->statistics()->total_candidates(),
822 status_fetcher_->model_based()
823 ->statistics()
824 ->rejection_reason_count()
825 ->Get(static_cast<int>(RejectionReason::TURRET_TOO_FAST)));
826 // We expect one more rejection to occur due to the time it takes all the
827 // information to propagate.
828 const int rejected_count =
829 status_fetcher_->model_based()->statistics()->total_candidates() + 1;
830 // Check that when we go back to being still we do successfully converge.
831 turret_velocity_ = 0.0;
832 turret_position_ = 1.0;
833 event_loop_factory_.RunFor(std::chrono::seconds(4));
834 CHECK(status_fetcher_.Fetch());
835 ASSERT_TRUE(status_fetcher_->model_based()->using_model());
James Kuszmaul5a5a7832022-03-16 23:15:08 -0700836 EXPECT_TRUE(VerifyEstimatorAccurate(5e-1));
James Kuszmaulf3ef9e12022-03-05 17:13:00 -0800837 ASSERT_TRUE(status_fetcher_->model_based()->has_statistics());
838 ASSERT_EQ(status_fetcher_->model_based()->statistics()->total_candidates(),
839 rejected_count +
840 status_fetcher_->model_based()->statistics()->total_accepted());
841}
842
James Kuszmaul8c4f6592022-02-26 15:49:30 -0800843// Tests that image corrections when we are in accel mode works.
844TEST_F(EventLoopLocalizerTest, ImageCorrectionsInAccel) {
845 output_voltages_ << 0.0, 0.0;
846 drivetrain_plant_.set_left_voltage_offset(200.0);
847 drivetrain_plant_.set_right_voltage_offset(200.0);
848 drivetrain_plant_.set_accel_sin_magnitude(0.01);
849 drivetrain_plant_.mutable_state()->x() = 2.0;
850 drivetrain_plant_.mutable_state()->y() = 2.0;
James Kuszmaul2b2f8772022-03-12 15:25:35 -0800851 SendLocalizerControl(6.0, 3.0, 0.0);
James Kuszmaul8c4f6592022-02-26 15:49:30 -0800852 event_loop_factory_.RunFor(std::chrono::seconds(1));
853 CHECK(output_fetcher_.Fetch());
854 CHECK(status_fetcher_.Fetch());
855 ASSERT_FALSE(status_fetcher_->model_based()->using_model());
James Kuszmaul2b2f8772022-03-12 15:25:35 -0800856 EXPECT_FALSE(VerifyEstimatorAccurate(3.0));
James Kuszmaul8c4f6592022-02-26 15:49:30 -0800857
858 send_targets_ = true;
859
860 event_loop_factory_.RunFor(std::chrono::seconds(4));
861 CHECK(status_fetcher_.Fetch());
862 ASSERT_FALSE(status_fetcher_->model_based()->using_model());
James Kuszmaul2b2f8772022-03-12 15:25:35 -0800863 EXPECT_TRUE(VerifyEstimatorAccurate(3.0));
James Kuszmaul8c4f6592022-02-26 15:49:30 -0800864 // y should be noticeably more accurate than x, since we are just driving
865 // straight.
Milind Upadhyayd67e9cf2022-03-13 13:56:57 -0700866 ASSERT_NEAR(drivetrain_plant_.state()(1), status_fetcher_->model_based()->y(),
867 0.1);
James Kuszmaul8c4f6592022-02-26 15:49:30 -0800868 ASSERT_TRUE(status_fetcher_->model_based()->has_statistics());
869 ASSERT_LT(10,
870 status_fetcher_->model_based()->statistics()->total_candidates());
871 ASSERT_EQ(status_fetcher_->model_based()->statistics()->total_candidates(),
872 status_fetcher_->model_based()->statistics()->total_accepted());
873}
874
Milind Upadhyayd67e9cf2022-03-13 13:56:57 -0700875TEST_F(EventLoopLocalizerTest, LedOutputs) {
876 send_targets_ = true;
877
878 event_loop_factory_.RunFor(std::chrono::milliseconds(10));
879 CHECK(output_fetcher_.Fetch());
880 EXPECT_EQ(output_fetcher_->led_outputs()->size(),
881 ModelBasedLocalizer::kNumPis);
882 for (LedOutput led_output : *output_fetcher_->led_outputs()) {
883 EXPECT_EQ(led_output, LedOutput::ON);
884 }
885}
886
887} // namespace frc971::controls::testing