blob: 23593ab86e05c374ea761fc31448f84f4f2392d6 [file] [log] [blame]
John Park33858a32018-09-28 23:05:48 -07001#ifndef AOS_CONTROLS_REPLAY_CONTROL_LOOP_H_
2#define AOS_CONTROLS_REPLAY_CONTROL_LOOP_H_
Brian Silvermand0575692015-02-21 16:24:02 -05003
4#include <fcntl.h>
5
John Park33858a32018-09-28 23:05:48 -07006#include "aos/queue.h"
7#include "aos/controls/control_loop.h"
8#include "aos/logging/replay.h"
9#include "aos/logging/queue_logging.h"
10#include "aos/time/time.h"
11#include "aos/macros.h"
Brian Silvermand0575692015-02-21 16:24:02 -050012
13namespace aos {
14namespace controls {
15
16// Handles reading logged messages and running them through a control loop
17// again.
18// T should be a queue group suitable for use with ControlLoop.
19template <class T>
20class ControlLoopReplayer {
21 public:
22 typedef typename ControlLoop<T>::GoalType GoalType;
23 typedef typename ControlLoop<T>::PositionType PositionType;
24 typedef typename ControlLoop<T>::StatusType StatusType;
25 typedef typename ControlLoop<T>::OutputType OutputType;
26
27 // loop_group is where to send the messages out.
28 // process_name is the name of the process which wrote the log messages in the
29 // file(s).
30 ControlLoopReplayer(T *loop_group, const ::std::string &process_name)
31 : loop_group_(loop_group) {
32 // Clear out any old messages which will confuse the code.
33 loop_group_->status.FetchLatest();
34 loop_group_->output.FetchLatest();
35
Austin Schuhdf6cbb12019-02-02 13:46:52 -080036 AddDirectQueueSender<::aos::JoystickState>(
37 "wpilib_interface.DSReader", "joystick_state", ".aos.joystick_state");
38 AddDirectQueueSender<::aos::RobotState>("wpilib_interface.SensorReader",
39 "robot_state", ".aos.robot_state");
Brian Silvermand0575692015-02-21 16:24:02 -050040
41 replayer_.AddHandler(
42 process_name, "position",
43 ::std::function<void(const PositionType &)>(::std::ref(position_)));
44 replayer_.AddHandler(
45 process_name, "output",
46 ::std::function<void(const OutputType &)>(::std::ref(output_)));
47 replayer_.AddHandler(
48 process_name, "status",
49 ::std::function<void(const StatusType &)>(::std::ref(status_)));
50 // The timing of goal messages doesn't matter, and we don't need to look
51 // back at them after running the loop.
Austin Schuhdf6cbb12019-02-02 13:46:52 -080052 AddDirectQueueSender<GoalType>(
53 process_name, "goal", ::std::string(loop_group_->name()) + ".goal");
Brian Silvermand0575692015-02-21 16:24:02 -050054 }
55
Austin Schuhd0e02df2015-11-26 12:48:51 -080056 template <class QT>
57 void AddDirectQueueSender(const ::std::string &process_name,
58 const ::std::string &log_message,
Austin Schuhdf6cbb12019-02-02 13:46:52 -080059 const ::std::string &name) {
60 replayer_.AddDirectQueueSender<QT>(process_name, log_message, name);
Austin Schuhd0e02df2015-11-26 12:48:51 -080061 }
62
Brian Silvermand0575692015-02-21 16:24:02 -050063 // Replays messages from a file.
64 // filename can be straight from the command line; all sanity checking etc is
65 // handled by this function.
66 void ProcessFile(const char *filename);
67
68 private:
69 // A message handler which saves off messages and records whether it currently
70 // has a new one or not.
71 template <class S>
72 class CaptureMessage {
73 public:
74 CaptureMessage() {}
75
76 void operator()(const S &message) {
Austin Schuhf257f3c2019-10-27 21:00:43 -070077 AOS_CHECK(!have_new_message_);
Brian Silvermand0575692015-02-21 16:24:02 -050078 saved_message_ = message;
79 have_new_message_ = true;
80 }
81
82 const S &saved_message() const { return saved_message_; }
83 bool have_new_message() const { return have_new_message_; }
84 void clear_new_message() { have_new_message_ = false; }
85
86 private:
87 S saved_message_;
88 bool have_new_message_ = false;
89
90 DISALLOW_COPY_AND_ASSIGN(CaptureMessage);
91 };
92
93 // Runs through the file currently loaded in replayer_.
94 // Returns after going through the entire file.
95 void DoProcessFile();
96
Austin Schuhdf6cbb12019-02-02 13:46:52 -080097 ::aos::ShmEventLoop event_loop_;
98
Brian Silvermand0575692015-02-21 16:24:02 -050099 T *const loop_group_;
100
101 CaptureMessage<PositionType> position_;
102 CaptureMessage<OutputType> output_;
103 CaptureMessage<StatusType> status_;
104
105 // The output that the loop sends for ZeroOutputs(). It might not actually be
106 // all fields zeroed, so we pick the first one and remember it to compare.
107 CaptureMessage<OutputType> zero_output_;
108
109 ::aos::logging::linux_code::LogReplayer replayer_;
110};
111
112template <class T>
113void ControlLoopReplayer<T>::ProcessFile(const char *filename) {
114 int fd;
115 if (strcmp(filename, "-") == 0) {
116 fd = STDIN_FILENO;
117 } else {
118 fd = open(filename, O_RDONLY);
119 }
120 if (fd == -1) {
Austin Schuhf257f3c2019-10-27 21:00:43 -0700121 AOS_PLOG(FATAL, "couldn't open file '%s' for reading", filename);
Brian Silvermand0575692015-02-21 16:24:02 -0500122 }
123
124 replayer_.OpenFile(fd);
125 DoProcessFile();
126 replayer_.CloseCurrentFile();
127
Austin Schuhf257f3c2019-10-27 21:00:43 -0700128 AOS_PCHECK(close(fd));
Brian Silvermand0575692015-02-21 16:24:02 -0500129}
130
131template <class T>
132void ControlLoopReplayer<T>::DoProcessFile() {
133 while (true) {
134 // Dig through messages until we get a status, which indicates the end of
135 // the control loop cycle.
136 while (!status_.have_new_message()) {
137 if (replayer_.ProcessMessage()) return;
138 }
139
140 // Send out the position message (after adjusting the time offset) so the
141 // loop will run right now.
Brian Silverman47474a42015-03-16 15:21:17 -0700142 if (!position_.have_new_message()) {
Austin Schuhf257f3c2019-10-27 21:00:43 -0700143 AOS_LOG(WARNING, "don't have a new position this cycle -> skipping\n");
Brian Silverman47474a42015-03-16 15:21:17 -0700144 status_.clear_new_message();
145 position_.clear_new_message();
146 output_.clear_new_message();
147 continue;
148 }
Brian Silvermand0575692015-02-21 16:24:02 -0500149 ::aos::time::OffsetToNow(position_.saved_message().sent_time);
150 {
151 auto position_message = loop_group_->position.MakeMessage();
152 *position_message = position_.saved_message();
Austin Schuhf257f3c2019-10-27 21:00:43 -0700153 AOS_CHECK(position_message.Send());
Brian Silvermand0575692015-02-21 16:24:02 -0500154 }
155 position_.clear_new_message();
156
157 // Wait for the loop to finish running.
158 loop_group_->status.FetchNextBlocking();
159
160 // Point out if the status is different.
161 if (!loop_group_->status->EqualsNoTime(status_.saved_message())) {
Austin Schuhf257f3c2019-10-27 21:00:43 -0700162 AOS_LOG_STRUCT(WARNING, "expected status", status_.saved_message());
163 AOS_LOG_STRUCT(WARNING, "got status", *loop_group_->status);
Brian Silvermand0575692015-02-21 16:24:02 -0500164 }
165 status_.clear_new_message();
166
167 // Point out if the output is different. This is a lot more complicated than
168 // for the status because there isn't always an output logged.
169 bool loop_new_output = loop_group_->output.FetchLatest();
170 if (output_.have_new_message()) {
171 if (!loop_new_output) {
Austin Schuhf257f3c2019-10-27 21:00:43 -0700172 AOS_LOG_STRUCT(WARNING, "no output, expected", output_.saved_message());
Brian Silvermand0575692015-02-21 16:24:02 -0500173 } else if (!loop_group_->output->EqualsNoTime(output_.saved_message())) {
Austin Schuhf257f3c2019-10-27 21:00:43 -0700174 AOS_LOG_STRUCT(WARNING, "expected output", output_.saved_message());
175 AOS_LOG_STRUCT(WARNING, "got output", *loop_group_->output);
Brian Silvermand0575692015-02-21 16:24:02 -0500176 }
177 } else if (loop_new_output) {
178 if (zero_output_.have_new_message()) {
179 if (!loop_group_->output->EqualsNoTime(zero_output_.saved_message())) {
Austin Schuhf257f3c2019-10-27 21:00:43 -0700180 AOS_LOG_STRUCT(WARNING, "expected null output",
181 zero_output_.saved_message());
182 AOS_LOG_STRUCT(WARNING, "got output", *loop_group_->output);
Brian Silvermand0575692015-02-21 16:24:02 -0500183 }
184 } else {
185 zero_output_(*loop_group_->output);
186 }
187 }
188 output_.clear_new_message();
189 }
190}
191
192} // namespace controls
193} // namespace aos
194
John Park33858a32018-09-28 23:05:48 -0700195#endif // AOS_CONTROLS_REPLAY_CONTROL_LOOP_H_