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