blob: 3c11dff736d0e361685eb58c9eda94a1af1529f4 [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"
5#include "gtest/gtest.h"
6#include "frc971/control_loops/drivetrain/drivetrain_test_lib.h"
James Kuszmaul8c4f6592022-02-26 15:49:30 -08007#include "frc971/control_loops/pose.h"
8#include "y2022/vision/target_estimate_generated.h"
9#include "y2022/control_loops/superstructure/superstructure_status_generated.h"
10#include "y2022/control_loops/drivetrain/drivetrain_base.h"
11
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
21using frc971::vision::calibration::CameraCalibrationT;
22using frc971::vision::calibration::TransformationMatrixT;
23using frc971::control_loops::drivetrain::DrivetrainConfig;
24using frc971::control_loops::drivetrain::LocalizerControl;
25using frc971::control_loops::Pose;
26using 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}
James Kuszmaul29c59522022-02-12 16:44:26 -080085}
86
87class LocalizerTest : public ::testing::Test {
88 protected:
89 LocalizerTest()
90 : dt_config_(
James Kuszmaul8c4f6592022-02-26 15:49:30 -080091 GetTest2022DrivetrainConfig()),
James Kuszmaul5ed29dd2022-02-13 18:32:06 -080092 localizer_(dt_config_) {
93 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_;
105
106};
107
108TEST_F(LocalizerTest, AccelIntegrationTest) {
109 AccelState state;
110 state.setZero();
111 AccelInput input;
112 input.setZero();
113
114 EXPECT_EQ(0.0, CallDiffAccel(state, input).norm());
115 // Non-zero x/y/theta states should still result in a zero derivative.
116 state(kX) = 1.0;
117 state(kY) = 1.0;
118 state(kTheta) = 1.0;
119 EXPECT_EQ(0.0, CallDiffAccel(state, input).norm());
120
121 state.setZero();
122 state(kVelocityX) = 1.0;
123 state(kVelocityY) = 2.0;
124 EXPECT_EQ((AccelState() << 1.0, 2.0, 0.0, 0.0, 0.0).finished(),
125 CallDiffAccel(state, input));
126 // Derivatives should be independent of theta.
127 state(kTheta) = M_PI / 2.0;
128 EXPECT_EQ((AccelState() << 1.0, 2.0, 0.0, 0.0, 0.0).finished(),
129 CallDiffAccel(state, input));
130
131 state.setZero();
132 input(kAccelX) = 1.0;
133 input(kAccelY) = 2.0;
134 input(kThetaRate) = 3.0;
135 EXPECT_EQ((AccelState() << 0.0, 0.0, 3.0, 1.0, 2.0).finished(),
136 CallDiffAccel(state, input));
137 state(kTheta) = M_PI / 2.0;
138 EXPECT_EQ((AccelState() << 0.0, 0.0, 3.0, 1.0, 2.0).finished(),
139 CallDiffAccel(state, input));
140}
141
142TEST_F(LocalizerTest, ModelIntegrationTest) {
143 ModelState state;
144 state.setZero();
145 ModelInput input;
146 input.setZero();
147 ModelState diff;
148
149 EXPECT_EQ(0.0, CallDiffModel(state, input).norm());
150 // Non-zero x/y/theta/encoder states should still result in a zero derivative.
151 state(kX) = 1.0;
152 state(kY) = 1.0;
153 state(kTheta) = 1.0;
154 state(kLeftEncoder) = 1.0;
155 state(kRightEncoder) = 1.0;
156 EXPECT_EQ(0.0, CallDiffModel(state, input).norm());
157
158 state.setZero();
159 state(kLeftVelocity) = 1.0;
160 state(kRightVelocity) = 1.0;
161 diff = CallDiffModel(state, input);
162 const ModelState mask_velocities =
163 (ModelState() << 1.0, 1.0, 1.0, 1.0, 0.0, 1.0, 1.0, 0.0, 1.0).finished();
164 EXPECT_EQ(
165 (ModelState() << 1.0, 0.0, 0.0, 1.0, 0.0, 0.0, 1.0, 0.0, 0.0).finished(),
166 diff.cwiseProduct(mask_velocities));
167 EXPECT_EQ(diff(kLeftVelocity), diff(kRightVelocity));
168 EXPECT_GT(0.0, diff(kLeftVelocity));
169 state(kTheta) = M_PI / 2.0;
170 diff = CallDiffModel(state, input);
171 EXPECT_NEAR(0.0,
172 ((ModelState() << 0.0, 1.0, 0.0, 1.0, 0.0, 0.0, 1.0, 0.0, 0.0)
173 .finished() -
174 diff.cwiseProduct(mask_velocities))
175 .norm(),
176 1e-12);
177 EXPECT_EQ(diff(kLeftVelocity), diff(kRightVelocity));
178 EXPECT_GT(0.0, diff(kLeftVelocity));
179
180 state.setZero();
181 state(kLeftVelocity) = -1.0;
182 state(kRightVelocity) = 1.0;
183 diff = CallDiffModel(state, input);
184 EXPECT_EQ((ModelState() << 0.0, 0.0, 1.0 / dt_config_.robot_radius, -1.0, 0.0,
185 0.0, 1.0, 0.0, 0.0)
186 .finished(),
187 diff.cwiseProduct(mask_velocities));
188 EXPECT_EQ(-diff(kLeftVelocity), diff(kRightVelocity));
189 EXPECT_LT(0.0, diff(kLeftVelocity));
190
191 state.setZero();
192 input(kLeftVoltage) = 5.0;
193 input(kRightVoltage) = 6.0;
194 diff = CallDiffModel(state, input);
195 EXPECT_EQ(0, diff(kX));
196 EXPECT_EQ(0, diff(kY));
197 EXPECT_EQ(0, diff(kTheta));
198 EXPECT_EQ(0, diff(kLeftEncoder));
199 EXPECT_EQ(0, diff(kRightEncoder));
200 EXPECT_EQ(0, diff(kLeftVoltageError));
201 EXPECT_EQ(0, diff(kRightVoltageError));
202 EXPECT_LT(0, diff(kLeftVelocity));
203 EXPECT_LT(0, diff(kRightVelocity));
204 EXPECT_LT(diff(kLeftVelocity), diff(kRightVelocity));
205
206 state.setZero();
207 state(kLeftVoltageError) = -1.0;
208 state(kRightVoltageError) = -2.0;
209 input(kLeftVoltage) = 1.0;
210 input(kRightVoltage) = 2.0;
211 EXPECT_EQ(ModelState::Zero(), CallDiffModel(state, input));
212}
213
214// Test that the HandleReset does indeed reset the state of the localizer.
215TEST_F(LocalizerTest, LocalizerReset) {
216 aos::monotonic_clock::time_point t = aos::monotonic_clock::epoch();
217 localizer_.HandleReset(t, {1.0, 2.0, 3.0});
218 EXPECT_EQ((Eigen::Vector3d{1.0, 2.0, 3.0}), localizer_.xytheta());
219 localizer_.HandleReset(t, {4.0, 5.0, 6.0});
220 EXPECT_EQ((Eigen::Vector3d{4.0, 5.0, 6.0}), localizer_.xytheta());
221}
222
223// Test that if we are moving only by accelerometer readings (and just assuming
224// zero voltage/encoders) that we initially don't believe it but then latch into
225// following the accelerometer.
226// Note: this test is somewhat sensitive to the exact tuning values used for the
227// filter.
228TEST_F(LocalizerTest, AccelOnly) {
229 const aos::monotonic_clock::time_point start = aos::monotonic_clock::epoch();
230 const std::chrono::microseconds kDt{500};
231 aos::monotonic_clock::time_point t = start - std::chrono::milliseconds(1000);
232 Eigen::Vector3d gyro{0.0, 0.0, 0.0};
233 const Eigen::Vector2d encoders{0.0, 0.0};
234 const Eigen::Vector2d voltages{0.0, 0.0};
James Kuszmaul5ed29dd2022-02-13 18:32:06 -0800235 Eigen::Vector3d accel{5.0, 2.0, 9.80665};
James Kuszmaul8c4f6592022-02-26 15:49:30 -0800236 Eigen::Vector3d accel_gs = 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]() {
424 timer->Setup(roborio_test_event_loop_->monotonic_now(),
425 std::chrono::milliseconds(5));
426 });
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
471 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});
745 ASSERT_NEAR(drivetrain_plant_.state()(0),
746 status_fetcher_->model_based()->x(), 1.0);
747 ASSERT_NEAR(drivetrain_plant_.state()(1),
748 status_fetcher_->model_based()->y(), 1e-6);
749}
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;
850 SendLocalizerControl(5.0, 3.0, 0.0);
851 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());
855 EXPECT_FALSE(VerifyEstimatorAccurate(0.5));
856
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());
862 EXPECT_TRUE(VerifyEstimatorAccurate(0.5));
863 // y should be noticeably more accurate than x, since we are just driving
864 // straight.
865 ASSERT_NEAR(drivetrain_plant_.state()(1), status_fetcher_->model_based()->y(), 0.1);
866 ASSERT_TRUE(status_fetcher_->model_based()->has_statistics());
867 ASSERT_LT(10,
868 status_fetcher_->model_based()->statistics()->total_candidates());
869 ASSERT_EQ(status_fetcher_->model_based()->statistics()->total_candidates(),
870 status_fetcher_->model_based()->statistics()->total_accepted());
871}
872
James Kuszmaul29c59522022-02-12 16:44:26 -0800873} // namespace frc91::controls::testing