John Park | 33858a3 | 2018-09-28 23:05:48 -0700 | [diff] [blame] | 1 | #ifndef AOS_CONTROLS_REPLAY_CONTROL_LOOP_H_ |
| 2 | #define AOS_CONTROLS_REPLAY_CONTROL_LOOP_H_ |
Brian Silverman | d057569 | 2015-02-21 16:24:02 -0500 | [diff] [blame] | 3 | |
| 4 | #include <fcntl.h> |
| 5 | |
John Park | 33858a3 | 2018-09-28 23:05:48 -0700 | [diff] [blame] | 6 | #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 Silverman | d057569 | 2015-02-21 16:24:02 -0500 | [diff] [blame] | 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 | |
Austin Schuh | df6cbb1 | 2019-02-02 13:46:52 -0800 | [diff] [blame] | 36 | 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 Silverman | d057569 | 2015-02-21 16:24:02 -0500 | [diff] [blame] | 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. |
Austin Schuh | df6cbb1 | 2019-02-02 13:46:52 -0800 | [diff] [blame] | 52 | AddDirectQueueSender<GoalType>( |
| 53 | process_name, "goal", ::std::string(loop_group_->name()) + ".goal"); |
Brian Silverman | d057569 | 2015-02-21 16:24:02 -0500 | [diff] [blame] | 54 | } |
| 55 | |
Austin Schuh | d0e02df | 2015-11-26 12:48:51 -0800 | [diff] [blame] | 56 | template <class QT> |
| 57 | void AddDirectQueueSender(const ::std::string &process_name, |
| 58 | const ::std::string &log_message, |
Austin Schuh | df6cbb1 | 2019-02-02 13:46:52 -0800 | [diff] [blame] | 59 | const ::std::string &name) { |
| 60 | replayer_.AddDirectQueueSender<QT>(process_name, log_message, name); |
Austin Schuh | d0e02df | 2015-11-26 12:48:51 -0800 | [diff] [blame] | 61 | } |
| 62 | |
Brian Silverman | d057569 | 2015-02-21 16:24:02 -0500 | [diff] [blame] | 63 | // 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 Schuh | f257f3c | 2019-10-27 21:00:43 -0700 | [diff] [blame] | 77 | AOS_CHECK(!have_new_message_); |
Brian Silverman | d057569 | 2015-02-21 16:24:02 -0500 | [diff] [blame] | 78 | 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 Schuh | df6cbb1 | 2019-02-02 13:46:52 -0800 | [diff] [blame] | 97 | ::aos::ShmEventLoop event_loop_; |
| 98 | |
Brian Silverman | d057569 | 2015-02-21 16:24:02 -0500 | [diff] [blame] | 99 | 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 | |
| 112 | template <class T> |
| 113 | void 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 Schuh | f257f3c | 2019-10-27 21:00:43 -0700 | [diff] [blame] | 121 | AOS_PLOG(FATAL, "couldn't open file '%s' for reading", filename); |
Brian Silverman | d057569 | 2015-02-21 16:24:02 -0500 | [diff] [blame] | 122 | } |
| 123 | |
| 124 | replayer_.OpenFile(fd); |
| 125 | DoProcessFile(); |
| 126 | replayer_.CloseCurrentFile(); |
| 127 | |
Austin Schuh | f257f3c | 2019-10-27 21:00:43 -0700 | [diff] [blame] | 128 | AOS_PCHECK(close(fd)); |
Brian Silverman | d057569 | 2015-02-21 16:24:02 -0500 | [diff] [blame] | 129 | } |
| 130 | |
| 131 | template <class T> |
| 132 | void 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 Silverman | 47474a4 | 2015-03-16 15:21:17 -0700 | [diff] [blame] | 142 | if (!position_.have_new_message()) { |
Austin Schuh | f257f3c | 2019-10-27 21:00:43 -0700 | [diff] [blame] | 143 | AOS_LOG(WARNING, "don't have a new position this cycle -> skipping\n"); |
Brian Silverman | 47474a4 | 2015-03-16 15:21:17 -0700 | [diff] [blame] | 144 | status_.clear_new_message(); |
| 145 | position_.clear_new_message(); |
| 146 | output_.clear_new_message(); |
| 147 | continue; |
| 148 | } |
Brian Silverman | d057569 | 2015-02-21 16:24:02 -0500 | [diff] [blame] | 149 | ::aos::time::OffsetToNow(position_.saved_message().sent_time); |
| 150 | { |
| 151 | auto position_message = loop_group_->position.MakeMessage(); |
| 152 | *position_message = position_.saved_message(); |
Austin Schuh | f257f3c | 2019-10-27 21:00:43 -0700 | [diff] [blame] | 153 | AOS_CHECK(position_message.Send()); |
Brian Silverman | d057569 | 2015-02-21 16:24:02 -0500 | [diff] [blame] | 154 | } |
| 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 Schuh | f257f3c | 2019-10-27 21:00:43 -0700 | [diff] [blame] | 162 | AOS_LOG_STRUCT(WARNING, "expected status", status_.saved_message()); |
| 163 | AOS_LOG_STRUCT(WARNING, "got status", *loop_group_->status); |
Brian Silverman | d057569 | 2015-02-21 16:24:02 -0500 | [diff] [blame] | 164 | } |
| 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 Schuh | f257f3c | 2019-10-27 21:00:43 -0700 | [diff] [blame] | 172 | AOS_LOG_STRUCT(WARNING, "no output, expected", output_.saved_message()); |
Brian Silverman | d057569 | 2015-02-21 16:24:02 -0500 | [diff] [blame] | 173 | } else if (!loop_group_->output->EqualsNoTime(output_.saved_message())) { |
Austin Schuh | f257f3c | 2019-10-27 21:00:43 -0700 | [diff] [blame] | 174 | AOS_LOG_STRUCT(WARNING, "expected output", output_.saved_message()); |
| 175 | AOS_LOG_STRUCT(WARNING, "got output", *loop_group_->output); |
Brian Silverman | d057569 | 2015-02-21 16:24:02 -0500 | [diff] [blame] | 176 | } |
| 177 | } else if (loop_new_output) { |
| 178 | if (zero_output_.have_new_message()) { |
| 179 | if (!loop_group_->output->EqualsNoTime(zero_output_.saved_message())) { |
Austin Schuh | f257f3c | 2019-10-27 21:00:43 -0700 | [diff] [blame] | 180 | AOS_LOG_STRUCT(WARNING, "expected null output", |
| 181 | zero_output_.saved_message()); |
| 182 | AOS_LOG_STRUCT(WARNING, "got output", *loop_group_->output); |
Brian Silverman | d057569 | 2015-02-21 16:24:02 -0500 | [diff] [blame] | 183 | } |
| 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 Park | 33858a3 | 2018-09-28 23:05:48 -0700 | [diff] [blame] | 195 | #endif // AOS_CONTROLS_REPLAY_CONTROL_LOOP_H_ |