blob: 479197306b964c8fecd94118ea3bbf86b0344f3a [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.
132 CHECK(position_.have_new_message());
133 ::aos::time::OffsetToNow(position_.saved_message().sent_time);
134 {
135 auto position_message = loop_group_->position.MakeMessage();
136 *position_message = position_.saved_message();
137 CHECK(position_message.Send());
138 }
139 position_.clear_new_message();
140
141 // Wait for the loop to finish running.
142 loop_group_->status.FetchNextBlocking();
143
144 // Point out if the status is different.
145 if (!loop_group_->status->EqualsNoTime(status_.saved_message())) {
146 LOG_STRUCT(WARNING, "expected status", status_.saved_message());
147 LOG_STRUCT(WARNING, "got status", *loop_group_->status);
148 }
149 status_.clear_new_message();
150
151 // Point out if the output is different. This is a lot more complicated than
152 // for the status because there isn't always an output logged.
153 bool loop_new_output = loop_group_->output.FetchLatest();
154 if (output_.have_new_message()) {
155 if (!loop_new_output) {
156 LOG_STRUCT(WARNING, "no output, expected", output_.saved_message());
157 } else if (!loop_group_->output->EqualsNoTime(output_.saved_message())) {
158 LOG_STRUCT(WARNING, "expected output", output_.saved_message());
159 LOG_STRUCT(WARNING, "got output", *loop_group_->output);
160 }
161 } else if (loop_new_output) {
162 if (zero_output_.have_new_message()) {
163 if (!loop_group_->output->EqualsNoTime(zero_output_.saved_message())) {
164 LOG_STRUCT(WARNING, "expected null output",
165 zero_output_.saved_message());
166 LOG_STRUCT(WARNING, "got output", *loop_group_->output);
167 }
168 } else {
169 zero_output_(*loop_group_->output);
170 }
171 }
172 output_.clear_new_message();
173 }
174}
175
176} // namespace controls
177} // namespace aos
178
179#endif // AOS_COMMON_CONTROLS_REPLAY_CONTROL_LOOP_H_