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