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