blob: d1436dd610fa60fdd30907f2af17ea9be470f02f [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
36 replayer_.AddDirectQueueSender("wpilib_interface.DSReader",
37 "joystick_state", ::aos::joystick_state);
38 replayer_.AddDirectQueueSender("wpilib_interface.SensorReader",
39 "robot_state", ::aos::robot_state);
40
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.
52 replayer_.AddDirectQueueSender(process_name, "goal", loop_group_->goal);
53 }
54
Austin Schuhd0e02df2015-11-26 12:48:51 -080055 template <class QT>
56 void AddDirectQueueSender(const ::std::string &process_name,
57 const ::std::string &log_message,
58 const ::aos::Queue<QT> &queue) {
59 replayer_.AddDirectQueueSender<QT>(process_name, log_message, queue);
60 }
61
Brian Silvermand0575692015-02-21 16:24:02 -050062 // Replays messages from a file.
63 // filename can be straight from the command line; all sanity checking etc is
64 // handled by this function.
65 void ProcessFile(const char *filename);
66
67 private:
68 // A message handler which saves off messages and records whether it currently
69 // has a new one or not.
70 template <class S>
71 class CaptureMessage {
72 public:
73 CaptureMessage() {}
74
75 void operator()(const S &message) {
76 CHECK(!have_new_message_);
77 saved_message_ = message;
78 have_new_message_ = true;
79 }
80
81 const S &saved_message() const { return saved_message_; }
82 bool have_new_message() const { return have_new_message_; }
83 void clear_new_message() { have_new_message_ = false; }
84
85 private:
86 S saved_message_;
87 bool have_new_message_ = false;
88
89 DISALLOW_COPY_AND_ASSIGN(CaptureMessage);
90 };
91
92 // Runs through the file currently loaded in replayer_.
93 // Returns after going through the entire file.
94 void DoProcessFile();
95
96 T *const loop_group_;
97
98 CaptureMessage<PositionType> position_;
99 CaptureMessage<OutputType> output_;
100 CaptureMessage<StatusType> status_;
101
102 // The output that the loop sends for ZeroOutputs(). It might not actually be
103 // all fields zeroed, so we pick the first one and remember it to compare.
104 CaptureMessage<OutputType> zero_output_;
105
106 ::aos::logging::linux_code::LogReplayer replayer_;
107};
108
109template <class T>
110void ControlLoopReplayer<T>::ProcessFile(const char *filename) {
111 int fd;
112 if (strcmp(filename, "-") == 0) {
113 fd = STDIN_FILENO;
114 } else {
115 fd = open(filename, O_RDONLY);
116 }
117 if (fd == -1) {
118 PLOG(FATAL, "couldn't open file '%s' for reading", filename);
119 }
120
121 replayer_.OpenFile(fd);
122 DoProcessFile();
123 replayer_.CloseCurrentFile();
124
125 PCHECK(close(fd));
126}
127
128template <class T>
129void ControlLoopReplayer<T>::DoProcessFile() {
130 while (true) {
131 // Dig through messages until we get a status, which indicates the end of
132 // the control loop cycle.
133 while (!status_.have_new_message()) {
134 if (replayer_.ProcessMessage()) return;
135 }
136
137 // Send out the position message (after adjusting the time offset) so the
138 // loop will run right now.
Brian Silverman47474a42015-03-16 15:21:17 -0700139 if (!position_.have_new_message()) {
140 LOG(WARNING, "don't have a new position this cycle -> skipping\n");
141 status_.clear_new_message();
142 position_.clear_new_message();
143 output_.clear_new_message();
144 continue;
145 }
Brian Silvermand0575692015-02-21 16:24:02 -0500146 ::aos::time::OffsetToNow(position_.saved_message().sent_time);
147 {
148 auto position_message = loop_group_->position.MakeMessage();
149 *position_message = position_.saved_message();
150 CHECK(position_message.Send());
151 }
152 position_.clear_new_message();
153
154 // Wait for the loop to finish running.
155 loop_group_->status.FetchNextBlocking();
156
157 // Point out if the status is different.
158 if (!loop_group_->status->EqualsNoTime(status_.saved_message())) {
159 LOG_STRUCT(WARNING, "expected status", status_.saved_message());
160 LOG_STRUCT(WARNING, "got status", *loop_group_->status);
161 }
162 status_.clear_new_message();
163
164 // Point out if the output is different. This is a lot more complicated than
165 // for the status because there isn't always an output logged.
166 bool loop_new_output = loop_group_->output.FetchLatest();
167 if (output_.have_new_message()) {
168 if (!loop_new_output) {
169 LOG_STRUCT(WARNING, "no output, expected", output_.saved_message());
170 } else if (!loop_group_->output->EqualsNoTime(output_.saved_message())) {
171 LOG_STRUCT(WARNING, "expected output", output_.saved_message());
172 LOG_STRUCT(WARNING, "got output", *loop_group_->output);
173 }
174 } else if (loop_new_output) {
175 if (zero_output_.have_new_message()) {
176 if (!loop_group_->output->EqualsNoTime(zero_output_.saved_message())) {
177 LOG_STRUCT(WARNING, "expected null output",
178 zero_output_.saved_message());
179 LOG_STRUCT(WARNING, "got output", *loop_group_->output);
180 }
181 } else {
182 zero_output_(*loop_group_->output);
183 }
184 }
185 output_.clear_new_message();
186 }
187}
188
189} // namespace controls
190} // namespace aos
191
John Park33858a32018-09-28 23:05:48 -0700192#endif // AOS_CONTROLS_REPLAY_CONTROL_LOOP_H_