Brian Silverman | d057569 | 2015-02-21 16:24:02 -0500 | [diff] [blame^] | 1 | #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 | |
| 13 | namespace aos { |
| 14 | namespace 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. |
| 19 | template <class T> |
| 20 | class 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 | |
| 102 | template <class T> |
| 103 | void 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 | |
| 121 | template <class T> |
| 122 | void 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_ |