Merge "Add arm/elevator profile instances into the control loop."
diff --git a/aos/build/aos.gypi b/aos/build/aos.gypi
index 22e96a0..e049182 100644
--- a/aos/build/aos.gypi
+++ b/aos/build/aos.gypi
@@ -24,13 +24,13 @@
],
},
'conditions': [
- ['PLATFORM=="linux-arm-gcc_frc"', {
+ ['PLATFORM=="linux-arm_frc-gcc"', {
'make_global_settings': [
['CC', '<(ccache)<!(which arm-frc-linux-gnueabi-gcc-4.9)'],
['CXX', '<(ccache)<!(which arm-frc-linux-gnueabi-g++-4.9)'],
],
},
- ], ['PLATFORM=="linux-arm-clang_frc"', {
+ ], ['PLATFORM=="linux-arm_frc-clang"', {
'variables': {
'arm-clang-symlinks': '<!(realpath -s <(AOS)/build/arm-clang-symlinks)',
'arm-clang-sysroot': '<(arm-clang-symlinks)/sysroot',
@@ -140,6 +140,7 @@
'__STDC_CONSTANT_MACROS',
'__STDC_LIMIT_MACROS',
'AOS_COMPILER_<!(echo <(FULL_COMPILER) | sed \'s/\./_/g\')',
+ 'AOS_ARCHITECTURE_<(ARCHITECTURE)',
'_FILE_OFFSET_BITS=64',
],
'ldflags': [
diff --git a/aos/build/build.py b/aos/build/build.py
index 21e0379..6281821 100755
--- a/aos/build/build.py
+++ b/aos/build/build.py
@@ -137,56 +137,24 @@
"""
return os.path.join(os.path.dirname(__file__), '..')
-def get_ip_base():
- """Retrieves the IP address base."""
+def get_ip():
+ """Retrieves the IP address to download code to."""
FILENAME = os.path.normpath(os.path.join(aos_path(), '..',
- 'output', 'ip_base.txt'))
+ 'output', 'ip_address.txt'))
if not os.access(FILENAME, os.R_OK):
os.makedirs(os.path.dirname(FILENAME), exist_ok=True)
with open(FILENAME, 'w') as f:
f.write('roboRIO-971.local')
with open(FILENAME, 'r') as f:
- base = f.readline().strip()
- return base
+ return f.readline().strip()
-def get_ip(device):
- """Retrieves the IP address for a given device."""
- base = get_ip_base()
- if device == 'prime':
- return base + '.179'
- elif device == 'robot':
- return base + '.2'
- elif device == 'roboRIO':
- return base
- else:
- raise Exception('Unknown device %s to get an IP address for.' % device)
+def get_temp_dir():
+ """Retrieves the temporary directory to use when downloading."""
+ return '/home/admin/tmp/aos_downloader'
-def get_user(device):
- """Retrieves the user for a given device."""
- if device == 'prime':
- return 'driver'
- elif device == 'roboRIO':
- return 'admin'
- else:
- raise Exception('Unknown device %s to get a user for.' % device)
-
-def get_temp_dir(device):
- """Retrieves the temporary download directory for a given device."""
- if device == 'prime':
- return '/tmp/aos_downloader'
- elif device == 'roboRIO':
- return '/home/admin/tmp/aos_downloader'
- else:
- raise Exception('Unknown device %s to get a temp_dir for.' % device)
-
-def get_target_dir(device):
- """Retrieves the tempory deploy directory for a given device."""
- if device == 'prime':
- return '/home/driver/robot_code/bin'
- elif device == 'roboRIO':
- return '/home/admin/robot_code'
- else:
- raise Exception('Unknown device %s to get a temp_dir for.' % device)
+def get_target_dir():
+ """Retrieves the tempory deploy directory for downloading code."""
+ return '/home/admin/robot_code'
def user_output(message):
"""Prints message to the user."""
@@ -434,16 +402,15 @@
return r
def deploy(self, dry_run):
- # Downloads code to the prime in a way that avoids clashing too badly with
- # starter (like the naive download everything one at a time).
- if self.compiler().endswith('_frc'):
- device = 'roboRIO'
- else:
- device = 'prime'
+ """Downloads code to the prime in a way that avoids clashing too badly with
+ starter (like the naive download everything one at a time)."""
+ if not self.architecture().endswith('_frc'):
+ raise Exception("Don't know how to download code to a %s." %
+ self.architecture())
SUM = 'md5sum'
- TARGET_DIR = get_target_dir(device)
- TEMP_DIR = get_temp_dir(device)
- TARGET = get_user(device) + '@' + get_ip(device)
+ TARGET_DIR = get_target_dir()
+ TEMP_DIR = get_temp_dir()
+ TARGET = 'admin@' + get_ip()
from_dir = os.path.join(self.outdir(), 'outputs')
sums = subprocess.check_output((SUM,) + tuple(os.listdir(from_dir)),
@@ -467,8 +434,7 @@
+ (('%s:%s' % (TARGET, TEMP_DIR)),))
if not dry_run:
mv_cmd = ['mv {TMPDIR}/* {TO_DIR} ']
- if device == 'roboRIO':
- mv_cmd.append('&& chmod u+s {TO_DIR}/starter_exe ')
+ mv_cmd.append('&& chmod u+s {TO_DIR}/starter_exe ')
mv_cmd.append('&& echo \'Done moving new executables into place\' ')
mv_cmd.append('&& bash -c \'sync && sync && sync\'')
subprocess.check_call(
@@ -511,8 +477,8 @@
return r
- ARCHITECTURES = ('arm', 'amd64')
- COMPILERS = ('clang', 'gcc_frc', 'clang_frc')
+ ARCHITECTURES = ('arm_frc', 'amd64')
+ COMPILERS = ('clang', 'gcc')
SANITIZERS = ('address', 'undefined', 'integer', 'memory', 'thread', 'none')
SANITIZER_TEST_WARNINGS = {
'memory': (True,
@@ -529,8 +495,7 @@
for architecture in PrimeProcessor.ARCHITECTURES:
for compiler in PrimeProcessor.COMPILERS:
for debug in [True, False]:
- if ((architecture == 'arm' and not compiler.endswith('_frc')) or
- (architecture == 'amd64' and compiler.endswith('_frc'))):
+ if architecture == 'amd64' and compiler == 'gcc':
# We don't have a compiler to use here.
continue
platforms.append(
@@ -556,8 +521,8 @@
if warning[0]:
default_platforms -= self.select_platforms(sanitizer=sanitizer)
elif is_deploy:
- default_platforms = self.select_platforms(architecture='arm',
- compiler='gcc_frc',
+ default_platforms = self.select_platforms(architecture='arm_frc',
+ compiler='gcc',
debug=False)
else:
default_platforms = self.select_platforms(debug=False)
@@ -578,13 +543,8 @@
if platforms & pie_sanitizers:
to_download.add(architecture + '-fPIE')
- frc_platforms = self.select_platforms(architecture=architecture,
- compiler='gcc_frc')
- if platforms & frc_platforms:
- to_download.add(architecture + '_frc')
-
if platforms & (self.select_platforms(architecture=architecture) -
- pie_sanitizers - frc_platforms):
+ pie_sanitizers):
to_download.add(architecture)
for download_target in to_download:
@@ -651,14 +611,13 @@
packages.add('clang-3.5')
packages.add('clang-format-3.5')
for platform in platforms:
- if (platform.compiler() == 'clang' or platform.compiler() == 'gcc_4.8' or
- platform.compiler() == 'clang_frc'):
+ if platform.compiler() == 'clang' or platform.compiler() == 'gcc_4.8':
packages.add('clang-3.5')
if platform.compiler() == 'gcc_4.8':
packages.add('libcloog-isl3:amd64')
if is_deploy:
packages.add('openssh-client')
- elif platform.compiler() == 'gcc_frc' or platform.compiler() == 'clang_frc':
+ elif platform.architecture == 'arm_frc':
packages.add('gcc-4.9-arm-frc-linux-gnueabi')
packages.add('g++-4.9-arm-frc-linux-gnueabi')
@@ -933,7 +892,7 @@
'-DSANITIZER=%s' % platform.sanitizer(),
'-DEXTERNALS_EXTRA=%s' %
('-fPIE' if platform.sanitizer() in PrimeProcessor.PIE_SANITIZERS
- else ('_frc' if platform.compiler().endswith('_frc') else ''))) +
+ else '')) +
processor.extra_gyp_flags() + (args.main_gyp,),
stdin=subprocess.PIPE)
gyp.communicate(("""
diff --git a/aos/common/controls/controls.gyp b/aos/common/controls/controls.gyp
index e24a121..4e0bc79 100644
--- a/aos/common/controls/controls.gyp
+++ b/aos/common/controls/controls.gyp
@@ -1,6 +1,27 @@
{
'targets': [
{
+ 'target_name': 'replay_control_loop',
+ 'type': 'static_library',
+ 'sources': [
+ #'replay_control_loop.h',
+ ],
+ 'dependencies': [
+ '<(AOS)/common/common.gyp:queues',
+ 'control_loop',
+ '<(AOS)/linux_code/logging/logging.gyp:log_replay',
+ '<(AOS)/common/logging/logging.gyp:queue_logging',
+ '<(AOS)/common/common.gyp:time',
+ ],
+ 'export_dependent_settings': [
+ '<(AOS)/common/common.gyp:queues',
+ 'control_loop',
+ '<(AOS)/linux_code/logging/logging.gyp:log_replay',
+ '<(AOS)/common/logging/logging.gyp:queue_logging',
+ '<(AOS)/common/common.gyp:time',
+ ],
+ },
+ {
'target_name': 'control_loop_test',
'type': 'static_library',
'sources': [
diff --git a/aos/common/controls/replay_control_loop.h b/aos/common/controls/replay_control_loop.h
new file mode 100644
index 0000000..4791973
--- /dev/null
+++ b/aos/common/controls/replay_control_loop.h
@@ -0,0 +1,179 @@
+#ifndef AOS_COMMON_CONTROLS_REPLAY_CONTROL_LOOP_H_
+#define AOS_COMMON_CONTROLS_REPLAY_CONTROL_LOOP_H_
+
+#include <fcntl.h>
+
+#include "aos/common/queue.h"
+#include "aos/common/controls/control_loop.h"
+#include "aos/linux_code/logging/log_replay.h"
+#include "aos/common/logging/queue_logging.h"
+#include "aos/common/time.h"
+#include "aos/common/macros.h"
+
+namespace aos {
+namespace controls {
+
+// Handles reading logged messages and running them through a control loop
+// again.
+// T should be a queue group suitable for use with ControlLoop.
+template <class T>
+class ControlLoopReplayer {
+ public:
+ typedef typename ControlLoop<T>::GoalType GoalType;
+ typedef typename ControlLoop<T>::PositionType PositionType;
+ typedef typename ControlLoop<T>::StatusType StatusType;
+ typedef typename ControlLoop<T>::OutputType OutputType;
+
+ // loop_group is where to send the messages out.
+ // process_name is the name of the process which wrote the log messages in the
+ // file(s).
+ ControlLoopReplayer(T *loop_group, const ::std::string &process_name)
+ : loop_group_(loop_group) {
+ // Clear out any old messages which will confuse the code.
+ loop_group_->status.FetchLatest();
+ loop_group_->output.FetchLatest();
+
+ replayer_.AddDirectQueueSender("wpilib_interface.DSReader",
+ "joystick_state", ::aos::joystick_state);
+ replayer_.AddDirectQueueSender("wpilib_interface.SensorReader",
+ "robot_state", ::aos::robot_state);
+
+ replayer_.AddHandler(
+ process_name, "position",
+ ::std::function<void(const PositionType &)>(::std::ref(position_)));
+ replayer_.AddHandler(
+ process_name, "output",
+ ::std::function<void(const OutputType &)>(::std::ref(output_)));
+ replayer_.AddHandler(
+ process_name, "status",
+ ::std::function<void(const StatusType &)>(::std::ref(status_)));
+ // The timing of goal messages doesn't matter, and we don't need to look
+ // back at them after running the loop.
+ replayer_.AddDirectQueueSender(process_name, "goal", loop_group_->goal);
+ }
+
+ // Replays messages from a file.
+ // filename can be straight from the command line; all sanity checking etc is
+ // handled by this function.
+ void ProcessFile(const char *filename);
+
+ private:
+ // A message handler which saves off messages and records whether it currently
+ // has a new one or not.
+ template <class S>
+ class CaptureMessage {
+ public:
+ CaptureMessage() {}
+
+ void operator()(const S &message) {
+ CHECK(!have_new_message_);
+ saved_message_ = message;
+ have_new_message_ = true;
+ }
+
+ const S &saved_message() const { return saved_message_; }
+ bool have_new_message() const { return have_new_message_; }
+ void clear_new_message() { have_new_message_ = false; }
+
+ private:
+ S saved_message_;
+ bool have_new_message_ = false;
+
+ DISALLOW_COPY_AND_ASSIGN(CaptureMessage);
+ };
+
+ // Runs through the file currently loaded in replayer_.
+ // Returns after going through the entire file.
+ void DoProcessFile();
+
+ T *const loop_group_;
+
+ CaptureMessage<PositionType> position_;
+ CaptureMessage<OutputType> output_;
+ CaptureMessage<StatusType> status_;
+
+ // The output that the loop sends for ZeroOutputs(). It might not actually be
+ // all fields zeroed, so we pick the first one and remember it to compare.
+ CaptureMessage<OutputType> zero_output_;
+
+ ::aos::logging::linux_code::LogReplayer replayer_;
+};
+
+template <class T>
+void ControlLoopReplayer<T>::ProcessFile(const char *filename) {
+ int fd;
+ if (strcmp(filename, "-") == 0) {
+ fd = STDIN_FILENO;
+ } else {
+ fd = open(filename, O_RDONLY);
+ }
+ if (fd == -1) {
+ PLOG(FATAL, "couldn't open file '%s' for reading", filename);
+ }
+
+ replayer_.OpenFile(fd);
+ DoProcessFile();
+ replayer_.CloseCurrentFile();
+
+ PCHECK(close(fd));
+}
+
+template <class T>
+void ControlLoopReplayer<T>::DoProcessFile() {
+ while (true) {
+ // Dig through messages until we get a status, which indicates the end of
+ // the control loop cycle.
+ while (!status_.have_new_message()) {
+ if (replayer_.ProcessMessage()) return;
+ }
+
+ // Send out the position message (after adjusting the time offset) so the
+ // loop will run right now.
+ CHECK(position_.have_new_message());
+ ::aos::time::OffsetToNow(position_.saved_message().sent_time);
+ {
+ auto position_message = loop_group_->position.MakeMessage();
+ *position_message = position_.saved_message();
+ CHECK(position_message.Send());
+ }
+ position_.clear_new_message();
+
+ // Wait for the loop to finish running.
+ loop_group_->status.FetchNextBlocking();
+
+ // Point out if the status is different.
+ if (!loop_group_->status->EqualsNoTime(status_.saved_message())) {
+ LOG_STRUCT(WARNING, "expected status", status_.saved_message());
+ LOG_STRUCT(WARNING, "got status", *loop_group_->status);
+ }
+ status_.clear_new_message();
+
+ // Point out if the output is different. This is a lot more complicated than
+ // for the status because there isn't always an output logged.
+ bool loop_new_output = loop_group_->output.FetchLatest();
+ if (output_.have_new_message()) {
+ if (!loop_new_output) {
+ LOG_STRUCT(WARNING, "no output, expected", output_.saved_message());
+ } else if (!loop_group_->output->EqualsNoTime(output_.saved_message())) {
+ LOG_STRUCT(WARNING, "expected output", output_.saved_message());
+ LOG_STRUCT(WARNING, "got output", *loop_group_->output);
+ }
+ } else if (loop_new_output) {
+ if (zero_output_.have_new_message()) {
+ if (!loop_group_->output->EqualsNoTime(zero_output_.saved_message())) {
+ LOG_STRUCT(WARNING, "expected null output",
+ zero_output_.saved_message());
+ LOG_STRUCT(WARNING, "got output", *loop_group_->output);
+ }
+ } else {
+ zero_output_(*loop_group_->output);
+ }
+ }
+ output_.clear_new_message();
+ }
+}
+
+} // namespace controls
+} // namespace aos
+
+#endif // AOS_COMMON_CONTROLS_REPLAY_CONTROL_LOOP_H_
diff --git a/aos/common/time.cc b/aos/common/time.cc
index 1c86811..7318afb 100644
--- a/aos/common/time.cc
+++ b/aos/common/time.cc
@@ -1,9 +1,13 @@
#include "aos/common/time.h"
#include <string.h>
+#include <inttypes.h>
+
+// We only use global_core from here, which is weak, so we don't really have a
+// dependency on it.
+#include "aos/linux_code/ipc_lib/shared_mem.h"
#include "aos/common/logging/logging.h"
-#include <inttypes.h>
#include "aos/common/mutex.h"
namespace aos {
@@ -33,7 +37,11 @@
PLOG(FATAL, "clock_gettime(%jd, %p) failed",
static_cast<uintmax_t>(clock), &temp);
}
- return Time(temp);
+
+ const timespec offset = (global_core == nullptr)
+ ? timespec{0, 0}
+ : global_core->mem_struct->time_offset;
+ return Time(temp) + Time(offset);
}
} // namespace
@@ -214,5 +222,12 @@
}
}
+void OffsetToNow(const Time &now) {
+ CHECK_NOTNULL(global_core);
+ global_core->mem_struct->time_offset.tv_nsec = 0;
+ global_core->mem_struct->time_offset.tv_sec = 0;
+ global_core->mem_struct->time_offset = (now - Time::Now()).ToTimespec();
+}
+
} // namespace time
} // namespace aos
diff --git a/aos/common/time.h b/aos/common/time.h
index 9c86168..9afe9a0 100644
--- a/aos/common/time.h
+++ b/aos/common/time.h
@@ -242,6 +242,14 @@
// Sleeps until clock is at the time represented by time.
void SleepUntil(const Time &time, clockid_t clock = Time::kDefaultClock);
+// Sets the global offset for all times so ::aos::time::Time::Now() will return
+// now.
+// There is no synchronization here, so this is only safe when only a single
+// task is running.
+// This is only allowed when the shared memory core infrastructure has been
+// initialized in this process.
+void OffsetToNow(const Time &now);
+
// RAII class that freezes Time::Now() (to avoid making large numbers of
// syscalls to find the real time).
class TimeFreezer {
diff --git a/aos/linux_code/ipc_lib/shared_mem.c b/aos/linux_code/ipc_lib/shared_mem.c
index 79b747a..65305ac 100644
--- a/aos/linux_code/ipc_lib/shared_mem.c
+++ b/aos/linux_code/ipc_lib/shared_mem.c
@@ -20,7 +20,7 @@
#define SIZEOFSHMSEG (4096 * 0x3000)
void init_shared_mem_core(aos_shm_core *shm_core) {
- clock_gettime(CLOCK_REALTIME, &shm_core->identifier);
+ memset(&shm_core->time_offset, 0 , sizeof(shm_core->time_offset));
memset(&shm_core->msg_alloc_lock, 0, sizeof(shm_core->msg_alloc_lock));
shm_core->queues.pointer = NULL;
memset(&shm_core->queues.lock, 0, sizeof(shm_core->queues.lock));
diff --git a/aos/linux_code/ipc_lib/shared_mem.h b/aos/linux_code/ipc_lib/shared_mem.h
index a33290c..423fd0c 100644
--- a/aos/linux_code/ipc_lib/shared_mem.h
+++ b/aos/linux_code/ipc_lib/shared_mem.h
@@ -11,7 +11,7 @@
extern "C" {
#endif
-extern struct aos_core *global_core;
+extern struct aos_core *global_core __attribute__((weak));
// Where the shared memory segment starts in each process's address space.
// Has to be the same in all of them so that stuff in shared memory
@@ -26,13 +26,17 @@
} aos_global_pointer;
typedef struct aos_shm_core_t {
- // clock_gettime(CLOCK_REALTIME, &identifier) gets called to identify
- // this shared memory area
- struct timespec identifier;
// Gets 0-initialized at the start (as part of shared memory) and
// the owner sets as soon as it finishes setting stuff up.
aos_condition creation_condition;
+ // An offset from CLOCK_REALTIME to times for all the code.
+ // This is currently only set to non-zero by the log replay code.
+ // There is no synchronization on this to avoid the overhead because it is
+ // only updated with proper memory barriers when only a single task is
+ // running.
+ struct timespec time_offset;
+
struct aos_mutex msg_alloc_lock;
void *msg_alloc;
diff --git a/aos/linux_code/logging/binary_log_writer.cc b/aos/linux_code/logging/binary_log_writer.cc
index 9943e7b..dbe61e0 100644
--- a/aos/linux_code/logging/binary_log_writer.cc
+++ b/aos/linux_code/logging/binary_log_writer.cc
@@ -108,7 +108,7 @@
fileindex, directory, previous);
}
-#ifdef AOS_COMPILER_gcc_frc
+#ifdef AOS_ARCHITECTURE_arm_frc
bool FoundThumbDrive(const char *path) {
FILE *mnt_fp = setmntent("/etc/mtab", "r");
if (mnt_fp == nullptr) {
@@ -148,7 +148,7 @@
int BinaryLogReaderMain() {
InitNRT();
-#ifdef AOS_COMPILER_gcc_frc
+#ifdef AOS_ARCHITECTURE_arm_frc
char folder[128];
{
diff --git a/aos/linux_code/logging/log_replay.cc b/aos/linux_code/logging/log_replay.cc
new file mode 100644
index 0000000..32991de
--- /dev/null
+++ b/aos/linux_code/logging/log_replay.cc
@@ -0,0 +1,44 @@
+#include "aos/linux_code/logging/log_replay.h"
+
+namespace aos {
+namespace logging {
+namespace linux_code {
+
+bool LogReplayer::ProcessMessage() {
+ const LogFileMessageHeader *message = reader_->ReadNextMessage(false);
+ if (message == nullptr) return true;
+ if (message->type != LogFileMessageHeader::MessageType::kStruct) return false;
+
+ const char *position = reinterpret_cast<const char *>(message + 1);
+
+ ::std::string process(position, message->name_size);
+ position += message->name_size;
+
+ uint32_t type_id;
+ memcpy(&type_id, position, sizeof(type_id));
+ position += sizeof(type_id);
+
+ uint32_t message_length;
+ memcpy(&message_length, position, sizeof(message_length));
+ position += sizeof(message_length);
+ ::std::string message_text(position, message_length);
+ position += message_length;
+
+ size_t split_index = message_text.find_first_of(':') + 2;
+ split_index = message_text.find_first_of(':', split_index) + 2;
+ message_text = message_text.substr(split_index);
+
+ auto handler = handlers_.find(Key(process, message_text));
+ if (handler == handlers_.end()) return false;
+
+ handler->second->HandleStruct(
+ ::aos::time::Time(message->time_sec, message->time_nsec), type_id,
+ position,
+ message->message_size -
+ (sizeof(type_id) + sizeof(message_length) + message_length));
+ return false;
+}
+
+} // namespace linux_code
+} // namespace logging
+} // namespace aos
diff --git a/aos/linux_code/logging/log_replay.h b/aos/linux_code/logging/log_replay.h
new file mode 100644
index 0000000..942f26f
--- /dev/null
+++ b/aos/linux_code/logging/log_replay.h
@@ -0,0 +1,164 @@
+#ifndef AOS_LINUX_CODE_LOGGING_LOG_REPLAY_H_
+#define AOS_LINUX_CODE_LOGGING_LOG_REPLAY_H_
+
+#include <unordered_map>
+#include <string>
+#include <functional>
+#include <memory>
+
+#include "aos/linux_code/logging/binary_log_file.h"
+#include "aos/common/queue.h"
+#include "aos/common/logging/logging.h"
+#include "aos/common/macros.h"
+#include "aos/linux_code/ipc_lib/queue.h"
+#include "aos/common/queue_types.h"
+
+namespace aos {
+namespace logging {
+namespace linux_code {
+
+// Manages pulling logged queue messages out of log files.
+//
+// Basic usage:
+// - Use the Add* methods to register handlers for various message sources.
+// - Call OpenFile to open a log file.
+// - Call ProcessMessage repeatedly until it returns true.
+//
+// This code could do something to adapt similar-but-not-identical
+// messages to the current versions, but currently it will LOG(FATAL) if any of
+// the messages don't match up exactly.
+class LogReplayer {
+ public:
+ LogReplayer() {}
+
+ // Gets ready to read messages from fd.
+ // Does not take ownership of fd.
+ void OpenFile(int fd) {
+ reader_.reset(new LogFileReader(fd));
+ }
+ // Closes the currently open file.
+ void CloseCurrentFile() { reader_.reset(); }
+ // Returns true if we have a file which is currently open.
+ bool HasCurrentFile() const { return reader_.get() != nullptr; }
+
+ // Processes a single message from the currently open file.
+ // Returns true if there are no more messages in the file.
+ // This will not call any of the handlers if the next message either has no
+ // registered handlers or is not a queue message.
+ bool ProcessMessage();
+
+ // Adds a handler for messages with a certain string from a certain process.
+ // T must be a Message with the same format as the messages generated by
+ // the .q files.
+ // LOG(FATAL)s for duplicate handlers.
+ template <class T>
+ void AddHandler(const ::std::string &process_name,
+ const ::std::string &log_message,
+ ::std::function<void(const T &message)> handler) {
+ CHECK(handlers_.emplace(
+ ::std::piecewise_construct,
+ ::std::forward_as_tuple(process_name, log_message),
+ ::std::forward_as_tuple(::std::unique_ptr<StructHandlerInterface>(
+ new TypedStructHandler<T>(handler)))).second);
+ }
+
+ // Adds a handler which takes messages and places them directly on a queue.
+ // T must be a Message with the same format as the messages generated by
+ // the .q files.
+ template <class T>
+ void AddDirectQueueSender(const ::std::string &process_name,
+ const ::std::string &log_message,
+ const ::aos::Queue<T> &queue) {
+ AddHandler(process_name, log_message,
+ ::std::function<void(const T &)>(
+ QueueDumpStructHandler<T>(queue.name())));
+ }
+
+ private:
+ // A generic handler of struct log messages.
+ class StructHandlerInterface {
+ public:
+ virtual ~StructHandlerInterface() {}
+
+ virtual void HandleStruct(::aos::time::Time log_time, uint32_t type_id,
+ const void *data, size_t data_size) = 0;
+ };
+
+ // Converts struct log messages to a message type and passes it to an
+ // ::std::function.
+ template <class T>
+ class TypedStructHandler : public StructHandlerInterface {
+ public:
+ TypedStructHandler(::std::function<void(const T &message)> handler)
+ : handler_(handler) {}
+
+ void HandleStruct(::aos::time::Time log_time, uint32_t type_id,
+ const void *data, size_t data_size) override {
+ CHECK_EQ(type_id, T::GetType()->id);
+ T message;
+ CHECK_EQ(data_size, T::Size());
+ CHECK_EQ(data_size, message.Deserialize(static_cast<const char *>(data)));
+ message.sent_time = log_time;
+ handler_(message);
+ }
+
+ private:
+ const ::std::function<void(T message)> handler_;
+ };
+
+ // A callable class which dumps messages straight to a queue.
+ template <class T>
+ class QueueDumpStructHandler {
+ public:
+ QueueDumpStructHandler(const ::std::string &queue_name)
+ : queue_(RawQueue::Fetch(queue_name.c_str(), sizeof(T), T::kHash,
+ T::kQueueLength)) {}
+
+ void operator()(const T &message) {
+ LOG_STRUCT(DEBUG, "re-sending", message);
+ void *raw_message = queue_->GetMessage();
+ CHECK_NOTNULL(raw_message);
+ memcpy(raw_message, &message, sizeof(message));
+ CHECK(queue_->WriteMessage(raw_message, RawQueue::kOverride));
+ }
+
+ private:
+ ::aos::RawQueue *const queue_;
+ };
+
+ // A key for specifying log messages to give to a certain handler.
+ struct Key {
+ Key(const ::std::string &process_name, const ::std::string &log_message)
+ : process_name(process_name), log_message(log_message) {}
+
+ ::std::string process_name;
+ ::std::string log_message;
+ };
+
+ struct KeyHash {
+ size_t operator()(const Key &key) const {
+ return string_hash(key.process_name) ^
+ (string_hash(key.log_message) << 1);
+ }
+
+ private:
+ const ::std::hash<::std::string> string_hash = ::std::hash<::std::string>();
+ };
+ struct KeyEqual {
+ bool operator()(const Key &a, const Key &b) const {
+ return a.process_name == b.process_name && a.log_message == b.log_message;
+ }
+ };
+
+ ::std::unordered_map<const Key, ::std::unique_ptr<StructHandlerInterface>,
+ KeyHash, KeyEqual> handlers_;
+ ::std::unique_ptr<LogFileReader> reader_;
+
+ DISALLOW_COPY_AND_ASSIGN(LogReplayer);
+};
+
+} // namespace linux_code
+} // namespace logging
+} // namespace aos
+
+#endif // AOS_LINUX_CODE_LOGGING_LOG_REPLAY_H_
diff --git a/aos/linux_code/logging/logging.gyp b/aos/linux_code/logging/logging.gyp
index 65939df..2ae9e0b 100644
--- a/aos/linux_code/logging/logging.gyp
+++ b/aos/linux_code/logging/logging.gyp
@@ -2,6 +2,19 @@
'targets': [
# linux_* is dealt with by aos/build/aos.gyp:logging.
{
+ 'target_name': 'log_replay',
+ 'type': 'static_library',
+ 'sources': [
+ 'log_replay.cc',
+ ],
+ 'dependencies': [
+ 'binary_log_file',
+ '<(AOS)/common/common.gyp:queues',
+ '<(AOS)/build/aos.gyp:logging',
+ '<(AOS)/linux_code/ipc_lib/ipc_lib.gyp:queue',
+ ],
+ },
+ {
'target_name': 'binary_log_writer',
'type': 'executable',
'sources': [
diff --git a/frc971/analysis/analysis.py b/frc971/analysis/analysis.py
index 66c5056..8152607 100755
--- a/frc971/analysis/analysis.py
+++ b/frc971/analysis/analysis.py
@@ -70,7 +70,8 @@
"""
for key in self.signal:
value = self.signal[key]
- pylab.plot(value.time, value.data, label=key[0] + ' ' + '.'.join(key[2]))
+ pylab.plot(value.time, value.data, label=key[0] + ' ' + key[1] + '.' + \
+ '.'.join(key[2]))
pylab.legend()
pylab.show()
diff --git a/frc971/control_loops/claw/claw.gyp b/frc971/control_loops/claw/claw.gyp
index b84b5c0..40fe95d 100644
--- a/frc971/control_loops/claw/claw.gyp
+++ b/frc971/control_loops/claw/claw.gyp
@@ -1,6 +1,21 @@
{
'targets': [
{
+ 'target_name': 'replay_claw',
+ 'type': 'executable',
+ 'variables': {
+ 'no_rsync': 1,
+ },
+ 'sources': [
+ 'replay_claw.cc',
+ ],
+ 'dependencies': [
+ 'claw_queue',
+ '<(AOS)/common/controls/controls.gyp:replay_control_loop',
+ '<(AOS)/linux_code/linux_code.gyp:init',
+ ],
+ },
+ {
'target_name': 'claw_queue',
'type': 'static_library',
'sources': ['claw.q'],
diff --git a/frc971/control_loops/claw/replay_claw.cc b/frc971/control_loops/claw/replay_claw.cc
new file mode 100644
index 0000000..00d3c6d
--- /dev/null
+++ b/frc971/control_loops/claw/replay_claw.cc
@@ -0,0 +1,24 @@
+#include "aos/common/controls/replay_control_loop.h"
+#include "aos/linux_code/init.h"
+
+#include "frc971/control_loops/claw/claw.q.h"
+
+// Reads one or more log files and sends out all the queue messages (in the
+// correct order and at the correct time) to feed a "live" claw process.
+
+int main(int argc, char **argv) {
+ if (argc <= 1) {
+ fprintf(stderr, "Need at least one file to replay!\n");
+ return EXIT_FAILURE;
+ }
+
+ ::aos::InitNRT();
+
+ ::aos::controls::ControlLoopReplayer<::frc971::control_loops::ClawQueue>
+ replayer(&::frc971::control_loops::claw_queue, "claw");
+ for (int i = 1; i < argc; ++i) {
+ replayer.ProcessFile(argv[i]);
+ }
+
+ ::aos::Cleanup();
+}
diff --git a/frc971/control_loops/drivetrain/drivetrain.gyp b/frc971/control_loops/drivetrain/drivetrain.gyp
index f1b28f3..fb32377 100644
--- a/frc971/control_loops/drivetrain/drivetrain.gyp
+++ b/frc971/control_loops/drivetrain/drivetrain.gyp
@@ -1,6 +1,21 @@
{
'targets': [
{
+ 'target_name': 'replay_drivetrain',
+ 'type': 'executable',
+ 'variables': {
+ 'no_rsync': 1,
+ },
+ 'sources': [
+ 'replay_drivetrain.cc',
+ ],
+ 'dependencies': [
+ 'drivetrain_queue',
+ '<(AOS)/common/controls/controls.gyp:replay_control_loop',
+ '<(AOS)/linux_code/linux_code.gyp:init',
+ ],
+ },
+ {
'target_name': 'drivetrain_queue',
'type': 'static_library',
'sources': ['drivetrain.q'],
diff --git a/frc971/control_loops/drivetrain/replay_drivetrain.cc b/frc971/control_loops/drivetrain/replay_drivetrain.cc
new file mode 100644
index 0000000..432efdc
--- /dev/null
+++ b/frc971/control_loops/drivetrain/replay_drivetrain.cc
@@ -0,0 +1,24 @@
+#include "aos/common/controls/replay_control_loop.h"
+#include "aos/linux_code/init.h"
+
+#include "frc971/control_loops/drivetrain/drivetrain.q.h"
+
+// Reads one or more log files and sends out all the queue messages (in the
+// correct order and at the correct time) to feed a "live" drivetrain process.
+
+int main(int argc, char **argv) {
+ if (argc <= 1) {
+ fprintf(stderr, "Need at least one file to replay!\n");
+ return EXIT_FAILURE;
+ }
+
+ ::aos::InitNRT();
+
+ ::aos::controls::ControlLoopReplayer<::frc971::control_loops::DrivetrainQueue>
+ replayer(&::frc971::control_loops::drivetrain_queue, "drivetrain");
+ for (int i = 1; i < argc; ++i) {
+ replayer.ProcessFile(argv[i]);
+ }
+
+ ::aos::Cleanup();
+}
diff --git a/frc971/control_loops/fridge/fridge.gyp b/frc971/control_loops/fridge/fridge.gyp
index 865d42d..aaf3153 100644
--- a/frc971/control_loops/fridge/fridge.gyp
+++ b/frc971/control_loops/fridge/fridge.gyp
@@ -1,6 +1,21 @@
{
'targets': [
{
+ 'target_name': 'replay_fridge',
+ 'type': 'executable',
+ 'variables': {
+ 'no_rsync': 1,
+ },
+ 'sources': [
+ 'replay_fridge.cc',
+ ],
+ 'dependencies': [
+ 'fridge_queue',
+ '<(AOS)/common/controls/controls.gyp:replay_control_loop',
+ '<(AOS)/linux_code/linux_code.gyp:init',
+ ],
+ },
+ {
'target_name': 'fridge_queue',
'type': 'static_library',
'sources': ['fridge.q'],
diff --git a/frc971/control_loops/fridge/replay_fridge.cc b/frc971/control_loops/fridge/replay_fridge.cc
new file mode 100644
index 0000000..87833ef
--- /dev/null
+++ b/frc971/control_loops/fridge/replay_fridge.cc
@@ -0,0 +1,24 @@
+#include "aos/common/controls/replay_control_loop.h"
+#include "aos/linux_code/init.h"
+
+#include "frc971/control_loops/fridge/fridge.q.h"
+
+// Reads one or more log files and sends out all the queue messages (in the
+// correct order and at the correct time) to feed a "live" fridge process.
+
+int main(int argc, char **argv) {
+ if (argc <= 1) {
+ fprintf(stderr, "Need at least one file to replay!\n");
+ return EXIT_FAILURE;
+ }
+
+ ::aos::InitNRT();
+
+ ::aos::controls::ControlLoopReplayer<::frc971::control_loops::FridgeQueue>
+ replayer(&::frc971::control_loops::fridge_queue, "fridge");
+ for (int i = 1; i < argc; ++i) {
+ replayer.ProcessFile(argv[i]);
+ }
+
+ ::aos::Cleanup();
+}
diff --git a/frc971/prime/prime.gyp b/frc971/prime/prime.gyp
index 1b19d7a..e30bd22 100644
--- a/frc971/prime/prime.gyp
+++ b/frc971/prime/prime.gyp
@@ -10,10 +10,13 @@
'../control_loops/control_loops.gyp:position_sensor_sim_test',
'../control_loops/drivetrain/drivetrain.gyp:drivetrain',
'../control_loops/drivetrain/drivetrain.gyp:drivetrain_lib_test',
+ '../control_loops/drivetrain/drivetrain.gyp:replay_drivetrain',
'../control_loops/fridge/fridge.gyp:fridge',
'../control_loops/fridge/fridge.gyp:fridge_lib_test',
+ '../control_loops/fridge/fridge.gyp:replay_fridge',
'../control_loops/claw/claw.gyp:claw',
'../control_loops/claw/claw.gyp:claw_lib_test',
+ '../control_loops/claw/claw.gyp:replay_claw',
'../autonomous/autonomous.gyp:auto',
'../frc971.gyp:joystick_reader',
'../zeroing/zeroing.gyp:zeroing_test',
diff --git a/output/.gitignore b/output/.gitignore
index b617a67..d675b14 100644
--- a/output/.gitignore
+++ b/output/.gitignore
@@ -2,14 +2,14 @@
/crio/
/flasher/
/compiled-*/
-/ip_base.txt
+/ip_address.txt
/ccache_dir/
/crio/
/crio-debug/
-/arm-[^-]*-[^-]*/
-/arm-[^-]*-debug-[^-]*/
+/arm_frc-[^-]*-[^-]*/
+/arm_frc-[^-]*-debug-[^-]*/
/amd64-[^-]*-[^-]*/
/amd64-[^-]*-debug-[^-]*/
/bot3-[^-]*-[^-]*-[^-]*/