blob: 77af69b697cbc1c0edc4a347d83ecd9edd9782bf [file] [log] [blame]
James Kuszmaul04a343c2023-02-20 16:38:22 -08001#include "y2023/localizer/localizer.h"
2
3#include "aos/events/logging/log_writer.h"
4#include "aos/events/simulated_event_loop.h"
5#include "frc971/control_loops/drivetrain/drivetrain_test_lib.h"
6#include "frc971/control_loops/drivetrain/localizer_generated.h"
7#include "frc971/control_loops/pose.h"
8#include "frc971/vision/target_map_generated.h"
9#include "gtest/gtest.h"
10#include "y2023/constants/simulated_constants_sender.h"
11#include "y2023/control_loops/drivetrain/drivetrain_base.h"
12#include "y2023/localizer/status_generated.h"
13
14DEFINE_string(output_folder, "",
15 "If set, logs all channels to the provided logfile.");
16DECLARE_bool(die_on_malloc);
17
18namespace y2023::localizer::testing {
19
20using frc971::control_loops::drivetrain::Output;
21
22class LocalizerTest : public ::testing::Test {
23 protected:
24 static constexpr uint64_t kTargetId = 1;
25 LocalizerTest()
26 : configuration_(aos::configuration::ReadConfig("y2023/aos_config.json")),
27 event_loop_factory_(&configuration_.message()),
28 roborio_node_([this]() {
29 // Get the constants sent before anything else happens.
30 // It has nothing to do with the roborio node.
31 SendSimulationConstants(&event_loop_factory_, 7971,
32 "y2023/constants/test_constants.json");
33 return aos::configuration::GetNode(&configuration_.message(),
34 "roborio");
35 }()),
36 imu_node_(
37 aos::configuration::GetNode(&configuration_.message(), "imu")),
38 camera_node_(
39 aos::configuration::GetNode(&configuration_.message(), "pi1")),
40 dt_config_(frc971::control_loops::drivetrain::testing::
41 GetTestDrivetrainConfig()),
42 localizer_event_loop_(
43 event_loop_factory_.MakeEventLoop("localizer", imu_node_)),
44 localizer_(localizer_event_loop_.get(), dt_config_),
45 drivetrain_plant_event_loop_(event_loop_factory_.MakeEventLoop(
46 "drivetrain_plant", roborio_node_)),
47 drivetrain_plant_imu_event_loop_(
48 event_loop_factory_.MakeEventLoop("drivetrain_plant", imu_node_)),
49 drivetrain_plant_(drivetrain_plant_event_loop_.get(),
50 drivetrain_plant_imu_event_loop_.get(), dt_config_,
51 std::chrono::microseconds(500)),
52 roborio_test_event_loop_(
53 event_loop_factory_.MakeEventLoop("test", roborio_node_)),
54 imu_test_event_loop_(
55 event_loop_factory_.MakeEventLoop("test", imu_node_)),
56 camera_test_event_loop_(
57 event_loop_factory_.MakeEventLoop("test", camera_node_)),
58 logger_test_event_loop_(
59 event_loop_factory_.GetNodeEventLoopFactory("logger")
60 ->MakeEventLoop("test")),
61 constants_fetcher_(imu_test_event_loop_.get()),
62 output_sender_(
63 roborio_test_event_loop_->MakeSender<Output>("/drivetrain")),
64 target_sender_(
65 camera_test_event_loop_->MakeSender<frc971::vision::TargetMap>(
66 "/camera")),
67 control_sender_(roborio_test_event_loop_->MakeSender<
68 frc971::control_loops::drivetrain::LocalizerControl>(
69 "/drivetrain")),
70 output_fetcher_(
71 roborio_test_event_loop_
72 ->MakeFetcher<frc971::controls::LocalizerOutput>("/localizer")),
73 status_fetcher_(
74 imu_test_event_loop_->MakeFetcher<Status>("/localizer")) {
75 FLAGS_die_on_malloc = true;
76 {
77 aos::TimerHandler *timer = roborio_test_event_loop_->AddTimer([this]() {
78 {
79 auto builder = output_sender_.MakeBuilder();
80 auto output_builder = builder.MakeBuilder<Output>();
81 output_builder.add_left_voltage(output_voltages_(0));
82 output_builder.add_right_voltage(output_voltages_(1));
83 builder.CheckOk(builder.Send(output_builder.Finish()));
84 }
85 });
86 roborio_test_event_loop_->OnRun([timer, this]() {
87 timer->Setup(roborio_test_event_loop_->monotonic_now(),
88 std::chrono::milliseconds(5));
89 });
90 }
91 {
92 // Sanity check that the test calibration files look like what we
93 // expect.
94 CHECK_EQ("pi1", constants_fetcher_.constants()
95 .cameras()
96 ->Get(0)
97 ->calibration()
98 ->node_name()
99 ->string_view());
100 const Eigen::Matrix<double, 4, 4> H_robot_camera =
101 frc971::control_loops::drivetrain::FlatbufferToTransformationMatrix(
102 *constants_fetcher_.constants()
103 .cameras()
104 ->Get(0)
105 ->calibration()
106 ->fixed_extrinsics());
107
108 CHECK_EQ(kTargetId, constants_fetcher_.constants()
109 .target_map()
110 ->target_poses()
111 ->Get(0)
112 ->id());
113 const Eigen::Matrix<double, 4, 4> H_field_target = PoseToTransform(
114 constants_fetcher_.constants().target_map()->target_poses()->Get(0));
115 // For reference, the camera should pointed straight forwards on the
116 // robot, offset by 1 meter.
117 aos::TimerHandler *timer = camera_test_event_loop_->AddTimer(
118 [this, H_robot_camera, H_field_target]() {
119 if (!send_targets_) {
120 return;
121 }
122 const frc971::control_loops::Pose robot_pose(
123 {drivetrain_plant_.GetPosition().x(),
124 drivetrain_plant_.GetPosition().y(), 0.0},
125 drivetrain_plant_.state()(2, 0));
126
127 const Eigen::Matrix<double, 4, 4> H_field_camera =
128 robot_pose.AsTransformationMatrix() * H_robot_camera;
129 const Eigen::Matrix<double, 4, 4> H_camera_target =
130 H_field_camera.inverse() * H_field_target;
131
132 const Eigen::Quaterniond quat(H_camera_target.block<3, 3>(0, 0));
133 const Eigen::Vector3d translation(
134 H_camera_target.block<3, 1>(0, 3));
135
136 auto builder = target_sender_.MakeBuilder();
137 frc971::vision::Quaternion::Builder quat_builder(*builder.fbb());
138 quat_builder.add_w(quat.w());
139 quat_builder.add_x(quat.x());
140 quat_builder.add_y(quat.y());
141 quat_builder.add_z(quat.z());
142 auto quat_offset = quat_builder.Finish();
143 frc971::vision::Position::Builder position_builder(*builder.fbb());
144 position_builder.add_x(translation.x());
145 position_builder.add_y(translation.y());
146 position_builder.add_z(translation.z());
147 auto position_offset = position_builder.Finish();
148
149 frc971::vision::TargetPoseFbs::Builder target_builder(
150 *builder.fbb());
151 target_builder.add_id(send_target_id_);
152 target_builder.add_position(position_offset);
153 target_builder.add_orientation(quat_offset);
154 auto target_offset = target_builder.Finish();
155
156 auto targets_offset = builder.fbb()->CreateVector({target_offset});
157 frc971::vision::TargetMap::Builder map_builder(*builder.fbb());
158 map_builder.add_target_poses(targets_offset);
159 map_builder.add_monotonic_timestamp_ns(
160 std::chrono::duration_cast<std::chrono::nanoseconds>(
161 camera_test_event_loop_->monotonic_now().time_since_epoch())
162 .count());
163
164 builder.CheckOk(builder.Send(map_builder.Finish()));
165 });
166 camera_test_event_loop_->OnRun([timer, this]() {
167 timer->Setup(camera_test_event_loop_->monotonic_now(),
168 std::chrono::milliseconds(50));
169 });
170 }
171
172 localizer_control_send_timer_ =
173 roborio_test_event_loop_->AddTimer([this]() {
174 auto builder = control_sender_.MakeBuilder();
175 auto control_builder = builder.MakeBuilder<
176 frc971::control_loops::drivetrain::LocalizerControl>();
177 control_builder.add_x(localizer_control_x_);
178 control_builder.add_y(localizer_control_y_);
179 control_builder.add_theta(localizer_control_theta_);
180 control_builder.add_theta_uncertainty(0.01);
181 control_builder.add_keep_current_theta(false);
182 builder.CheckOk(builder.Send(control_builder.Finish()));
183 });
184
185 // Get things zeroed.
186 event_loop_factory_.RunFor(std::chrono::seconds(10));
187 CHECK(status_fetcher_.Fetch());
188 CHECK(status_fetcher_->imu()->zeroed());
189
190 if (!FLAGS_output_folder.empty()) {
191 logger_event_loop_ =
192 event_loop_factory_.MakeEventLoop("logger", imu_node_);
193 logger_ = std::make_unique<aos::logger::Logger>(logger_event_loop_.get());
194 logger_->StartLoggingOnRun(FLAGS_output_folder);
195 }
196 }
197
198 void SendLocalizerControl(double x, double y, double theta) {
199 localizer_control_x_ = x;
200 localizer_control_y_ = y;
201 localizer_control_theta_ = theta;
202 localizer_control_send_timer_->Setup(
203 roborio_test_event_loop_->monotonic_now());
204 }
205 ::testing::AssertionResult IsNear(double expected, double actual,
206 double epsilon) {
207 if (std::abs(expected - actual) < epsilon) {
208 return ::testing::AssertionSuccess();
209 } else {
210 return ::testing::AssertionFailure()
211 << "Expected " << expected << " but got " << actual
212 << " with a max difference of " << epsilon
213 << " and an actual difference of " << std::abs(expected - actual);
214 }
215 }
216 ::testing::AssertionResult VerifyEstimatorAccurate(double eps) {
217 const Eigen::Matrix<double, 5, 1> true_state = drivetrain_plant_.state();
218 ::testing::AssertionResult result(true);
219 status_fetcher_.Fetch();
220 if (!(result = IsNear(status_fetcher_->state()->x(), true_state(0), eps))) {
221 return result;
222 }
223 if (!(result = IsNear(status_fetcher_->state()->y(), true_state(1), eps))) {
224 return result;
225 }
226 if (!(result =
227 IsNear(status_fetcher_->state()->theta(), true_state(2), eps))) {
228 return result;
229 }
230 return result;
231 }
232
233 aos::FlatbufferDetachedBuffer<aos::Configuration> configuration_;
234 aos::SimulatedEventLoopFactory event_loop_factory_;
235 const aos::Node *const roborio_node_;
236 const aos::Node *const imu_node_;
237 const aos::Node *const camera_node_;
238 const frc971::control_loops::drivetrain::DrivetrainConfig<double> dt_config_;
239 std::unique_ptr<aos::EventLoop> localizer_event_loop_;
240 Localizer localizer_;
241
242 std::unique_ptr<aos::EventLoop> drivetrain_plant_event_loop_;
243 std::unique_ptr<aos::EventLoop> drivetrain_plant_imu_event_loop_;
244 frc971::control_loops::drivetrain::testing::DrivetrainSimulation
245 drivetrain_plant_;
246
247 std::unique_ptr<aos::EventLoop> roborio_test_event_loop_;
248 std::unique_ptr<aos::EventLoop> imu_test_event_loop_;
249 std::unique_ptr<aos::EventLoop> camera_test_event_loop_;
250 std::unique_ptr<aos::EventLoop> logger_test_event_loop_;
251
252 frc971::constants::ConstantsFetcher<Constants> constants_fetcher_;
253
254 aos::Sender<Output> output_sender_;
255 aos::Sender<frc971::vision::TargetMap> target_sender_;
256 aos::Sender<frc971::control_loops::drivetrain::LocalizerControl>
257 control_sender_;
258 aos::Fetcher<frc971::controls::LocalizerOutput> output_fetcher_;
259 aos::Fetcher<Status> status_fetcher_;
260
261 Eigen::Vector2d output_voltages_ = Eigen::Vector2d::Zero();
262
263 aos::TimerHandler *localizer_control_send_timer_;
264
265 bool send_targets_ = false;
266
267 double localizer_control_x_ = 0.0;
268 double localizer_control_y_ = 0.0;
269 double localizer_control_theta_ = 0.0;
270
271 std::unique_ptr<aos::EventLoop> logger_event_loop_;
272 std::unique_ptr<aos::logger::Logger> logger_;
273
274 uint64_t send_target_id_ = kTargetId;
275
276 gflags::FlagSaver flag_saver_;
277};
278
279// Test a simple scenario with no errors where the robot should just drive
280// straight forwards.
281TEST_F(LocalizerTest, Nominal) {
282 output_voltages_ << 1.0, 1.0;
283 event_loop_factory_.RunFor(std::chrono::seconds(2));
284 CHECK(output_fetcher_.Fetch());
285 CHECK(status_fetcher_.Fetch());
286 // The two can be different because they may've been sent at different
287 // times.
288 EXPECT_NEAR(output_fetcher_->x(), status_fetcher_->state()->x(), 1e-2);
289 EXPECT_NEAR(output_fetcher_->y(), status_fetcher_->state()->y(), 1e-6);
290 EXPECT_NEAR(output_fetcher_->theta(), status_fetcher_->state()->theta(),
291 1e-6);
292 // Confirm that we did indeed drive forwards (and straight), as expected.
293 EXPECT_LT(0.1, output_fetcher_->x());
294 EXPECT_NEAR(0.0, output_fetcher_->y(), 1e-10);
295 EXPECT_NEAR(0.0, output_fetcher_->theta(), 1e-10);
296 EXPECT_NEAR(0.0, status_fetcher_->state()->left_voltage_error(), 1e-1);
297 EXPECT_NEAR(0.0, status_fetcher_->state()->right_voltage_error(), 1e-1);
298
299 // And check that we actually think that we are near where the simulator
300 // says we are.
301 EXPECT_TRUE(VerifyEstimatorAccurate(1e-2));
302}
303
304// Confirm that when the robot drives backwards that we localize correctly.
305TEST_F(LocalizerTest, NominalReverse) {
306 output_voltages_ << -1.0, -1.0;
307 event_loop_factory_.RunFor(std::chrono::seconds(2));
308 CHECK(output_fetcher_.Fetch());
309 CHECK(status_fetcher_.Fetch());
310 // The two can be different because they may've been sent at different
311 // times.
312 EXPECT_NEAR(output_fetcher_->x(), status_fetcher_->state()->x(), 1e-2);
313 EXPECT_NEAR(output_fetcher_->y(), status_fetcher_->state()->y(), 1e-6);
314 EXPECT_NEAR(output_fetcher_->theta(), status_fetcher_->state()->theta(),
315 1e-6);
316 // Confirm that we did indeed drive backwards (and straight), as expected.
317 EXPECT_GT(-0.1, output_fetcher_->x());
318 EXPECT_NEAR(0.0, output_fetcher_->y(), 1e-10);
319 EXPECT_NEAR(0.0, output_fetcher_->theta(), 1e-10);
320 EXPECT_NEAR(0.0, status_fetcher_->state()->left_voltage_error(), 1e-1);
321 EXPECT_NEAR(0.0, status_fetcher_->state()->right_voltage_error(), 1e-1);
322
323 // And check that we actually think that we are near where the simulator
324 // says we are.
325 EXPECT_TRUE(VerifyEstimatorAccurate(1e-2));
326}
327
328// Confirm that when the robot turns counter-clockwise that we localize
329// correctly.
330TEST_F(LocalizerTest, NominalSpinInPlace) {
331 output_voltages_ << -1.0, 1.0;
332 event_loop_factory_.RunFor(std::chrono::seconds(2));
333 CHECK(output_fetcher_.Fetch());
334 CHECK(status_fetcher_.Fetch());
335 // The two can be different because they may've been sent at different
336 // times.
337 EXPECT_NEAR(output_fetcher_->x(), status_fetcher_->state()->x(), 1e-6);
338 EXPECT_NEAR(output_fetcher_->y(), status_fetcher_->state()->y(), 1e-6);
339 EXPECT_NEAR(output_fetcher_->theta(), status_fetcher_->state()->theta(),
340 1e-2);
341 // Confirm that we did indeed turn counter-clockwise.
342 EXPECT_NEAR(0.0, output_fetcher_->x(), 1e-10);
343 EXPECT_NEAR(0.0, output_fetcher_->y(), 1e-10);
344 EXPECT_LT(0.1, output_fetcher_->theta());
345 EXPECT_NEAR(0.0, status_fetcher_->state()->left_voltage_error(), 1e-1);
346 EXPECT_NEAR(0.0, status_fetcher_->state()->right_voltage_error(), 1e-1);
347
348 // And check that we actually think that we are near where the simulator
349 // says we are.
350 EXPECT_TRUE(VerifyEstimatorAccurate(1e-2));
351}
352
353// Confirm that when the robot drives in a curve that we localize
354// successfully.
355TEST_F(LocalizerTest, NominalCurve) {
356 output_voltages_ << 2.0, 3.0;
357 event_loop_factory_.RunFor(std::chrono::seconds(4));
358 CHECK(output_fetcher_.Fetch());
359 CHECK(status_fetcher_.Fetch());
360 // The two can be different because they may've been sent at different
361 // times.
362 EXPECT_NEAR(output_fetcher_->x(), status_fetcher_->state()->x(), 1e-2);
363 EXPECT_NEAR(output_fetcher_->y(), status_fetcher_->state()->y(), 1e-2);
364 EXPECT_NEAR(output_fetcher_->theta(), status_fetcher_->state()->theta(),
365 1e-2);
366 // Confirm that we did indeed drive in a rough, counter-clockwise, curve.
367 EXPECT_LT(0.1, output_fetcher_->x());
368 EXPECT_LT(0.1, output_fetcher_->y());
369 EXPECT_LT(0.1, output_fetcher_->theta());
370
371 // And check that we actually think that we are near where the simulator
372 // says we are.
373 EXPECT_TRUE(VerifyEstimatorAccurate(1e-2));
374}
375
376// Tests that, in the presence of a non-zero voltage error, that we correct
377// for it.
378TEST_F(LocalizerTest, VoltageErrorDisabled) {
379 output_voltages_ << 0.0, 0.0;
380 drivetrain_plant_.set_left_voltage_offset(2.0);
381 drivetrain_plant_.set_right_voltage_offset(2.0);
382
383 event_loop_factory_.RunFor(std::chrono::seconds(2));
384 CHECK(output_fetcher_.Fetch());
385 CHECK(status_fetcher_.Fetch());
386 // We should've just ended up driving straight forwards.
387 EXPECT_LT(0.1, output_fetcher_->x());
388 EXPECT_NEAR(0.0, output_fetcher_->y(), 1e-10);
389 EXPECT_NEAR(0.0, output_fetcher_->theta(), 1e-10);
390 EXPECT_NEAR(2.0, status_fetcher_->state()->left_voltage_error(), 1.0);
391 EXPECT_NEAR(2.0, status_fetcher_->state()->right_voltage_error(), 1.0);
392
393 // And check that we actually think that we are near where the simulator
394 // says we are.
395 EXPECT_TRUE(VerifyEstimatorAccurate(0.05));
396}
397
398// Tests that image corrections in the nominal case (no errors) causes no
399// issues.
400TEST_F(LocalizerTest, NominalImageCorrections) {
401 output_voltages_ << 3.0, 2.0;
402 send_targets_ = true;
403
404 event_loop_factory_.RunFor(std::chrono::seconds(4));
405 CHECK(status_fetcher_.Fetch());
406 EXPECT_TRUE(VerifyEstimatorAccurate(1e-2));
407 ASSERT_TRUE(status_fetcher_->has_statistics());
408 ASSERT_EQ(4u /* number of cameras */, status_fetcher_->statistics()->size());
409 ASSERT_EQ(status_fetcher_->statistics()->Get(0)->total_candidates(),
410 status_fetcher_->statistics()->Get(0)->total_accepted());
411 ASSERT_LT(10, status_fetcher_->statistics()->Get(0)->total_candidates());
412}
413
414// Tests that image corrections when there is an error at the start results
415// in us actually getting corrected over time.
416TEST_F(LocalizerTest, ImageCorrections) {
417 output_voltages_ << 0.0, 0.0;
418 drivetrain_plant_.mutable_state()->x() = 2.0;
419 drivetrain_plant_.mutable_state()->y() = 2.0;
420 SendLocalizerControl(5.0, 3.0, 0.0);
421 event_loop_factory_.RunFor(std::chrono::seconds(4));
422 CHECK(output_fetcher_.Fetch());
423 ASSERT_NEAR(5.0, output_fetcher_->x(), 1e-5);
424 ASSERT_NEAR(3.0, output_fetcher_->y(), 1e-5);
425 ASSERT_NEAR(0.0, output_fetcher_->theta(), 1e-5);
426
427 send_targets_ = true;
428
429 event_loop_factory_.RunFor(std::chrono::seconds(4));
430 CHECK(status_fetcher_.Fetch());
431 EXPECT_TRUE(VerifyEstimatorAccurate(0.1));
432 ASSERT_TRUE(status_fetcher_->has_statistics());
433 ASSERT_EQ(4u /* number of cameras */, status_fetcher_->statistics()->size());
434 ASSERT_EQ(status_fetcher_->statistics()->Get(0)->total_candidates(),
435 status_fetcher_->statistics()->Get(0)->total_accepted());
436 ASSERT_LT(10, status_fetcher_->statistics()->Get(0)->total_candidates());
437}
438
439// Tests that we correctly reject an invalid target.
440TEST_F(LocalizerTest, InvalidTargetId) {
441 output_voltages_ << 0.0, 0.0;
442 send_targets_ = true;
443 send_target_id_ = 100;
444
445 event_loop_factory_.RunFor(std::chrono::seconds(4));
446 CHECK(status_fetcher_.Fetch());
447 ASSERT_TRUE(status_fetcher_->has_statistics());
448 ASSERT_EQ(4u /* number of cameras */, status_fetcher_->statistics()->size());
449 ASSERT_EQ(0, status_fetcher_->statistics()->Get(0)->total_accepted());
450 ASSERT_LT(10, status_fetcher_->statistics()->Get(0)->total_candidates());
451 ASSERT_EQ(status_fetcher_->statistics()
452 ->Get(0)
453 ->rejection_reasons()
454 ->Get(static_cast<size_t>(RejectionReason::NO_SUCH_TARGET))
455 ->count(),
456 status_fetcher_->statistics()->Get(0)->total_candidates());
457}
458
459} // namespace y2023::localizer::testing