blob: 2e7591a17cd52d29d248502e7575bdf63047235e [file] [log] [blame]
James Kuszmaulf5f50dd2019-03-23 12:43:15 -07001#include <fcntl.h>
2
Austin Schuh99f7c6a2024-06-25 22:07:44 -07003#include "absl/flags/flag.h"
Philipp Schrader790cb542023-07-05 21:06:52 -07004
James Kuszmaulf5f50dd2019-03-23 12:43:15 -07005#include "aos/init.h"
6#include "aos/logging/implementations.h"
7#include "aos/logging/replay.h"
8#include "aos/network/team_number.h"
9#include "aos/queue.h"
10#include "frc971/control_loops/drivetrain/drivetrain.q.h"
11#include "frc971/control_loops/drivetrain/localizer.q.h"
12#include "frc971/wpilib/imu.q.h"
James Kuszmaulf5f50dd2019-03-23 12:43:15 -070013#include "y2019/constants.h"
14#include "y2019/control_loops/drivetrain/drivetrain_base.h"
15#include "y2019/control_loops/drivetrain/event_loop_localizer.h"
16#if defined(SUPPORT_PLOT)
17#include "third_party/matplotlib-cpp/matplotlibcpp.h"
18#endif
19
Austin Schuh99f7c6a2024-06-25 22:07:44 -070020ABSL_FLAG(std::string, logfile, "", "Path to the logfile to replay.");
James Kuszmaulf5f50dd2019-03-23 12:43:15 -070021// TODO(james): Figure out how to reliably source team number from logfile.
Austin Schuh99f7c6a2024-06-25 22:07:44 -070022ABSL_FLAG(int32_t, team, 971, "Team number to use for logfile replay.");
23ABSL_FLAG(int32_t, plot_duration, 30,
24 "Duration, in seconds, to plot after the start time.");
25ABSL_FLAG(int32_t, start_offset, 0,
26 "Time, in seconds, to start replay plot after the first enable.");
James Kuszmaulf5f50dd2019-03-23 12:43:15 -070027
Stephan Pleinesf63bde82024-01-13 15:59:33 -080028namespace y2019::control_loops::drivetrain {
James Kuszmaulf5f50dd2019-03-23 12:43:15 -070029using ::y2019::constants::Field;
30
Philipp Schrader790cb542023-07-05 21:06:52 -070031typedef TypedLocalizer<constants::Values::kNumCameras, Field::kNumTargets,
32 Field::kNumObstacles,
33 EventLoopLocalizer::kMaxTargetsPerFrame, double>
34 TestLocalizer;
James Kuszmaulf5f50dd2019-03-23 12:43:15 -070035typedef typename TestLocalizer::Camera TestCamera;
36typedef typename TestCamera::Pose Pose;
37typedef typename TestCamera::LineSegment Obstacle;
38
39#if defined(SUPPORT_PLOT)
40// Plots a line from a vector of Pose's.
41void PlotPlotPts(const ::std::vector<Pose> &poses,
42 const ::std::map<::std::string, ::std::string> &kwargs) {
43 ::std::vector<double> x;
44 ::std::vector<double> y;
Philipp Schrader790cb542023-07-05 21:06:52 -070045 for (const Pose &p : poses) {
James Kuszmaulf5f50dd2019-03-23 12:43:15 -070046 x.push_back(p.abs_pos().x());
47 y.push_back(p.abs_pos().y());
48 }
49 matplotlibcpp::plot(x, y, kwargs);
50}
51#endif
52
53class LocalizerReplayer {
54 // This class is for replaying logfiles from the 2019 robot and seeing how the
55 // localizer performs withi any new changes versus how it performed during the
56 // real match. This starts running replay on the first enable and then will
57 // actually plot a subset of the run based on the --plot_duration and
58 // --start_offset flags.
59 public:
60 typedef ::frc971::control_loops::DrivetrainQueue::Goal DrivetrainGoal;
61 typedef ::frc971::control_loops::DrivetrainQueue::Position DrivetrainPosition;
62 typedef ::frc971::control_loops::DrivetrainQueue::Status DrivetrainStatus;
63 typedef ::frc971::control_loops::DrivetrainQueue::Output DrivetrainOutput;
64 typedef ::frc971::IMUValues IMUValues;
65 typedef ::aos::JoystickState JoystickState;
66
Austin Schuh9fe68f72019-08-10 19:32:03 -070067 LocalizerReplayer() : localizer_(&event_loop_, GetDrivetrainConfig()) {
James Kuszmaulf5f50dd2019-03-23 12:43:15 -070068 replayer_.AddDirectQueueSender<CameraFrame>(
69 "wpilib_interface.stripped.IMU", "camera_frames",
70 ".y2019.control_loops.drivetrain.camera_frames");
71
72 const char *drivetrain = "drivetrain.stripped";
73
74 replayer_.AddHandler<DrivetrainPosition>(
75 drivetrain, "position", [this](const DrivetrainPosition &msg) {
76 if (has_been_enabled_) {
77 localizer_.Update(last_last_U_ * battery_voltage_ / 12.0,
78 msg.sent_time, msg.left_encoder,
79 msg.right_encoder, latest_gyro_, 0.0);
80 if (start_time_ <= msg.sent_time &&
81 start_time_ + plot_duration_ >= msg.sent_time) {
82 t_pos_.push_back(
83 ::std::chrono::duration_cast<::std::chrono::duration<double>>(
84 msg.sent_time.time_since_epoch())
85 .count());
86 new_x_.push_back(localizer_.x());
87 new_y_.push_back(localizer_.y());
88 new_theta_.push_back(localizer_.theta());
89 new_left_encoder_.push_back(localizer_.left_encoder());
90 new_right_encoder_.push_back(localizer_.right_encoder());
91 new_left_velocity_.push_back(localizer_.left_velocity());
92 new_right_velocity_.push_back(localizer_.right_velocity());
93
94 left_voltage_.push_back(last_last_U_(0, 0));
95 right_voltage_.push_back(last_last_U_(1, 0));
96 }
97 }
98 });
99
100 replayer_.AddHandler<DrivetrainGoal>(
101 drivetrain, "goal", [this](const DrivetrainGoal &msg) {
102 if (has_been_enabled_ && start_time_ <= msg.sent_time &&
103 (start_time_ + plot_duration_ >= msg.sent_time)) {
104 t_goal_.push_back(
105 ::std::chrono::duration_cast<::std::chrono::duration<double>>(
106 msg.sent_time.time_since_epoch())
107 .count());
108 drivetrain_mode_.push_back(msg.controller_type);
109 throttle_.push_back(msg.throttle);
110 wheel_.push_back(msg.wheel);
111 }
112 });
113
114 replayer_.AddHandler<DrivetrainStatus>(
115 drivetrain, "status", [this](const DrivetrainStatus &msg) {
116 if (has_been_enabled_ && start_time_ <= msg.sent_time &&
117 (start_time_ + plot_duration_ >= msg.sent_time)) {
118 t_status_.push_back(
119 ::std::chrono::duration_cast<::std::chrono::duration<double>>(
120 msg.sent_time.time_since_epoch())
121 .count());
122 original_x_.push_back(msg.x);
123 original_y_.push_back(msg.y);
124 original_theta_.push_back(msg.theta);
125 original_line_theta_.push_back(msg.line_follow_logging.rel_theta);
126 original_line_goal_theta_.push_back(
127 msg.line_follow_logging.goal_theta);
128 original_line_distance_.push_back(
129 msg.line_follow_logging.distance_to_target);
130 }
131 });
132
133 replayer_.AddHandler<DrivetrainOutput>(
134 drivetrain, "output", [this](const DrivetrainOutput &msg) {
135 last_last_U_ = last_U_;
136 last_U_ << msg.left_voltage, msg.right_voltage;
137 });
138
139 replayer_.AddHandler<frc971::control_loops::drivetrain::LocalizerControl>(
140 drivetrain, "localizer_control",
141 [this](const frc971::control_loops::drivetrain::LocalizerControl &msg) {
Austin Schuhf257f3c2019-10-27 21:00:43 -0700142 AOS_LOG_STRUCT(DEBUG, "localizer_control", msg);
James Kuszmaulf5f50dd2019-03-23 12:43:15 -0700143 localizer_.ResetPosition(msg.sent_time, msg.x, msg.y, msg.theta,
144 msg.theta_uncertainty,
145 !msg.keep_current_theta);
146 });
147
148 replayer_.AddHandler<JoystickState>(
149 "wpilib_interface.stripped.DSReader", "joystick_state",
150 [this](const JoystickState &msg) {
151 if (msg.enabled && !has_been_enabled_) {
152 has_been_enabled_ = true;
153 start_time_ =
154 msg.sent_time + ::std::chrono::seconds(FLAGS_start_offset);
155 }
156 });
157
158 replayer_.AddHandler<IMUValues>(
159 "wpilib_interface.stripped.IMU", "sending",
160 [this](const IMUValues &msg) {
161 latest_gyro_ = msg.gyro_z;
162 if (has_been_enabled_ && start_time_ <= msg.sent_time &&
163 (start_time_ + plot_duration_ >= msg.sent_time)) {
164 t_imu_.push_back(
165 ::std::chrono::duration_cast<::std::chrono::duration<double>>(
166 msg.sent_time.time_since_epoch())
167 .count());
168 lat_accel_.push_back(msg.accelerometer_y);
169 long_accel_.push_back(msg.accelerometer_x);
170 z_accel_.push_back(msg.accelerometer_z);
171 }
172 });
173
174 replayer_.AddHandler<::aos::RobotState>(
175 "wpilib_interface.stripped.SensorReader", "robot_state",
176 [this](const ::aos::RobotState &msg) {
177 battery_voltage_ = msg.voltage_battery;
178 });
179 }
180
181 void ProcessFile(const char *filename) {
182 int fd;
183 if (strcmp(filename, "-") == 0) {
184 fd = STDIN_FILENO;
185 } else {
186 fd = open(filename, O_RDONLY);
187 }
188 if (fd == -1) {
Austin Schuhf257f3c2019-10-27 21:00:43 -0700189 AOS_PLOG(FATAL, "couldn't open file '%s' for reading", filename);
James Kuszmaulf5f50dd2019-03-23 12:43:15 -0700190 }
191
192 replayer_.OpenFile(fd);
193 ::aos::RawQueue *queue = ::aos::logging::GetLoggingQueue();
194 while (!replayer_.ProcessMessage()) {
195 const ::aos::logging::LogMessage *const msg =
196 static_cast<const ::aos::logging::LogMessage *>(
197 queue->ReadMessage(::aos::RawQueue::kNonBlock));
198 if (msg != NULL) {
199 ::aos::logging::internal::PrintMessage(stdout, *msg);
200
201 queue->FreeMessage(msg);
202 }
203 continue;
204 }
205 replayer_.CloseCurrentFile();
206
Austin Schuhf257f3c2019-10-27 21:00:43 -0700207 AOS_PCHECK(close(fd));
James Kuszmaulf5f50dd2019-03-23 12:43:15 -0700208
209 Plot();
210 }
211
212 void Plot() {
213#if defined(SUPPORT_PLOT)
214 int start_idx = 0;
215 for (const float t : t_pos_) {
216 if (t < ::std::chrono::duration_cast<::std::chrono::duration<double>>(
217 start_time_.time_since_epoch())
218 .count() +
219 0.0) {
220 ++start_idx;
221 } else {
222 break;
223 }
224 }
225 ::std::vector<float> trunc_orig_x(original_x_.begin() + start_idx,
226 original_x_.end());
227 ::std::vector<float> trunc_orig_y(original_y_.begin() + start_idx,
228 original_y_.end());
229 ::std::vector<float> trunc_new_x(new_x_.begin() + start_idx, new_x_.end());
230 ::std::vector<float> trunc_new_y(new_y_.begin() + start_idx, new_y_.end());
231 matplotlibcpp::figure();
232 matplotlibcpp::plot(trunc_orig_x, trunc_orig_y,
233 {{"label", "original"}, {"marker", "o"}});
234 matplotlibcpp::plot(trunc_new_x, trunc_new_y,
235 {{"label", "new"}, {"marker", "o"}});
236 matplotlibcpp::xlim(-0.1, 19.0);
237 matplotlibcpp::ylim(-5.0, 5.0);
238 Field field;
239 for (const Target &target : field.targets()) {
240 PlotPlotPts(target.PlotPoints(), {{"c", "g"}});
241 }
242 for (const Obstacle &obstacle : field.obstacles()) {
243 PlotPlotPts(obstacle.PlotPoints(), {{"c", "k"}});
244 }
245 matplotlibcpp::grid(true);
246 matplotlibcpp::legend();
247
248 matplotlibcpp::figure();
249 matplotlibcpp::plot(t_status_, original_x_,
250 {{"label", "original x"}, {"marker", "o"}});
251 matplotlibcpp::plot(t_status_, original_y_,
252 {{"label", "original y"}, {"marker", "o"}});
253 matplotlibcpp::plot(t_status_, original_theta_,
254 {{"label", "original theta"}, {"marker", "o"}});
255 matplotlibcpp::plot(t_pos_, new_x_, {{"label", "new x"}, {"marker", "o"}});
256 matplotlibcpp::plot(t_pos_, new_y_, {{"label", "new y"}, {"marker", "o"}});
257 matplotlibcpp::plot(t_pos_, new_theta_,
258 {{"label", "new theta"}, {"marker", "o"}});
259 matplotlibcpp::grid(true);
260 matplotlibcpp::legend();
261
262 matplotlibcpp::figure();
263 matplotlibcpp::plot(t_pos_, left_voltage_,
264 {{"label", "left voltage"}, {"marker", "o"}});
265 matplotlibcpp::plot(t_pos_, right_voltage_,
266 {{"label", "right voltage"}, {"marker", "o"}});
267 matplotlibcpp::grid(true);
268 matplotlibcpp::legend();
269
270 matplotlibcpp::figure();
271 matplotlibcpp::plot(t_pos_, new_left_encoder_,
272 {{"label", "left encoder"}, {"marker", "o"}});
273 matplotlibcpp::plot(t_pos_, new_right_encoder_,
274 {{"label", "right encoder"}, {"marker", "o"}});
275 matplotlibcpp::plot(t_pos_, new_left_velocity_,
276 {{"label", "left velocity"}, {"marker", "o"}});
277 matplotlibcpp::plot(t_pos_, new_right_velocity_,
278 {{"label", "right velocity"}, {"marker", "o"}});
279 matplotlibcpp::grid(true);
280 matplotlibcpp::legend();
281
282 matplotlibcpp::figure();
283 matplotlibcpp::plot(t_goal_, drivetrain_mode_,
284 {{"label", "mode"}, {"marker", "o"}});
285 matplotlibcpp::plot(t_goal_, throttle_,
286 {{"label", "throttle"}, {"marker", "o"}});
287 matplotlibcpp::plot(t_goal_, wheel_, {{"label", "wheel"}, {"marker", "o"}});
288 matplotlibcpp::plot(t_status_, original_line_theta_,
289 {{"label", "relative theta"}, {"marker", "o"}});
290 matplotlibcpp::plot(t_status_, original_line_goal_theta_,
291 {{"label", "goal theta"}, {"marker", "o"}});
292 matplotlibcpp::plot(t_status_, original_line_distance_,
293 {{"label", "dist to target"}, {"marker", "o"}});
294 matplotlibcpp::grid(true);
295 matplotlibcpp::legend();
296
297 matplotlibcpp::figure();
298 ::std::vector<double> filt_lat_accel_ = FilterValues(lat_accel_, 10);
299 ::std::vector<double> filt_long_accel_ = FilterValues(long_accel_, 10);
300 ::std::vector<double> filt_z_accel_ = FilterValues(z_accel_, 10);
301 matplotlibcpp::plot(t_imu_, filt_lat_accel_,
302 {{"label", "lateral accel"}, {"marker", "o"}});
303 matplotlibcpp::plot(t_imu_, filt_long_accel_,
304 {{"label", "longitudinal accel"}, {"marker", "o"}});
305 matplotlibcpp::plot(t_imu_, filt_z_accel_,
306 {{"label", "z accel"}, {"marker", "o"}});
307 matplotlibcpp::grid(true);
308 matplotlibcpp::legend();
309
310 matplotlibcpp::show();
311#endif
312 }
313
314 private:
315 // Do a simple moving average with a width of N over the given raw data and
316 // return the results. For calculating values with N/2 of either edge, we
317 // assume that the space past the edges is filled with zeroes.
318 ::std::vector<double> FilterValues(const ::std::vector<double> &raw, int N) {
319 ::std::vector<double> filt;
320 for (int ii = 0; ii < static_cast<int>(raw.size()); ++ii) {
321 double sum = 0;
322 for (int jj = ::std::max(0, ii - N / 2);
323 jj < ::std::min(ii + N / 2, static_cast<int>(raw.size()) - 1);
324 ++jj) {
325 sum += raw[jj];
326 }
327 filt.push_back(sum / static_cast<double>(N));
328 }
329 return filt;
330 }
331
332 // TODO(james): Move to some sort of virtual event loop.
333 ::aos::ShmEventLoop event_loop_;
334
335 EventLoopLocalizer localizer_;
336 // Whether the robot has been enabled yet.
337 bool has_been_enabled_ = false;
338 // Cache of last gyro value to forward to the localizer.
Philipp Schrader790cb542023-07-05 21:06:52 -0700339 double latest_gyro_ = 0.0; // rad/sec
340 double battery_voltage_ = 12.0; // volts
James Kuszmaulf5f50dd2019-03-23 12:43:15 -0700341 ::Eigen::Matrix<double, 2, 1> last_U_{0, 0};
342 ::Eigen::Matrix<double, 2, 1> last_last_U_{0, 0};
343
344 ::aos::logging::linux_code::LogReplayer replayer_;
345
346 // Time at which to start the plot.
347 ::aos::monotonic_clock::time_point start_time_;
348 // Length of time to plot.
349 ::std::chrono::seconds plot_duration_{FLAGS_plot_duration};
350
351 // All the values to plot, organized in blocks by vectors of data that come in
352 // at the same time (e.g., status messages come in offset from position
353 // messages and so while they are all generally the same frequency, we can end
354 // receiving slightly different numbers of messages on different queues).
355
356 // TODO(james): Improve plotting support.
357 ::std::vector<double> t_status_;
358 ::std::vector<double> original_x_;
359 ::std::vector<double> original_y_;
360 ::std::vector<double> original_theta_;
361 ::std::vector<double> original_line_theta_;
362 ::std::vector<double> original_line_goal_theta_;
363 ::std::vector<double> original_line_distance_;
364
365 ::std::vector<double> t_pos_;
366 ::std::vector<double> new_x_;
367 ::std::vector<double> new_y_;
368 ::std::vector<double> new_left_encoder_;
369 ::std::vector<double> new_right_encoder_;
370 ::std::vector<double> new_left_velocity_;
371 ::std::vector<double> new_right_velocity_;
372 ::std::vector<double> new_theta_;
373 ::std::vector<double> left_voltage_;
374 ::std::vector<double> right_voltage_;
375
376 ::std::vector<double> t_goal_;
377 ::std::vector<double> drivetrain_mode_;
378 ::std::vector<double> throttle_;
379 ::std::vector<double> wheel_;
380
381 ::std::vector<double> t_imu_;
382 ::std::vector<double> lat_accel_;
383 ::std::vector<double> z_accel_;
384 ::std::vector<double> long_accel_;
385};
386
Stephan Pleinesf63bde82024-01-13 15:59:33 -0800387} // namespace y2019::control_loops::drivetrain
James Kuszmaulf5f50dd2019-03-23 12:43:15 -0700388
389int main(int argc, char *argv[]) {
390 gflags::ParseCommandLineFlags(&argc, &argv, false);
391
392 if (FLAGS_logfile.empty()) {
393 fprintf(stderr, "Need a file to replay!\n");
394 return EXIT_FAILURE;
395 }
396
397 ::aos::network::OverrideTeamNumber(FLAGS_team);
398
James Kuszmaulf5f50dd2019-03-23 12:43:15 -0700399 ::y2019::control_loops::drivetrain::LocalizerReplayer replay;
400 replay.ProcessFile(FLAGS_logfile.c_str());
401
Austin Schuhae87e312020-08-01 16:15:01 -0700402 return 0;
James Kuszmaulf5f50dd2019-03-23 12:43:15 -0700403}