Merge changes I631ff230,Ic97f5f67

* changes:
  Add a driver for the new IMU
  Fix bounds of SPI AutoRead sizes
diff --git a/WORKSPACE b/WORKSPACE
index 5f29700..d0d6347 100644
--- a/WORKSPACE
+++ b/WORKSPACE
@@ -50,6 +50,10 @@
     "//debian:python_gtk.bzl",
     python_gtk_debs = "files",
 )
+load(
+    "//debian:opencv_armhf.bzl",
+    opencv_armhf_debs = "files",
+)
 load("//debian:packages.bzl", "generate_repositories_for_debs")
 
 generate_repositories_for_debs(python_debs)
@@ -76,6 +80,8 @@
 
 generate_repositories_for_debs(python_gtk_debs)
 
+generate_repositories_for_debs(opencv_armhf_debs)
+
 http_archive(
     name = "python_repo",
     build_file = "@//debian:python.BUILD",
@@ -144,6 +150,17 @@
     url = "http://frc971.org/Build-Dependencies/gcc-linaro-7.4.1-2019.02-x86_64_arm-linux-gnueabihf.tar.xz",
 )
 
+# The main partition from https://downloads.raspberrypi.org/raspbian_lite/images/raspbian_lite-2019-09-30/2019-09-26-raspbian-buster-lite.zip.
+# The following folders are removed to make bazel happy with it:
+#   usr/share/ca-certificates
+# This copy command to make clang happy: `cp usr/lib/arm-linux-gnueabihf/*.o usr/lib`
+http_archive(
+    name = "armhf_debian_rootfs",
+    build_file = "@//:compilers/armhf_debian_rootfs.BUILD",
+    sha256 = "8c827bdb79615046ee3e13e85664e5d01286ca1721f7169341667a634e599eb6",
+    url = "http://frc971.org/Build-Dependencies/2019-09-26-raspbian-buster-lite_rootfs.tar.bz2",
+)
+
 new_git_repository(
     name = "python_gflags_repo",
     build_file = "@//debian:gflags.BUILD",
@@ -560,3 +577,11 @@
     sha256 = "91c98edee0c90a19992792c711dde4a6743af2d6d7e45b5079ec228fdf51ff11",
     urls = ["http://www.frc971.org/Build-Dependencies/small_sample_logfile.fbs"],
 )
+
+# OpenCV armhf (for raspberry pi)
+http_archive(
+    name = "opencv_armhf",
+    build_file = "@//debian:opencv.BUILD",
+    sha256 = "1dd496ad0947ed6ce5d89cbefcfa55ea15ccb5bf70fa6ad7701c62cf2fcdd657",
+    url = "http://www.frc971.org/Build-Dependencies/opencv_armhf_v3.tar.gz",
+)
diff --git a/aos/controls/polytope.h b/aos/controls/polytope.h
index c63166f..28f011e 100644
--- a/aos/controls/polytope.h
+++ b/aos/controls/polytope.h
@@ -109,13 +109,15 @@
 
 
 #ifdef __linux__
+
 template <int number_of_dimensions>
 class HPolytope : public Polytope<number_of_dimensions> {
  public:
   // Constructs a polytope given the H and k matrices.
-  HPolytope(Eigen::Ref<const Eigen::Matrix<double, Eigen::Dynamic,
-                                           number_of_dimensions>> H,
-            Eigen::Ref<const Eigen::Matrix<double, Eigen::Dynamic, 1>> k)
+  HPolytope(
+      Eigen::Ref<
+          const Eigen::Matrix<double, Eigen::Dynamic, number_of_dimensions>> H,
+      Eigen::Ref<const Eigen::Matrix<double, Eigen::Dynamic, 1>> k)
       : H_(H), k_(k), vertices_(CalculateVertices(H, k)) {}
 
   // This is an initialization function shared across all instantiations of this
@@ -133,6 +135,8 @@
     return k_;
   }
 
+  // NOTE: If you are getting bizarre errors that you are tracing back to the
+  // vertex matrix being funky, please check that you correctly called Init().
   Eigen::Matrix<double, number_of_dimensions, Eigen::Dynamic> Vertices()
       const override {
     return vertices_;
diff --git a/aos/dump_rtprio.cc b/aos/dump_rtprio.cc
index 12000f0..5d9a0b4 100644
--- a/aos/dump_rtprio.cc
+++ b/aos/dump_rtprio.cc
@@ -248,7 +248,7 @@
 
 int main() {
   ::aos::logging::Init();
-  ::aos::logging::AddImplementation(
+  ::aos::logging::SetImplementation(
       new ::aos::logging::StreamLogImplementation(stdout));
 
   const int pid_max = find_pid_max();
diff --git a/aos/events/logging/log_stats.cc b/aos/events/logging/log_stats.cc
index 9383fdd..2d9604f 100644
--- a/aos/events/logging/log_stats.cc
+++ b/aos/events/logging/log_stats.cc
@@ -79,7 +79,6 @@
     if (channel->name()->string_view().find(FLAGS_name) != std::string::npos) {
       // Add a record to the stats vector.
       channel_stats.push_back({channel});
-      it++;
       // Lambda to read messages and parse for information
       stats_event_loop->MakeRawWatcher(
           channel,
@@ -99,6 +98,7 @@
             // update the overall logfile statistics
             logfile_stats.logfile_length += context.size;
           });
+      it++;
       // TODO (Stephan): Frequency of messages per second
       // - Sliding window
       // - Max / Deviation
diff --git a/aos/ipc_lib/ipc_comparison.cc b/aos/ipc_lib/ipc_comparison.cc
index 1553159..cf5581e 100644
--- a/aos/ipc_lib/ipc_comparison.cc
+++ b/aos/ipc_lib/ipc_comparison.cc
@@ -955,7 +955,7 @@
   ::gflags::ParseCommandLineFlags(&argc, &argv, true);
 
   ::aos::InitNRT();
-  ::aos::logging::AddImplementation(
+  ::aos::logging::SetImplementation(
       new ::aos::logging::StreamLogImplementation(stdout));
 
   return ::aos::Main(argc, argv);
diff --git a/aos/ipc_lib/raw_queue_test.cc b/aos/ipc_lib/raw_queue_test.cc
index 3048e1b..1c02c53 100644
--- a/aos/ipc_lib/raw_queue_test.cc
+++ b/aos/ipc_lib/raw_queue_test.cc
@@ -994,52 +994,52 @@
 
   EXPECT_DEATH(
       {
-        logging::AddImplementation(new util::DeathTestLogImplementation());
+        logging::SetImplementation(new util::DeathTestLogImplementation());
         queue->WriteMessage(nullptr, RawQueue::kPeek);
       },
       ".*illegal write option.*");
   EXPECT_DEATH(
       {
-        logging::AddImplementation(new util::DeathTestLogImplementation());
+        logging::SetImplementation(new util::DeathTestLogImplementation());
         queue->WriteMessage(nullptr, RawQueue::kFromEnd);
       },
       ".*illegal write option.*");
   EXPECT_DEATH(
       {
-        logging::AddImplementation(new util::DeathTestLogImplementation());
+        logging::SetImplementation(new util::DeathTestLogImplementation());
         queue->WriteMessage(nullptr, RawQueue::kPeek | RawQueue::kFromEnd);
       },
       ".*illegal write option.*");
   EXPECT_DEATH(
       {
-        logging::AddImplementation(new util::DeathTestLogImplementation());
+        logging::SetImplementation(new util::DeathTestLogImplementation());
         queue->WriteMessage(nullptr, RawQueue::kNonBlock | RawQueue::kBlock);
       },
       ".*invalid write option.*");
 
   EXPECT_DEATH(
       {
-        logging::AddImplementation(new util::DeathTestLogImplementation());
+        logging::SetImplementation(new util::DeathTestLogImplementation());
         queue->ReadMessageIndex(
             RawQueue::kBlock | RawQueue::kFromEnd | RawQueue::kPeek, nullptr);
       },
       ".*ReadMessageIndex.*is not allowed.*");
   EXPECT_DEATH(
       {
-        logging::AddImplementation(new util::DeathTestLogImplementation());
+        logging::SetImplementation(new util::DeathTestLogImplementation());
         queue->ReadMessageIndex(RawQueue::kOverride, nullptr);
       },
       ".*illegal read option.*");
   EXPECT_DEATH(
       {
-        logging::AddImplementation(new util::DeathTestLogImplementation());
+        logging::SetImplementation(new util::DeathTestLogImplementation());
         queue->ReadMessageIndex(RawQueue::kOverride | RawQueue::kBlock,
                                 nullptr);
       },
       ".*illegal read option.*");
   EXPECT_DEATH(
       {
-        logging::AddImplementation(new util::DeathTestLogImplementation());
+        logging::SetImplementation(new util::DeathTestLogImplementation());
         queue->ReadMessage(RawQueue::kNonBlock | RawQueue::kBlock);
       },
       ".*invalid read option.*");
diff --git a/aos/logging/implementations.cc b/aos/logging/implementations.cc
index 89fc904..cfc14c0 100644
--- a/aos/logging/implementations.cc
+++ b/aos/logging/implementations.cc
@@ -1,16 +1,16 @@
 #include "aos/logging/implementations.h"
 
-#include <stdarg.h>
 #include <inttypes.h>
+#include <stdarg.h>
 
 #include <algorithm>
 #include <chrono>
 
+#include "absl/base/call_once.h"
 #include "aos/die.h"
+#include "aos/ipc_lib/queue.h"
 #include "aos/logging/printf_formats.h"
 #include "aos/time/time.h"
-#include "aos/ipc_lib/queue.h"
-#include "absl/base/call_once.h"
 
 namespace aos {
 namespace logging {
@@ -23,30 +23,18 @@
 // apply here (mostly the parts about being able to use AOS_LOG) because this is
 // the root one.
 class RootLogImplementation : public LogImplementation {
- public:
-  void have_other_implementation() { only_implementation_ = false; }
-
  protected:
   virtual ::aos::monotonic_clock::time_point monotonic_now() const {
     return ::aos::monotonic_clock::now();
   }
 
  private:
-  void set_next(LogImplementation *) override {
-    AOS_LOG(FATAL, "can't have a next logger from here\n");
-  }
-
-  __attribute__((format(GOOD_PRINTF_FORMAT_TYPE, 3, 0)))
-  void DoLog(log_level level, const char *format, va_list ap) override {
+  __attribute__((format(GOOD_PRINTF_FORMAT_TYPE, 3, 0))) void DoLog(
+      log_level level, const char *format, va_list ap) override {
     LogMessage message;
     internal::FillInMessage(level, monotonic_now(), format, ap, &message);
     internal::PrintMessage(stderr, message);
-    if (!only_implementation_) {
-      fputs("root logger got used, see stderr for message\n", stdout);
-    }
   }
-
-  bool only_implementation_ = true;
 };
 
 RootLogImplementation *root_implementation = nullptr;
@@ -63,15 +51,13 @@
   internal::global_top_implementation.store(implementation);
 }
 
-void NewContext() {
-  internal::Context::Delete();
-}
+void NewContext() { internal::Context::Delete(); }
 
 void DoInit() {
   SetGlobalImplementation(root_implementation = new RootLogImplementation());
 
-  if (pthread_atfork(NULL /*prepare*/, NULL /*parent*/,
-                     NewContext /*child*/) != 0) {
+  if (pthread_atfork(NULL /*prepare*/, NULL /*parent*/, NewContext /*child*/) !=
+      0) {
     AOS_LOG(FATAL, "pthread_atfork(NULL, NULL, %p) failed\n", NewContext);
   }
 }
@@ -142,22 +128,26 @@
   internal::PrintMessage(stream_, message);
 }
 
-void AddImplementation(LogImplementation *implementation) {
+void SetImplementation(LogImplementation *implementation, bool update_global) {
   internal::Context *context = internal::Context::Get();
 
-  if (implementation->next() != NULL) {
-    AOS_LOG(FATAL,
-            "%p already has a next implementation, but it's not"
-            " being used yet\n",
-            implementation);
+  if (implementation == nullptr) {
+    AOS_LOG(FATAL, "SetImplementation got invalid implementation");
   }
 
-  LogImplementation *old = context->implementation;
-  if (old != NULL) {
-    implementation->set_next(old);
+  context->implementation = implementation;
+  if (update_global) {
+    SetGlobalImplementation(implementation);
   }
-  SetGlobalImplementation(implementation);
-  root_implementation->have_other_implementation();
+}
+
+LogImplementation *SwapImplementation(LogImplementation *implementation) {
+  internal::Context *context = internal::Context::Get();
+
+  LogImplementation *old = context->implementation;
+  context->implementation = implementation;
+
+  return old;
 }
 
 void Init() {
@@ -165,13 +155,9 @@
   absl::call_once(once, DoInit);
 }
 
-void Load() {
-  internal::Context::Get();
-}
+void Load() { internal::Context::Get(); }
 
-void Cleanup() {
-  internal::Context::Delete();
-}
+void Cleanup() { internal::Context::Delete(); }
 
 namespace {
 
@@ -239,14 +225,26 @@
     return ::aos::monotonic_clock::now();
   }
 
-  __attribute__((format(GOOD_PRINTF_FORMAT_TYPE, 3, 0)))
-  void DoLog(log_level level, const char *format, va_list ap) override {
+  __attribute__((format(GOOD_PRINTF_FORMAT_TYPE, 3, 0))) void DoLog(
+      log_level level, const char *format, va_list ap) override {
     LogMessage *message = GetMessageOrDie();
     internal::FillInMessage(level, monotonic_now(), format, ap, message);
     Write(message);
   }
 };
 
+class CallbackLogImplementation : public HandleMessageLogImplementation {
+ public:
+  CallbackLogImplementation(
+      const ::std::function<void(const LogMessage &)> &callback)
+      : callback_(callback) {}
+
+ private:
+  void HandleMessage(const LogMessage &message) override { callback_(message); }
+
+  ::std::function<void(const LogMessage &)> callback_;
+};
+
 }  // namespace
 
 RawQueue *GetLoggingQueue() {
@@ -261,7 +259,14 @@
     Die("logging: couldn't fetch queue\n");
   }
 
-  AddImplementation(new LinuxQueueLogImplementation());
+  SetImplementation(new LinuxQueueLogImplementation());
+}
+
+void RegisterCallbackImplementation(
+    const ::std::function<void(const LogMessage &)> &callback,
+    bool update_global = true) {
+  Init();
+  SetImplementation(new CallbackLogImplementation(callback), update_global);
 }
 
 }  // namespace logging
diff --git a/aos/logging/implementations.h b/aos/logging/implementations.h
index 27c0472..78980ff 100644
--- a/aos/logging/implementations.h
+++ b/aos/logging/implementations.h
@@ -1,17 +1,17 @@
 #ifndef AOS_LOGGING_IMPLEMENTATIONS_H_
 #define AOS_LOGGING_IMPLEMENTATIONS_H_
 
+#include <limits.h>
+#include <stdarg.h>
+#include <stdint.h>
+#include <stdio.h>
+#include <string.h>
 #include <sys/types.h>
 #include <unistd.h>
-#include <stdint.h>
-#include <limits.h>
-#include <string.h>
-#include <stdio.h>
-#include <stdarg.h>
 
-#include <string>
-#include <functional>
 #include <atomic>
+#include <functional>
+#include <string>
 
 #include "aos/logging/context.h"
 #include "aos/logging/interface.h"
@@ -42,9 +42,7 @@
 
 // Contains all of the information about a given logging call.
 struct LogMessage {
-  enum class Type : uint8_t {
-    kString
-  };
+  enum class Type : uint8_t { kString };
 
   int32_t seconds, nseconds;
   // message_length is just the length of the actual data (which member depends
@@ -71,8 +69,7 @@
       int rows, cols;
       size_t string_length;
       // The message string and then the serialized matrix.
-      char
-          data[LOG_MESSAGE_LEN - sizeof(type) - sizeof(rows) - sizeof(cols)];
+      char data[LOG_MESSAGE_LEN - sizeof(type) - sizeof(rows) - sizeof(cols)];
     } matrix;
   };
 };
@@ -87,14 +84,16 @@
 
 // Returns a string representing level or "unknown".
 static inline const char *log_str(log_level level) {
-#define DECL_LEVEL(name, value) if (level == name) return #name;
+#define DECL_LEVEL(name, value) \
+  if (level == name) return #name;
   DECL_LEVELS;
 #undef DECL_LEVEL
   return "unknown";
 }
 // Returns the log level represented by str or LOG_UNKNOWN.
 static inline log_level str_log(const char *str) {
-#define DECL_LEVEL(name, value) if (!strcmp(str, #name)) return name;
+#define DECL_LEVEL(name, value) \
+  if (!strcmp(str, #name)) return name;
   DECL_LEVELS;
 #undef DECL_LEVEL
   return LOG_UNKNOWN;
@@ -109,8 +108,8 @@
   }
 
  private:
-  __attribute__((format(GOOD_PRINTF_FORMAT_TYPE, 3, 0)))
-  void DoLog(log_level level, const char *format, va_list ap) override;
+  __attribute__((format(GOOD_PRINTF_FORMAT_TYPE, 3, 0))) void DoLog(
+      log_level level, const char *format, va_list ap) override;
 
   virtual void HandleMessage(const LogMessage &message) = 0;
 };
@@ -133,7 +132,12 @@
 // when needed or by calling Load()).
 // The logging system takes ownership of implementation. It will delete it if
 // necessary, so it must be created with new.
-void AddImplementation(LogImplementation *implementation);
+void SetImplementation(LogImplementation *implementation,
+                       bool update_global = true);
+
+// Updates the log implementation for the current thread, returning the current
+// implementation.
+LogImplementation *SwapImplementation(LogImplementation *implementation);
 
 // Must be called at least once per process/load before anything else is
 // called. This function is safe to call multiple times from multiple
@@ -153,12 +157,15 @@
 // The caller takes ownership.
 RawQueue *GetLoggingQueue();
 
-// Calls AddImplementation to register the standard linux logging implementation
+// Calls SetImplementation to register the standard linux logging implementation
 // which sends the messages through a queue. This implementation relies on
 // another process(es) to read the log messages that it puts into the queue.
 // This function is usually called by aos::Init*.
 void RegisterQueueImplementation();
 
+void RegisterCallbackImplementation(
+    const ::std::function<void(const LogMessage &)> &callback);
+
 // This is where all of the code that is only used by actual LogImplementations
 // goes.
 namespace internal {
diff --git a/aos/logging/implementations_test.cc b/aos/logging/implementations_test.cc
index d97e165..a7b8b7d 100644
--- a/aos/logging/implementations_test.cc
+++ b/aos/logging/implementations_test.cc
@@ -1,17 +1,17 @@
+#include "aos/logging/implementations.h"
+
 #include <inttypes.h>
 
 #include <chrono>
 #include <string>
 
+#include "aos/logging/printf_formats.h"
+#include "aos/time/time.h"
 #include "gtest/gtest.h"
 
-#include "aos/logging/implementations.h"
-#include "aos/time/time.h"
-#include "aos/logging/printf_formats.h"
-
+using ::testing::AssertionFailure;
 using ::testing::AssertionResult;
 using ::testing::AssertionSuccess;
-using ::testing::AssertionFailure;
 
 namespace aos {
 namespace logging {
@@ -51,8 +51,8 @@
  protected:
   AssertionResult WasAnythingLogged() {
     if (log_implementation->used()) {
-      return AssertionSuccess() << "read message '" <<
-          log_implementation->message().message << "'";
+      return AssertionSuccess() << "read message '"
+                                << log_implementation->message().message << "'";
     }
     return AssertionFailure();
   }
@@ -61,9 +61,9 @@
       return AssertionFailure() << "nothing was logged";
     }
     if (log_implementation->message().level != level) {
-      return AssertionFailure() << "a message with level " <<
-          log_str(log_implementation->message().level) <<
-          " was logged instead of " << log_str(level);
+      return AssertionFailure() << "a message with level "
+                                << log_str(log_implementation->message().level)
+                                << " was logged instead of " << log_str(level);
     }
     internal::Context *context = internal::Context::Get();
     if (log_implementation->message().source != context->source) {
@@ -73,17 +73,16 @@
     }
     if (log_implementation->message().name_length != context->name_size ||
         memcmp(log_implementation->message().name, context->name,
-               context->name_size) !=
-            0) {
+               context->name_size) != 0) {
       AOS_LOG(FATAL, "got a message from %.*s, but we're %s\n",
               static_cast<int>(log_implementation->message().name_length),
               log_implementation->message().name, context->name);
     }
-    if (strstr(log_implementation->message().message, message.c_str())
-        == NULL) {
-      return AssertionFailure() << "got a message of '" <<
-          log_implementation->message().message <<
-          "' but expected it to contain '" << message << "'";
+    if (strstr(log_implementation->message().message, message.c_str()) ==
+        NULL) {
+      return AssertionFailure()
+             << "got a message of '" << log_implementation->message().message
+             << "' but expected it to contain '" << message << "'";
     }
 
     return AssertionSuccess() << log_implementation->message().message;
@@ -96,14 +95,12 @@
       first = false;
 
       Init();
-      AddImplementation(log_implementation = new TestLogImplementation());
+      SetImplementation(log_implementation = new TestLogImplementation());
     }
 
     log_implementation->reset_used();
   }
-  void TearDown() override {
-    Cleanup();
-  }
+  void TearDown() override { Cleanup(); }
 
   static TestLogImplementation *log_implementation;
 };
@@ -169,7 +166,7 @@
 
 TEST_F(LoggingTest, Timing) {
   // For writing only.
-  //static const long kTimingCycles = 5000000;
+  // static const long kTimingCycles = 5000000;
   static const long kTimingCycles = 5000;
 
   monotonic_clock::time_point start = monotonic_clock::now();
diff --git a/aos/logging/interface.cc b/aos/logging/interface.cc
index b72f8e1..e6e9203 100644
--- a/aos/logging/interface.cc
+++ b/aos/logging/interface.cc
@@ -4,8 +4,8 @@
 #include <stdio.h>
 #include <string.h>
 
-#include <type_traits>
 #include <functional>
+#include <type_traits>
 
 #include "aos/die.h"
 #include "aos/logging/context.h"
@@ -21,8 +21,8 @@
   const int ret = vsnprintf(output, size, format, ap);
   typedef ::std::common_type<int, size_t>::type RetType;
   if (ret < 0) {
-    AOS_PLOG(FATAL, "vsnprintf(%p, %zd, %s, args) failed",
-         output, size, format);
+    AOS_PLOG(FATAL, "vsnprintf(%p, %zd, %s, args) failed", output, size,
+             format);
   } else if (static_cast<RetType>(ret) >= static_cast<RetType>(size)) {
     // Overwrite the '\0' at the end of the existing data and
     // copy in the one on the end of continued.
@@ -32,30 +32,22 @@
 }
 
 void RunWithCurrentImplementation(
-    int levels, ::std::function<void(LogImplementation *)> function) {
+    ::std::function<void(LogImplementation *)> function) {
   Context *context = Context::Get();
 
-  LogImplementation *const top_implementation = context->implementation;
-  LogImplementation *new_implementation = top_implementation;
-  LogImplementation *implementation = NULL;
-  for (int i = 0; i < levels; ++i) {
-    implementation = new_implementation;
-    if (new_implementation == NULL) {
-      Die("no logging implementation to use\n");
-    }
-    new_implementation = new_implementation->next();
+  LogImplementation *const implementation = context->implementation;
+  if (implementation == NULL) {
+    Die("no logging implementation to use\n");
   }
-  context->implementation = new_implementation;
   function(implementation);
-  context->implementation = top_implementation;
 }
 
 }  // namespace internal
 
 using internal::Context;
 
-void LogImplementation::DoVLog(log_level level, const char *format, va_list ap,
-                               int levels) {
+void LogImplementation::DoVLog(log_level level, const char *format,
+                               va_list ap) {
   auto log_impl = [&](LogImplementation *implementation) {
     va_list ap1;
     va_copy(ap1, ap);
@@ -66,11 +58,11 @@
       VDie(format, ap);
     }
   };
-  internal::RunWithCurrentImplementation(levels, ::std::ref(log_impl));
+  internal::RunWithCurrentImplementation(::std::ref(log_impl));
 }
 
 void VLog(log_level level, const char *format, va_list ap) {
-  LogImplementation::DoVLog(level, format, ap, 1);
+  LogImplementation::DoVLog(level, format, ap);
 }
 
 void VCork(int line, const char *function, const char *format, va_list ap) {
@@ -101,9 +93,8 @@
 
   VCork(line, function, format, ap);
 
-  log_do(level, "%s: %d-%d: %s: %s", file,
-         context->cork_data.line_min, context->cork_data.line_max, function,
-         context->cork_data.message);
+  log_do(level, "%s: %d-%d: %s: %s", file, context->cork_data.line_min,
+         context->cork_data.line_max, function, context->cork_data.message);
 
   context->cork_data.Reset();
 }
diff --git a/aos/logging/interface.h b/aos/logging/interface.h
index 92f0b10..3695e40 100644
--- a/aos/logging/interface.h
+++ b/aos/logging/interface.h
@@ -3,8 +3,8 @@
 
 #include <stdarg.h>
 
-#include <string>
 #include <functional>
+#include <string>
 
 #include "aos/logging/logging.h"
 #include "aos/macros.h"
@@ -38,24 +38,19 @@
 // overriden methods may end up logging through a given implementation's DoLog.
 class LogImplementation {
  public:
-  LogImplementation() : next_(NULL) {}
+  LogImplementation() {}
 
-  // The one that this one's implementation logs to.
-  // NULL means that there is no next one.
-  LogImplementation *next() { return next_; }
-  // Virtual in case a subclass wants to perform checks. There will be a valid
-  // logger other than this one available while this is called.
-  virtual void set_next(LogImplementation *next) { next_ = next; }
+  virtual ~LogImplementation() {}
 
   virtual bool fill_type_cache() { return true; }
 
  protected:
   // Actually logs the given message. Implementations should somehow create a
   // LogMessage and then call internal::FillInMessage.
-  __attribute__((format(GOOD_PRINTF_FORMAT_TYPE, 3, 0)))
-  virtual void DoLog(log_level level, const char *format, va_list ap) = 0;
-  __attribute__((format(GOOD_PRINTF_FORMAT_TYPE, 3, 4)))
-  void DoLogVariadic(log_level level, const char *format, ...) {
+  __attribute__((format(GOOD_PRINTF_FORMAT_TYPE, 3, 0))) virtual void DoLog(
+      log_level level, const char *format, va_list ap) = 0;
+  __attribute__((format(GOOD_PRINTF_FORMAT_TYPE, 3, 4))) void DoLogVariadic(
+      log_level level, const char *format, ...) {
     va_list ap;
     va_start(ap, format);
     DoLog(level, format, ap);
@@ -66,12 +61,10 @@
   // These functions call similar methods on the "current" LogImplementation or
   // Die if they can't find one.
   // levels is how many LogImplementations to not use off the stack.
-  static void DoVLog(log_level, const char *format, va_list ap, int levels)
+  static void DoVLog(log_level, const char *format, va_list ap)
       __attribute__((format(GOOD_PRINTF_FORMAT_TYPE, 2, 0)));
 
   friend void VLog(log_level, const char *, va_list);
-
-  LogImplementation *next_;
 };
 
 namespace internal {
diff --git a/aos/logging/log_displayer.cc b/aos/logging/log_displayer.cc
index d3e5728..fa8fe6b 100644
--- a/aos/logging/log_displayer.cc
+++ b/aos/logging/log_displayer.cc
@@ -154,7 +154,7 @@
   int32_t source_pid = -1;
 
   ::aos::logging::Init();
-  ::aos::logging::AddImplementation(
+  ::aos::logging::SetImplementation(
       new ::aos::logging::StreamLogImplementation(stdout));
 
   while (true) {
diff --git a/aos/mutex/mutex_test.cc b/aos/mutex/mutex_test.cc
index 53b47ca..e7bd617 100644
--- a/aos/mutex/mutex_test.cc
+++ b/aos/mutex/mutex_test.cc
@@ -72,7 +72,7 @@
   test_mutex_.Unlock();
   EXPECT_DEATH(
       {
-        logging::AddImplementation(new util::DeathTestLogImplementation());
+        logging::SetImplementation(new util::DeathTestLogImplementation());
         test_mutex_.Unlock();
       },
       ".*multiple unlock.*");
@@ -83,7 +83,7 @@
   logging::Init();
   EXPECT_DEATH(
       {
-        logging::AddImplementation(new util::DeathTestLogImplementation());
+        logging::SetImplementation(new util::DeathTestLogImplementation());
         test_mutex_.Unlock();
       },
       ".*multiple unlock.*");
@@ -93,7 +93,7 @@
 TEST_F(MutexDeathTest, RepeatLock) {
   EXPECT_DEATH(
       {
-        logging::AddImplementation(new util::DeathTestLogImplementation());
+        logging::SetImplementation(new util::DeathTestLogImplementation());
         ASSERT_FALSE(test_mutex_.Lock());
         ASSERT_FALSE(test_mutex_.Lock());
       },
@@ -297,7 +297,7 @@
   });
   EXPECT_DEATH(
       {
-        logging::AddImplementation(new util::DeathTestLogImplementation());
+        logging::SetImplementation(new util::DeathTestLogImplementation());
         MutexLocker locker(mutex);
       },
       ".*previous owner of mutex [^ ]+ died.*");
diff --git a/aos/network/message_bridge_client_lib.cc b/aos/network/message_bridge_client_lib.cc
index 3652db2..39c3d17 100644
--- a/aos/network/message_bridge_client_lib.cc
+++ b/aos/network/message_bridge_client_lib.cc
@@ -32,6 +32,7 @@
   CHECK(config->has_nodes()) << ": Config must have nodes to transfer.";
 
   flatbuffers::FlatBufferBuilder fbb;
+  fbb.ForceDefaults(1);
 
   flatbuffers::Offset<Node> node_offset = CopyFlatBuffer<Node>(my_node, &fbb);
   const std::string_view node_name = my_node->name()->string_view();
@@ -106,6 +107,7 @@
 aos::FlatbufferDetachedBuffer<aos::logger::MessageHeader>
 MakeMessageHeaderReply() {
   flatbuffers::FlatBufferBuilder fbb;
+  fbb.ForceDefaults(1);
   logger::MessageHeader::Builder message_header_builder(fbb);
   message_header_builder.add_channel_index(0);
   message_header_builder.add_monotonic_sent_time(0);
@@ -121,6 +123,7 @@
     const std::vector<std::string_view> &source_node_names,
     const Configuration *configuration) {
   flatbuffers::FlatBufferBuilder fbb;
+  fbb.ForceDefaults(1);
 
   std::vector<flatbuffers::Offset<ClientConnection>> connection_offsets;
   for (const std::string_view node_name : source_node_names) {
diff --git a/aos/testing/test_logging.cc b/aos/testing/test_logging.cc
index bc60081..1799cda 100644
--- a/aos/testing/test_logging.cc
+++ b/aos/testing/test_logging.cc
@@ -143,7 +143,7 @@
 
 void *DoEnableTestLogging() {
   logging::Init();
-  logging::AddImplementation(TestLogImplementation::GetInstance());
+  logging::SetImplementation(TestLogImplementation::GetInstance());
 
   ::testing::UnitTest::GetInstance()->listeners().Append(
       new MyTestEventListener());
diff --git a/aos/transaction/transaction_test.cc b/aos/transaction/transaction_test.cc
index f7551c8..5d1fe90 100644
--- a/aos/transaction/transaction_test.cc
+++ b/aos/transaction/transaction_test.cc
@@ -93,7 +93,7 @@
   logging::Init();
   EXPECT_DEATH(
       {
-        logging::AddImplementation(new util::DeathTestLogImplementation());
+        logging::SetImplementation(new util::DeathTestLogImplementation());
         for (int i = 0; i < 1000; ++i) {
           CreateWork(i);
         }
diff --git a/aos/vision/debug/debug_framework.cc b/aos/vision/debug/debug_framework.cc
index 7b3ad76..47e0856 100644
--- a/aos/vision/debug/debug_framework.cc
+++ b/aos/vision/debug/debug_framework.cc
@@ -151,7 +151,7 @@
 void DebugFrameworkMain(int argc, char **argv, FilterHarness *filter,
                         CameraParams camera_params) {
   ::aos::logging::Init();
-  ::aos::logging::AddImplementation(
+  ::aos::logging::SetImplementation(
       new ::aos::logging::StreamLogImplementation(stdout));
 
   gtk_init(&argc, &argv);
diff --git a/aos/vision/tools/camera_primer.cc b/aos/vision/tools/camera_primer.cc
index 1ac96fd..e1d5fc5 100644
--- a/aos/vision/tools/camera_primer.cc
+++ b/aos/vision/tools/camera_primer.cc
@@ -27,7 +27,7 @@
 // target_sender
 int main(int argc, char **argv) {
   ::aos::logging::Init();
-  ::aos::logging::AddImplementation(
+  ::aos::logging::SetImplementation(
       new ::aos::logging::StreamLogImplementation(stdout));
 
   aos::vision::CameraParams params;
diff --git a/aos/vision/tools/jpeg_vision_test.cc b/aos/vision/tools/jpeg_vision_test.cc
index 5267041..4ba290c 100644
--- a/aos/vision/tools/jpeg_vision_test.cc
+++ b/aos/vision/tools/jpeg_vision_test.cc
@@ -112,7 +112,7 @@
 
 int main(int argc, char *argv[]) {
   ::aos::logging::Init();
-  ::aos::logging::AddImplementation(
+  ::aos::logging::SetImplementation(
       new ::aos::logging::StreamLogImplementation(stdout));
   aos::events::EpollLoop loop;
   gtk_init(&argc, &argv);
diff --git a/compilers/armhf_debian_rootfs.BUILD b/compilers/armhf_debian_rootfs.BUILD
new file mode 100644
index 0000000..004b82a
--- /dev/null
+++ b/compilers/armhf_debian_rootfs.BUILD
@@ -0,0 +1,15 @@
+filegroup(
+    name = "sysroot_files",
+    srcs = glob(
+        include = [
+            "include/**",
+            "lib/**",
+            "usr/include/**",
+            "usr/lib/**",
+        ],
+        exclude = [
+            "usr/share/**",
+        ],
+    ),
+    visibility = ["//visibility:public"],
+)
diff --git a/compilers/linaro_linux_gcc.BUILD b/compilers/linaro_linux_gcc.BUILD
index aea261d..f2fae98 100644
--- a/compilers/linaro_linux_gcc.BUILD
+++ b/compilers/linaro_linux_gcc.BUILD
@@ -58,14 +58,15 @@
 
 filegroup(
     name = "compiler_pieces",
-    srcs = glob([
-        "arm-linux-gnueabihf/**",
-        "libexec/**",
-        "lib/gcc/arm-linux-gnueabihf/**",
-        "include/**",
-    ], exclude=["arm-linux-gnueabihf/libc/usr/include/linux/sctp.h"]) +
-    [
-        "@org_frc971//third_party/linux:sctp",
+    srcs = glob(
+        include = [
+            "arm-linux-gnueabihf/**",
+            "libexec/**",
+            "lib/gcc/arm-linux-gnueabihf/**",
+            "include/**",
+        ],
+    ) + [
+        "@armhf_debian_rootfs//:sysroot_files",
     ],
 )
 
diff --git a/debian/BUILD b/debian/BUILD
index 5c9d814..11f9349 100644
--- a/debian/BUILD
+++ b/debian/BUILD
@@ -48,6 +48,10 @@
     ":python_gtk.bzl",
     python_gtk_debs = "files",
 )
+load(
+    ":opencv_armhf.bzl",
+    opencv_armhf_debs = "files",
+)
 load("//debian:packages.bzl", "download_packages", "generate_deb_tarball")
 
 filegroup(
@@ -282,6 +286,13 @@
     files = python_gtk_debs,
 )
 
+# This list was generated with download_packages.py on armhf and then
+# hand-tweaked to get everything it needs.
+generate_deb_tarball(
+    name = "opencv_armhf_v3",
+    files = opencv_armhf_debs,
+)
+
 exports_files([
     "ssh_wrapper.sh",
 ])
diff --git a/debian/opencv.BUILD b/debian/opencv.BUILD
new file mode 100644
index 0000000..1915d25
--- /dev/null
+++ b/debian/opencv.BUILD
@@ -0,0 +1,258 @@
+load("@//tools/build_rules:select.bzl", "cpu_select")
+
+cc_library(
+    name = "opencv",
+    srcs = cpu_select({
+        "amd64": [
+        ],
+        "roborio": [
+        ],
+        "armhf": [
+            "usr/lib/arm-linux-gnueabihf/libopencv_core.so.3.2",
+            "usr/lib/arm-linux-gnueabihf/libopencv_features2d.so.3.2",
+            "usr/lib/arm-linux-gnueabihf/libopencv_imgproc.so.3.2",
+            "usr/lib/arm-linux-gnueabihf/libopencv_flann.so.3.2",
+            "usr/lib/arm-linux-gnueabihf/libopencv_highgui.so.3.2",
+            "usr/lib/arm-linux-gnueabihf/libopencv_videoio.so.3.2",
+            "usr/lib/arm-linux-gnueabihf/libopencv_imgcodecs.so.3.2",
+            "usr/lib/arm-linux-gnueabihf/libopencv_ml.so.3.2",
+            "usr/lib/arm-linux-gnueabihf/libtbb.so.2",
+            "usr/lib/arm-linux-gnueabihf/libgtk-3.so.0",
+            "usr/lib/arm-linux-gnueabihf/libgdk-3.so.0",
+            "usr/lib/arm-linux-gnueabihf/libpangocairo-1.0.so.0",
+            "usr/lib/arm-linux-gnueabihf/libpango-1.0.so.0",
+            "usr/lib/arm-linux-gnueabihf/libatk-1.0.so.0",
+            "usr/lib/arm-linux-gnueabihf/libgdcmDICT.so.2.8",
+            "usr/lib/arm-linux-gnueabihf/libgdcmCommon.so.2.8",
+            "usr/lib/arm-linux-gnueabihf/libgdcmIOD.so.2.8",
+            "usr/lib/arm-linux-gnueabihf/libgdcmMSFF.so.2.8",
+            "usr/lib/arm-linux-gnueabihf/libavutil.so.56",
+            "usr/lib/arm-linux-gnueabihf/libswscale.so.5",
+            "usr/lib/arm-linux-gnueabihf/libavresample.so.4",
+            "usr/lib/arm-linux-gnueabihf/libcairo-gobject.so.2",
+            "usr/lib/arm-linux-gnueabihf/libcairo.so.2",
+            "usr/lib/arm-linux-gnueabihf/libgdk_pixbuf-2.0.so.0",
+            "usr/lib/arm-linux-gnueabihf/libgio-2.0.so.0",
+            "usr/lib/arm-linux-gnueabihf/libgobject-2.0.so.0",
+            "usr/lib/arm-linux-gnueabihf/libglib-2.0.so.0",
+            "usr/lib/arm-linux-gnueabihf/libgthread-2.0.so.0",
+            "usr/lib/arm-linux-gnueabihf/libdc1394.so.22",
+            "usr/lib/arm-linux-gnueabihf/libgphoto2.so.6",
+            "usr/lib/arm-linux-gnueabihf/libgphoto2_port.so.12",
+            "usr/lib/arm-linux-gnueabihf/libavcodec.so.58",
+            "usr/lib/arm-linux-gnueabihf/libavformat.so.58",
+            "usr/lib/arm-linux-gnueabihf/libjpeg.so.62",
+            "usr/lib/arm-linux-gnueabihf/libwebp.so.6",
+            "usr/lib/arm-linux-gnueabihf/libpng16.so.16",
+            "usr/lib/arm-linux-gnueabihf/libtiff.so.5",
+            "usr/lib/arm-linux-gnueabihf/libImath-2_2.so.23",
+            "usr/lib/arm-linux-gnueabihf/libIlmImf-2_2.so.23",
+            "usr/lib/arm-linux-gnueabihf/libIex-2_2.so.23",
+            "usr/lib/arm-linux-gnueabihf/libHalf.so.23",
+            "usr/lib/arm-linux-gnueabihf/libIlmThread-2_2.so.23",
+            "usr/lib/libgdal.so.20",
+            "usr/lib/arm-linux-gnueabihf/libgdcmDSED.so.2.8",
+            "usr/lib/arm-linux-gnueabihf/libgmodule-2.0.so.0",
+            "usr/lib/arm-linux-gnueabihf/libX11.so.6",
+            "usr/lib/arm-linux-gnueabihf/libXi.so.6",
+            "usr/lib/arm-linux-gnueabihf/libXcomposite.so.1",
+            "usr/lib/arm-linux-gnueabihf/libXdamage.so.1",
+            "usr/lib/arm-linux-gnueabihf/libXfixes.so.3",
+            "usr/lib/arm-linux-gnueabihf/libatk-bridge-2.0.so.0",
+            "usr/lib/arm-linux-gnueabihf/libxkbcommon.so.0",
+            "usr/lib/arm-linux-gnueabihf/libwayland-cursor.so.0",
+            "usr/lib/arm-linux-gnueabihf/libwayland-egl.so.1",
+            "usr/lib/arm-linux-gnueabihf/libwayland-client.so.0",
+            "usr/lib/arm-linux-gnueabihf/libepoxy.so.0",
+            "usr/lib/arm-linux-gnueabihf/libharfbuzz.so.0",
+            "usr/lib/arm-linux-gnueabihf/libpangoft2-1.0.so.0",
+            "usr/lib/arm-linux-gnueabihf/libfontconfig.so.1",
+            "usr/lib/arm-linux-gnueabihf/libfreetype.so.6",
+            "usr/lib/arm-linux-gnueabihf/libXinerama.so.1",
+            "usr/lib/arm-linux-gnueabihf/libXrandr.so.2",
+            "usr/lib/arm-linux-gnueabihf/libXcursor.so.1",
+            "usr/lib/arm-linux-gnueabihf/libXext.so.6",
+            "usr/lib/arm-linux-gnueabihf/libthai.so.0",
+            "usr/lib/arm-linux-gnueabihf/libfribidi.so.0",
+            "usr/lib/arm-linux-gnueabihf/libcrypto.so.1.1",
+            "lib/arm-linux-gnueabihf/libexpat.so.1",
+            "usr/lib/arm-linux-gnueabihf/libgdcmjpeg8.so.2.8",
+            "usr/lib/arm-linux-gnueabihf/libgdcmjpeg12.so.2.8",
+            "usr/lib/arm-linux-gnueabihf/libgdcmjpeg16.so.2.8",
+            "usr/lib/arm-linux-gnueabihf/libopenjp2.so.7",
+            "usr/lib/arm-linux-gnueabihf/libCharLS.so.2",
+            "lib/arm-linux-gnueabihf/libuuid.so.1",
+            "usr/lib/arm-linux-gnueabihf/libjson-c.so.3",
+            "usr/lib/arm-linux-gnueabihf/libva-drm.so.2",
+            "usr/lib/arm-linux-gnueabihf/libva.so.2",
+            "usr/lib/arm-linux-gnueabihf/libva-x11.so.2",
+            "usr/lib/arm-linux-gnueabihf/libvdpau.so.1",
+            "usr/lib/arm-linux-gnueabihf/libdrm.so.2",
+            "usr/lib/arm-linux-gnueabihf/libpixman-1.so.0",
+            "usr/lib/arm-linux-gnueabihf/libxcb-shm.so.0",
+            "usr/lib/arm-linux-gnueabihf/libxcb.so.1",
+            "usr/lib/arm-linux-gnueabihf/libxcb-render.so.0",
+            "usr/lib/arm-linux-gnueabihf/libXrender.so.1",
+            "lib/arm-linux-gnueabihf/libmount.so.1",
+            "usr/lib/arm-linux-gnueabihf/libffi.so.6",
+            "usr/lib/arm-linux-gnueabihf/libraw1394.so.11",
+            "lib/arm-linux-gnueabihf/libusb-1.0.so.0",
+            "usr/lib/arm-linux-gnueabihf/libltdl.so.7",
+            "usr/lib/arm-linux-gnueabihf/libexif.so.12",
+            "usr/lib/arm-linux-gnueabihf/libswresample.so.3",
+            "usr/lib/arm-linux-gnueabihf/libvpx.so.5",
+            "usr/lib/arm-linux-gnueabihf/libwebpmux.so.3",
+            "opt/vc/lib/libmmal_core.so",
+            "opt/vc/lib/libmmal_util.so",
+            "opt/vc/lib/libmmal_vc_client.so",
+            "opt/vc/lib/libbcm_host.so",
+            "usr/lib/arm-linux-gnueabihf/librsvg-2.so.2",
+            "usr/lib/arm-linux-gnueabihf/libzvbi.so.0",
+            "usr/lib/arm-linux-gnueabihf/libsnappy.so.1",
+            "usr/lib/arm-linux-gnueabihf/libaom.so.0",
+            "usr/lib/arm-linux-gnueabihf/libcodec2.so.0.8.1",
+            "usr/lib/arm-linux-gnueabihf/libgsm.so.1",
+            "usr/lib/arm-linux-gnueabihf/libmp3lame.so.0",
+            "usr/lib/arm-linux-gnueabihf/libopus.so.0",
+            "usr/lib/arm-linux-gnueabihf/libshine.so.3",
+            "usr/lib/arm-linux-gnueabihf/libspeex.so.1",
+            "usr/lib/arm-linux-gnueabihf/libtheoraenc.so.1",
+            "usr/lib/arm-linux-gnueabihf/libtheoradec.so.1",
+            "usr/lib/arm-linux-gnueabihf/libtwolame.so.0",
+            "usr/lib/arm-linux-gnueabihf/libvorbis.so.0",
+            "usr/lib/arm-linux-gnueabihf/libvorbisenc.so.2",
+            "usr/lib/arm-linux-gnueabihf/libwavpack.so.1",
+            "usr/lib/arm-linux-gnueabihf/libx264.so.155",
+            "usr/lib/arm-linux-gnueabihf/libx265.so.165",
+            "usr/lib/arm-linux-gnueabihf/libxvidcore.so.4",
+            "usr/lib/arm-linux-gnueabihf/libxml2.so.2",
+            "usr/lib/arm-linux-gnueabihf/libgme.so.0",
+            "usr/lib/arm-linux-gnueabihf/libopenmpt.so.0",
+            "usr/lib/arm-linux-gnueabihf/libchromaprint.so.1",
+            "usr/lib/arm-linux-gnueabihf/libbluray.so.2",
+            "usr/lib/arm-linux-gnueabihf/libgnutls.so.30",
+            "usr/lib/arm-linux-gnueabihf/libssh-gcrypt.so.4",
+            "usr/lib/arm-linux-gnueabihf/libzstd.so.1",
+            "usr/lib/arm-linux-gnueabihf/libjbig.so.0",
+            "usr/lib/libarmadillo.so.9",
+            "usr/lib/arm-linux-gnueabihf/libproj.so.13",
+            "usr/lib/arm-linux-gnueabihf/libpoppler.so.82",
+            "usr/lib/arm-linux-gnueabihf/libfreexl.so.1",
+            "usr/lib/arm-linux-gnueabihf/libqhull.so.7",
+            "usr/lib/arm-linux-gnueabihf/libgeos_c.so.1",
+            "usr/lib/arm-linux-gnueabihf/libepsilon.so.1",
+            "usr/lib/arm-linux-gnueabihf/libodbc.so.2",
+            "usr/lib/arm-linux-gnueabihf/libodbcinst.so.2",
+            "usr/lib/arm-linux-gnueabihf/libkmlbase.so.1",
+            "usr/lib/arm-linux-gnueabihf/libkmldom.so.1",
+            "usr/lib/arm-linux-gnueabihf/libkmlengine.so.1",
+            "usr/lib/arm-linux-gnueabihf/libkmlxsd.so.1",
+            "usr/lib/arm-linux-gnueabihf/libkmlregionator.so.1",
+            "usr/lib/arm-linux-gnueabihf/libxerces-c-3.2.so",
+            "usr/lib/arm-linux-gnueabihf/libnetcdf.so.13",
+            "usr/lib/arm-linux-gnueabihf/libhdf5_serial_hl.so.100",
+            "usr/lib/arm-linux-gnueabihf/libsz.so.2",
+            "usr/lib/arm-linux-gnueabihf/libhdf5_serial.so.103",
+            "usr/lib/libmfhdfalt.so.0",
+            "usr/lib/libdfalt.so.0",
+            "usr/lib/libogdi.so.3.2",
+            "usr/lib/arm-linux-gnueabihf/libgif.so.7",
+            "usr/lib/arm-linux-gnueabihf/libgeotiff.so.2",
+            "usr/lib/arm-linux-gnueabihf/libpq.so.5",
+            "usr/lib/arm-linux-gnueabihf/libdapclient.so.6",
+            "usr/lib/arm-linux-gnueabihf/libdapserver.so.7",
+            "usr/lib/arm-linux-gnueabihf/libdap.so.25",
+            "usr/lib/arm-linux-gnueabihf/libspatialite.so.7",
+            "usr/lib/arm-linux-gnueabihf/libcurl-gnutls.so.4",
+            "usr/lib/arm-linux-gnueabihf/libfyba.so.0",
+            "usr/lib/arm-linux-gnueabihf/libfygm.so.0",
+            "usr/lib/arm-linux-gnueabihf/libfyut.so.0",
+            "usr/lib/arm-linux-gnueabihf/libmariadb.so.3",
+            "lib/arm-linux-gnueabihf/libdbus-1.so.3",
+            "usr/lib/arm-linux-gnueabihf/libatspi.so.0",
+            "usr/lib/arm-linux-gnueabihf/libgraphite2.so.3",
+            "usr/lib/arm-linux-gnueabihf/libdatrie.so.1",
+            "usr/lib/arm-linux-gnueabihf/libXau.so.6",
+            "usr/lib/arm-linux-gnueabihf/libXdmcp.so.6",
+            "lib/arm-linux-gnueabihf/libblkid.so.1",
+            "lib/arm-linux-gnueabihf/libudev.so.1",
+            "usr/lib/arm-linux-gnueabihf/libsoxr.so.0",
+            "opt/vc/lib/libvcos.so",
+            "opt/vc/lib/libvchiq_arm.so",
+            "opt/vc/lib/libvcsm.so",
+            "usr/lib/arm-linux-gnueabihf/libcroco-0.6.so.3",
+            "usr/lib/arm-linux-gnueabihf/libogg.so.0",
+            "usr/lib/arm-linux-gnueabihf/libicui18n.so.63",
+            "usr/lib/arm-linux-gnueabihf/libicuuc.so.63",
+            "usr/lib/arm-linux-gnueabihf/libicudata.so.63",
+            "usr/lib/arm-linux-gnueabihf/libmpg123.so.0",
+            "usr/lib/arm-linux-gnueabihf/libvorbisfile.so.3",
+            "usr/lib/arm-linux-gnueabihf/libp11-kit.so.0",
+            "usr/lib/arm-linux-gnueabihf/libidn2.so.0",
+            "usr/lib/arm-linux-gnueabihf/libunistring.so.2",
+            "usr/lib/arm-linux-gnueabihf/libtasn1.so.6",
+            "usr/lib/arm-linux-gnueabihf/libnettle.so.6",
+            "usr/lib/arm-linux-gnueabihf/libhogweed.so.4",
+            "usr/lib/arm-linux-gnueabihf/libgmp.so.10",
+            "lib/arm-linux-gnueabihf/libgcrypt.so.20",
+            "usr/lib/arm-linux-gnueabihf/libgssapi_krb5.so.2",
+            "usr/lib/arm-linux-gnueabihf/blas/libblas.so.3",
+            "usr/lib/arm-linux-gnueabihf/lapack/liblapack.so.3",
+            "usr/lib/arm-linux-gnueabihf/libarpack.so.2",
+            "usr/lib/arm-linux-gnueabihf/libsuperlu.so.5",
+            "usr/lib/arm-linux-gnueabihf/libnss3.so",
+            "usr/lib/arm-linux-gnueabihf/libsmime3.so",
+            "usr/lib/arm-linux-gnueabihf/libnspr4.so",
+            "usr/lib/arm-linux-gnueabihf/liblcms2.so.2",
+            "usr/lib/arm-linux-gnueabihf/libgeos-3.7.1.so",
+            "usr/lib/arm-linux-gnueabihf/libpopt.so.0",
+            "usr/lib/arm-linux-gnueabihf/libminizip.so.1",
+            "usr/lib/arm-linux-gnueabihf/liburiparser.so.1",
+            "usr/lib/arm-linux-gnueabihf/libkmlconvenience.so.1",
+            "usr/lib/arm-linux-gnueabihf/libaec.so.0",
+            "usr/lib/arm-linux-gnueabihf/libssl.so.1.1",
+            "usr/lib/arm-linux-gnueabihf/libldap_r-2.4.so.2",
+            "usr/lib/arm-linux-gnueabihf/libsqlite3.so.0",
+            "usr/lib/arm-linux-gnueabihf/libnghttp2.so.14",
+            "usr/lib/arm-linux-gnueabihf/librtmp.so.1",
+            "usr/lib/arm-linux-gnueabihf/libssh2.so.1",
+            "usr/lib/arm-linux-gnueabihf/libpsl.so.5",
+            "usr/lib/arm-linux-gnueabihf/libkrb5.so.3",
+            "usr/lib/arm-linux-gnueabihf/libk5crypto.so.3",
+            "lib/arm-linux-gnueabihf/libcom_err.so.2",
+            "usr/lib/arm-linux-gnueabihf/liblber-2.4.so.2",
+            "lib/arm-linux-gnueabihf/libsystemd.so.0",
+            "usr/lib/arm-linux-gnueabihf/libbsd.so.0",
+            "lib/arm-linux-gnueabihf/libgpg-error.so.0",
+            "usr/lib/arm-linux-gnueabihf/libkrb5support.so.0",
+            "lib/arm-linux-gnueabihf/libkeyutils.so.1",
+            "usr/lib/arm-linux-gnueabihf/libgfortran.so.5",
+            "usr/lib/arm-linux-gnueabihf/libnssutil3.so",
+            "usr/lib/arm-linux-gnueabihf/libplc4.so",
+            "usr/lib/arm-linux-gnueabihf/libplds4.so",
+            "usr/lib/arm-linux-gnueabihf/libsasl2.so.2",
+            "usr/lib/arm-linux-gnueabihf/liblz4.so.1",
+            "lib/arm-linux-gnueabihf/libz.so.1",
+            "usr/lib/arm-linux-gnueabihf/libatomic.so.1",
+            "lib/arm-linux-gnueabihf/libselinux.so.1",
+            "lib/arm-linux-gnueabihf/libpcre.so.3",
+            "lib/arm-linux-gnueabihf/liblzma.so.5",
+            "lib/arm-linux-gnueabihf/libbz2.so.1.0",
+            "usr/lib/arm-linux-gnueabihf/libgomp.so.1",
+        ],
+        "cortex-m": [],
+    }),
+    hdrs = glob([
+        "usr/include/opencv/**",
+        "usr/include/opencv2/**",
+    ]),
+    includes = [
+        "usr/include",
+    ],
+    linkopts = [
+        "-ldl",
+        "-lnsl",
+        "-lresolv",
+    ],
+    visibility = ["//visibility:public"],
+)
diff --git a/debian/opencv_armhf.bzl b/debian/opencv_armhf.bzl
new file mode 100644
index 0000000..d48ee39
--- /dev/null
+++ b/debian/opencv_armhf.bzl
@@ -0,0 +1,380 @@
+files = {
+    "adduser_3.118_all.deb": "bd71dd1ab8dcd6005390708f23741d07f1913877affb7604dfd55f85d009aa2b",
+    "adwaita-icon-theme_3.30.1-1_all.deb": "698b3f0fa337bb36ea4fe072a37a32a1c81875db13042368677490bb087ccb93",
+    "coreutils_8.30-3_armhf.deb": "6a578920fe016ce628065f4c7a2639a6ffc3d52637e4b4f20a46ea76fcc05539",
+    "dconf-gsettings-backend_0.30.1-2_armhf.deb": "61bd02ba2da9e549e245ab2f5152baa2f27ea40fd0b0cde5873c63048feaa708",
+    "dconf-service_0.30.1-2_armhf.deb": "2ddc0eddff21e18afda15379d6414ffea5ea21a10e958c2ddc85625feab5cf70",
+    "fontconfig-config_2.13.1-2_all.deb": "9f5d34ba20eb156ef62d8126866a376be985c6a83fdcfb33f12cd83acac480c2",
+    "fontconfig_2.13.1-2_armhf.deb": "f2d17a9588f37d149e2777bdeb6acbc8bad203b814c8983b34ddee24ce316421",
+    "fonts-dejavu-core_2.37-1_all.deb": "58d21a255606191e6512cca51f32c4480e7a798945cc980623377696acfa3cfc",
+    "fonts-liberation_1.07.4-9_all.deb": "c936aebbfd0af7851399ae5ab08bb01744f5e3381f7678fb87cc77114f95ef53",
+    "gdal-data_2.4.0+dfsg-1_all.deb": "6e0fce32cf2e85ad2539482087d712bf2258d05e1838f3586a17ad2dc6bb7410",
+    "glib-networking-common_2.58.0-2_all.deb": "79831fd09fc96dc5729e8ed360563b05100d6bff70b03f3badf4e0f4759bb7ec",
+    "glib-networking-services_2.58.0-2_armhf.deb": "f901428286b7d10acac92159d6e0b9c3e09691dbe7d5ec4848f962491f0805d6",
+    "glib-networking_2.58.0-2_armhf.deb": "713ec6741147cc75468f8c16cda12185aa5b11ec79cfcc5786790febf1664aaf",
+    "gsettings-desktop-schemas_3.28.1-1_all.deb": "a75aed8781a781c4b819b2d1e952791b123580b1a02a4bb35fdbbba2e3ab8310",
+    "gtk-update-icon-cache_3.24.5-1+rpt2_armhf.deb": "8ed36c27190370354987249416dd4d19545a9e82e6020f271499bf72db067e8b",
+    "hicolor-icon-theme_0.17-2_all.deb": "20304d34b85a734ec1e4830badf3a3a70a5dc5f9c1afc0b2230ecd760c81b5e0",
+    "libaec0_1.0.2-1_armhf.deb": "6a8107a0253577259ccadd5c274664bf7cb7f0d6e67a694d7ff0da7af850a7e9",
+    "libaom0_1.0.0-3_armhf.deb": "eca4cfebdc6f8afcdf141e17097c37c4094da61bb220c5c6fdf7faf2ef9badd6",
+    "libarmadillo9_9.200.7+dfsg-1_armhf.deb": "30e835a8de5c42bcede50b98f10ac2292d2677de12fbb44349b4611cc8803ad8",
+    "libarpack2_3.7.0-2_armhf.deb": "a1a466a360902651a8722539a39521787afe10df067a47b8d6b070e3cdc35b60",
+    "libatk-bridge2.0-0_2.30.0-5_armhf.deb": "7c7900d671e6a04cada9f36162a7a2e411763b19b08a2cd6b81ec9c249d24445",
+    "libatk1.0-0_2.30.0-2_armhf.deb": "c2c32c8784a995894da3b76d9c5e9269f64cb2cf3c28971a1cbede53190605c2",
+    "libatk1.0-data_2.30.0-2_all.deb": "cf0c94611ff2245ae31d12a5a43971eb4ca628f42e93b0e003fd2c4c0de5e533",
+    "libatomic1_8.3.0-6+rpi1_armhf.deb": "f0c29af98f8358dc7d38a25f12a1f82ee8f876336ca459c25c08f42754b03865",
+    "libatspi2.0-0_2.30.0-7_armhf.deb": "05980a3e666d895433b8bd24306d67d2362ead58199253ce2e699f9dc4e8fa5d",
+    "libaudit-common_2.8.4-3_all.deb": "4e51dc247cde083528d410f525c6157b08be8b69511891cf972bc87025311371",
+    "libaudit1_2.8.4-3_armhf.deb": "25378f4115b0c71b352ea91095e85d5ddced7ceb7b46448abb8cb53a0bc02da9",
+    "libavahi-client3_0.7-4+b1_armhf.deb": "8555f041940308d4bb24558d8eed6c506287d95ea151814c9fb5572ef68b9797",
+    "libavahi-common-data_0.7-4+b1_armhf.deb": "064992922f2ff006f0dea327fb5c38e1328fe58e17eb55a6c2ceac4dc531c46d",
+    "libavahi-common3_0.7-4+b1_armhf.deb": "6d40047dc95d3d24c75763288367eb651cac1e93ad167d9c4cae6eb6ffc7fa59",
+    "libavcodec-dev_4.1.4-1+rpt1~deb10u1_armhf.deb": "3dfe147c25d8a852edd763722afc3e46c83a4c87092624af59f89cb8fcde20cc",
+    "libavcodec58_4.1.4-1+rpt1~deb10u1_armhf.deb": "38010fa402aad07d8d68a93f2f9518ba7ac997371e2ef424b8d7d083687a4ad4",
+    "libavformat-dev_4.1.4-1+rpt1~deb10u1_armhf.deb": "1d43679f4c7bd834ded1da3e82d1d09380dc8dcba169b57abdcee931e5fb5d23",
+    "libavformat58_4.1.4-1+rpt1~deb10u1_armhf.deb": "6e61fd1b2c724214def3c50eda0dfcd6ef4982dbf69fa8be33d91c77b25a39e8",
+    "libavresample-dev_4.1.4-1+rpt1~deb10u1_armhf.deb": "f733261644f240dc73c8fbcb85bec57797183df2f83a8a30245b2d8c80a2b8f9",
+    "libavresample4_4.1.4-1+rpt1~deb10u1_armhf.deb": "4d06883a40682cd2348d7bfbb657340bcb23f57bea5151554483ca99704d6d1e",
+    "libavutil-dev_4.1.4-1+rpt1~deb10u1_armhf.deb": "b2d034461daa7e3ab80bcfb5e0975ac243dc16dfe2c1526a3d658cc1f8aaf0b2",
+    "libavutil56_4.1.4-1+rpt1~deb10u1_armhf.deb": "e17b9be1e6ea97fe7ce2bcebc66e4d975501bb3f5408ebbd7b922ad739f22445",
+    "libblas3_3.8.0-2_armhf.deb": "4231b6d249eb60cb13e9fc65e4378bc9e7908407a3b49a6fcdd4e88eb5df9f3d",
+    "libblkid1_2.33.1-0.1_armhf.deb": "92801e35c3dbe24f2cb45d76e0837bc3928ecf2c1016ab07e827097634afa2c0",
+    "libbluray2_1.1.0-1_armhf.deb": "670a11be9d786fa07e472928893f78ba57140cd7caecfb1e396802d3ef8863dd",
+    "libbsd0_0.9.1-2_armhf.deb": "49164a38e2aa95b45e154dafa9974765554bad662c6ee865244a1b115d568b94",
+    "libcairo-gobject2_1.16.0-4+rpt1_armhf.deb": "e3fdc6667bb647e0804cbad1eb369949d7caa8a711592786b158d0bc62c576cf",
+    "libcairo2_1.16.0-4+rpt1_armhf.deb": "1f651a306f87337b4b493ba248a09813a6da8acf60bea8ad3669a06d9d522c9f",
+    "libcap-ng0_0.7.9-2_armhf.deb": "5218a1d5d264620e027391df53c66ddc3cb5715e0aa6065238118fa3515b5e7b",
+    "libcharls2_2.0.0+dfsg-1_armhf.deb": "fa95753cbe407167cf4959f9ac02ac5db804fbaad1aaf5e04fc0bc2839502ee4",
+    "libchromaprint1_1.4.3-3_armhf.deb": "ae8106e0e758a423a89443fd1abb180d5ba3e6b208c93fc4f4a619390331abd1",
+    "libcodec2-0.8.1_0.8.1-2_armhf.deb": "21b065c1587dfa8ca1d8b9cbf0c797c56fa19738b2f697886091305cbdd891e6",
+    "libcolord2_1.4.3-4_armhf.deb": "4eec3912d5e1df92ef8d71a9c72aad2d7cc31c05edde8b583f3ee94c0181fe25",
+    "libcom-err2_1.44.5-1+deb10u2_armhf.deb": "2bc8807d701b7a41f0aed2c6ae869e26424bb66709fa5f217bb93aef8d87dcd6",
+    "libcroco3_0.6.12-3_armhf.deb": "45940fc83e016ab6921a17001125b4fd0743ff37a87d2cf503c97a9b73e50f3b",
+    "libcups2_2.2.10-6+deb10u1_armhf.deb": "f9e40ab35364c65998d4bfb217a1f1db2d1a0520a9c18d089e09b87c444aaa11",
+    "libcurl3-gnutls_7.64.0-4_armhf.deb": "1f52639539ccc0b358c4bac4d4044fe95db30acbaa2d27765c7132632a11047e",
+    "libdap25_3.20.3-1_armhf.deb": "e4a7a8502e233d355eadbcfad793d23a6e2b5dfbfda813aef3b76e91da6178f6",
+    "libdapclient6v5_3.20.3-1_armhf.deb": "beee7cb7642fcfd2d550908ae019a833195eb46ae5e5fac3732ab8208e0626a9",
+    "libdapserver7v5_3.20.3-1_armhf.deb": "cb4b57096e13161c5368db9d2ae868ba377a177a17dbb47503bfc1d022019b6e",
+    "libdatrie1_0.2.12-2_armhf.deb": "8e57fcfce1f6cad89e48332931d3ac3e7d65838ef36108dcb8cb9b8689268704",
+    "libdb5.3_5.3.28+dfsg1-0.5_armhf.deb": "e9bfd3904dfbdab095f24f4e3d2736c1cabd0fc0a13c06239fc795dc3fd394fa",
+    "libdbus-1-3_1.12.16-1_armhf.deb": "8956d26ed5e36da1827b295af1db20c4c3326c2fb6286df0611af1fddadfe148",
+    "libdc1394-22-dev_2.2.5-1_armhf.deb": "e1181d30983e2388f30168a06e347af77f63dadd472d7f10a06c0453c9854492",
+    "libdc1394-22_2.2.5-1_armhf.deb": "dc9d04ccaaf4c9d8e789a81b5eee65f3b7dbc16dedd492106d5494a14cc024f6",
+    "libdconf1_0.30.1-2_armhf.deb": "6ede031759943492bbc180e8d370b68d22279158a73ed692501b0e2347491cde",
+    "libdpkg-perl_1.19.7_all.deb": "1cb272a8168138e9b8334e87cc26388259f232b74667b3a7f3856f227adcc4ba",
+    "libdrm-amdgpu1_2.4.99-1~bpo10~1_armhf.deb": "f432b784cb753da0a763ba3f6bed0f5e74578cc6c1a27de617a27f25ad89391e",
+    "libdrm-common_2.4.99-1~bpo10~1_all.deb": "e48b70787ace4ee4874df9078f28353119af80792c85c76db45aed26a2bbd813",
+    "libdrm-nouveau2_2.4.99-1~bpo10~1_armhf.deb": "11bb0b958997810924afa5eba00dbce4ce5f036409a86b309823c40a32420581",
+    "libdrm-radeon1_2.4.99-1~bpo10~1_armhf.deb": "79d4d288276a852b18e6b02e409c589a6cdca5f9bc712accdc062e60db500b4a",
+    "libdrm2_2.4.99-1~bpo10~1_armhf.deb": "ff95d213633ecaa8450cb9f77b04b34cc5017459c32e42f6ec90138436faafd8",
+    "libedit2_3.1-20181209-1_armhf.deb": "0d35fbdf5952df7a78dbc4776b89361a2fef8006342a94ab9d602483ad3578da",
+    "libelf1_0.176-1.1_armhf.deb": "013c168e64666e33e431701f7b1d683d2234f39caa2d801e389ef69266e88167",
+    "libepoxy0_1.5.3-0.1_armhf.deb": "1c09ff3084a59f3fbb519ca43b3d5d2bd32eb2835ec1b8f5703d02ddfefef2fc",
+    "libepsilon1_0.9.2+dfsg-4_armhf.deb": "22394acdfe3159dbb6a17aca4fa4edc641c1d6f04c5eed09802b10f0cbd24a29",
+    "libevent-2.1-6_2.1.8-stable-4_armhf.deb": "b8bca67f980502877981d8891e751fa0bd879e785c63e2dd25b61ef629442adc",
+    "libevent-core-2.1-6_2.1.8-stable-4_armhf.deb": "24cd3b8e29650bd0e4b4efe6c1d770b1e75df9682c07eb3276fa22eb55276c44",
+    "libevent-pthreads-2.1-6_2.1.8-stable-4_armhf.deb": "cb009ff0d23de8d1de1972b712c210899fd5e4380027d9ac6846d5bb3b7e8c25",
+    "libexif-dev_0.6.21-5.1_armhf.deb": "bc9c03655e49f038b308228d92998677429b63f3aa1e97ef336dbdd0fe6cdbf0",
+    "libexif12_0.6.21-5.1_armhf.deb": "9105590ee18cb2113e8dee17f270ce81d9cdb2199904886278b99c44c661cbfc",
+    "libexpat1_2.2.6-2+deb10u1_armhf.deb": "869f0de1b5548c13e395554f454dcd17398479e12b5b1e7079fd8e5246113365",
+    "libffi6_3.2.1-9_armhf.deb": "dd24f5e9fa15e16c80c8a13869d63f1a1fbef153b63c628d09f9bc4ed513112e",
+    "libfontconfig1_2.13.1-2_armhf.deb": "3c9b6ab7c53742599ba2d43f67181b01b77442c0bd48539466e3a117c555e094",
+    "libfreetype6_2.9.1-3+deb10u1_armhf.deb": "84a520466752a39ac67acd32403fd00b18f41bf5477044e8475d643fdfaefd78",
+    "libfreexl1_1.0.5-3_armhf.deb": "42a5ad5b00b79271a9651cd0fa220e488bc700b691e3e9789b7b0d0c27219a5e",
+    "libfribidi0_1.0.5-3.1+deb10u1_armhf.deb": "c1fd57da1608f48bd699d853f0149e47bb21baa4d7328be5f87fb0f908a5ed54",
+    "libfyba0_4.1.1-6_armhf.deb": "331d150a88b29e2cc16139dc2ba3c1c77ab0fd577be4f2f08de603bbaec0e59b",
+    "libgcrypt20_1.8.4-5_armhf.deb": "19ec0ba3e4d133ade463dedc1ca4f2b37344eab058213cc384ea14488a7272d5",
+    "libgd3_2.2.5-5.2_armhf.deb": "77e8999b903a4b576ae05f3c3776f69a0277a8200857aba6fa3bc8fb290c874c",
+    "libgdal20_2.4.0+dfsg-1+b2_armhf.deb": "7b4f71a576320aecbeadd11dbd507c5a6f7c9c519606bd30efa5e192183112c4",
+    "libgdcm2-dev_2.8.8-9_armhf.deb": "8b27b3ab3e2c265bd92c78aa39bc485f692da499bcf5ed0e2d9ff6b52d5d6eff",
+    "libgdcm2.8_2.8.8-9_armhf.deb": "e07feea0e5724c4ea9ab24af5dae2ba5bc0d3be20d6c9596b09f7067dd037768",
+    "libgdk-pixbuf2.0-0_2.38.1+dfsg-1_armhf.deb": "68dc44a106ef642247847657567890d7f36a4eeed16d2b7d1e7e733a0442a265",
+    "libgdk-pixbuf2.0-common_2.38.1+dfsg-1_all.deb": "1310e3f0258866eb4d0e95f140d5d9025cf6be1e3e2c375f4a426ccc2e78cf68",
+    "libgeos-3.7.1_3.7.1-1_armhf.deb": "2fd2fc54180965df1f3921ced9186de9e97204bc08f05558a48de4fcfcec69e3",
+    "libgeos-c1v5_3.7.1-1_armhf.deb": "6efa1978880f24e97214163972ff29f652ffcb8a2cebff3d17235704e204f57b",
+    "libgeotiff2_1.4.3-1_armhf.deb": "044798114f725f781ec3f2415bdf12bba85c4e762e6a2d93fff0508ab8fa2346",
+    "libgfortran5_8.3.0-6+rpi1_armhf.deb": "a5f5a383d8e617a11316ec335f83ee5bafade9cc7de5c9d83dc79f5c5858f9ad",
+    "libgif7_5.1.4-3_armhf.deb": "b88a0b203bf0f88264dd932ee13a52d28b1e92eb76bfbc7e62a124eae71f9de5",
+    "libgl1-mesa-dri_19.2.0~rc1-1~bpo10+1~rpt3_armhf.deb": "f7291de6f9dfc56a412330f306ae89f809ed7c7d021b7da15347784803a2111f",
+    "libgl1-mesa-glx_19.2.0~rc1-1~bpo10+1~rpt3_armhf.deb": "261e3a9fef4f4f7912c6cf61a09fed47c3c1eeef1d563ae757b879284a0d084e",
+    "libgl1_1.1.0-1_armhf.deb": "7243594e50a2f898a023fc2ec4b17d59e2e71fd415afeafe6dc8af104268c9fe",
+    "libgl2ps1.4_1.4.0+dfsg1-2_armhf.deb": "61aa9ebb7c7205365cf6adb2318b4d8376e30f6dbed9271bdda63135e6d57c37",
+    "libglapi-mesa_19.2.0~rc1-1~bpo10+1~rpt3_armhf.deb": "03ce3b9a80c6eadb3f5dd5ac421c4a45bb6beacb7748f244931343025abd6173",
+    "libglib2.0-0_2.58.3-2+deb10u2_armhf.deb": "6c9edefc08726bc9e63b31e487c022db6b55dd710fe3e022e1a414a17f33328f",
+    "libglu1-mesa_9.0.0-2.1_armhf.deb": "1a3aaf79151e412a4af3316873e8ae5f73a8e78ef7e361b37e49aed186470e91",
+    "libglvnd0_1.1.0-1_armhf.deb": "c449f17f2c86dc60b49161a84414a7c355bd329a49979e4a5ba92e7a1dd37927",
+    "libglx-mesa0_19.2.0~rc1-1~bpo10+1~rpt3_armhf.deb": "787521cf777871c9aa59ddfd563ac428bec27454d91f85248df21d96c559f32f",
+    "libglx0_1.1.0-1_armhf.deb": "c2495916884ddb8d0360fcba429f42463ec03d7935eef24a2f3548b57bb80aa7",
+    "libgme0_0.6.2-1_armhf.deb": "09a4d473e20c1cbd76119c45a42d00fe953ea7a58cda45abaf65897bea82e21d",
+    "libgmp10_6.1.2+dfsg-4_armhf.deb": "ca3cd65e915de80716dd976fd9e6b9588342e39117ec07ac5a00e60bcb1a27df",
+    "libgnutls30_3.6.7-4_armhf.deb": "439dbc35caa9e3baacc7ddf200c60385e69c262c37b20294295c3c2071606812",
+    "libgomp1_8.3.0-6+rpi1_armhf.deb": "243f49f947c8a992ecb8c38a301288661254bc10099d27c98eafd2e05fe88100",
+    "libgpg-error0_1.35-1_armhf.deb": "6549092b313862bf3959fa4a0903a885ff81a777bed2b4925ab85df03588eee2",
+    "libgphoto2-6_2.5.22-3_armhf.deb": "b20dd5e04ecce954151680f4aa35e6f5a2c8c7f09a8bbfc5a769345d99481861",
+    "libgphoto2-dev_2.5.22-3_armhf.deb": "30b98ae9bb8fa42ce16e2231f509bf68c425d1bc81003ef66ed8fda1774fa135",
+    "libgphoto2-port12_2.5.22-3_armhf.deb": "0eae07c8307af1d629d475c9a50570e032941c6ef065b2551684b03dbc0e7c46",
+    "libgraphite2-3_1.3.13-7_armhf.deb": "461cc0fec95f74dae2c031e7c7123774877e8bb4f0341b607d163ee0e58d1186",
+    "libgsm1_1.0.18-2_armhf.deb": "148dd82999418b9d1e70b412b5fe2d2e1d4de7407cc23ec7e2c485a7fd73ef57",
+    "libgssapi-krb5-2_1.17-3_armhf.deb": "63a06b5943840f841aacc34032974f228a3c0023fca05d9b4b6329650390361a",
+    "libgtk-3-0_3.24.5-1+rpt2_armhf.deb": "3ee5f878748e4a6301a631a1fc1e5afad3a0bc07cf5d2485e12511545609732b",
+    "libgtk-3-common_3.24.5-1+rpt2_all.deb": "eb65d4610b98e5927a23ce4f36bfd76e2d53e1303b94c6d2a0c634d8fe4506fd",
+    "libharfbuzz0b_2.3.1-1_armhf.deb": "cb57cfe0e2c3e36a9cdbf032eed11269eeda8ae5c66203fb95c19cc8c2fa1ed0",
+    "libhdf4-0-alt_4.2.13-4+b1_armhf.deb": "df376e0f0413e52cd59ddc937a4c9fde565cc4d5cf56cdc63f9c32c709ac8053",
+    "libhdf5-103_1.10.4+repack-10_armhf.deb": "ebe9eff643cb5e5fb0f78038ee81ae8a7ee29bd2e1d34eeb92452c3c172291ff",
+    "libhdf5-openmpi-103_1.10.4+repack-10_armhf.deb": "eb53ab0db447b08d50f3c8a5d22f8c643f65075ace448efe86d6bea5e194352a",
+    "libhogweed4_3.4.1-1_armhf.deb": "9eafecd38110191e285293a770eb13ef7a291cea2e0ff18cf511c6cf21b947b6",
+    "libhwloc-plugins_1.11.12-3+rpi1_armhf.deb": "5703cbe54214331b879aa8bc07577dc7e4e3c748df6a9c8f89af9e6e6e5cb20d",
+    "libhwloc5_1.11.12-3+rpi1_armhf.deb": "a9c20eeaa0f5abff444a3f12639ccb8554fae05d97cef1840e5de54c7d3c394b",
+    "libibverbs1_22.1-1_armhf.deb": "37aebd2d0c1cffe2b9a8678bbde786ae57b9e04ca8977fce5baa334378e661f7",
+    "libice6_1.0.9-2_armhf.deb": "92374e7e8076ad0062638c7438c886d0c23000b1a8a9b361a057d0625dc20a65",
+    "libicu63_63.1-6_armhf.deb": "94010cc7c8ce2de49ad2dcdf2d70eccb32b890a8d5e9b30ec5ba3ce72f681fdc",
+    "libidn2-0_2.0.5-1_armhf.deb": "4652f117181607de335c7ded31109310934bc40051c6afb7bbdedb9fbbb2b28c",
+    "libilmbase-dev_2.2.1-2_armhf.deb": "bcd411d9f601549cbbb343b672e6ce0be2704c701f2cc6cdbc254cc8a8b61bce",
+    "libilmbase23_2.2.1-2_armhf.deb": "7d8995d3db7cfe4ff6705553d00679682f55cd4021436e7bd2e83bb56d23d8c2",
+    "libjbig-dev_2.1-3.1+b2_armhf.deb": "8324e57714c0e44ed47235ef3510cd4f1acc8b098eb2140b7773935cfdd4a7e6",
+    "libjbig0_2.1-3.1+b2_armhf.deb": "b50783fe5974f648125b6ce2487ba05f99e4f11929f5b75bdc5baa94890a563f",
+    "libjpeg-dev_1.5.2-2_all.deb": "71b42025bdeb9fcc30054b54c84c4306da59466fbd419f46471f15ec54d435aa",
+    "libjpeg62-turbo-dev_1.5.2-2+b1_armhf.deb": "c8b85c158cff2deb164da3e67eba70fa13cfddc40ef7e721eaa4bf0c770f9194",
+    "libjpeg62-turbo_1.5.2-2+b1_armhf.deb": "bc28dbc5b68fe0268aa7692562bb0a39908e1bd0901be1990affd585fec773b3",
+    "libjson-c3_0.12.1+ds-2_armhf.deb": "ca3de6f029fb22f0efb576734f27a97583ebd9b9137b1c7cfd0f6228fae44423",
+    "libjson-glib-1.0-0_1.4.4-2_armhf.deb": "a790c43ed7957d646337df29628b17e812869b1e087a59002f5b1b97a42b400f",
+    "libjson-glib-1.0-common_1.4.4-2_all.deb": "c27dbb0cf9c73e2a09d5c774fb46ecf6d2b634facaf3b37b20a4654d9c549187",
+    "libjsoncpp1_1.7.4-3_armhf.deb": "25674de33c2105228048b9380b71045faf0716e63c3f901f4d9bc67ed4579c8a",
+    "libk5crypto3_1.17-3_armhf.deb": "abcc38ec1ec6f0c84feb2cb14b8a96517957cbcbdc20f6183e7fe3c0e133975c",
+    "libkeyutils1_1.6-6_armhf.deb": "ee0948ea16c2520d5a8612ba74c95c820966ed8dba78334729aef37571161d58",
+    "libkmlbase1_1.3.0-7_armhf.deb": "7ffa17e6e3487fd5745d32416ff82dba541b926b9eaab2e16ac7811a38de2486",
+    "libkmlconvenience1_1.3.0-7_armhf.deb": "4bfcc0187e12a3eef08372c3b8be8205d4eecddaaf4d7467ce29585466bc2365",
+    "libkmldom1_1.3.0-7_armhf.deb": "168b96f0e36b863517afc16ea6a37f00acb20dac80a40ffe2a6039412db0630d",
+    "libkmlengine1_1.3.0-7_armhf.deb": "d1d5df02935b20105d94e9ea8d4d1b186d3592f9197d9bea36d69b2cc2952d80",
+    "libkmlregionator1_1.3.0-7_armhf.deb": "3eba2098651bd33e7a51e7c54f9996ac11f0167c133ce59ddea4415ad7f5cecc",
+    "libkmlxsd1_1.3.0-7_armhf.deb": "820b7705568f69c54b7ac30feb9bc36935aecbbcaac55a801b6675f1bfe1a599",
+    "libkrb5-3_1.17-3_armhf.deb": "eb91711bd2f1606354c27216c89cef3c85d78407902b750ee228018f9134f8a1",
+    "libkrb5support0_1.17-3_armhf.deb": "5b0d26f4a7f8a0991087b917b2a9d93d353c4c9cc18f6a345db45e1c06391564",
+    "liblapack3_3.8.0-2_armhf.deb": "b6b2d62fe5f607efbb383d5b39edffa414a1bdad89cb886a60e0b5ee55c8ecbd",
+    "liblcms2-2_2.9-3_armhf.deb": "6d771698dd7b90af8f53d744775ad0f8a669be7a5ee8bf2c285f7bced0c64822",
+    "libldap-2.4-2_2.4.47+dfsg-3+rpi1+deb10u1_armhf.deb": "b61e759ffe122e843dd2b5117a421fcd344deac94c75b1892e338ab6042ce4a9",
+    "libldap-common_2.4.47+dfsg-3+rpi1+deb10u1_all.deb": "16f2cc9f5faaf9a539697d8adf05c0f460d274d785497aa8027dca6b0e9236d0",
+    "liblept5_1.76.0-1_armhf.deb": "9eb19fa5d74b861bdca63d195e9f23c90f359e6702ab2140df36804d7098f495",
+    "libllvm8_8-3+rpi1_armhf.deb": "2e3a98a357ebccfcc630cdb8a7177ec1561d31c11c5ffaceb5c449a14f817660",
+    "libltdl7_2.4.6-9_armhf.deb": "0109cd8ee5f2216584d21dcbb8b85786d5d38cd3d88fa8602c756088c38ba29a",
+    "liblz4-1_1.8.3-1_armhf.deb": "99661a8b347d55fc0212b8176f691eaee1e13e2ee75aa316c549ac669fe77925",
+    "liblzma-dev_5.2.4-1_armhf.deb": "94c1b419a70af792590eb26582f3ab5fd6908ee0f045ee649c65523503290bd4",
+    "libmariadb3_10.3.17-0+deb10u1_armhf.deb": "d141db7c82826cead85bbc3c8808e9d2148029f87c41b676c425f21cd8397639",
+    "libminizip1_1.1-8+b1_armhf.deb": "7ac58a7fb21b641d00d5485c0068ab4aca024f795ee220eec5ac1501cbfe6b7c",
+    "libmount1_2.33.1-0.1_armhf.deb": "9443056463d7ddedde9bf28e1f2b6486198b68143fa0b7a2688e3edf823d566d",
+    "libmp3lame0_3.100-2+b1_armhf.deb": "1b5334f976afe0a16c0faa29832ff35e6d442beca23062b9f385079a120d4017",
+    "libmpg123-0_1.25.10-2_armhf.deb": "e552789597110f8cc7300ca34578a2e93700db189ee4732c2adce39a339ad617",
+    "libncursesw6_6.1+20181013-2+deb10u2_armhf.deb": "2435c3c7d6f27d907584a36583da629927eec4c2d8e2deff7bc8d814ff2b97b6",
+    "libnetcdf-c++4_4.2-11_armhf.deb": "ddbc876f3a37f78386f7d4611ad9ef095dce27a8dffa95f65a64c10381324d27",
+    "libnetcdf13_4.6.2-1+b1_armhf.deb": "0323f376ec2d0be39683adfdebaba1a0ee062d4387a4b1cd5946b389b6fd0409",
+    "libnettle6_3.4.1-1_armhf.deb": "49010bb7544c086eb20d5330fd1b1bce61bf29f66f0bfe7da5381f1ddcc6abf0",
+    "libnghttp2-14_1.36.0-2+deb10u1_armhf.deb": "3e47c770b48f555decbb31bc38f38b985c8d6009f39f7923c0fc7973bac99348",
+    "libnl-3-200_3.4.0-1_armhf.deb": "61c07f733be04122faa5f86e50138f27b639b10852fa19c5109b63ce7b4f1d8d",
+    "libnl-route-3-200_3.4.0-1_armhf.deb": "3761f4d6c6b255873b5ddf9c615ff9c396f00b212bde9d81cf83a86373316b44",
+    "libnspr4_4.20-1_armhf.deb": "1a5c311c0b2d3de1d53bb8bed8034c475dcd8293319e69f4bea2d765f00c87ee",
+    "libnss3_3.42.1-1+deb10u2_armhf.deb": "bf52021aac6e4c10183f10155fb554286831c75639e766c19c0f4946cde76718",
+    "libodbc1_2.3.6-0.1_armhf.deb": "07ce132f8fc2dab2e11f6988896cfdaf2e865b81da96456f42fde8f5e1e1708f",
+    "libogdi3.2_3.2.1+ds-4_armhf.deb": "f2089377ed36ef36327e8a982ea3fdde736806fd7288be67da19b69a7d1f6bb1",
+    "libogg0_1.3.2-1+b2_armhf.deb": "2518b3214e3c709eb0df6bb71127d1b9e24fc642513f6a8a9e729de98f789d50",
+    "libopencv-calib3d-dev_3.2.0+dfsg-6_armhf.deb": "8c7f3ca52bf148f39483a6e1ed864c36107f5461fed78934b9ae3cecf027e48a",
+    "libopencv-calib3d3.2_3.2.0+dfsg-6_armhf.deb": "a7ce99efec8b00d8db399352a7099ab78153d5aec72fb1f53d3570b6213d1c4c",
+    "libopencv-contrib-dev_3.2.0+dfsg-6_armhf.deb": "b733091fe4bafb84f40b1221af788ec5df7f64108bd15c700575054a9846d1b0",
+    "libopencv-contrib3.2_3.2.0+dfsg-6_armhf.deb": "41ce0e6af765c46d2fbfd9d9b8ecca678537e53ac637dbc28775f26b22b18d49",
+    "libopencv-core-dev_3.2.0+dfsg-6_armhf.deb": "a83d0f59ce1e23cfa2f7c400e669f12b905acbbc9349998b9eab451c78ce91ca",
+    "libopencv-core3.2_3.2.0+dfsg-6_armhf.deb": "d2b7ecda65da3ba6610711dc9ab95f7bc8f90a6dead77ad06f93a082b5ae36f2",
+    "libopencv-dev_3.2.0+dfsg-6_armhf.deb": "aa519c3e572b655f039803117c737bc7c0f0638fbc86ff8989294b5df294a8fb",
+    "libopencv-features2d-dev_3.2.0+dfsg-6_armhf.deb": "b614de275538bcb5a12ed7bc9cf7dc644572b1792cab7f5c821d329fbf05ae54",
+    "libopencv-features2d3.2_3.2.0+dfsg-6_armhf.deb": "2acd7864a39c01528f87b8d34fb5b620004e04cc287200c74ca44e0712a161b2",
+    "libopencv-flann-dev_3.2.0+dfsg-6_armhf.deb": "b81522782181f3d39d48a6b61b3b61fa45388c305ab7952d98fd7b6084314e5d",
+    "libopencv-flann3.2_3.2.0+dfsg-6_armhf.deb": "097da5fec0d3828e7e2de1bd1d38cccf86c3e0f866a94e9ae0f116fe69afdace",
+    "libopencv-highgui-dev_3.2.0+dfsg-6_armhf.deb": "20c31e48c84f7ac8ff74e603aa91453671fe0f13292f5c05370ce7c984eaeb76",
+    "libopencv-highgui3.2_3.2.0+dfsg-6_armhf.deb": "f52232ca0db2aec76bf55605f7268eb3f5969524d0dd4627a5e0c75900655b38",
+    "libopencv-imgcodecs-dev_3.2.0+dfsg-6_armhf.deb": "055d2f33c8b1b20edc0d989a6ff047d0b9eea0347237febbcf27f9380fc1b843",
+    "libopencv-imgcodecs3.2_3.2.0+dfsg-6_armhf.deb": "3a87d0dd4a0d534242394de932526365a9e080f9fe783a1caaaeb72b909762c0",
+    "libopencv-imgproc-dev_3.2.0+dfsg-6_armhf.deb": "0069eb4e75c2133ca12b94f979e5c2ec1c4be31b7ca42e39a90200bbb287f4c8",
+    "libopencv-imgproc3.2_3.2.0+dfsg-6_armhf.deb": "2561604f98264ade28e5ecb31b1e5590fab4c861bb170de41e8533d09dd868c6",
+    "libopencv-ml-dev_3.2.0+dfsg-6_armhf.deb": "f576a5bd460c64914e6091d5229ecf5a021a9541319d5dae472105f2d3c6e3aa",
+    "libopencv-ml3.2_3.2.0+dfsg-6_armhf.deb": "f187fa8c4cb52c982a19c5199b4df4d311bb19a561744e9086a46b4f40de69f9",
+    "libopencv-objdetect-dev_3.2.0+dfsg-6_armhf.deb": "5a1e49091efe0af562cbbc3fcd5035c4770d076503f4266360528b3aacd170ba",
+    "libopencv-objdetect3.2_3.2.0+dfsg-6_armhf.deb": "a908f56acfd787d76ab91bf3e10374c6c34070fe73acf0b58683d15d845bd5ce",
+    "libopencv-photo-dev_3.2.0+dfsg-6_armhf.deb": "6f93654b78007bb54b6294a6294d2ee1b2c8c34f9557f7fb6f51caf8a996680c",
+    "libopencv-photo3.2_3.2.0+dfsg-6_armhf.deb": "2dbb93becd211a9543711c99c32a9f6e7480f554c068a945bd76c20111756207",
+    "libopencv-shape-dev_3.2.0+dfsg-6_armhf.deb": "a56b63c6706af19b3f6e556ee7cac51dd80af48d948f7226bc5d60d4d57dda16",
+    "libopencv-shape3.2_3.2.0+dfsg-6_armhf.deb": "a6906df95067c61b18ebdbe36e30e836752c2a4c1b6a4160b1dc991b347cbe34",
+    "libopencv-stitching-dev_3.2.0+dfsg-6_armhf.deb": "d343a82f4190e0cf10ba72846d01fd38a132f7e22024f8e8ba19171c0a65feab",
+    "libopencv-stitching3.2_3.2.0+dfsg-6_armhf.deb": "e8abf81b53d28ee5e68ae5d6e423a1278e0619b02920efbc247665b5b7bbe497",
+    "libopencv-superres-dev_3.2.0+dfsg-6_armhf.deb": "24511a36a510be5943842931933577334b4e8410ded4be3fe6ad634831b5baba",
+    "libopencv-superres3.2_3.2.0+dfsg-6_armhf.deb": "81e8882fbeeae7d07665c6c07e7885b5f43fdd7d536b7007e298cdeea4acc510",
+    "libopencv-ts-dev_3.2.0+dfsg-6_armhf.deb": "2e9520d3c83ac2ef3af690ff4241c68a8cad067c0cc50d3ec9395ac7d75e29dc",
+    "libopencv-video-dev_3.2.0+dfsg-6_armhf.deb": "c7ae7ced16c7cf6aa1a1647758b0c1cf01e38bf84444dbd3baf568d4328c6f86",
+    "libopencv-video3.2_3.2.0+dfsg-6_armhf.deb": "e6d079903ce88b25558046b4ce94261eafd2fb5de0617e7fcffbbef02521ac59",
+    "libopencv-videoio-dev_3.2.0+dfsg-6_armhf.deb": "f712aa3626fd0efc42240c1b500e05a37f8e05bfab459046a6a1cf2364541127",
+    "libopencv-videoio3.2_3.2.0+dfsg-6_armhf.deb": "ed38ad4aded75bc4b5ee1a2e7acc67fc0a7a0484d9d5fe46e56f5a9edaafeb57",
+    "libopencv-videostab-dev_3.2.0+dfsg-6_armhf.deb": "942519168d7be208736394024612285bacc9c41f66edcce5a33eb86aedae6dfc",
+    "libopencv-videostab3.2_3.2.0+dfsg-6_armhf.deb": "a4dae91092fe9e9e60b2c185d610a15452f7f9df9b9b635e8deaa3b0aa93cbbf",
+    "libopencv-viz-dev_3.2.0+dfsg-6_armhf.deb": "64a9b47eb603860c60fe62f4024b3f23a4df23fe7a0e185090ba730a32ec7fc2",
+    "libopencv-viz3.2_3.2.0+dfsg-6_armhf.deb": "e3a859dc1426c7eddfb181f0e37c8c20bebced557fabf5161de795195c12c9b4",
+    "libopencv3.2-java_3.2.0+dfsg-6_all.deb": "6a177762d8dbe7e2a54cfc03aa523802848e0567ded674314d1919652b07f81b",
+    "libopencv3.2-jni_3.2.0+dfsg-6_armhf.deb": "4fae611a082c059c2344d4f39ad60d3c00c79e243b3e218d6ac5fa5a829c63bb",
+    "libopenexr-dev_2.2.1-4.1_armhf.deb": "fb634227cc4fb67662fd45a8a5e741dd9609a2872a841856cd21c20aa7f4d0e8",
+    "libopenexr23_2.2.1-4.1_armhf.deb": "437f125bc53e5749d32de0625f8aaa4eb3c06df096ce147889cf9bd380876dde",
+    "libopenjp2-7_2.3.0-2_armhf.deb": "0bc6c631f13494dda21f9df8ae87ccd35f7aa3c8945b2ee5014d410633dd7c58",
+    "libopenmpi3_3.1.3-11+rpi1_armhf.deb": "b6d19977698ae4860690574ce43dd947462e41ab96498f6cc557c4a122ad2cb7",
+    "libopenmpt0_0.4.3-1_armhf.deb": "85eb98a60a45992c9345583c5869a124a71e6d9179737bc7ad5597c615b08530",
+    "libopus0_1.3-1_armhf.deb": "69cd56d03aaa51a4d62ad8f98d2ff487ea062bbdfe13e983afcefa99cb0c010e",
+    "libp11-kit0_0.23.15-2_armhf.deb": "56de64f62447b20b4f24f3c1d5cf2036f0971f22e1e820e25ff16b8cf59a7490",
+    "libpam-modules-bin_1.3.1-5+rpt1_armhf.deb": "d10f1ff2fa6e1b486e2d1443793ee86eecaa15db9487f553e659696d4a9c7e01",
+    "libpam-modules_1.3.1-5+rpt1_armhf.deb": "a7294f87afe55e0972ed7bba8269f62226b53824a6e0f25a8348776173be0907",
+    "libpam0g_1.3.1-5+rpt1_armhf.deb": "3f85873f6bda68084c597ccc7ec985cb5406b5335eaf0fd225ecce892d7c24dc",
+    "libpango-1.0-0_1.42.4-7~deb10u1_armhf.deb": "23d2b3f5e3ba20bc858adcd1e1718e1794ab34e7d50050d8af0f22c64d4c2afd",
+    "libpangocairo-1.0-0_1.42.4-7~deb10u1_armhf.deb": "a66aa6ac56c5d0f62d90c3015f2c9d8b6d40bbe00d0e2edc3f7ee14b030ae400",
+    "libpangoft2-1.0-0_1.42.4-7~deb10u1_armhf.deb": "40cd486567b4207f2fe367d704a9ad6224c3e032129df5d6cb625bd3435a3bb8",
+    "libpciaccess0_0.14-1_armhf.deb": "37f01b81f204bfd7ab1ffbd3e4f2ef1355dd0f65167e8081ac3639bf12af912b",
+    "libpixman-1-0_0.36.0-1+rpt1_armhf.deb": "e24b5249c31dcccc246a88df767cc1b05ad47c98d484773f9e18982e1b3c2739",
+    "libpmix2_3.1.2-3_armhf.deb": "dc28717bcaffa242bc81a4e55d37819fdc73d6e204303555cf836f85973ab1e4",
+    "libpng-dev_1.6.36-6_armhf.deb": "91d8e235856389d40018e6a1568cf23c7f22c8a8fb076e9d9515ffec7159a676",
+    "libpng16-16_1.6.36-6_armhf.deb": "e5d547ed5bcc30045e8812602324c41a8e59536bed73d2af0405cbe3b596eb44",
+    "libpoppler82_0.71.0-5_armhf.deb": "78add7ce54ba679fcba6a87545ff99ed4a586c506642982caad8b529f60a6cb6",
+    "libpopt0_1.16-12_armhf.deb": "260b2ba983c6489f86cbfa590316b6e4fa3ba7501bfe9475f00c46fbf3ee76e4",
+    "libpq5_11.5-1+deb10u1_armhf.deb": "6ab0c723546189d39f793a79d7042b2b58d8ee0f349c64e196c29d563122ca68",
+    "libproj13_5.2.0-1_armhf.deb": "01226abbfaf179ce9f19397ee085bced5a29ee89e8af012b817196b8d173a857",
+    "libproxy1v5_0.4.15-5_armhf.deb": "6786d3190e0ab7069b207679d93b9d2f204aeb091aa87cbf0b899902521c7407",
+    "libpsl5_0.20.2-2_armhf.deb": "e4d0c0fc1b232cc3aee36351a474d55e56c45c587edbb4e3b4ce58ef399bdc3e",
+    "libpython2.7-minimal_2.7.16-2+deb10u1_armhf.deb": "e84407a0d58e7dff3adac497db1519dbdeba952a7caabd4f7ea2a14abb0e346d",
+    "libpython2.7-stdlib_2.7.16-2+deb10u1_armhf.deb": "dd2479f925a3da9b605b8dfb5a14ff58642e450252d7d4d99f05ca249c0d0280",
+    "libpython2.7_2.7.16-2+deb10u1_armhf.deb": "d5617bddfb0927d53471aee0ce553f22786fa488725ed09c22c13ffd8d97d884",
+    "libqhull7_2015.2-4_armhf.deb": "498f825e3c31489dc47fb9312110333ebf8bad5f1e1fd850a312fff4694f6a92",
+    "libraspberrypi0_1.20190925+1-1_armhf.deb": "9bd18328b4a040a5da558093e6d458f046372bb278decb28920d21095f7432f7",
+    "libraw1394-11_2.1.2-1+b1_armhf.deb": "e7691347dfdc9096a69068f22a2f88a81f132e1cb0d1619cce89177a79fd02aa",
+    "libraw1394-dev_2.1.2-1+b1_armhf.deb": "23620ea90abf64a75431beb2939a129473fb9de4ab1f6b6fe9a414f85abc7b53",
+    "libreadline7_7.0-5_armhf.deb": "f655bfd17328343631ea6dd3fc7d885184a518fa397881f4d32f2a30b1e8fcb5",
+    "librest-0.7-0_0.8.1-1_armhf.deb": "ac4b777c967ae0f31b6d1ff51c32c8098c9d17e742ebdd2cbaa152b6f375e820",
+    "librsvg2-2_2.44.10-2.1+rpi1_armhf.deb": "9bfade393582432caa8f96868cd2a67b974ff04b9dc94a266e1bf578d14b124b",
+    "librsvg2-common_2.44.10-2.1+rpi1_armhf.deb": "02d96caf56f77643744d9d902c0d413b50224ad1a95757da65ddc2471dbb6cd0",
+    "librtmp1_2.4+20151223.gitfa8646d.1-2_armhf.deb": "c4adf4780f3e19b55fba417a0edc8d0d3b40be6d61c996a23d1a60cc3d1a3980",
+    "libsasl2-2_2.1.27+dfsg-1+b1_armhf.deb": "02b64b1ad87e6ae75095f7cef2b38e7b59888569bed25951830aad2b5aa319e9",
+    "libsasl2-modules-db_2.1.27+dfsg-1+b1_armhf.deb": "c54eee01f398398cb1e8af9c801750f00fbfa0e8c53cba819992e0ac39c4bbcc",
+    "libsemanage-common_2.8-2_all.deb": "fa3c50e11afa9250f823218898084bdefea73c7cd1995ef5ed5e7c12e7b46331",
+    "libsemanage1_2.8-2_armhf.deb": "7f403dccd00375eb91786db9fbea41496833cb9391f78bd6ea1136d83203b325",
+    "libsensors-config_3.5.0-3_all.deb": "a064dbafa1590562e979852aca9802fc10ecfb6fda5403369c903fb38fa9802a",
+    "libsensors5_3.5.0-3_armhf.deb": "695f500a247e8a7762fe044c6fd9081db2e9806818eb4cd0593075de53ad5f5f",
+    "libsepol1_2.8-1_armhf.deb": "9941f76c1d378ed0322cb413e0340455fe812f6b7451cf86a78065b2e5db69ef",
+    "libshine3_3.1.1-2+b1_armhf.deb": "7853acc136660422b7d3423caaf2ed5cb526001bb5064f932dfedf702fb5a35b",
+    "libsm6_1.2.3-1_armhf.deb": "92eccccb771f738c18bec33b38c2bf95a281e216608e91341a7e2dbb1f8703fd",
+    "libsnappy1v5_1.1.7-1_armhf.deb": "8b87dfb35872804edd1fd002383762e593e54e3860123d0089a5b7bb26b8aef9",
+    "libsocket++1_1.12.13-10_armhf.deb": "64af2171d010a98d3cb8ca200ce24738a8fb92ef057f6b4d0c059885bf233531",
+    "libsoup-gnome2.4-1_2.64.2-2_armhf.deb": "f36e6e41e88d1ea5e203fcd1a833ef0198693a2156a6abc4a29baae817746073",
+    "libsoup2.4-1_2.64.2-2_armhf.deb": "6ef422515aa22db398887e4b0efaaeeb980a3e0d27ec1dbe3199a186d6ac19fc",
+    "libsoxr0_0.1.2-3_armhf.deb": "82c3a5098221423a3213cb7754758034e24ff114ca4e8562bf37038efc7e8afd",
+    "libspatialite7_4.3.0a-5+b2_armhf.deb": "26db41b6b1f2fee9a39673e624fe74172cc4a12b4324737dd7c066b2ae205098",
+    "libspeex1_1.2~rc1.2-1+b2_armhf.deb": "a05502ef24e63edcb3410bce0fb654c3d5a8d3129df7cfe60e0e2a330ddbc114",
+    "libsqlite3-0_3.27.2-3_armhf.deb": "09efeaead3ce02fe3b390952a30c2207be518acdcf0210715595c486212dbe53",
+    "libssh-gcrypt-4_0.8.7-1_armhf.deb": "6e8ee452c5c3fede30ee89ab80f95532f63614a76afe3d138213e33986df768b",
+    "libssh2-1_1.8.0-2.1_armhf.deb": "ab4159f8bbd8491349d75231d09bc2fdca61f91756abaa2bac95210cfc21d310",
+    "libssl1.1_1.1.1d-0+deb10u2_armhf.deb": "893e3bfdfa84cfcc48d870a1d26341de408d71cbae0fc74788a9917bb5187910",
+    "libstdc++6_8.3.0-6+rpi1_armhf.deb": "bfc0533cc7d6a4d8adfb62205b39a79ee6df7c2f7c48a1dc6ff15f5af519aed4",
+    "libsuperlu5_5.2.1+dfsg1-4_armhf.deb": "f4d797c904bbedb0ea341bd7667661137004403a86a2a8b3e7d1c2365d08dc35",
+    "libswresample-dev_4.1.4-1+rpt1~deb10u1_armhf.deb": "78691ab4c0df8a0d4810f5388958007463c9e1df9e43b218cd70ef5315688ecd",
+    "libswresample3_4.1.4-1+rpt1~deb10u1_armhf.deb": "4bee948872147a2b53ee073d5816a377c827c1f0f23eeaba1d9422fdf920f815",
+    "libswscale-dev_4.1.4-1+rpt1~deb10u1_armhf.deb": "7d294825bd34e57e3831b524a1454bba2402f8b6361e83f89294f7dcfeccfc63",
+    "libswscale5_4.1.4-1+rpt1~deb10u1_armhf.deb": "07ad95a40cd28eb1dfb537c8bd45b45dfe40c35e7aa6f3ab9784d2e3913c76b6",
+    "libsystemd0_241-7~deb10u2+rpi1_armhf.deb": "b05e56f47b281fd84054f8ed6babdb67467f1d4a32c98d79334821d841988cab",
+    "libsz2_1.0.2-1_armhf.deb": "ce5347b6d722e01899fc49a39073da7a16985ceadcf8238985e8109617a2a11b",
+    "libtasn1-6_4.13-3_armhf.deb": "594f82946858a332bfbe55ddb2b10247a52486b8b183fd818231fef8a70ff682",
+    "libtbb-dev_2018~U6-4_armhf.deb": "ff7b27eae8c89056677a0479667448c0a2d8e20f75ed84862ccc183d9739ae7c",
+    "libtbb2_2018~U6-4_armhf.deb": "4ed379b2c64bdc16b6cf1cff7b0b859c125bfc311ebfa933f17c8f6efb8f65af",
+    "libtcl8.6_8.6.9+dfsg-2_armhf.deb": "b0f0b25f4bdbb95020ed1476fbc9a84e9a22b3d5278c0dd3df4a5963b5daf3f1",
+    "libtesseract4_4.0.0-2_armhf.deb": "1f46f21a995d76aa42c83ea6272876292520d04a51936fbd4752811ea5e73be1",
+    "libthai-data_0.1.28-2_all.deb": "267d6b251f77c17fb1415ac0727675cb978c895cc1c77d7540e7133125614366",
+    "libthai0_0.1.28-2_armhf.deb": "dad127d817507db95d10a5010db28cef882b51567d5fae58da97fc7bed55f7ae",
+    "libtheora0_1.1.1+dfsg.1-15_armhf.deb": "92f9de0685e30d293e113072b429651a6b2f783c23ffdbdc430da851e9f48236",
+    "libtiff-dev_4.0.10-4_armhf.deb": "0119ae4eb3003f5aa842daa83044a56629f36970ddbf3ff27eaea1a556657720",
+    "libtiff5_4.0.10-4_armhf.deb": "93bb72344e7663b74d0d4fe19ac216a8386f235d5bd3e39ecda17f4468c489a8",
+    "libtiffxx5_4.0.10-4_armhf.deb": "7cd65c17d98224aae86c800c82ab5a197906b4a195089e5948ff3f58ba4626c4",
+    "libtinfo6_6.1+20181013-2+deb10u2_armhf.deb": "48f25a4a8c6629126aa77d9665030b83867f520e50cf8317249e22d8ec204963",
+    "libtk8.6_8.6.9-2_armhf.deb": "d15d84339d668d91cc78e66122265fbccbb56f2ab5b37f2792f3112e44b9dded",
+    "libtwolame0_0.3.13-4_armhf.deb": "2fc0bb23e5ba08b77fce5651d9c3b536478eebfd00ff8078633187538b8bdb4a",
+    "libudev1_241-7~deb10u2+rpi1_armhf.deb": "a10d8ac4dc6b4fe4296f6e2df732a3e4e1f53fd10179fe73b8ab28182a6628f8",
+    "libunistring2_0.9.10-1_armhf.deb": "7e9a8046fde4a3471e9f5477bdcecd079e423aff2b916791e0d4a224e5a6c999",
+    "liburiparser1_0.9.1-1_armhf.deb": "ed680831b4a4236a27707cd50d4649fd812876eccf1f1bfec772bb9255f65cba",
+    "libusb-1.0-0_1.0.22-2_armhf.deb": "11df519acc304a52083bbcdf018bc842510fa9f6621ac957c0e3e994dc6a1843",
+    "libuuid1_2.33.1-0.1_armhf.deb": "31dd55f3044d29370d22f956aa86965b085a201f133771aed5a44631bf989791",
+    "libva-drm2_2.4.0-1_armhf.deb": "2475f97e6e91b6c5afb81ffa0ec00e57727ab44fcbc0eb6947d4ae3dabecd397",
+    "libva-x11-2_2.4.0-1_armhf.deb": "96a84184a734f4795ff0553b1ccb31c29641024b2967327c121f46dc794d9dd1",
+    "libva2_2.4.0-1_armhf.deb": "f4a11116c295ff059b74f2aab5b0156b6e5de493595ede9ccdca21dd2a0b6d24",
+    "libvdpau1_1.1.1-10_armhf.deb": "174cc3df89c9cce18253b832f896dfe4189b765d7122f3dfe8efc48d4b9f2528",
+    "libvorbis0a_1.3.6-2_armhf.deb": "10c7ef81708ea3382fa08dd9185d7f633788362e08e9d5e7549604d6c54bc33c",
+    "libvorbisenc2_1.3.6-2_armhf.deb": "5274a1593ea161d8a4511e4f771eaf83234cc40a383857209d8f38637dee2489",
+    "libvorbisfile3_1.3.6-2_armhf.deb": "22803a4d65a855683ce59f4d95663b786a75a35c2fff78574bdcd70d113318b5",
+    "libvpx5_1.7.0-3+deb10u1_armhf.deb": "44339d7f9ee6a467524aca298a71009092680ff17af4c50b654a0e4ea081f12b",
+    "libvtk6.3_6.3.0+dfsg2-2+b6_armhf.deb": "6f0a4ea94d410d4543fa1f3345b0481960bae5969405c177212c179a177ccf15",
+    "libwavpack1_5.1.0-6_armhf.deb": "d5f7a739bd2ec74e224d205ef2dd331ced7044f687636922c0c3da6250af94a0",
+    "libwayland-client0_1.16.0-1_armhf.deb": "384c3b3288e9a1ecd1014cdb62aece060b47383cb564a001a056bb78f66b2c09",
+    "libwayland-cursor0_1.16.0-1_armhf.deb": "384fd0dbcd9760d62348b5426f3d3072e582a99fd83218ac9d4a91d1758fd40c",
+    "libwayland-egl1_1.16.0-1_armhf.deb": "6270413558873bd434d112e789796d6cba5e0d8703ae19903db0234db2c71924",
+    "libwebp6_0.6.1-2_armhf.deb": "979fc61f16f7887e4ad602a7df402ed8f12d461fda376fde31de90873920494f",
+    "libwebpmux3_0.6.1-2_armhf.deb": "6237227b67a31609eeaa20c164028447c8db0f07c6aba29da0c0d08d2f758375",
+    "libx11-6_1.6.7-1_armhf.deb": "40450a640133af52c6ca90c150cbb6ff549d3ad0e81c80f8916bc57f6af5d918",
+    "libx11-data_1.6.7-1_all.deb": "eb9e373fa57bf61fe3a3ecb2e869deb639aab5c7a53c90144ce903da255f7431",
+    "libx11-xcb1_1.6.7-1_armhf.deb": "13085f3f991abfab2fd17176c0cd97c9ade0856cd864cdb1d560451ee903b967",
+    "libx264-155_0.155.2917+git0a84d98-2+rpi1_armhf.deb": "307de7bd1053117095523c7b4cfa3ca3843490a6f10023beb77c7201143691ab",
+    "libx265-165_2.9-4_armhf.deb": "aeb74dbd170aee841a1908444e6d6997c81da92fc532c41f3908595ea86dd090",
+    "libxau6_1.0.8-1+b2_armhf.deb": "1d1c083edfc29fa3a0c7539565357fcf2f90488dee919444a155afee59ca85eb",
+    "libxcb-dri2-0_1.13.1-2_armhf.deb": "dd81a9718fec85632b80fbac71f2b03972c1c588ed570f4a6c26b7de15ba0914",
+    "libxcb-dri3-0_1.13.1-2_armhf.deb": "7760da9fec785977eea7a1dad02601d7db1841ee36bdba1d05ee8dfd5c65a11a",
+    "libxcb-glx0_1.13.1-2_armhf.deb": "d787c79efcad262895de9fa662cf7646448c1c447b4c8603daa5ac2e49d56aaa",
+    "libxcb-present0_1.13.1-2_armhf.deb": "00d64156b4710ff5621fa95c33a95d608fb59c22cb293dee26c0a09e701b80b2",
+    "libxcb-render0_1.13.1-2_armhf.deb": "842d08da35fd84d9c52d189bb412fc238ada6391da803f4e8a3bc8f9dddeded0",
+    "libxcb-shm0_1.13.1-2_armhf.deb": "d6d35c9e57153832d88a521eb22acb19639e80003de7f3d9c834162fe8e4b5da",
+    "libxcb-sync1_1.13.1-2_armhf.deb": "3a150594eb919886708a37a3c4ad13383ad798780db9175632fd442510fc436b",
+    "libxcb1_1.13.1-2_armhf.deb": "9be3930e901f475e377dd0b3fb598d785826699be1e0e4cb1b4c24ed0ad3a46d",
+    "libxcomposite1_0.4.4-2_armhf.deb": "8550a66e62a33368988efbf9c77008e3b030a03a21421a96b595584481b15331",
+    "libxcursor1_1.1.15-2_armhf.deb": "c7ac382c659528b58c053a0c552d5cc9f26aded0caf2e2e3fcd602d978380fe4",
+    "libxdamage1_1.1.4-3+b3_armhf.deb": "51339efb637c4a3bf800ed0e605158e330732cd01c9ff6b8de94f2edc5bc9b29",
+    "libxdmcp6_1.1.2-3_armhf.deb": "c871d428ca79b15b31967a8e2f19507f06c4af10bcc29429a903a175634af6e4",
+    "libxerces-c3.2_3.2.2+debian-1+b1_armhf.deb": "df1a22c853bf85b6e9afa79751860c57280406d8b40a098ac3bc8f66eceb3255",
+    "libxext6_1.3.3-1+b2_armhf.deb": "4cff4cba6aae865ca4d5e72061d51c16c87985de0232751afce0d05909c755cc",
+    "libxfixes3_5.0.3-1_armhf.deb": "92ee46160bc288442c8e8cd7e0eb2a4dd24e69904333f49371b703af8a9e1b94",
+    "libxft2_2.3.2-2_armhf.deb": "502631a6a91f4a8fccbde895aeedcb518a54e11987f97d20866c325b2eeef220",
+    "libxi6_1.7.9-1_armhf.deb": "f03478e7a8bcf4c144e46d416fb01e74352bddb57a737f3ce78da308784f9639",
+    "libxinerama1_1.1.4-2_armhf.deb": "fb715bf6feefd3695dbaf963191673864a8f73976aa3f52f1197a551af66010e",
+    "libxkbcommon0_0.8.2-1_armhf.deb": "6a45884e50e7e7438e58b6c8387dfeed5f571b79cc8a3e9dc373ffcd6f4a76de",
+    "libxml2_2.9.4+dfsg1-7+b3_armhf.deb": "2629f83a6a430149ed091098e25e22884fb163df01a1f1a3a19765bd205b1a8b",
+    "libxpm4_3.5.12-1_armhf.deb": "f1a677cb3ef3b45e2262e326af71d939ff67dcd0fa3c7a6741706836900612fd",
+    "libxrandr2_1.5.1-1_armhf.deb": "5668f1bf32b9c1d3fe13a90ffb0a15aa5b6445029d24d1718865c08b08581d8a",
+    "libxrender1_0.9.10-1_armhf.deb": "82343e14e073be48577ae1c2c5f95886bc2dddf9a1966b77ba76a827a8e62e44",
+    "libxshmfence1_1.3-1_armhf.deb": "4c9c872c366037d4535e2b5749f34bae782e19171efec6eaaf8c14c9f2486225",
+    "libxss1_1.2.3-1_armhf.deb": "8ce41b86c573c234016450b188551001f7c7da606f090d865adde9c326e1cbc1",
+    "libxt6_1.1.5-1+b3_armhf.deb": "20e1bfa25f403a7014bb3c096a2140b5a6b4db0d370b30774965fc23bb7db122",
+    "libxvidcore4_1.3.5-1_armhf.deb": "caf1801fb13ee60bdc12235f5cd4138a5479b3769be598d29e1864dd7ffd5160",
+    "libxxf86vm1_1.1.4-1+b2_armhf.deb": "cbe30a767f4decb6203bc09661e877579a8adff99ccf73459c698ad0de8efce7",
+    "libzstd1_1.3.8+dfsg-3+rpi1_armhf.deb": "250e609240c682a90b85f2d90024acc63bd0b3f586699929246c1a5d4ba0458c",
+    "libzvbi-common_0.2.35-16_all.deb": "5eea3f86857626ce4371a8a70ba9ce89f1abfc47ed033d1de12ebc0c7d1dd3ea",
+    "libzvbi0_0.2.35-16_armhf.deb": "b8e412ce669fde535a3250714eda0a446c6791771bb6360f93f676efa3d6376d",
+    "lsb-base_10.2019051400+rpi1_all.deb": "b3e203037786d00dd83a5fa9412c8395090921d373e914cb166b395ee2aedaa4",
+    "mariadb-common_10.3.17-0+deb10u1_all.deb": "43edeb3274f132675447d7375f39dd324495a430b0cddcf608875c22cbd4e9c3",
+    "mime-support_3.62_all.deb": "776efd686af26fa26325450280e3305463b1faef75d82b383bb00da61893d8ca",
+    "mysql-common_5.8+1.0.5_all.deb": "340c68aaf03b9c4372467a907575b6a7c980c6d31f90f1d6abc6707a0630608a",
+    "ocl-icd-libopencl1_2.2.12-2_armhf.deb": "634dd778eb0a073609a773b4af463999be6c77b7a757b270ba2759d52e28f16d",
+    "odbcinst1debian2_2.3.6-0.1_armhf.deb": "a2fa334961f985d37602f2eb8ec2a69338897a8e0cba6438b55d365e06624f4c",
+    "odbcinst_2.3.6-0.1_armhf.deb": "81f2678332309805a18b7120dca0c0d76e29ba4e67cca1a629c100893d65a19c",
+    "passwd_4.5-1.1_armhf.deb": "beae91f59bddfe2ca8bf99a70131263d120ada1bdee6d1b3bb46cf96093c44b3",
+    "perl_5.28.1-6_armhf.deb": "464d3c3c46d40e18ebb233106d83a1855931b01b02bd761e72217b161e87ec48",
+    "pkg-config_0.29-6_armhf.deb": "cd1b397b846e4a8b815be6a8e1edbf9a3f509b924030a008c07f2fa3ddd20911",
+    "proj-data_5.2.0-1_all.deb": "fa7126aa00742ccf75f0e9867b54ea70f733436b97f600bec39408c5d3c54bd2",
+    "raspberrypi-bootloader_1.20190925+1-1_armhf.deb": "f43da527cde12548ac439ca496305bd8d818e0981d1506adb5dd7dea943f7673",
+    "readline-common_7.0-5_all.deb": "153d8a5ddb04044d10f877a8955d944612ec9035f4c73eec99d85a92c3816712",
+    "sensible-utils_0.0.12_all.deb": "2043859f8bf39a20d075bf52206549f90dcabd66665bb9d6837273494fc6a598",
+    "shared-mime-info_1.10-1_armhf.deb": "9cc1069b361b8c229b4e2afa4c5b7014e0258cca867204f2b9d4735cb7941e68",
+    "ttf-bitstream-vera_1.10-8_all.deb": "328def7f581bf94b3b06d21e641f3e5df9a9b2e84e93b4206bc952fe8e80f38a",
+    "tzdata_2019c-0+deb10u1_all.deb": "59b295b0e669af26d494ed2803eb9011408f768a77395db2573e67c388e8a509",
+    "ucf_3.0038+nmu1_all.deb": "d02a82455faab988a52121f37d97c528a4f967ed75e9398e1d8db571398c12f9",
+    "x11-common_7.7+19_all.deb": "221b2e71e0e98b8cafa4fbc674b3fbe293db031c51d35570a3c8cdfb02a5a155",
+    "xkb-data_2.26-2_all.deb": "17d21564c940dd8d89e0a1b69d6fea0144d057e4698902378f5c83500612b779",
+    "zlib1g-dev_1.2.11.dfsg-1_armhf.deb": "51561e557bd16f56e1e28b276184f0a6d82617afce987fc8d5322369ab0da478",
+    "zlib1g_1.1.2.11.dfsg-1_armhf.deb": "408be89eee242d4836aa0fa5ef1bedeae68f6ddb3b13b792a4df0363b09320c4",
+    "libselinux1_2.8-1+b1_armhf.deb": "cc2fee966330b3d362358434ae60fa08dd7dcec81b606f4ac94ce83dd6097a39",
+    "libpcre3_2.8.39-12_armhf.deb": "394b0ce553f25fe1bcca1ab367ac86e374c30688c213f95c50f62d0c9101a9df",
+    "liblzma5_5.2.4-1_armhf.deb": "825babb4ce905472493d6f26a5ec6dfa055447f3a9f4b3418cec9e0d56681f03",
+    "libbz2-1.0_1.0.6-9.2~deb10u1_armhf.deb": "22840e43aa2b48fb0ebe441d7cef8b33380063f722769fe1382574052e214d0e",
+}
diff --git a/debian/packages.bzl b/debian/packages.bzl
index 8054d1d..ba1f840 100644
--- a/debian/packages.bzl
+++ b/debian/packages.bzl
@@ -55,13 +55,14 @@
     )
 
 def _convert_deb_to_target(deb):
-    """Converts a debian package filename to a valid bazel target name."""
-    target = deb.split("_")[0]
-    target = target.replace("-", "_")
-    target = target.replace(".", "_")
-    target = target.replace(":", "_")
-    target = target.replace("+", "x")
-    return "deb_%s_repo" % target
+  """Converts a debian package filename to a valid bazel target name."""
+  target = deb
+  target = target.replace('-', '_')
+  target = target.replace('.', '_')
+  target = target.replace(':', '_')
+  target = target.replace('+', 'x')
+  target = target.replace('~', '_')
+  return "deb_%s_repo" % target
 
 def generate_repositories_for_debs(files, base_url = "http://www.frc971.org/Build-Dependencies"):
     """A WORKSPACE helper to add all the deb packages in the dictionary as a repo.
diff --git a/frc971/analysis/plot.py b/frc971/analysis/plot.py
index 124968f..d325bd7 100644
--- a/frc971/analysis/plot.py
+++ b/frc971/analysis/plot.py
@@ -11,6 +11,7 @@
 from frc971.analysis.plot_config_pb2 import PlotConfig, Signal
 from google.protobuf import text_format
 
+import numpy as np
 import matplotlib
 from matplotlib import pyplot as plt
 
@@ -42,6 +43,39 @@
                 parsed_json = json.loads(valid_json)
                 self.data[channel.alias].append((message[0], message[1],
                                                  parsed_json))
+        self.calculate_signals()
+
+    def calculate_imu_signals(self):
+        if 'IMU' in self.data:
+            entries = []
+            for entry in self.data['IMU']:
+                accel_x = 'accelerometer_x'
+                accel_y = 'accelerometer_y'
+                accel_z = 'accelerometer_z'
+                msg = entry[2]
+                new_msg = {}
+                if accel_x in msg and accel_y in msg and accel_x in msg:
+                    total_acceleration = np.sqrt(
+                        msg[accel_x]**2 + msg[accel_y]**2 + msg[accel_z]**2)
+                    new_msg['total_acceleration'] = total_acceleration
+                entries.append((entry[0], entry[1], new_msg))
+            if 'CalcIMU' in self.data:
+                raise RuntimeError('CalcIMU is already a member of data.')
+            self.data['CalcIMU'] = entries
+
+    def calculate_signals(self):
+        """Calculate any derived signals for plotting.
+
+        See calculate_imu_signals for an example, but the basic idea is that
+        for any data that is read in from the logfile, we may want to calculate
+        some derived signals--possibly as simple as doing unit conversions,
+        or more complicated version where we do some filtering or the such.
+        The way this will work is that the calculate_* functions will, if the
+        raw data is available, calculate the derived signals and place them into
+        fake "messages" with an alias of "Calc*". E.g., we currently calculate
+        an overall magnitude for the accelerometer readings, which is helpful
+        to understanding how some internal filters work."""
+        self.calculate_imu_signals()
 
     def plot_signal(self, axes: matplotlib.axes.Axes, signal: Signal):
         if not signal.channel in self.data:
@@ -53,7 +87,13 @@
             monotonic_time.append(entry[0] * 1e-9)
             value = entry[2]
             for name in field_path:
-                value = value[name]
+                # If the value wasn't populated in a given message, fill in
+                # NaN rather than crashing.
+                if name in value:
+                    value = value[name]
+                else:
+                    value = float("nan")
+                    break
             # Catch NaNs and convert them to floats.
             value = float(value)
             signal_data.append(value)
@@ -66,7 +106,8 @@
             fig = plt.figure()
             num_subplots = len(figure_config.axes)
             for ii in range(num_subplots):
-                axes = fig.add_subplot(num_subplots, 1, ii + 1, sharex=shared_axis)
+                axes = fig.add_subplot(
+                    num_subplots, 1, ii + 1, sharex=shared_axis)
                 shared_axis = shared_axis or axes
                 axes_config = figure_config.axes[ii]
                 for signal in axes_config.signal:
diff --git a/frc971/analysis/plot_configs/drivetrain.pb b/frc971/analysis/plot_configs/drivetrain.pb
index 25d482e..2b15220 100644
--- a/frc971/analysis/plot_configs/drivetrain.pb
+++ b/frc971/analysis/plot_configs/drivetrain.pb
@@ -103,6 +103,10 @@
     }
     signal {
       channel: "Status"
+      field: "trajectory_logging.left_velocity"
+    }
+    signal {
+      channel: "Status"
       field: "poly_drive_logging.goal_left_velocity"
     }
     signal {
@@ -111,6 +115,10 @@
     }
     signal {
       channel: "Status"
+      field: "trajectory_logging.right_velocity"
+    }
+    signal {
+      channel: "Status"
       field: "poly_drive_logging.goal_right_velocity"
     }
     signal {
diff --git a/frc971/analysis/plot_configs/gyro.pb b/frc971/analysis/plot_configs/gyro.pb
index e398a2e..f246835 100644
--- a/frc971/analysis/plot_configs/gyro.pb
+++ b/frc971/analysis/plot_configs/gyro.pb
@@ -22,6 +22,10 @@
   }
   axes {
     signal {
+      channel: "CalcIMU"
+      field: "total_acceleration"
+    }
+    signal {
       channel: "IMU"
       field: "accelerometer_x"
     }
diff --git a/frc971/control_loops/BUILD b/frc971/control_loops/BUILD
index 17613ab..7d3b915 100644
--- a/frc971/control_loops/BUILD
+++ b/frc971/control_loops/BUILD
@@ -142,6 +142,22 @@
     ],
 )
 
+cc_test(
+    name = "coerce_goal_test",
+    srcs = [
+        "coerce_goal_test.cc",
+    ],
+    linkopts = [
+        "-lm",
+    ],
+    deps = [
+        ":coerce_goal",
+        "//aos/controls:polytope",
+        "//aos/testing:googletest",
+        "@org_tuxfamily_eigen//:eigen",
+    ],
+)
+
 # TODO(austin): Select isn't working right.  We should be able to remove
 # logging conditionally with select and have CPU constraints work correctly.
 cc_library(
diff --git a/frc971/control_loops/coerce_goal.cc b/frc971/control_loops/coerce_goal.cc
index 311d882..6fdc70f 100644
--- a/frc971/control_loops/coerce_goal.cc
+++ b/frc971/control_loops/coerce_goal.cc
@@ -1,11 +1,8 @@
 #include "frc971/control_loops/coerce_goal.h"
 
 #include "Eigen/Dense"
-
 #include "aos/controls/polytope.h"
 
 namespace frc971 {
-namespace control_loops {
-
-}  // namespace control_loops
+namespace control_loops {}  // namespace control_loops
 }  // namespace frc971
diff --git a/frc971/control_loops/coerce_goal.h b/frc971/control_loops/coerce_goal.h
index 6e927b0..b682b85 100644
--- a/frc971/control_loops/coerce_goal.h
+++ b/frc971/control_loops/coerce_goal.h
@@ -35,14 +35,34 @@
     if (is_inside) *is_inside = true;
     return R;
   }
+  const Scalar norm_K = K.norm();
   Eigen::Matrix<Scalar, 2, 1> parallel_vector;
   Eigen::Matrix<Scalar, 2, 1> perpendicular_vector;
-  perpendicular_vector = K.transpose().normalized();
+  // Calculate a vector that is perpendicular to the line defined by K * x = w.
+  perpendicular_vector = K.transpose() / norm_K;
+  // Calculate a vector that is parallel to the line defined by K * x = w.
   parallel_vector << perpendicular_vector(1, 0), -perpendicular_vector(0, 0);
 
-  Eigen::Matrix<Scalar, 4, 1> projectedh = region.static_H() * parallel_vector;
-  Eigen::Matrix<Scalar, 4, 1> projectedk =
-      region.static_k() - region.static_H() * perpendicular_vector * w;
+  // Calculate the location along the K x = w line where each boundary of the
+  // polytope would intersect.
+  // I.e., we want to calculate the distance along the K x = w line, as
+  // represented by
+  // parallel_vector * dist + perpendicular_vector * w / norm(K),
+  // such that it intersects with H_i * x = k_i.
+  // This gives us H_i * (parallel * dist + perp * w / norm(K)) = k_i.
+  // dist = (k_i - H_i * perp * w / norm(K)) / (H_i * parallel)
+  // projectedh is the numerator, projectedk is the denominator.
+  // The case where H_i * parallel is zero indicates a scenario where the given
+  // boundary of the polytope is parallel to the line, and so there is no
+  // meaningful value of dist to return.
+  // Note that the sign of projectedh will also indicate whether the distance is
+  // an upper or lower bound. If since valid points satisfy H_i * x < k_i, then
+  // if H_i * parallel is less than zero, then dist will be a lower bound and
+  // if it is greater than zero, then dist will be an upper bound.
+  const Eigen::Matrix<Scalar, 4, 1> projectedh =
+      region.static_H() * parallel_vector;
+  const Eigen::Matrix<Scalar, 4, 1> projectedk =
+      region.static_k() - region.static_H() * perpendicular_vector * w / norm_K;
 
   Scalar min_boundary = ::std::numeric_limits<Scalar>::lowest();
   Scalar max_boundary = ::std::numeric_limits<Scalar>::max();
@@ -50,7 +70,7 @@
     if (projectedh(i, 0) > 0) {
       max_boundary =
           ::std::min(max_boundary, projectedk(i, 0) / projectedh(i, 0));
-    } else {
+    } else if (projectedh(i, 0) != 0) {
       min_boundary =
           ::std::max(min_boundary, projectedk(i, 0) / projectedh(i, 0));
     }
@@ -60,11 +80,16 @@
   vertices << max_boundary, min_boundary;
 
   if (max_boundary > min_boundary) {
+    // The line goes through the region (if the line just intersects a single
+    // vertex, then we fall through to the next clause), but R is not
+    // inside the region. The returned point will be one of the two points where
+    // the line intersects the edge of the region.
     Scalar min_distance_sqr = 0;
     Eigen::Matrix<Scalar, 2, 1> closest_point;
     for (int i = 0; i < vertices.innerSize(); i++) {
       Eigen::Matrix<Scalar, 2, 1> point;
-      point = parallel_vector * vertices(0, i) + perpendicular_vector * w;
+      point =
+          parallel_vector * vertices(0, i) + perpendicular_vector * w / norm_K;
       const Scalar length = (R - point).squaredNorm();
       if (i == 0 || length < min_distance_sqr) {
         closest_point = point;
@@ -74,6 +99,8 @@
     if (is_inside) *is_inside = true;
     return closest_point;
   } else {
+    // The line does not pass through the region; identify the vertex closest to
+    // the line.
     Eigen::Matrix<Scalar, 2, 4> region_vertices = region.StaticVertices();
 #ifdef __linux__
     CHECK_GT(reinterpret_cast<ssize_t>(region_vertices.outerSize()), 0);
@@ -83,8 +110,11 @@
     Scalar min_distance = INFINITY;
     int closest_i = 0;
     for (int i = 0; i < region_vertices.outerSize(); i++) {
+      // Calculate the distance of the vertex from the line. The closest vertex
+      // will be the one to return.
       const Scalar length = ::std::abs(
-          (perpendicular_vector.transpose() * (region_vertices.col(i)))(0, 0));
+          (perpendicular_vector.transpose() * (region_vertices.col(i)))(0, 0) -
+          w);
       if (i == 0 || length < min_distance) {
         closest_i = i;
         min_distance = length;
diff --git a/frc971/control_loops/coerce_goal_test.cc b/frc971/control_loops/coerce_goal_test.cc
new file mode 100644
index 0000000..ae04d6c
--- /dev/null
+++ b/frc971/control_loops/coerce_goal_test.cc
@@ -0,0 +1,217 @@
+#include "frc971/control_loops/coerce_goal.h"
+
+#include <unistd.h>
+
+#include "aos/controls/polytope.h"
+#include "gtest/gtest.h"
+
+namespace frc971 {
+namespace control_loops {
+
+namespace {
+
+aos::controls::HVPolytope<2, 4, 4> MakeBox(double x1_min, double x1_max,
+                                           double x2_min, double x2_max) {
+  Eigen::Matrix<double, 4, 2> box_H;
+  box_H << /*[[*/ 1.0, 0.0 /*]*/,
+      /*[*/ -1.0, 0.0 /*]*/,
+      /*[*/ 0.0, 1.0 /*]*/,
+      /*[*/ 0.0, -1.0 /*]]*/;
+  Eigen::Matrix<double, 4, 1> box_k;
+  box_k << /*[[*/ x1_max /*]*/,
+      /*[*/ -x1_min /*]*/,
+      /*[*/ x2_max /*]*/,
+      /*[*/ -x2_min /*]]*/;
+  aos::controls::HPolytope<2> t_poly(box_H, box_k);
+  return aos::controls::HVPolytope<2, 4, 4>(t_poly.H(), t_poly.k(),
+                                            t_poly.Vertices());
+}
+}  // namespace
+
+class CoerceGoalTest : public ::testing::Test {
+ public:
+  void SetUp() override { aos::controls::HPolytope<2>::Init(); }
+  EIGEN_MAKE_ALIGNED_OPERATOR_NEW
+};
+
+
+TEST_F(CoerceGoalTest, Inside) {
+  aos::controls::HVPolytope<2, 4, 4> box = MakeBox(1, 2, 1, 2);
+
+  Eigen::Matrix<double, 1, 2> K;
+  K << /*[[*/ 1, -1 /*]]*/;
+
+  Eigen::Matrix<double, 2, 1> R;
+  R << /*[[*/ 1.5, 1.5 /*]]*/;
+
+  Eigen::Matrix<double, 2, 1> output =
+      frc971::control_loops::CoerceGoal<double>(box, K, 0, R);
+
+  EXPECT_EQ(R(0, 0), output(0, 0));
+  EXPECT_EQ(R(1, 0), output(1, 0));
+}
+
+TEST_F(CoerceGoalTest, LineOutside) {
+  aos::controls::HVPolytope<2, 4, 4> box = MakeBox(1, 2, 1, 2);
+
+  // Make a line equivalent to y = -x, which does not pass through the box and
+  // is nearest the box at (1, 1).
+  Eigen::Matrix<double, 1, 2> K;
+  K << 1, 1;
+
+  Eigen::Matrix<double, 2, 1> R;
+  R << /*[[*/ 0.0, 0.0 /*]]*/;
+
+  Eigen::Matrix<double, 2, 1> output =
+      frc971::control_loops::CoerceGoal<double>(box, K, 0, R);
+
+  EXPECT_EQ(1.0, output(0, 0));
+  EXPECT_EQ(1.0, output(1, 0));
+
+  // Test the same line, but on the other side of the box, where the (2, 2)
+  // vertex will be closest.
+  output = frc971::control_loops::CoerceGoal<double>(box, K, 5, R);
+  EXPECT_EQ(2.0, output(0, 0));
+  EXPECT_EQ(2.0, output(1, 0));
+}
+
+TEST_F(CoerceGoalTest, GoalOutsideLineInsideThroughOrigin) {
+  aos::controls::HVPolytope<2, 4, 4> box = MakeBox(1, 2, 1, 2);
+
+  Eigen::Matrix<double, 1, 2> K;
+  K << 1, -1;
+
+  Eigen::Matrix<double, 2, 1> R;
+  R << 5, 5;
+
+  Eigen::Matrix<double, 2, 1> output =
+      frc971::control_loops::CoerceGoal<double>(box, K, 0, R);
+
+  EXPECT_EQ(2.0, output(0, 0));
+  EXPECT_EQ(2.0, output(1, 0));
+}
+
+TEST_F(CoerceGoalTest, GoalOutsideLineNotThroughOrigin) {
+  aos::controls::HVPolytope<2, 4, 4> box = MakeBox(1, 2, 1, 2);
+
+  Eigen::Matrix<double, 1, 2> K;
+  K << 1, 1;
+
+  Eigen::Matrix<double, 2, 1> R;
+  R << 0, 3;
+
+  Eigen::Matrix<double, 2, 1> output =
+      frc971::control_loops::CoerceGoal<double>(box, K, 3, R);
+
+  EXPECT_EQ(1.0, output(0, 0));
+  EXPECT_DOUBLE_EQ(2.0, output(1, 0));
+}
+
+TEST_F(CoerceGoalTest, GoalOutsideLineThroughVertex) {
+  aos::controls::HVPolytope<2, 4, 4> box = MakeBox(1, 2, 1, 2);
+
+  Eigen::Matrix<double, 1, 2> K;
+  K << 1, -1;
+
+  Eigen::Matrix<double, 2, 1> R;
+  R << 5, 5;
+
+  Eigen::Matrix<double, 2, 1> output =
+      frc971::control_loops::CoerceGoal<double>(box, K, 1, R);
+
+  EXPECT_EQ(2.0, output(0, 0));
+  EXPECT_EQ(1.0, output(1, 0));
+}
+
+TEST_F(CoerceGoalTest, LineAndGoalOutside) {
+  aos::controls::HVPolytope<2, 4, 4> box = MakeBox(3, 4, 1, 2);
+
+  Eigen::Matrix<double, 1, 2> K;
+  K << 1, -1;
+
+  Eigen::Matrix<double, 2, 1> R;
+  R << 5, 5;
+
+  Eigen::Matrix<double, 2, 1> output =
+      frc971::control_loops::CoerceGoal<double>(box, K, 0, R);
+
+  EXPECT_EQ(3.0, output(0, 0));
+  EXPECT_EQ(2.0, output(1, 0));
+}
+
+TEST_F(CoerceGoalTest, LineThroughEdgeOfBox) {
+  aos::controls::HVPolytope<2, 4, 4> box = MakeBox(0, 4, 1, 2);
+
+  Eigen::Matrix<double, 1, 2> K;
+  K << -1, 1;
+
+  Eigen::Matrix<double, 2, 1> R;
+  R << 5, 5;
+
+  Eigen::Matrix<double, 2, 1> output =
+      frc971::control_loops::CoerceGoal<double>(box, K, 0, R);
+
+  EXPECT_EQ(2.0, output(0, 0));
+  EXPECT_EQ(2.0, output(1, 0));
+}
+
+TEST_F(CoerceGoalTest, PerpendicularLine) {
+  aos::controls::HVPolytope<2, 4, 4> box = MakeBox(1, 2, 1, 2);
+
+  Eigen::Matrix<double, 1, 2> K;
+  K << 1, 1;
+
+  Eigen::Matrix<double, 2, 1> R;
+  R << 5, 5;
+
+  Eigen::Matrix<double, 2, 1> output =
+      frc971::control_loops::CoerceGoal<double>(box, K, 0, R);
+
+  EXPECT_EQ(1.0, output(0, 0));
+  EXPECT_EQ(1.0, output(1, 0));
+}
+
+TEST_F(CoerceGoalTest, WithinRegion) {
+  const auto upoly = MakeBox(-12.0, 12.0, -12.0, 12.0);
+  Eigen::Matrix<double, 1, 2> k;
+  k << 2, 2;
+
+  Eigen::Matrix<double, 2, 1> goal;
+  goal << -2, 2;
+
+  auto result = CoerceGoal<double>(upoly, k, 0, goal);
+
+  EXPECT_EQ(result(0, 0), goal(0, 0));
+  EXPECT_EQ(result(1, 0), goal(1, 0));
+}
+
+TEST_F(CoerceGoalTest, VerticalLine) {
+  const auto upoly = MakeBox(-12.0, 12.0, -12.0, 12.0);
+  Eigen::Matrix<double, 1, 2> k;
+  k << 2, 0;
+
+  Eigen::Matrix<double, 2, 1> goal;
+  goal << 0, 13;
+
+  auto result = CoerceGoal<double>(upoly, k, 0, goal);
+
+  EXPECT_EQ(result(0, 0), 0);
+  EXPECT_EQ(result(1, 0), 12);
+}
+
+TEST_F(CoerceGoalTest, HorizontalLine) {
+  const auto upoly = MakeBox(-12.0, 12.0, -12.0, 12.0);
+  Eigen::Matrix<double, 1, 2> k;
+  k << 0, 2;
+
+  Eigen::Matrix<double, 2, 1> goal;
+  goal << 13, 2;
+
+  auto result = CoerceGoal<double>(upoly, k, 0, goal);
+
+  EXPECT_EQ(result(0, 0), 12);
+  EXPECT_EQ(result(1, 0), 0);
+}
+
+}  // namespace control_loops
+}  // namespace frc971
diff --git a/frc971/control_loops/drivetrain/BUILD b/frc971/control_loops/drivetrain/BUILD
index a723d96..6f46965 100644
--- a/frc971/control_loops/drivetrain/BUILD
+++ b/frc971/control_loops/drivetrain/BUILD
@@ -106,7 +106,8 @@
         ":drivetrain_status_fbs",
         ":drivetrain_position_fbs",
         ":localizer_fbs",
-        "//frc971/queues:gyro",
+        "//frc971/queues:gyro_fbs",
+        "//frc971/queues:gyro_uid_fbs",
         "//frc971/wpilib:imu_fbs",
     ],
     visibility = ["//visibility:public"],
@@ -382,7 +383,7 @@
         "//aos/controls:control_loop",
         "//aos/util:log_interval",
         "//frc971/control_loops:runge_kutta",
-        "//frc971/queues:gyro",
+        "//frc971/queues:gyro_fbs",
         "//frc971/wpilib:imu_fbs",
     ],
 )
@@ -403,7 +404,7 @@
         "//aos/testing:googletest",
         "//frc971/control_loops:control_loops_fbs",
         "//frc971/control_loops:state_feedback_loop",
-        "//frc971/queues:gyro",
+        "//frc971/queues:gyro_fbs",
         "//y2016:constants",
         "//y2016/control_loops/drivetrain:polydrivetrain_plants",
     ],
@@ -434,7 +435,7 @@
         ":drivetrain_test_lib",
         "//aos/controls:control_loop_test",
         "//aos/testing:googletest",
-        "//frc971/queues:gyro",
+        "//frc971/queues:gyro_fbs",
     ] + cpu_select({
         "amd64": [
             "//third_party/matplotlib-cpp",
@@ -641,3 +642,31 @@
         "arm": [],
     }),
 )
+
+cc_library(
+    name = "improved_down_estimator",
+    srcs = [
+        "improved_down_estimator.cc",
+    ],
+    hdrs = [
+        "improved_down_estimator.h",
+    ],
+    deps = [
+        "//frc971/control_loops:runge_kutta",
+        "@com_github_google_glog//:glog",
+        "@org_tuxfamily_eigen//:eigen",
+    ],
+)
+
+cc_test(
+    name = "improved_down_estimator_test",
+    srcs = [
+        "improved_down_estimator_test.cc",
+    ],
+    deps = [
+        "//aos/testing:googletest",
+        "//aos/testing:random_seed",
+        "//frc971/control_loops/drivetrain:improved_down_estimator",
+        "@org_tuxfamily_eigen//:eigen",
+    ],
+)
diff --git a/frc971/control_loops/drivetrain/drivetrain_config.json b/frc971/control_loops/drivetrain/drivetrain_config.json
index dc828bb..df64659 100644
--- a/frc971/control_loops/drivetrain/drivetrain_config.json
+++ b/frc971/control_loops/drivetrain/drivetrain_config.json
@@ -13,6 +13,11 @@
     },
     {
       "name": "/drivetrain",
+      "type": "frc971.sensors.Uid",
+      "frequency": 200
+    },
+    {
+      "name": "/drivetrain",
       "type": "frc971.control_loops.drivetrain.Goal",
       "frequency": 200
     },
diff --git a/frc971/control_loops/drivetrain/drivetrain_lib_test.cc b/frc971/control_loops/drivetrain/drivetrain_lib_test.cc
index 3b668dd..a289fed 100644
--- a/frc971/control_loops/drivetrain/drivetrain_lib_test.cc
+++ b/frc971/control_loops/drivetrain/drivetrain_lib_test.cc
@@ -1414,109 +1414,6 @@
   EXPECT_EQ(1.0, localizer_.theta());
 }
 
-::aos::controls::HVPolytope<2, 4, 4> MakeBox(double x1_min, double x1_max,
-                                             double x2_min, double x2_max) {
-  Eigen::Matrix<double, 4, 2> box_H;
-  box_H << /*[[*/ 1.0, 0.0 /*]*/,
-      /*[*/ -1.0, 0.0 /*]*/,
-      /*[*/ 0.0, 1.0 /*]*/,
-      /*[*/ 0.0, -1.0 /*]]*/;
-  Eigen::Matrix<double, 4, 1> box_k;
-  box_k << /*[[*/ x1_max /*]*/,
-      /*[*/ -x1_min /*]*/,
-      /*[*/ x2_max /*]*/,
-      /*[*/ -x2_min /*]]*/;
-  ::aos::controls::HPolytope<2> t_poly(box_H, box_k);
-  return ::aos::controls::HVPolytope<2, 4, 4>(t_poly.H(), t_poly.k(),
-                                              t_poly.Vertices());
-}
-
-class CoerceGoalTest : public ::testing::Test {
- public:
-  EIGEN_MAKE_ALIGNED_OPERATOR_NEW
-};
-
-// WHOOOHH!
-TEST_F(CoerceGoalTest, Inside) {
-  ::aos::controls::HVPolytope<2, 4, 4> box = MakeBox(1, 2, 1, 2);
-
-  Eigen::Matrix<double, 1, 2> K;
-  K << /*[[*/ 1, -1 /*]]*/;
-
-  Eigen::Matrix<double, 2, 1> R;
-  R << /*[[*/ 1.5, 1.5 /*]]*/;
-
-  Eigen::Matrix<double, 2, 1> output =
-      ::frc971::control_loops::CoerceGoal<double>(box, K, 0, R);
-
-  EXPECT_EQ(R(0, 0), output(0, 0));
-  EXPECT_EQ(R(1, 0), output(1, 0));
-}
-
-TEST_F(CoerceGoalTest, Outside_Inside_Intersect) {
-  ::aos::controls::HVPolytope<2, 4, 4> box = MakeBox(1, 2, 1, 2);
-
-  Eigen::Matrix<double, 1, 2> K;
-  K << 1, -1;
-
-  Eigen::Matrix<double, 2, 1> R;
-  R << 5, 5;
-
-  Eigen::Matrix<double, 2, 1> output =
-      ::frc971::control_loops::CoerceGoal<double>(box, K, 0, R);
-
-  EXPECT_EQ(2.0, output(0, 0));
-  EXPECT_EQ(2.0, output(1, 0));
-}
-
-TEST_F(CoerceGoalTest, Outside_Inside_no_Intersect) {
-  ::aos::controls::HVPolytope<2, 4, 4> box = MakeBox(3, 4, 1, 2);
-
-  Eigen::Matrix<double, 1, 2> K;
-  K << 1, -1;
-
-  Eigen::Matrix<double, 2, 1> R;
-  R << 5, 5;
-
-  Eigen::Matrix<double, 2, 1> output =
-      ::frc971::control_loops::CoerceGoal<double>(box, K, 0, R);
-
-  EXPECT_EQ(3.0, output(0, 0));
-  EXPECT_EQ(2.0, output(1, 0));
-}
-
-TEST_F(CoerceGoalTest, Middle_Of_Edge) {
-  ::aos::controls::HVPolytope<2, 4, 4> box = MakeBox(0, 4, 1, 2);
-
-  Eigen::Matrix<double, 1, 2> K;
-  K << -1, 1;
-
-  Eigen::Matrix<double, 2, 1> R;
-  R << 5, 5;
-
-  Eigen::Matrix<double, 2, 1> output =
-      ::frc971::control_loops::CoerceGoal<double>(box, K, 0, R);
-
-  EXPECT_EQ(2.0, output(0, 0));
-  EXPECT_EQ(2.0, output(1, 0));
-}
-
-TEST_F(CoerceGoalTest, PerpendicularLine) {
-  ::aos::controls::HVPolytope<2, 4, 4> box = MakeBox(1, 2, 1, 2);
-
-  Eigen::Matrix<double, 1, 2> K;
-  K << 1, 1;
-
-  Eigen::Matrix<double, 2, 1> R;
-  R << 5, 5;
-
-  Eigen::Matrix<double, 2, 1> output =
-      ::frc971::control_loops::CoerceGoal<double>(box, K, 0, R);
-
-  EXPECT_EQ(1.0, output(0, 0));
-  EXPECT_EQ(1.0, output(1, 0));
-}
-
 // TODO(austin): Make sure the profile reset code when we disable works.
 
 }  // namespace testing
diff --git a/frc971/control_loops/drivetrain/improved_down_estimator.cc b/frc971/control_loops/drivetrain/improved_down_estimator.cc
new file mode 100644
index 0000000..40dc8e3
--- /dev/null
+++ b/frc971/control_loops/drivetrain/improved_down_estimator.cc
@@ -0,0 +1,287 @@
+#include "frc971/control_loops/drivetrain/improved_down_estimator.h"
+
+#include "Eigen/Dense"
+#include "Eigen/Geometry"
+
+namespace frc971 {
+namespace control_loops {
+namespace drivetrain {
+
+Eigen::Matrix<double, 4, 1> ToQuaternionFromRotationVector(
+    const Eigen::Matrix<double, 3, 1> &X, const double max_angle_cap) {
+  const double unclipped_angle = X.norm();
+  const double angle_scalar =
+      (unclipped_angle > max_angle_cap) ? max_angle_cap / unclipped_angle : 1.0;
+  const double angle = unclipped_angle * angle_scalar;
+  const double half_angle = angle * 0.5;
+
+  const double half_angle_squared = half_angle * half_angle;
+
+  // sin(x)/x = 1
+  double sinx_x = 1.0;
+
+  // - x^2/3!
+  double value = half_angle_squared / 6.0;
+  sinx_x -= value;
+
+  // + x^4/5!
+  value = value * half_angle_squared / 20.0;
+  sinx_x += value;
+
+  // - x^6/7!
+  value = value * half_angle_squared / (6.0 * 7.0);
+  sinx_x -= value;
+
+  // + x^8/9!
+  value = value * half_angle_squared / (8.0 * 9.0);
+  sinx_x += value;
+
+  // - x^10/11!
+  value = value * half_angle_squared / (10.0 * 11.0);
+  sinx_x -= value;
+
+  // + x^12/13!
+  value = value * half_angle_squared / (12.0 * 13.0);
+  sinx_x += value;
+
+  // - x^14/15!
+  value = value * half_angle_squared / (14.0 * 15.0);
+  sinx_x -= value;
+
+  // + x^16/17!
+  value = value * half_angle_squared / (16.0 * 17.0);
+  sinx_x += value;
+
+  // To plot the residual in matplotlib, run:
+  // import numpy
+  // import scipy
+  // from matplotlib import pyplot
+  // x = numpy.arange(-numpy.pi, numpy.pi, 0.01)
+  // pyplot.plot(x, 1 - x**2 / scipy.misc.factorial(3) +
+  //                   x**4 / scipy.misc.factorial(5) -
+  //                   x**6 / scipy.misc.factorial(7) +
+  //                   x**8 / scipy.misc.factorial(9) -
+  //                   x ** 10 / scipy.misc.factorial(11) +
+  //                   x ** 12 / scipy.misc.factorial(13) -
+  //                   x ** 14 / scipy.misc.factorial(15) +
+  //                   x ** 16 / scipy.misc.factorial(17) -
+  //                   numpy.sin(x) / x)
+
+  const double scalar = sinx_x * 0.5;
+
+  Eigen::Matrix<double, 4, 1> result;
+  result.block<3, 1>(0, 0) = X * scalar * angle_scalar;
+  result(3, 0) = std::cos(half_angle);
+  return result;
+}
+
+inline Eigen::Matrix<double, 4, 1> MaybeFlipX(
+    const Eigen::Matrix<double, 4, 1> &X) {
+  if (X(3, 0) < 0.0) {
+    return -X;
+  } else {
+    return X;
+  }
+}
+
+Eigen::Matrix<double, 3, 1> ToRotationVectorFromQuaternion(
+    const Eigen::Matrix<double, 4, 1> &X) {
+  // TODO(austin): Verify we still need it.
+  const Eigen::Matrix<double, 4, 1> corrected_X = MaybeFlipX(X);
+  const double half_angle =
+      std::atan2(corrected_X.block<3, 1>(0, 0).norm(), corrected_X(3, 0));
+
+  const double half_angle_squared = half_angle * half_angle;
+
+  // TODO(austin): We are doing a division at the end of this. Do the taylor
+  // series expansion of x/sin(x) instead to avoid this.
+
+  // sin(x)/x = 1
+  double sinx_x = 1.0;
+
+  // - x^2/3!
+  double value = half_angle_squared / 6.0;
+  sinx_x -= value;
+
+  // + x^4/5!
+  value = value * half_angle_squared / 20.0;
+  sinx_x += value;
+
+  // - x^6/7!
+  value = value * half_angle_squared / (6.0 * 7.0);
+  sinx_x -= value;
+
+  // + x^8/9!
+  value = value * half_angle_squared / (8.0 * 9.0);
+  sinx_x += value;
+
+  // - x^10/11!
+  value = value * half_angle_squared / (10.0 * 11.0);
+  sinx_x -= value;
+
+  // + x^12/13!
+  value = value * half_angle_squared / (12.0 * 13.0);
+  sinx_x += value;
+
+  // - x^14/15!
+  value = value * half_angle_squared / (14.0 * 15.0);
+  sinx_x -= value;
+
+  // + x^16/17!
+  value = value * half_angle_squared / (16.0 * 17.0);
+  sinx_x += value;
+
+  const double scalar = 2.0 / sinx_x;
+
+  return corrected_X.block<3, 1>(0, 0) * scalar;
+}
+
+// States are X_hat_bar (position estimate) and P (Covariance)
+
+void QuaternionUkf::Predict(const Eigen::Matrix<double, 3, 1> &U,
+                            const Eigen::Matrix<double, 3, 1> &measurement) {
+  // Compute the sigma points.
+  // Our system is pretty linear. The traditional way of dealing with process
+  // noise is to augment your state vector with the mean of the process noise,
+  // and augment your covariance matrix with the covariance of your process
+  // noise. Sigma points are then computed. These points are then propegated
+  // through the model. This ends up effectively meaning that perturbations
+  // from the unaugmented state with covariance P are propegated through the
+  // model, and points which are at the mean but with perturbations to simulated
+  // process noise are propegated through the system. The covariance is then
+  // calculated from this set of points, and works out to have a covariance of
+  // essentially P + Q.
+  //
+  // Since our noise is just additive, and quaternian rotation preserves
+  // distance, we can add our noise first and it'll be a good representation of
+  // our distance. This will reduce the number of math operations we need to
+  // do. If we break this assumption in the future by adding a nonlinear model
+  // somewhere in this system, we'll have to revisit this assumption.
+
+  // Now, compute the actual sigma points using the columns of S as the
+  // pertubation vectors. The last point is the original mean.
+  const Eigen::Matrix<double, 4, 3 * 2 + 1> X =
+      GenerateSigmaPoints(X_hat_, P_ + Q_);
+
+  // Now, compute Y, the sigma points which have been propegated forwards by the
+  // model.
+  Eigen::Matrix<double, 4, 3 * 2 + 1> Y;
+  for (int i = 0; i < Y.cols(); ++i) {
+    // Y = Transformed sigma points
+    Y.col(i) = A(X.col(i), U);
+  }
+
+  // We now have the sigma points after the model update.
+  // Compute the mean of the transformed sigma point
+  X_hat_ = Eigen::Quaternion<double>(QuaternionMean(Y));
+
+  // And the covariance.
+  Eigen::Matrix<double, 3, 2 * 3 + 1> Wprime;
+  Eigen::Matrix<double, 3, 3> P_prior =
+      ComputeQuaternionCovariance(X_hat_, Y, &Wprime);
+
+  // If the only obvious acceleration is that due to gravity, then accept the
+  // measurement.
+  constexpr double kUseAccelThreshold = 0.1;
+  if (std::abs(measurement.squaredNorm() - 1.0) < kUseAccelThreshold) {
+    P_ = P_prior;
+    return;
+  }
+
+  // TODO(austin): Maybe re-calculate the sigma points here before transforming
+  // them?  Otherwise we can't cleanly decouple the model and measurement updates.
+
+  // Apply the measurement transform to all the sigma points to get a
+  // representation of the distribution of the measurement.
+  Eigen::Matrix<double, kNumMeasurements, 3 * 2 + 1> Z;
+  Z_hat_.setZero();
+  for (int i = 0; i < Z.cols(); ++i) {
+    Z.col(i) = H(Y.col(i));
+
+    // Compute the mean in addition.
+    Z_hat_ += Z.col(i) / Z.cols();
+  }
+
+  // Now compute the measurement covariance.
+  Eigen::Matrix<double, 3, 3> P_zz;
+  P_zz.setZero();
+  Eigen::Matrix<double, 3, 2 * 3 + 1> Zprime;
+  for (int i = 0; i < 7; ++i) {
+    // Compute the error vector for each sigma point.
+    Eigen::Matrix<double, 3, 1> Zprimei = Z.col(i) - Z_hat_;
+
+    // Now, compute the contribution of this sigma point to P_zz.
+    P_zz += 1.0 / 12.0 * Zprimei * Zprimei.transpose();
+    // Save the error for the cross-correlation matrix.
+    Zprime.col(i) = Zprimei;
+  }
+
+  // Compute the measurement error and innovation uncertanty.
+  const Eigen::Matrix<double, kNumMeasurements, kNumMeasurements> P_vv =
+      P_zz + R_;
+
+  // Now compute the cross correlation matrix P_xz.
+  Eigen::Matrix<double, 3, 3> P_xz;
+  P_xz.setZero();
+  for (int i = 0; i < 7; ++i) {
+    // Now, compute the contribution of this sigma point to P_prior.
+    P_xz += 1.0 / 12.0 * Wprime.col(i) * Zprime.col(i).transpose();
+  }
+
+  // Compute the kalman gain.
+  const Eigen::Matrix<double, 3, kNumMeasurements> K =
+      P_xz * P_vv.inverse();
+
+  // Update X_hat and the covariance P
+  X_hat_ = X_hat_ * Eigen::Quaternion<double>(ToQuaternionFromRotationVector(
+                        K * (measurement - Z_hat_)));
+  P_ = P_prior - K * P_vv * K.transpose();
+}
+
+Eigen::Matrix<double, 3, 3> ComputeQuaternionCovariance(
+    const Eigen::Quaternion<double> &mean,
+    const Eigen::Matrix<double, 4, 7> &points,
+    Eigen::Matrix<double, 3, 7> *residual) {
+  Eigen::Matrix<double, 3, 3> P_prior;
+  P_prior.setZero();
+
+  for (int i = 0; i < 7; ++i) {
+    // Compute the error vector for each sigma point.
+    Eigen::Matrix<double, 3, 1> Wprimei = ToRotationVectorFromQuaternion(
+        Eigen::Quaternion<double>(mean).conjugate() *
+        Eigen::Quaternion<double>(points.col(i)));
+    // Now, compute the contribution of this sigma point to P_prior.
+    P_prior += 1.0 / 6.0 * (Wprimei * Wprimei.transpose());
+    // Save the error for the cross-correlation matrix.
+    residual->col(i) = Wprimei;
+  }
+  return P_prior / 2.0;
+}
+
+Eigen::Matrix<double, 4, 3 * 2 + 1> GenerateSigmaPoints(
+    const Eigen::Quaternion<double> &mean,
+    const Eigen::Matrix<double, 3, 3> &covariance) {
+  // Take the matrix square root.
+  Eigen::Matrix<double, 3, 3> S = covariance.llt().matrixL();
+
+  S *= std::sqrt(2.0 * 3.0);
+  // TODO(austin): Make sure the sigma points aren't outside +- PI/2.0.
+  // Otherwise they wrap on themselves and we get a mess.
+
+  // Now, compute the actual sigma points using the columns of S as the
+  // pertubation vectors. The last point is the original mean.
+  Eigen::Matrix<double, 4, 3 * 2 + 1> X;
+  for (int i = 0; i < 3; ++i) {
+    Eigen::Quaternion<double> perturbation(
+        ToQuaternionFromRotationVector(S.col(i), M_PI_2));
+
+    X.col(i * 2) = (mean * perturbation).coeffs();
+    X.col(i * 2 + 1) = (mean * perturbation.conjugate()).coeffs();
+  }
+  X.col(6) = mean.coeffs();
+  return X;
+}
+
+}  // namespace drivetrain
+}  // namespace control_loops
+}  // namespace frc971
diff --git a/frc971/control_loops/drivetrain/improved_down_estimator.h b/frc971/control_loops/drivetrain/improved_down_estimator.h
new file mode 100644
index 0000000..2b47f9e
--- /dev/null
+++ b/frc971/control_loops/drivetrain/improved_down_estimator.h
@@ -0,0 +1,219 @@
+#ifndef FRC971_CONTROL_LOOPS_DRIVETRAIN_IMPROVED_DOWN_ESTIMATOR_H_
+#define FRC971_CONTROL_LOOPS_DRIVETRAIN_IMPROVED_DOWN_ESTIMATOR_H_
+
+#include "Eigen/Dense"
+#include "Eigen/Geometry"
+
+#include "frc971/control_loops/runge_kutta.h"
+#include "glog/logging.h"
+
+namespace frc971 {
+namespace control_loops {
+namespace drivetrain {
+
+// Function to compute the quaternion average of a bunch of quaternions. Each
+// column in the input matrix is a quaternion (optionally scaled by it's
+// weight).
+template <int SM>
+Eigen::Matrix<double, 4, 1> QuaternionMean(
+    Eigen::Matrix<double, 4, SM> input) {
+  // Algorithm to compute the average of a bunch of quaternions:
+  // http://www.acsu.buffalo.edu/~johnc/ave_quat07.pdf
+
+  Eigen::Matrix<double, 4, 4> m = input * input.transpose();
+
+  Eigen::EigenSolver<Eigen::Matrix<double, 4, 4>> solver;
+  solver.compute(m);
+
+  Eigen::EigenSolver<Eigen::Matrix<double, 4, 4>>::EigenvectorsType
+      eigenvectors = solver.eigenvectors();
+  Eigen::EigenSolver<Eigen::Matrix<double, 4, 4>>::EigenvalueType eigenvalues =
+      solver.eigenvalues();
+
+  int max_index = 0;
+  double max_eigenvalue = 0.0;
+  for (int i = 0; i < 4; ++i) {
+    const double eigenvalue = std::abs(eigenvalues(i, 0));
+    if (eigenvalue > max_eigenvalue) {
+      max_eigenvalue = eigenvalue;
+      max_index = i;
+    }
+  }
+
+  // Assume that there shouldn't be any imaginary components to the eigenvector.
+  // I can't prove this is true, but everyone else seems to assume it...
+  // TODO(james): Handle this more rigorously.
+  for (int i = 0; i < 4; ++i) {
+    CHECK_LT(eigenvectors(i, max_index).imag(), 1e-4)
+        << eigenvectors(i, max_index);
+  }
+  return eigenvectors.col(max_index).real().normalized();
+}
+
+// Converts from a quaternion to a rotation vector, where the rotation vector's
+// direction represents the axis to rotate around and its magnitude represents
+// the number of radians to rotate.
+Eigen::Matrix<double, 3, 1> ToRotationVectorFromQuaternion(
+    const Eigen::Matrix<double, 4, 1> &X);
+
+inline Eigen::Matrix<double, 3, 1> ToRotationVectorFromQuaternion(
+    const Eigen::Quaternion<double> &X) {
+  return ToRotationVectorFromQuaternion(X.coeffs());
+};
+
+// Converts from a rotation vector to a quaternion. If you supply max_angle_cap,
+// then the rotation vector's magnitude will be clipped to be no more than
+// max_angle_cap before being converted to a quaternion.
+Eigen::Matrix<double, 4, 1> ToQuaternionFromRotationVector(
+    const Eigen::Matrix<double, 3, 1> &X,
+    const double max_angle_cap = std::numeric_limits<double>::infinity());
+
+// Generates the sigma points to use in the UKF given the current estimate and
+// covariance.
+Eigen::Matrix<double, 4, 3 * 2 + 1> GenerateSigmaPoints(
+    const Eigen::Quaternion<double> &mean,
+    const Eigen::Matrix<double, 3, 3> &covariance);
+
+// Computes the covariance of the noise given the mean and the transformed sigma
+// points. The residual corresponds with the W' variable from the original
+// paper.
+Eigen::Matrix<double, 3, 3> ComputeQuaternionCovariance(
+    const Eigen::Quaternion<double> &mean,
+    const Eigen::Matrix<double, 4, 7> &points,
+    Eigen::Matrix<double, 3, 7> *residual);
+
+// This class provides a quaternion-based Kalman filter for estimating
+// orientation using a 3-axis gyro and 3-axis accelerometer. It does leave open
+// the option of overridding the system process model and the function used to
+// calculate the expected measurement (which is relevant if, e.g., the IMU is
+// not mounted horizontally in the robot).
+class QuaternionUkf {
+ public:
+  // The state is just a quaternion representing the current robot orientaiton.
+  // The zero/identity quaternion (1, 0, 0, 0) implies that the robot is
+  // position flat on the ground with a heading of zero (which, in our normal
+  // field coordinates, means pointed straight away from our driver's station
+  // wall).
+  constexpr static int kNumStates = 4;
+  // Inputs to the system--we use the (x, y, z)  gyro measurements as the inputs
+  // to the system.
+  constexpr static int kNumInputs = 3;
+  // Measurements to use for correcting the estimated system state. These
+  // correspond to (x, y, z) measurements from the accelerometer.
+  constexpr static int kNumMeasurements = 3;
+  QuaternionUkf() {
+    // TODO(james): Tune the process/measurement noises.
+    R_.setIdentity();
+    R_ /= 100.0;
+
+    Q_.setIdentity();
+    Q_ /= 10000.0;
+
+    // Assume that the robot starts flat on the ground pointed straight forward.
+    X_hat_ = Eigen::Quaternion<double>(1.0, 0.0, 0.0, 0.0);
+
+    // TODO(james): Determine an appropriate starting noise estimate. Probably
+    // not too critical.
+    P_.setIdentity();
+    P_ /= 1000.0;
+  }
+
+  // Handles updating the state of the UKF, given the gyro and accelerometer
+  // measurements.
+  void Predict(const Eigen::Matrix<double, kNumInputs, 1> &U,
+               const Eigen::Matrix<double, 3, 1> &measurement);
+
+  // Returns the updated state for X after one time step, given the current
+  // state and gyro measurements.
+  virtual Eigen::Matrix<double, 4, 1> A(
+      const Eigen::Matrix<double, 4, 1> &X,
+      const Eigen::Matrix<double, kNumInputs, 1> &U) const = 0;
+
+  // Returns the current expected accelerometer measurements given the current
+  // state.
+  virtual Eigen::Matrix<double, 3, 1> H(
+      const Eigen::Matrix<double, 4, 1> &X) const = 0;
+
+  // Returns the current estimate of the robot's orientation. Note that this
+  // filter does not have anything other than the gyro with which to estimate
+  // the robot's yaw heading, and so it may need to be corrected for by upstream
+  // filters.
+  const Eigen::Quaternion<double> &X_hat() const { return X_hat_; }
+
+  Eigen::Matrix<double, 3, 1> Z_hat() const { return Z_hat_; };
+
+ private:
+  // Measurement Noise (Uncertainty)
+  Eigen::Matrix<double, kNumInputs, kNumInputs> R_;
+  // Model noise. Note that both this and P are 3 x 3 matrices, despite the
+  // state having 4 dimensions.
+  Eigen::Matrix<double, 3, 3> Q_;
+  // Current estimate covariance.
+  Eigen::Matrix<double, 3, 3> P_;
+
+  // Current state estimate.
+  Eigen::Quaternion<double> X_hat_;
+
+  // Current expected accelerometer measurement.
+  Eigen::Matrix<double, 3, 1> Z_hat_;
+};
+
+class DrivetrainUkf : public QuaternionUkf {
+ public:
+  constexpr static double kDt = 0.00505;
+
+  // UKF for http://kodlab.seas.upenn.edu/uploads/Arun/UKFpaper.pdf
+  // Reference in case the link is dead:
+  // Kraft, Edgar. "A quaternion-based unscented Kalman filter for orientation
+  // tracking." In Proceedings of the Sixth International Conference of
+  // Information Fusion, vol. 1, pp. 47-54. 2003.
+
+  // A good reference for quaternions is available at
+  // http://www.euclideanspace.com/maths/algebra/realNormedAlgebra/quaternions/
+  //
+  // A good reference for angular velocity vectors with quaternions is at
+  // http://www.euclideanspace.com/physics/kinematics/angularvelocity/
+
+  // Creates a rotational velocity vector to be integrated.
+  //
+  // omega is the rotational velocity vector in body coordinates.
+  // q is a matrix with the compononents of the quaternion in it.
+  //
+  // Returns dq / dt
+  static Eigen::Vector4d QuaternionDerivative(Eigen::Vector3d omega,
+                                              const Eigen::Vector4d &q_matrix) {
+    Eigen::Quaternion<double> q(q_matrix);
+
+    Eigen::Quaternion<double> omega_q;
+    omega_q.w() = 0.0;
+    omega_q.vec() = 0.5 * (q * omega);
+
+    Eigen::Quaternion<double> deriv = omega_q * q;
+    return deriv.coeffs();
+  }
+
+  // Moves the robot by the provided rotation vector (U).
+  Eigen::Matrix<double, kNumStates, 1> A(
+      const Eigen::Matrix<double, kNumStates, 1> &X,
+      const Eigen::Matrix<double, kNumInputs, 1> &U) const override {
+    return RungeKutta(
+        std::bind(&QuaternionDerivative, U, std::placeholders::_1), X, kDt);
+  }
+
+  // Returns the expected accelerometer measurement (which is just going to be
+  // 1g downwards).
+  Eigen::Matrix<double, kNumMeasurements, 1> H(
+      const Eigen::Matrix<double, kNumStates, 1> &X) const override {
+    // TODO(austin): Figure out how to compute what the sensors *should* read.
+    Eigen::Quaternion<double> Xquat(X);
+    Eigen::Matrix<double, 3, 1> gprime =
+        Xquat * Eigen::Matrix<double, 3, 1>(0.0, 0.0, -1.0);
+    return gprime;
+  }
+};
+
+}  // namespace drivetrain
+}  // namespace control_loops
+}  // namespace frc971
+
+#endif  // FRC971_CONTROL_LOOPS_DRIVETRAIN_IMPROVED_DOWN_ESTIMATOR_H_
diff --git a/frc971/control_loops/drivetrain/improved_down_estimator_test.cc b/frc971/control_loops/drivetrain/improved_down_estimator_test.cc
new file mode 100644
index 0000000..db7eeee
--- /dev/null
+++ b/frc971/control_loops/drivetrain/improved_down_estimator_test.cc
@@ -0,0 +1,280 @@
+#include "frc971/control_loops/runge_kutta.h"
+
+#include <Eigen/Geometry>
+#include <random>
+
+#include "aos/testing/random_seed.h"
+#include "frc971/control_loops/drivetrain/improved_down_estimator.h"
+#include "glog/logging.h"
+#include "gtest/gtest.h"
+
+namespace frc971 {
+namespace control_loops {
+namespace testing {
+
+// Do a known transformation to see if quaternion integration is working
+// correctly.
+TEST(RungeKuttaTest, QuaternionIntegral) {
+  Eigen::Vector3d ux = Eigen::Vector3d::UnitX();
+  Eigen::Vector3d uy = Eigen::Vector3d::UnitY();
+  Eigen::Vector3d uz = Eigen::Vector3d::UnitZ();
+
+  Eigen::Quaternion<double> q(
+      Eigen::AngleAxis<double>(0.5 * M_PI, Eigen::Vector3d::UnitY()));
+
+  Eigen::Quaternion<double> q0(
+      Eigen::AngleAxis<double>(0, Eigen::Vector3d::UnitY()));
+
+  auto qux = q * ux;
+
+  VLOG(1) << "Q is w: " << q.w() << " vec: " << q.vec();
+  VLOG(1) << "ux is " << ux;
+  VLOG(1) << "qux is " << qux;
+
+  // Start by rotating around the X world vector for pi/2
+  Eigen::Quaternion<double> integral1(
+      RungeKutta(std::bind(&drivetrain::DrivetrainUkf::QuaternionDerivative, ux,
+                           std::placeholders::_1),
+                 q0.coeffs(), 0.5 * M_PI));
+
+  VLOG(1) << "integral1 * uz => " << integral1 * uz;
+
+  // Then rotate around the Y world vector for pi/2
+  Eigen::Quaternion<double> integral2(
+      RungeKutta(std::bind(&drivetrain::DrivetrainUkf::QuaternionDerivative, uy,
+                           std::placeholders::_1),
+                 integral1.normalized().coeffs(), 0.5 * M_PI));
+
+  VLOG(1) << "integral2 * uz => " << integral2 * uz;
+
+  // Then rotate around the X world vector for -pi/2
+  Eigen::Quaternion<double> integral3(
+      RungeKutta(std::bind(&drivetrain::DrivetrainUkf::QuaternionDerivative,
+                           -ux, std::placeholders::_1),
+                 integral2.normalized().coeffs(), 0.5 * M_PI));
+
+  integral3.normalize();
+
+  VLOG(1) << "Integral is w: " << integral1.w() << " vec: " << integral1.vec()
+          << " norm " << integral1.norm();
+
+  VLOG(1) << "Integral is w: " << integral3.w() << " vec: " << integral3.vec()
+          << " norm " << integral3.norm();
+
+  VLOG(1) << "ux => " << integral3 * ux;
+  EXPECT_NEAR(0.0, (uy - integral3 * ux).norm(), 5e-2);
+}
+
+TEST(RungeKuttaTest, Ukf) {
+  drivetrain::DrivetrainUkf dtukf;
+  Eigen::Vector3d ux = Eigen::Vector3d::UnitX();
+  Eigen::Matrix<double, 3, 1> measurement;
+  measurement.setZero();
+  dtukf.Predict(ux, measurement);
+}
+
+// Tests that small perturbations around a couple quaternions averaged out
+// return the original quaternion.
+TEST(RungeKuttaTest, QuaternionMean) {
+  Eigen::Matrix<double, 4, 7> vectors;
+  vectors.col(0) << 0, 0, 0, 1;
+  for (int i = 0; i < 3; ++i) {
+    Eigen::Matrix<double, 4, 1> perturbation;
+    perturbation.setZero();
+    perturbation(i, 0) = 0.1;
+
+    vectors.col(i * 2 + 1) = vectors.col(0) + perturbation;
+    vectors.col(i * 2 + 2) = vectors.col(0) - perturbation;
+  }
+
+  for (int i = 0; i < 7; ++i) {
+    vectors.col(i).normalize();
+  }
+
+  Eigen::Matrix<double, 4, 1> mean = drivetrain::QuaternionMean(vectors);
+
+  for (int i = 0; i < 4; ++i) {
+    EXPECT_NEAR(mean(i, 0), vectors(i, 0), 0.001) << ": Failed on index " << i;
+  }
+}
+
+// Tests that computing sigma points, and then computing the mean and covariance
+// returns the original answer.
+TEST(RungeKuttaTest, SigmaPoints) {
+  const Eigen::Quaternion<double> mean(
+      Eigen::AngleAxis<double>(M_PI / 2.0, Eigen::Vector3d::UnitX()));
+
+  Eigen::Matrix<double, 3, 3> covariance;
+  covariance << 0.4, -0.1, 0.2, -0.1, 0.6, 0.0, 0.2, 0.0, 0.5;
+  covariance *= 0.1;
+
+  const Eigen::Matrix<double, 4, 3 * 2 + 1> vectors =
+      drivetrain::GenerateSigmaPoints(mean, covariance);
+
+  const Eigen::Matrix<double, 4, 1> calculated_mean =
+      drivetrain::QuaternionMean(vectors);
+
+  VLOG(1) << "actual mean: " << mean.coeffs();
+  VLOG(1) << "calculated mean: " << calculated_mean;
+
+  Eigen::Matrix<double, 3, 3 * 2 + 1> Wprime;
+  Eigen::Matrix<double, 3, 3> calculated_covariance =
+      drivetrain::ComputeQuaternionCovariance(
+          Eigen::Quaternion<double>(calculated_mean), vectors, &Wprime);
+
+  EXPECT_NEAR(1.0,
+              (mean.conjugate().coeffs() * calculated_mean.transpose()).norm(),
+              1e-4);
+
+  EXPECT_NEAR(0.0, (calculated_covariance - covariance).norm(), 1e-8);
+}
+
+// Tests that computing sigma points with a large covariance that will precisely
+// wrap, that we do clip the perturbations.
+TEST(RungeKuttaTest, ClippedSigmaPoints) {
+  const Eigen::Quaternion<double> mean(
+      Eigen::AngleAxis<double>(M_PI / 2.0, Eigen::Vector3d::UnitX()));
+
+  Eigen::Matrix<double, 3, 3> covariance;
+  covariance << 0.4, -0.1, 0.2, -0.1, 0.6, 0.0, 0.2, 0.0, 0.5;
+  covariance *= 100.0;
+
+  const Eigen::Matrix<double, 4, 3 * 2 + 1> vectors =
+      drivetrain::GenerateSigmaPoints(mean, covariance);
+
+  const Eigen::Matrix<double, 4, 1> calculated_mean =
+      drivetrain::QuaternionMean(vectors);
+
+  Eigen::Matrix<double, 3, 3 * 2 + 1> Wprime;
+  Eigen::Matrix<double, 3, 3> calculated_covariance =
+      drivetrain::ComputeQuaternionCovariance(
+          Eigen::Quaternion<double>(calculated_mean), vectors, &Wprime);
+
+  EXPECT_NEAR(1.0,
+              (mean.conjugate().coeffs() * calculated_mean.transpose()).norm(),
+              1e-4);
+
+  const double calculated_covariance_norm = calculated_covariance.norm();
+  const double covariance_norm = covariance.norm();
+  EXPECT_LT(calculated_covariance_norm, covariance_norm / 2.0)
+      << "Calculated covariance should be much smaller than the original "
+         "covariance.";
+}
+
+// Tests that ToRotationVectorFromQuaternion works for a 0 rotation.
+TEST(RungeKuttaTest, ToRotationVectorFromQuaternionAtZero) {
+  Eigen::Matrix<double, 3, 1> vector =
+      drivetrain::ToRotationVectorFromQuaternion(
+          Eigen::Quaternion<double>(
+              Eigen::AngleAxis<double>(0.0, Eigen::Vector3d::UnitX()))
+              .coeffs());
+
+  EXPECT_NEAR(0.0, (vector - Eigen::Vector3d::Zero()).norm(), 1e-4);
+}
+
+// Tests that ToRotationVectorFromQuaternion works for a real rotation.
+TEST(RungeKuttaTest, ToRotationVectorFromQuaternion) {
+  Eigen::Matrix<double, 3, 1> vector =
+      drivetrain::ToRotationVectorFromQuaternion(
+          Eigen::Quaternion<double>(
+              Eigen::AngleAxis<double>(M_PI * 0.5, Eigen::Vector3d::UnitX()))
+              .coeffs());
+
+  EXPECT_NEAR(0.0, (vector - Eigen::Vector3d::UnitX() * M_PI * 0.5).norm(),
+              1e-4);
+}
+
+// Tests that ToRotationVectorFromQuaternion works for a solution with negative
+// coefficients.
+TEST(RungeKuttaTest, ToRotationVectorFromQuaternionNegative) {
+  Eigen::Matrix<double, 3, 1> vector =
+      drivetrain::ToRotationVectorFromQuaternion(
+          Eigen::Quaternion<double>(
+              -Eigen::Quaternion<double>(
+                   Eigen::AngleAxis<double>(M_PI * 0.5,
+                                              Eigen::Vector3d::UnitX()))
+                   .coeffs())
+              .coeffs());
+
+  EXPECT_NEAR(0.0, (vector - Eigen::Vector3d::UnitX() * M_PI * 0.5).norm(),
+              1e-4);
+}
+
+// Tests that ToQuaternionFromRotationVector works for a 0 rotation.
+TEST(RungeKuttaTest, ToQuaternionFromRotationVectorAtZero) {
+  Eigen::Matrix<double, 4, 1> quaternion =
+      drivetrain::ToQuaternionFromRotationVector(Eigen::Vector3d::Zero());
+
+  EXPECT_NEAR(0.0, (quaternion -
+                    Eigen::Quaternion<double>(
+                        Eigen::AngleAxis<double>(0.0, Eigen::Vector3d::UnitX()))
+                        .coeffs()).norm(),
+              1e-4);
+}
+
+// Tests that ToQuaternionFromRotationVector works for a real rotation.
+TEST(RungeKuttaTest, ToQuaternionFromRotationVector) {
+  Eigen::Matrix<double, 4, 1> quaternion =
+      drivetrain::ToQuaternionFromRotationVector(Eigen::Vector3d::UnitX() *
+                                                 M_PI * 0.5);
+
+  EXPECT_NEAR(0.0, (quaternion -
+                    Eigen::Quaternion<double>(
+                        Eigen::AngleAxis<double>(
+                            M_PI * 0.5, Eigen::Vector3d::UnitX())).coeffs())
+
+                       .norm(),
+              1e-4);
+}
+
+// Tests that ToQuaternionFromRotationVector correctly clips a rotation vector
+// that is too large in magnitude.
+TEST(RungeKuttaTest, ToQuaternionFromLargeRotationVector) {
+  const double kMaxAngle = 2.0;
+  const Eigen::Vector3d rotation_vector =
+      Eigen::Vector3d::UnitX() * kMaxAngle * 2.0;
+  const Eigen::Matrix<double, 3, 1> clipped_vector =
+      drivetrain::ToRotationVectorFromQuaternion(
+          drivetrain::ToQuaternionFromRotationVector(rotation_vector,
+                                                     kMaxAngle));
+
+  EXPECT_NEAR(0.0, (rotation_vector / 2.0 - clipped_vector).norm(), 1e-4);
+}
+
+// Tests that ToQuaternionFromRotationVector and ToRotationVectorFromQuaternion
+// works for random rotations.
+TEST(RungeKuttaTest, RandomQuaternions) {
+  std::mt19937 generator(aos::testing::RandomSeed());
+  std::uniform_real_distribution<double> random_scalar(-1.0, 1.0);
+
+  for (int i = 0; i < 1000; ++i) {
+    Eigen::Matrix<double, 3, 1> axis;
+    axis << random_scalar(generator), random_scalar(generator),
+        random_scalar(generator);
+    EXPECT_GE(axis.norm(), 1e-6);
+    axis.normalize();
+
+    const double angle = random_scalar(generator) * M_PI;
+
+    Eigen::Matrix<double, 4, 1> quaternion =
+        drivetrain::ToQuaternionFromRotationVector(axis * angle);
+
+    Eigen::Quaternion<double> answer(Eigen::AngleAxis<double>(angle, axis));
+
+    EXPECT_NEAR(quaternion(3, 0), std::cos(angle / 2.0), 1e-8);
+    EXPECT_NEAR(answer.w(), std::cos(angle / 2.0), 1e-8);
+
+    EXPECT_NEAR(1.0, (answer.coeffs() * quaternion.transpose()).norm(), 1e-6);
+
+    const Eigen::Matrix<double, 3, 1> recalculated_axis =
+        drivetrain::ToRotationVectorFromQuaternion(quaternion);
+
+    EXPECT_NEAR(std::abs(angle), recalculated_axis.norm(), 1e-8);
+
+    EXPECT_NEAR(0.0, (axis * angle - recalculated_axis).norm(), 1e-8);
+  }
+}
+
+}  // namespace testing
+}  // namespace control_loops
+}  // namespace frc971
diff --git a/frc971/control_loops/python/control_loop.py b/frc971/control_loops/python/control_loop.py
index 8bd70fe..919968e 100644
--- a/frc971/control_loops/python/control_loop.py
+++ b/frc971/control_loops/python/control_loop.py
@@ -661,3 +661,27 @@
         self.Kt = 1.0 / self.Kv
         # Stall Torque in N m
         self.stall_torque = self.Kt * self.stall_current
+
+
+class Falcon(object):
+    """Class representing the VexPro Falcon 500 motor.
+
+    All numbers based on data from
+    https://www.vexrobotics.com/vexpro/falcon-500."""
+
+    def __init__(self):
+        # Stall Torque in N m
+        self.stall_torque = 4.69
+        # Stall Current in Amps
+        self.stall_current = 257.0
+        # Free Speed in rad / sec
+        self.free_speed = 6380.0 / 60.0 * 2.0 * numpy.pi
+        # Free Current in Amps
+        self.free_current = 1.5
+        # Resistance of the motor, divided by 2 to account for the 2 motors
+        self.resistance = 12.0 / self.stall_current
+        # Motor velocity constant
+        self.Kv = (self.free_speed /
+                   (12.0 - self.resistance * self.free_current))
+        # Torque constant
+        self.Kt = self.stall_torque / self.stall_current
diff --git a/frc971/queues/BUILD b/frc971/queues/BUILD
index e440638..4005092 100644
--- a/frc971/queues/BUILD
+++ b/frc971/queues/BUILD
@@ -3,9 +3,17 @@
 load("@com_github_google_flatbuffers//:build_defs.bzl", "flatbuffer_cc_library")
 
 flatbuffer_cc_library(
-    name = "gyro",
+    name = "gyro_fbs",
     srcs = [
         "gyro.fbs",
     ],
     gen_reflections = 1,
 )
+
+flatbuffer_cc_library(
+    name = "gyro_uid_fbs",
+    srcs = [
+        "gyro_uid.fbs",
+    ],
+    gen_reflections = 1,
+)
diff --git a/frc971/queues/gyro.fbs b/frc971/queues/gyro.fbs
index 8a76678..a6dce45 100644
--- a/frc971/queues/gyro.fbs
+++ b/frc971/queues/gyro.fbs
@@ -10,9 +10,4 @@
   velocity:double;
 }
 
-// Published on "/drivetrain"
-table Uid {
-  uid:uint;
-}
-
 root_type GyroReading;
diff --git a/frc971/queues/gyro_uid.fbs b/frc971/queues/gyro_uid.fbs
new file mode 100644
index 0000000..05934bf
--- /dev/null
+++ b/frc971/queues/gyro_uid.fbs
@@ -0,0 +1,8 @@
+namespace frc971.sensors;
+
+// Published on "/drivetrain"
+table Uid {
+  uid:uint;
+}
+
+root_type Uid;
diff --git a/frc971/wpilib/BUILD b/frc971/wpilib/BUILD
index 18a1775..bcdd2ce 100644
--- a/frc971/wpilib/BUILD
+++ b/frc971/wpilib/BUILD
@@ -117,7 +117,8 @@
         "//aos/robot_state:robot_state_fbs",
         "//aos/time",
         "//aos/util:phased_loop",
-        "//frc971/queues:gyro",
+        "//frc971/queues:gyro_fbs",
+        "//frc971/queues:gyro_uid_fbs",
         "//frc971/zeroing:averager",
     ],
 )
diff --git a/frc971/wpilib/gyro_sender.cc b/frc971/wpilib/gyro_sender.cc
index 4f88f9e..e7273ee 100644
--- a/frc971/wpilib/gyro_sender.cc
+++ b/frc971/wpilib/gyro_sender.cc
@@ -14,6 +14,7 @@
 #include "aos/time/time.h"
 
 #include "frc971/queues/gyro_generated.h"
+#include "frc971/queues/gyro_uid_generated.h"
 #include "frc971/zeroing/averager.h"
 
 namespace frc971 {
diff --git a/frc971/wpilib/gyro_sender.h b/frc971/wpilib/gyro_sender.h
index 8125598..a0048fb 100644
--- a/frc971/wpilib/gyro_sender.h
+++ b/frc971/wpilib/gyro_sender.h
@@ -9,6 +9,7 @@
 #include "aos/events/shm_event_loop.h"
 #include "aos/robot_state/robot_state_generated.h"
 #include "frc971/queues/gyro_generated.h"
+#include "frc971/queues/gyro_uid_generated.h"
 #include "frc971/wpilib/gyro_interface.h"
 #include "frc971/zeroing/averager.h"
 
diff --git a/frc971/zeroing/BUILD b/frc971/zeroing/BUILD
index fe95bdc..b066dea 100644
--- a/frc971/zeroing/BUILD
+++ b/frc971/zeroing/BUILD
@@ -73,3 +73,14 @@
         "//aos/testing:googletest",
     ],
 )
+
+cc_test(
+    name = "unwrap_test",
+    srcs = [
+        "unwrap_test.cc",
+    ],
+    deps = [
+        ":wrap",
+        "//aos/testing:googletest",
+    ],
+)
diff --git a/y2018/control_loops/superstructure/intake/unwrap_test.cc b/frc971/zeroing/unwrap_test.cc
similarity index 89%
rename from y2018/control_loops/superstructure/intake/unwrap_test.cc
rename to frc971/zeroing/unwrap_test.cc
index 6211846..d1e6db9 100644
--- a/y2018/control_loops/superstructure/intake/unwrap_test.cc
+++ b/frc971/zeroing/unwrap_test.cc
@@ -1,10 +1,8 @@
+#include "frc971/zeroing/wrap.h"
 #include "gtest/gtest.h"
-#include "y2018/control_loops/superstructure/intake/sensor_unwrap.h"
 
-namespace y2018 {
-namespace control_loops {
-namespace superstructure {
-namespace intake {
+namespace frc971 {
+namespace zeroing {
 namespace testing {
 
 TEST(SensorTest, UnwrapOnce) {
@@ -65,7 +63,5 @@
 }
 
 }  // namespace testing
-}  // namespace intake
-}  // namespace superstructure
-}  // namespace control_loops
-}  // namespace y2018
+}  // namespace zeroing
+}  // namespace frc971
diff --git a/frc971/zeroing/wrap.cc b/frc971/zeroing/wrap.cc
index 593ac97..1a0504f 100644
--- a/frc971/zeroing/wrap.cc
+++ b/frc971/zeroing/wrap.cc
@@ -5,6 +5,57 @@
 namespace frc971 {
 namespace zeroing {
 
+UnwrapSensor::UnwrapSensor(double sensor_offset, double sensor_range)
+    : sensor_offset_(sensor_offset), sensor_range_(sensor_range) {
+  Reset();
+}
+
+double UnwrapSensor::Unwrap(double current_sensor_value) {
+  // First time the function is called it will use that value to initialize the
+  // wrap calculation. This catches cases where the offset and first value
+  // difference triggers an unwanted wrap at the first calculation.
+  if (uninitialized_ == true) {
+    uninitialized_ = false;
+  } else {
+    // Calculates the lower sensor value and set the max sensor range
+    // If offset is not 0, this will correct the zeroing offset
+    const double sensor_min_value = sensor_offset_;
+    const double sensor_max_value = sensor_range_ + sensor_min_value;
+
+    // Check if provided sensor value is within the range. This to prevent the
+    // function to get out of sync. Will not throw an error, but continue and
+    // return the value + wrapped factor and not process this value.
+    if (current_sensor_value < sensor_min_value ||
+        current_sensor_value > sensor_max_value) {
+      return current_sensor_value + (sensor_range_ * wrap_count_);
+    }
+
+    // Calculate the positive or negative movement
+    const double sensor_move = current_sensor_value - sensor_last_value_;
+
+    // Function assumes that a movement of more then 1/2 of the range
+    // indicates that we wrapped, instead of moved very fast.
+    if (std::abs(sensor_move) > (sensor_range_ / 2)) {
+      if (sensor_move >= 0) {
+        // sensor moved past the sensor_min_value
+        wrap_count_ -= 1;
+      } else {
+        // sensor moved past the sensor_max_value
+        wrap_count_ += 1;
+      }
+    }
+  }
+  sensor_last_value_ = current_sensor_value;
+  // return the unwrapped sensor value
+  return current_sensor_value + (sensor_range_ * wrap_count_);
+}
+
+void UnwrapSensor::Reset() {
+  wrap_count_ = 0;
+  sensor_last_value_ = sensor_offset_;
+  uninitialized_ = true;
+}
+
 float Wrap(float nearest, float value, float period) {
   return remainderf(value - nearest, period) + nearest;
 }
diff --git a/frc971/zeroing/wrap.h b/frc971/zeroing/wrap.h
index 976e605..f96b9fb 100644
--- a/frc971/zeroing/wrap.h
+++ b/frc971/zeroing/wrap.h
@@ -4,6 +4,40 @@
 namespace frc971 {
 namespace zeroing {
 
+// UnwrapSensor takes in a sensor value from a sensor that loops in a certain
+// interval. ex(the sensor moves from 0 to 10 and back to 0 while moving the
+// same direction) By checking for big gaps in sensor readings it assumes you
+// have wrapped either back or forwards and handles accordingly. It returns the
+// overall sensor value.
+
+class UnwrapSensor {
+ public:
+  // The sensor_offset (+ or -) present the sensor value that is 'zero'
+  // The sensor_range presents the absolute value of the sensor range from 0 to
+  // sensor_range. This will be adjusted using the sensor_offset
+  UnwrapSensor(double sensor_offset, double sensor_range);
+
+  // Takes a wrapped sensor value and unwraps it to give you its total position.
+  double Unwrap(double current_sensor_value);
+
+  void Reset();
+
+  int sensor_wrapped() const { return wrap_count_; }
+
+ private:
+  const double sensor_offset_, sensor_range_;
+
+  // The last value given from set_position, starts at offset
+  double sensor_last_value_ = sensor_offset_;
+
+  // Log if sensor is in wrapped state in either direction
+  int wrap_count_ = 0;
+
+  // function waits for first call with a value to set sensor_last_value_. Will
+  // start to calculate the spring unwrap at the second function call.
+  bool uninitialized_ = true;
+};
+
 // Returns a modified value which has been wrapped such that it is +- period/2
 // away from nearest.
 double Wrap(double nearest, double value, double period);
diff --git a/third_party/linux/BUILD b/third_party/linux/BUILD
deleted file mode 100644
index 007b913..0000000
--- a/third_party/linux/BUILD
+++ /dev/null
@@ -1,10 +0,0 @@
-licenses(["notice"])
-
-package(default_visibility = ["//visibility:public"])
-
-filegroup(
-    name = "sctp",
-    srcs = [
-        "sctp.h",
-    ],
-)
diff --git a/third_party/linux/sctp.h b/third_party/linux/sctp.h
deleted file mode 100644
index bd82046..0000000
--- a/third_party/linux/sctp.h
+++ /dev/null
@@ -1,1105 +0,0 @@
-/* SPDX-License-Identifier: GPL-2.0+ WITH Linux-syscall-note */
-/* SCTP kernel implementation
- * (C) Copyright IBM Corp. 2001, 2004
- * Copyright (c) 1999-2000 Cisco, Inc.
- * Copyright (c) 1999-2001 Motorola, Inc.
- * Copyright (c) 2002 Intel Corp.
- *
- * This file is part of the SCTP kernel implementation
- *
- * This header represents the structures and constants needed to support
- * the SCTP Extension to the Sockets API.
- *
- * This SCTP implementation is free software;
- * you can redistribute it and/or modify it under the terms of
- * the GNU General Public License as published by
- * the Free Software Foundation; either version 2, or (at your option)
- * any later version.
- *
- * This SCTP implementation is distributed in the hope that it
- * will be useful, but WITHOUT ANY WARRANTY; without even the implied
- *                 ************************
- * warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
- * See the GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License
- * along with GNU CC; see the file COPYING.  If not, see
- * <http://www.gnu.org/licenses/>.
- *
- * Please send any bug reports or fixes you make to the
- * email address(es):
- *    lksctp developers <linux-sctp@vger.kernel.org>
- *
- * Or submit a bug report through the following website:
- *    http://www.sf.net/projects/lksctp
- *
- * Written or modified by:
- *    La Monte H.P. Yarroll    <piggy@acm.org>
- *    R. Stewart               <randall@sctp.chicago.il.us>
- *    K. Morneau               <kmorneau@cisco.com>
- *    Q. Xie                   <qxie1@email.mot.com>
- *    Karl Knutson             <karl@athena.chicago.il.us>
- *    Jon Grimm                <jgrimm@us.ibm.com>
- *    Daisy Chang              <daisyc@us.ibm.com>
- *    Ryan Layer               <rmlayer@us.ibm.com>
- *    Ardelle Fan              <ardelle.fan@intel.com>
- *    Sridhar Samudrala        <sri@us.ibm.com>
- *    Inaky Perez-Gonzalez     <inaky.gonzalez@intel.com>
- *    Vlad Yasevich            <vladislav.yasevich@hp.com>
- *
- * Any bugs reported given to us we will try to fix... any fixes shared will
- * be incorporated into the next SCTP release.
- */
-
-#ifndef _SCTP_H
-#define _SCTP_H
-
-#include <linux/types.h>
-#include <linux/socket.h>
-
-typedef __s32 sctp_assoc_t;
-
-/* The following symbols come from the Sockets API Extensions for
- * SCTP <draft-ietf-tsvwg-sctpsocket-07.txt>.
- */
-#define SCTP_RTOINFO	0
-#define SCTP_ASSOCINFO  1
-#define SCTP_INITMSG	2
-#define SCTP_NODELAY	3		/* Get/set nodelay option. */
-#define SCTP_AUTOCLOSE	4
-#define SCTP_SET_PEER_PRIMARY_ADDR 5
-#define SCTP_PRIMARY_ADDR	6
-#define SCTP_ADAPTATION_LAYER	7
-#define SCTP_DISABLE_FRAGMENTS	8
-#define SCTP_PEER_ADDR_PARAMS	9
-#define SCTP_DEFAULT_SEND_PARAM	10
-#define SCTP_EVENTS	11
-#define SCTP_I_WANT_MAPPED_V4_ADDR 12	/* Turn on/off mapped v4 addresses  */
-#define SCTP_MAXSEG	13		/* Get/set maximum fragment. */
-#define SCTP_STATUS	14
-#define SCTP_GET_PEER_ADDR_INFO	15
-#define SCTP_DELAYED_ACK_TIME	16
-#define SCTP_DELAYED_ACK SCTP_DELAYED_ACK_TIME
-#define SCTP_DELAYED_SACK SCTP_DELAYED_ACK_TIME
-#define SCTP_CONTEXT	17
-#define SCTP_FRAGMENT_INTERLEAVE	18
-#define SCTP_PARTIAL_DELIVERY_POINT	19 /* Set/Get partial delivery point */
-#define SCTP_MAX_BURST	20		/* Set/Get max burst */
-#define SCTP_AUTH_CHUNK	21	/* Set only: add a chunk type to authenticate */
-#define SCTP_HMAC_IDENT	22
-#define SCTP_AUTH_KEY	23
-#define SCTP_AUTH_ACTIVE_KEY	24
-#define SCTP_AUTH_DELETE_KEY	25
-#define SCTP_PEER_AUTH_CHUNKS	26	/* Read only */
-#define SCTP_LOCAL_AUTH_CHUNKS	27	/* Read only */
-#define SCTP_GET_ASSOC_NUMBER	28	/* Read only */
-#define SCTP_GET_ASSOC_ID_LIST	29	/* Read only */
-#define SCTP_AUTO_ASCONF       30
-#define SCTP_PEER_ADDR_THLDS	31
-#define SCTP_RECVRCVINFO	32
-#define SCTP_RECVNXTINFO	33
-#define SCTP_DEFAULT_SNDINFO	34
-
-/* Internal Socket Options. Some of the sctp library functions are
- * implemented using these socket options.
- */
-#define SCTP_SOCKOPT_BINDX_ADD	100	/* BINDX requests for adding addrs */
-#define SCTP_SOCKOPT_BINDX_REM	101	/* BINDX requests for removing addrs. */
-#define SCTP_SOCKOPT_PEELOFF	102	/* peel off association. */
-/* Options 104-106 are deprecated and removed. Do not use this space */
-#define SCTP_SOCKOPT_CONNECTX_OLD	107	/* CONNECTX old requests. */
-#define SCTP_GET_PEER_ADDRS	108		/* Get all peer address. */
-#define SCTP_GET_LOCAL_ADDRS	109		/* Get all local address. */
-#define SCTP_SOCKOPT_CONNECTX	110		/* CONNECTX requests. */
-#define SCTP_SOCKOPT_CONNECTX3	111	/* CONNECTX requests (updated) */
-#define SCTP_GET_ASSOC_STATS	112	/* Read only */
-#define SCTP_PR_SUPPORTED	113
-#define SCTP_DEFAULT_PRINFO	114
-#define SCTP_PR_ASSOC_STATUS	115
-#define SCTP_PR_STREAM_STATUS	116
-#define SCTP_RECONFIG_SUPPORTED	117
-#define SCTP_ENABLE_STREAM_RESET	118
-#define SCTP_RESET_STREAMS	119
-#define SCTP_RESET_ASSOC	120
-#define SCTP_ADD_STREAMS	121
-#define SCTP_SOCKOPT_PEELOFF_FLAGS 122
-#define SCTP_STREAM_SCHEDULER	123
-#define SCTP_STREAM_SCHEDULER_VALUE	124
-
-/* PR-SCTP policies */
-#define SCTP_PR_SCTP_NONE	0x0000
-#define SCTP_PR_SCTP_TTL	0x0010
-#define SCTP_PR_SCTP_RTX	0x0020
-#define SCTP_PR_SCTP_PRIO	0x0030
-#define SCTP_PR_SCTP_MAX	SCTP_PR_SCTP_PRIO
-#define SCTP_PR_SCTP_MASK	0x0030
-
-#define __SCTP_PR_INDEX(x)	((x >> 4) - 1)
-#define SCTP_PR_INDEX(x)	__SCTP_PR_INDEX(SCTP_PR_SCTP_ ## x)
-
-#define SCTP_PR_POLICY(x)	((x) & SCTP_PR_SCTP_MASK)
-#define SCTP_PR_SET_POLICY(flags, x)	\
-	do {				\
-		flags &= ~SCTP_PR_SCTP_MASK;	\
-		flags |= x;		\
-	} while (0)
-
-#define SCTP_PR_TTL_ENABLED(x)	(SCTP_PR_POLICY(x) == SCTP_PR_SCTP_TTL)
-#define SCTP_PR_RTX_ENABLED(x)	(SCTP_PR_POLICY(x) == SCTP_PR_SCTP_RTX)
-#define SCTP_PR_PRIO_ENABLED(x)	(SCTP_PR_POLICY(x) == SCTP_PR_SCTP_PRIO)
-
-/* For enable stream reset */
-#define SCTP_ENABLE_RESET_STREAM_REQ	0x01
-#define SCTP_ENABLE_RESET_ASSOC_REQ	0x02
-#define SCTP_ENABLE_CHANGE_ASSOC_REQ	0x04
-#define SCTP_ENABLE_STRRESET_MASK	0x07
-
-#define SCTP_STREAM_RESET_INCOMING	0x01
-#define SCTP_STREAM_RESET_OUTGOING	0x02
-
-/* These are bit fields for msghdr->msg_flags.  See section 5.1.  */
-/* On user space Linux, these live in <bits/socket.h> as an enum.  */
-enum sctp_msg_flags {
-	MSG_NOTIFICATION = 0x8000,
-#define MSG_NOTIFICATION MSG_NOTIFICATION
-};
-
-/* 5.3.1 SCTP Initiation Structure (SCTP_INIT)
- *
- *   This cmsghdr structure provides information for initializing new
- *   SCTP associations with sendmsg().  The SCTP_INITMSG socket option
- *   uses this same data structure.  This structure is not used for
- *   recvmsg().
- *
- *   cmsg_level    cmsg_type      cmsg_data[]
- *   ------------  ------------   ----------------------
- *   IPPROTO_SCTP  SCTP_INIT      struct sctp_initmsg
- */
-struct sctp_initmsg {
-	__u16 sinit_num_ostreams;
-	__u16 sinit_max_instreams;
-	__u16 sinit_max_attempts;
-	__u16 sinit_max_init_timeo;
-};
-
-/* 5.3.2 SCTP Header Information Structure (SCTP_SNDRCV)
- *
- *   This cmsghdr structure specifies SCTP options for sendmsg() and
- *   describes SCTP header information about a received message through
- *   recvmsg().
- *
- *   cmsg_level    cmsg_type      cmsg_data[]
- *   ------------  ------------   ----------------------
- *   IPPROTO_SCTP  SCTP_SNDRCV    struct sctp_sndrcvinfo
- */
-struct sctp_sndrcvinfo {
-	__u16 sinfo_stream;
-	__u16 sinfo_ssn;
-	__u16 sinfo_flags;
-	__u32 sinfo_ppid;
-	__u32 sinfo_context;
-	__u32 sinfo_timetolive;
-	__u32 sinfo_tsn;
-	__u32 sinfo_cumtsn;
-	sctp_assoc_t sinfo_assoc_id;
-};
-
-/* 5.3.4 SCTP Send Information Structure (SCTP_SNDINFO)
- *
- *   This cmsghdr structure specifies SCTP options for sendmsg().
- *
- *   cmsg_level    cmsg_type      cmsg_data[]
- *   ------------  ------------   -------------------
- *   IPPROTO_SCTP  SCTP_SNDINFO   struct sctp_sndinfo
- */
-struct sctp_sndinfo {
-	__u16 snd_sid;
-	__u16 snd_flags;
-	__u32 snd_ppid;
-	__u32 snd_context;
-	sctp_assoc_t snd_assoc_id;
-};
-
-/* 5.3.5 SCTP Receive Information Structure (SCTP_RCVINFO)
- *
- *   This cmsghdr structure describes SCTP receive information
- *   about a received message through recvmsg().
- *
- *   cmsg_level    cmsg_type      cmsg_data[]
- *   ------------  ------------   -------------------
- *   IPPROTO_SCTP  SCTP_RCVINFO   struct sctp_rcvinfo
- */
-struct sctp_rcvinfo {
-	__u16 rcv_sid;
-	__u16 rcv_ssn;
-	__u16 rcv_flags;
-	__u32 rcv_ppid;
-	__u32 rcv_tsn;
-	__u32 rcv_cumtsn;
-	__u32 rcv_context;
-	sctp_assoc_t rcv_assoc_id;
-};
-
-/* 5.3.6 SCTP Next Receive Information Structure (SCTP_NXTINFO)
- *
- *   This cmsghdr structure describes SCTP receive information
- *   of the next message that will be delivered through recvmsg()
- *   if this information is already available when delivering
- *   the current message.
- *
- *   cmsg_level    cmsg_type      cmsg_data[]
- *   ------------  ------------   -------------------
- *   IPPROTO_SCTP  SCTP_NXTINFO   struct sctp_nxtinfo
- */
-struct sctp_nxtinfo {
-	__u16 nxt_sid;
-	__u16 nxt_flags;
-	__u32 nxt_ppid;
-	__u32 nxt_length;
-	sctp_assoc_t nxt_assoc_id;
-};
-
-/*
- *  sinfo_flags: 16 bits (unsigned integer)
- *
- *   This field may contain any of the following flags and is composed of
- *   a bitwise OR of these values.
- */
-enum sctp_sinfo_flags {
-	SCTP_UNORDERED		= (1 << 0), /* Send/receive message unordered. */
-	SCTP_ADDR_OVER		= (1 << 1), /* Override the primary destination. */
-	SCTP_ABORT		= (1 << 2), /* Send an ABORT message to the peer. */
-	SCTP_SACK_IMMEDIATELY	= (1 << 3), /* SACK should be sent without delay. */
-	SCTP_NOTIFICATION	= MSG_NOTIFICATION, /* Next message is not user msg but notification. */
-	SCTP_EOF		= MSG_FIN,  /* Initiate graceful shutdown process. */
-};
-
-typedef union {
-	__u8   			raw;
-	struct sctp_initmsg	init;
-	struct sctp_sndrcvinfo	sndrcv;
-} sctp_cmsg_data_t;
-
-/* These are cmsg_types.  */
-typedef enum sctp_cmsg_type {
-	SCTP_INIT,		/* 5.2.1 SCTP Initiation Structure */
-#define SCTP_INIT	SCTP_INIT
-	SCTP_SNDRCV,		/* 5.2.2 SCTP Header Information Structure */
-#define SCTP_SNDRCV	SCTP_SNDRCV
-	SCTP_SNDINFO,		/* 5.3.4 SCTP Send Information Structure */
-#define SCTP_SNDINFO	SCTP_SNDINFO
-	SCTP_RCVINFO,		/* 5.3.5 SCTP Receive Information Structure */
-#define SCTP_RCVINFO	SCTP_RCVINFO
-	SCTP_NXTINFO,		/* 5.3.6 SCTP Next Receive Information Structure */
-#define SCTP_NXTINFO	SCTP_NXTINFO
-} sctp_cmsg_t;
-
-/*
- * 5.3.1.1 SCTP_ASSOC_CHANGE
- *
- *   Communication notifications inform the ULP that an SCTP association
- *   has either begun or ended. The identifier for a new association is
- *   provided by this notificaion. The notification information has the
- *   following format:
- *
- */
-struct sctp_assoc_change {
-	__u16 sac_type;
-	__u16 sac_flags;
-	__u32 sac_length;
-	__u16 sac_state;
-	__u16 sac_error;
-	__u16 sac_outbound_streams;
-	__u16 sac_inbound_streams;
-	sctp_assoc_t sac_assoc_id;
-	__u8 sac_info[0];
-};
-
-/*
- *   sac_state: 32 bits (signed integer)
- *
- *   This field holds one of a number of values that communicate the
- *   event that happened to the association.  They include:
- *
- *   Note:  The following state names deviate from the API draft as
- *   the names clash too easily with other kernel symbols.
- */
-enum sctp_sac_state {
-	SCTP_COMM_UP,
-	SCTP_COMM_LOST,
-	SCTP_RESTART,
-	SCTP_SHUTDOWN_COMP,
-	SCTP_CANT_STR_ASSOC,
-};
-
-/*
- * 5.3.1.2 SCTP_PEER_ADDR_CHANGE
- *
- *   When a destination address on a multi-homed peer encounters a change
- *   an interface details event is sent.  The information has the
- *   following structure:
- */
-struct sctp_paddr_change {
-	__u16 spc_type;
-	__u16 spc_flags;
-	__u32 spc_length;
-	struct sockaddr_storage spc_aaddr;
-	int spc_state;
-	int spc_error;
-	sctp_assoc_t spc_assoc_id;
-} __attribute__((packed, aligned(4)));
-
-/*
- *    spc_state:  32 bits (signed integer)
- *
- *   This field holds one of a number of values that communicate the
- *   event that happened to the address.  They include:
- */
-enum sctp_spc_state {
-	SCTP_ADDR_AVAILABLE,
-	SCTP_ADDR_UNREACHABLE,
-	SCTP_ADDR_REMOVED,
-	SCTP_ADDR_ADDED,
-	SCTP_ADDR_MADE_PRIM,
-	SCTP_ADDR_CONFIRMED,
-};
-
-
-/*
- * 5.3.1.3 SCTP_REMOTE_ERROR
- *
- *   A remote peer may send an Operational Error message to its peer.
- *   This message indicates a variety of error conditions on an
- *   association. The entire error TLV as it appears on the wire is
- *   included in a SCTP_REMOTE_ERROR event.  Please refer to the SCTP
- *   specification [SCTP] and any extensions for a list of possible
- *   error formats. SCTP error TLVs have the format:
- */
-struct sctp_remote_error {
-	__u16 sre_type;
-	__u16 sre_flags;
-	__u32 sre_length;
-	__be16 sre_error;
-	sctp_assoc_t sre_assoc_id;
-	__u8 sre_data[0];
-};
-
-
-/*
- * 5.3.1.4 SCTP_SEND_FAILED
- *
- *   If SCTP cannot deliver a message it may return the message as a
- *   notification.
- */
-struct sctp_send_failed {
-	__u16 ssf_type;
-	__u16 ssf_flags;
-	__u32 ssf_length;
-	__u32 ssf_error;
-	struct sctp_sndrcvinfo ssf_info;
-	sctp_assoc_t ssf_assoc_id;
-	__u8 ssf_data[0];
-};
-
-/*
- *   ssf_flags: 16 bits (unsigned integer)
- *
- *   The flag value will take one of the following values
- *
- *   SCTP_DATA_UNSENT  - Indicates that the data was never put on
- *                       the wire.
- *
- *   SCTP_DATA_SENT    - Indicates that the data was put on the wire.
- *                       Note that this does not necessarily mean that the
- *                       data was (or was not) successfully delivered.
- */
-enum sctp_ssf_flags {
-	SCTP_DATA_UNSENT,
-	SCTP_DATA_SENT,
-};
-
-/*
- * 5.3.1.5 SCTP_SHUTDOWN_EVENT
- *
- *   When a peer sends a SHUTDOWN, SCTP delivers this notification to
- *   inform the application that it should cease sending data.
- */
-struct sctp_shutdown_event {
-	__u16 sse_type;
-	__u16 sse_flags;
-	__u32 sse_length;
-	sctp_assoc_t sse_assoc_id;
-};
-
-/*
- * 5.3.1.6 SCTP_ADAPTATION_INDICATION
- *
- *   When a peer sends a Adaptation Layer Indication parameter , SCTP
- *   delivers this notification to inform the application
- *   that of the peers requested adaptation layer.
- */
-struct sctp_adaptation_event {
-	__u16 sai_type;
-	__u16 sai_flags;
-	__u32 sai_length;
-	__u32 sai_adaptation_ind;
-	sctp_assoc_t sai_assoc_id;
-};
-
-/*
- * 5.3.1.7 SCTP_PARTIAL_DELIVERY_EVENT
- *
- *   When a receiver is engaged in a partial delivery of a
- *   message this notification will be used to indicate
- *   various events.
- */
-struct sctp_pdapi_event {
-	__u16 pdapi_type;
-	__u16 pdapi_flags;
-	__u32 pdapi_length;
-	__u32 pdapi_indication;
-	sctp_assoc_t pdapi_assoc_id;
-};
-
-enum { SCTP_PARTIAL_DELIVERY_ABORTED=0, };
-
-/*
- * 5.3.1.8.  SCTP_AUTHENTICATION_EVENT
- *
- *  When a receiver is using authentication this message will provide
- *  notifications regarding new keys being made active as well as errors.
- */
-struct sctp_authkey_event {
-	__u16 auth_type;
-	__u16 auth_flags;
-	__u32 auth_length;
-	__u16 auth_keynumber;
-	__u16 auth_altkeynumber;
-	__u32 auth_indication;
-	sctp_assoc_t auth_assoc_id;
-};
-
-enum { SCTP_AUTH_NEWKEY = 0, };
-
-/*
- * 6.1.9. SCTP_SENDER_DRY_EVENT
- *
- * When the SCTP stack has no more user data to send or retransmit, this
- * notification is given to the user. Also, at the time when a user app
- * subscribes to this event, if there is no data to be sent or
- * retransmit, the stack will immediately send up this notification.
- */
-struct sctp_sender_dry_event {
-	__u16 sender_dry_type;
-	__u16 sender_dry_flags;
-	__u32 sender_dry_length;
-	sctp_assoc_t sender_dry_assoc_id;
-};
-
-#define SCTP_STREAM_RESET_INCOMING_SSN	0x0001
-#define SCTP_STREAM_RESET_OUTGOING_SSN	0x0002
-#define SCTP_STREAM_RESET_DENIED	0x0004
-#define SCTP_STREAM_RESET_FAILED	0x0008
-struct sctp_stream_reset_event {
-	__u16 strreset_type;
-	__u16 strreset_flags;
-	__u32 strreset_length;
-	sctp_assoc_t strreset_assoc_id;
-	__u16 strreset_stream_list[];
-};
-
-#define SCTP_ASSOC_RESET_DENIED		0x0004
-#define SCTP_ASSOC_RESET_FAILED		0x0008
-struct sctp_assoc_reset_event {
-	__u16 assocreset_type;
-	__u16 assocreset_flags;
-	__u32 assocreset_length;
-	sctp_assoc_t assocreset_assoc_id;
-	__u32 assocreset_local_tsn;
-	__u32 assocreset_remote_tsn;
-};
-
-#define SCTP_ASSOC_CHANGE_DENIED	0x0004
-#define SCTP_ASSOC_CHANGE_FAILED	0x0008
-#define SCTP_STREAM_CHANGE_DENIED	SCTP_ASSOC_CHANGE_DENIED
-#define SCTP_STREAM_CHANGE_FAILED	SCTP_ASSOC_CHANGE_FAILED
-struct sctp_stream_change_event {
-	__u16 strchange_type;
-	__u16 strchange_flags;
-	__u32 strchange_length;
-	sctp_assoc_t strchange_assoc_id;
-	__u16 strchange_instrms;
-	__u16 strchange_outstrms;
-};
-
-/*
- * Described in Section 7.3
- *   Ancillary Data and Notification Interest Options
- */
-struct sctp_event_subscribe {
-	__u8 sctp_data_io_event;
-	__u8 sctp_association_event;
-	__u8 sctp_address_event;
-	__u8 sctp_send_failure_event;
-	__u8 sctp_peer_error_event;
-	__u8 sctp_shutdown_event;
-	__u8 sctp_partial_delivery_event;
-	__u8 sctp_adaptation_layer_event;
-	__u8 sctp_authentication_event;
-	__u8 sctp_sender_dry_event;
-	__u8 sctp_stream_reset_event;
-	__u8 sctp_assoc_reset_event;
-	__u8 sctp_stream_change_event;
-};
-
-/*
- * 5.3.1 SCTP Notification Structure
- *
- *   The notification structure is defined as the union of all
- *   notification types.
- *
- */
-union sctp_notification {
-	struct {
-		__u16 sn_type;             /* Notification type. */
-		__u16 sn_flags;
-		__u32 sn_length;
-	} sn_header;
-	struct sctp_assoc_change sn_assoc_change;
-	struct sctp_paddr_change sn_paddr_change;
-	struct sctp_remote_error sn_remote_error;
-	struct sctp_send_failed sn_send_failed;
-	struct sctp_shutdown_event sn_shutdown_event;
-	struct sctp_adaptation_event sn_adaptation_event;
-	struct sctp_pdapi_event sn_pdapi_event;
-	struct sctp_authkey_event sn_authkey_event;
-	struct sctp_sender_dry_event sn_sender_dry_event;
-	struct sctp_stream_reset_event sn_strreset_event;
-	struct sctp_assoc_reset_event sn_assocreset_event;
-	struct sctp_stream_change_event sn_strchange_event;
-};
-
-/* Section 5.3.1
- * All standard values for sn_type flags are greater than 2^15.
- * Values from 2^15 and down are reserved.
- */
-
-enum sctp_sn_type {
-	SCTP_SN_TYPE_BASE     = (1<<15),
-	SCTP_ASSOC_CHANGE,
-#define SCTP_ASSOC_CHANGE		SCTP_ASSOC_CHANGE
-	SCTP_PEER_ADDR_CHANGE,
-#define SCTP_PEER_ADDR_CHANGE		SCTP_PEER_ADDR_CHANGE
-	SCTP_SEND_FAILED,
-#define SCTP_SEND_FAILED		SCTP_SEND_FAILED
-	SCTP_REMOTE_ERROR,
-#define SCTP_REMOTE_ERROR		SCTP_REMOTE_ERROR
-	SCTP_SHUTDOWN_EVENT,
-#define SCTP_SHUTDOWN_EVENT		SCTP_SHUTDOWN_EVENT
-	SCTP_PARTIAL_DELIVERY_EVENT,
-#define SCTP_PARTIAL_DELIVERY_EVENT	SCTP_PARTIAL_DELIVERY_EVENT
-	SCTP_ADAPTATION_INDICATION,
-#define SCTP_ADAPTATION_INDICATION	SCTP_ADAPTATION_INDICATION
-	SCTP_AUTHENTICATION_EVENT,
-#define SCTP_AUTHENTICATION_INDICATION	SCTP_AUTHENTICATION_EVENT
-	SCTP_SENDER_DRY_EVENT,
-#define SCTP_SENDER_DRY_EVENT		SCTP_SENDER_DRY_EVENT
-	SCTP_STREAM_RESET_EVENT,
-#define SCTP_STREAM_RESET_EVENT		SCTP_STREAM_RESET_EVENT
-	SCTP_ASSOC_RESET_EVENT,
-#define SCTP_ASSOC_RESET_EVENT		SCTP_ASSOC_RESET_EVENT
-	SCTP_STREAM_CHANGE_EVENT,
-#define SCTP_STREAM_CHANGE_EVENT	SCTP_STREAM_CHANGE_EVENT
-};
-
-/* Notification error codes used to fill up the error fields in some
- * notifications.
- * SCTP_PEER_ADDRESS_CHAGE 	: spc_error
- * SCTP_ASSOC_CHANGE		: sac_error
- * These names should be potentially included in the draft 04 of the SCTP
- * sockets API specification.
- */
-typedef enum sctp_sn_error {
-	SCTP_FAILED_THRESHOLD,
-	SCTP_RECEIVED_SACK,
-	SCTP_HEARTBEAT_SUCCESS,
-	SCTP_RESPONSE_TO_USER_REQ,
-	SCTP_INTERNAL_ERROR,
-	SCTP_SHUTDOWN_GUARD_EXPIRES,
-	SCTP_PEER_FAULTY,
-} sctp_sn_error_t;
-
-/*
- * 7.1.1 Retransmission Timeout Parameters (SCTP_RTOINFO)
- *
- *   The protocol parameters used to initialize and bound retransmission
- *   timeout (RTO) are tunable.  See [SCTP] for more information on how
- *   these parameters are used in RTO calculation.
- */
-struct sctp_rtoinfo {
-	sctp_assoc_t	srto_assoc_id;
-	__u32		srto_initial;
-	__u32		srto_max;
-	__u32		srto_min;
-};
-
-/*
- * 7.1.2 Association Parameters (SCTP_ASSOCINFO)
- *
- *   This option is used to both examine and set various association and
- *   endpoint parameters.
- */
-struct sctp_assocparams {
-	sctp_assoc_t	sasoc_assoc_id;
-	__u16		sasoc_asocmaxrxt;
-	__u16		sasoc_number_peer_destinations;
-	__u32		sasoc_peer_rwnd;
-	__u32		sasoc_local_rwnd;
-	__u32		sasoc_cookie_life;
-};
-
-/*
- * 7.1.9 Set Peer Primary Address (SCTP_SET_PEER_PRIMARY_ADDR)
- *
- *  Requests that the peer mark the enclosed address as the association
- *  primary. The enclosed address must be one of the association's
- *  locally bound addresses. The following structure is used to make a
- *   set primary request:
- */
-struct sctp_setpeerprim {
-	sctp_assoc_t            sspp_assoc_id;
-	struct sockaddr_storage sspp_addr;
-} __attribute__((packed, aligned(4)));
-
-/*
- * 7.1.10 Set Primary Address (SCTP_PRIMARY_ADDR)
- *
- *  Requests that the local SCTP stack use the enclosed peer address as
- *  the association primary. The enclosed address must be one of the
- *  association peer's addresses. The following structure is used to
- *  make a set peer primary request:
- */
-struct sctp_prim {
-	sctp_assoc_t            ssp_assoc_id;
-	struct sockaddr_storage ssp_addr;
-} __attribute__((packed, aligned(4)));
-
-/* For backward compatibility use, define the old name too */
-#define sctp_setprim	sctp_prim
-
-/*
- * 7.1.11 Set Adaptation Layer Indicator (SCTP_ADAPTATION_LAYER)
- *
- * Requests that the local endpoint set the specified Adaptation Layer
- * Indication parameter for all future INIT and INIT-ACK exchanges.
- */
-struct sctp_setadaptation {
-	__u32	ssb_adaptation_ind;
-};
-
-/*
- * 7.1.13 Peer Address Parameters  (SCTP_PEER_ADDR_PARAMS)
- *
- *   Applications can enable or disable heartbeats for any peer address
- *   of an association, modify an address's heartbeat interval, force a
- *   heartbeat to be sent immediately, and adjust the address's maximum
- *   number of retransmissions sent before an address is considered
- *   unreachable. The following structure is used to access and modify an
- *   address's parameters:
- */
-enum  sctp_spp_flags {
-	SPP_HB_ENABLE = 1<<0,		/*Enable heartbeats*/
-	SPP_HB_DISABLE = 1<<1,		/*Disable heartbeats*/
-	SPP_HB = SPP_HB_ENABLE | SPP_HB_DISABLE,
-	SPP_HB_DEMAND = 1<<2,		/*Send heartbeat immediately*/
-	SPP_PMTUD_ENABLE = 1<<3,	/*Enable PMTU discovery*/
-	SPP_PMTUD_DISABLE = 1<<4,	/*Disable PMTU discovery*/
-	SPP_PMTUD = SPP_PMTUD_ENABLE | SPP_PMTUD_DISABLE,
-	SPP_SACKDELAY_ENABLE = 1<<5,	/*Enable SACK*/
-	SPP_SACKDELAY_DISABLE = 1<<6,	/*Disable SACK*/
-	SPP_SACKDELAY = SPP_SACKDELAY_ENABLE | SPP_SACKDELAY_DISABLE,
-	SPP_HB_TIME_IS_ZERO = 1<<7,	/* Set HB delay to 0 */
-};
-
-struct sctp_paddrparams {
-	sctp_assoc_t		spp_assoc_id;
-	struct sockaddr_storage	spp_address;
-	__u32			spp_hbinterval;
-	__u16			spp_pathmaxrxt;
-	__u32			spp_pathmtu;
-	__u32			spp_sackdelay;
-	__u32			spp_flags;
-} __attribute__((packed, aligned(4)));
-
-/*
- * 7.1.18.  Add a chunk that must be authenticated (SCTP_AUTH_CHUNK)
- *
- * This set option adds a chunk type that the user is requesting to be
- * received only in an authenticated way.  Changes to the list of chunks
- * will only effect future associations on the socket.
- */
-struct sctp_authchunk {
-	__u8		sauth_chunk;
-};
-
-/*
- * 7.1.19.  Get or set the list of supported HMAC Identifiers (SCTP_HMAC_IDENT)
- *
- * This option gets or sets the list of HMAC algorithms that the local
- * endpoint requires the peer to use.
- */
-/* This here is only used by user space as is. It might not be a good idea
- * to export/reveal the whole structure with reserved fields etc.
- */
-enum {
-	SCTP_AUTH_HMAC_ID_SHA1 = 1,
-	SCTP_AUTH_HMAC_ID_SHA256 = 3,
-};
-
-struct sctp_hmacalgo {
-	__u32		shmac_num_idents;
-	__u16		shmac_idents[];
-};
-
-/* Sadly, user and kernel space have different names for
- * this structure member, so this is to not break anything.
- */
-#define shmac_number_of_idents	shmac_num_idents
-
-/*
- * 7.1.20.  Set a shared key (SCTP_AUTH_KEY)
- *
- * This option will set a shared secret key which is used to build an
- * association shared key.
- */
-struct sctp_authkey {
-	sctp_assoc_t	sca_assoc_id;
-	__u16		sca_keynumber;
-	__u16		sca_keylength;
-	__u8		sca_key[];
-};
-
-/*
- * 7.1.21.  Get or set the active shared key (SCTP_AUTH_ACTIVE_KEY)
- *
- * This option will get or set the active shared key to be used to build
- * the association shared key.
- */
-
-struct sctp_authkeyid {
-	sctp_assoc_t	scact_assoc_id;
-	__u16		scact_keynumber;
-};
-
-
-/*
- * 7.1.23.  Get or set delayed ack timer (SCTP_DELAYED_SACK)
- *
- * This option will effect the way delayed acks are performed.  This
- * option allows you to get or set the delayed ack time, in
- * milliseconds.  It also allows changing the delayed ack frequency.
- * Changing the frequency to 1 disables the delayed sack algorithm.  If
- * the assoc_id is 0, then this sets or gets the endpoints default
- * values.  If the assoc_id field is non-zero, then the set or get
- * effects the specified association for the one to many model (the
- * assoc_id field is ignored by the one to one model).  Note that if
- * sack_delay or sack_freq are 0 when setting this option, then the
- * current values will remain unchanged.
- */
-struct sctp_sack_info {
-	sctp_assoc_t	sack_assoc_id;
-	uint32_t	sack_delay;
-	uint32_t	sack_freq;
-};
-
-struct sctp_assoc_value {
-    sctp_assoc_t            assoc_id;
-    uint32_t                assoc_value;
-};
-
-struct sctp_stream_value {
-	sctp_assoc_t assoc_id;
-	uint16_t stream_id;
-	uint16_t stream_value;
-};
-
-/*
- * 7.2.2 Peer Address Information
- *
- *   Applications can retrieve information about a specific peer address
- *   of an association, including its reachability state, congestion
- *   window, and retransmission timer values.  This information is
- *   read-only. The following structure is used to access this
- *   information:
- */
-struct sctp_paddrinfo {
-	sctp_assoc_t		spinfo_assoc_id;
-	struct sockaddr_storage	spinfo_address;
-	__s32			spinfo_state;
-	__u32			spinfo_cwnd;
-	__u32			spinfo_srtt;
-	__u32			spinfo_rto;
-	__u32			spinfo_mtu;
-} __attribute__((packed, aligned(4)));
-
-/* Peer addresses's state. */
-/* UNKNOWN: Peer address passed by the upper layer in sendmsg or connect[x]
- * calls.
- * UNCONFIRMED: Peer address received in INIT/INIT-ACK address parameters.
- *              Not yet confirmed by a heartbeat and not available for data
- *		transfers.
- * ACTIVE : Peer address confirmed, active and available for data transfers.
- * INACTIVE: Peer address inactive and not available for data transfers.
- */
-enum sctp_spinfo_state {
-	SCTP_INACTIVE,
-	SCTP_PF,
-	SCTP_ACTIVE,
-	SCTP_UNCONFIRMED,
-	SCTP_UNKNOWN = 0xffff  /* Value used for transport state unknown */
-};
-
-/*
- * 7.2.1 Association Status (SCTP_STATUS)
- *
- *   Applications can retrieve current status information about an
- *   association, including association state, peer receiver window size,
- *   number of unacked data chunks, and number of data chunks pending
- *   receipt.  This information is read-only.  The following structure is
- *   used to access this information:
- */
-struct sctp_status {
-	sctp_assoc_t		sstat_assoc_id;
-	__s32			sstat_state;
-	__u32			sstat_rwnd;
-	__u16			sstat_unackdata;
-	__u16			sstat_penddata;
-	__u16			sstat_instrms;
-	__u16			sstat_outstrms;
-	__u32			sstat_fragmentation_point;
-	struct sctp_paddrinfo	sstat_primary;
-};
-
-/*
- * 7.2.3.  Get the list of chunks the peer requires to be authenticated
- *         (SCTP_PEER_AUTH_CHUNKS)
- *
- * This option gets a list of chunks for a specified association that
- * the peer requires to be received authenticated only.
- */
-struct sctp_authchunks {
-	sctp_assoc_t	gauth_assoc_id;
-	__u32		gauth_number_of_chunks;
-	uint8_t		gauth_chunks[];
-};
-
-/* The broken spelling has been released already in lksctp-tools header,
- * so don't break anyone, now that it's fixed.
- */
-#define guth_number_of_chunks	gauth_number_of_chunks
-
-/* Association states.  */
-enum sctp_sstat_state {
-	SCTP_EMPTY                = 0,
-	SCTP_CLOSED               = 1,
-	SCTP_COOKIE_WAIT          = 2,
-	SCTP_COOKIE_ECHOED        = 3,
-	SCTP_ESTABLISHED          = 4,
-	SCTP_SHUTDOWN_PENDING     = 5,
-	SCTP_SHUTDOWN_SENT        = 6,
-	SCTP_SHUTDOWN_RECEIVED    = 7,
-	SCTP_SHUTDOWN_ACK_SENT    = 8,
-};
-
-/*
- * 8.2.6. Get the Current Identifiers of Associations
- *        (SCTP_GET_ASSOC_ID_LIST)
- *
- * This option gets the current list of SCTP association identifiers of
- * the SCTP associations handled by a one-to-many style socket.
- */
-struct sctp_assoc_ids {
-	__u32		gaids_number_of_ids;
-	sctp_assoc_t	gaids_assoc_id[];
-};
-
-/*
- * 8.3, 8.5 get all peer/local addresses in an association.
- * This parameter struct is used by SCTP_GET_PEER_ADDRS and
- * SCTP_GET_LOCAL_ADDRS socket options used internally to implement
- * sctp_getpaddrs() and sctp_getladdrs() API.
- */
-struct sctp_getaddrs_old {
-	sctp_assoc_t            assoc_id;
-	int			addr_num;
-	struct sockaddr		*addrs;
-};
-
-struct sctp_getaddrs {
-	sctp_assoc_t		assoc_id; /*input*/
-	__u32			addr_num; /*output*/
-	__u8			addrs[0]; /*output, variable size*/
-};
-
-/* A socket user request obtained via SCTP_GET_ASSOC_STATS that retrieves
- * association stats. All stats are counts except sas_maxrto and
- * sas_obs_rto_ipaddr. maxrto is the max observed rto + transport since
- * the last call. Will return 0 when RTO was not update since last call
- */
-struct sctp_assoc_stats {
-	sctp_assoc_t	sas_assoc_id;    /* Input */
-					 /* Transport of observed max RTO */
-	struct sockaddr_storage sas_obs_rto_ipaddr;
-	__u64		sas_maxrto;      /* Maximum Observed RTO for period */
-	__u64		sas_isacks;	 /* SACKs received */
-	__u64		sas_osacks;	 /* SACKs sent */
-	__u64		sas_opackets;	 /* Packets sent */
-	__u64		sas_ipackets;	 /* Packets received */
-	__u64		sas_rtxchunks;   /* Retransmitted Chunks */
-	__u64		sas_outofseqtsns;/* TSN received > next expected */
-	__u64		sas_idupchunks;  /* Dups received (ordered+unordered) */
-	__u64		sas_gapcnt;      /* Gap Acknowledgements Received */
-	__u64		sas_ouodchunks;  /* Unordered data chunks sent */
-	__u64		sas_iuodchunks;  /* Unordered data chunks received */
-	__u64		sas_oodchunks;	 /* Ordered data chunks sent */
-	__u64		sas_iodchunks;	 /* Ordered data chunks received */
-	__u64		sas_octrlchunks; /* Control chunks sent */
-	__u64		sas_ictrlchunks; /* Control chunks received */
-};
-
-/*
- * 8.1 sctp_bindx()
- *
- * The flags parameter is formed from the bitwise OR of zero or more of the
- * following currently defined flags:
- */
-#define SCTP_BINDX_ADD_ADDR 0x01
-#define SCTP_BINDX_REM_ADDR 0x02
-
-/* This is the structure that is passed as an argument(optval) to
- * getsockopt(SCTP_SOCKOPT_PEELOFF).
- */
-typedef struct {
-	sctp_assoc_t associd;
-	int sd;
-} sctp_peeloff_arg_t;
-
-typedef struct {
-	sctp_peeloff_arg_t p_arg;
-	unsigned flags;
-} sctp_peeloff_flags_arg_t;
-
-/*
- *  Peer Address Thresholds socket option
- */
-struct sctp_paddrthlds {
-	sctp_assoc_t spt_assoc_id;
-	struct sockaddr_storage spt_address;
-	__u16 spt_pathmaxrxt;
-	__u16 spt_pathpfthld;
-};
-
-/*
- * Socket Option for Getting the Association/Stream-Specific PR-SCTP Status
- */
-struct sctp_prstatus {
-	sctp_assoc_t sprstat_assoc_id;
-	__u16 sprstat_sid;
-	__u16 sprstat_policy;
-	__u64 sprstat_abandoned_unsent;
-	__u64 sprstat_abandoned_sent;
-};
-
-struct sctp_default_prinfo {
-	sctp_assoc_t pr_assoc_id;
-	__u32 pr_value;
-	__u16 pr_policy;
-};
-
-struct sctp_info {
-	__u32	sctpi_tag;
-	__u32	sctpi_state;
-	__u32	sctpi_rwnd;
-	__u16	sctpi_unackdata;
-	__u16	sctpi_penddata;
-	__u16	sctpi_instrms;
-	__u16	sctpi_outstrms;
-	__u32	sctpi_fragmentation_point;
-	__u32	sctpi_inqueue;
-	__u32	sctpi_outqueue;
-	__u32	sctpi_overall_error;
-	__u32	sctpi_max_burst;
-	__u32	sctpi_maxseg;
-	__u32	sctpi_peer_rwnd;
-	__u32	sctpi_peer_tag;
-	__u8	sctpi_peer_capable;
-	__u8	sctpi_peer_sack;
-	__u16	__reserved1;
-
-	/* assoc status info */
-	__u64	sctpi_isacks;
-	__u64	sctpi_osacks;
-	__u64	sctpi_opackets;
-	__u64	sctpi_ipackets;
-	__u64	sctpi_rtxchunks;
-	__u64	sctpi_outofseqtsns;
-	__u64	sctpi_idupchunks;
-	__u64	sctpi_gapcnt;
-	__u64	sctpi_ouodchunks;
-	__u64	sctpi_iuodchunks;
-	__u64	sctpi_oodchunks;
-	__u64	sctpi_iodchunks;
-	__u64	sctpi_octrlchunks;
-	__u64	sctpi_ictrlchunks;
-
-	/* primary transport info */
-	struct sockaddr_storage	sctpi_p_address;
-	__s32	sctpi_p_state;
-	__u32	sctpi_p_cwnd;
-	__u32	sctpi_p_srtt;
-	__u32	sctpi_p_rto;
-	__u32	sctpi_p_hbinterval;
-	__u32	sctpi_p_pathmaxrxt;
-	__u32	sctpi_p_sackdelay;
-	__u32	sctpi_p_sackfreq;
-	__u32	sctpi_p_ssthresh;
-	__u32	sctpi_p_partial_bytes_acked;
-	__u32	sctpi_p_flight_size;
-	__u16	sctpi_p_error;
-	__u16	__reserved2;
-
-	/* sctp sock info */
-	__u32	sctpi_s_autoclose;
-	__u32	sctpi_s_adaptation_ind;
-	__u32	sctpi_s_pd_point;
-	__u8	sctpi_s_nodelay;
-	__u8	sctpi_s_disable_fragments;
-	__u8	sctpi_s_v4mapped;
-	__u8	sctpi_s_frag_interleave;
-	__u32	sctpi_s_type;
-	__u32	__reserved3;
-};
-
-struct sctp_reset_streams {
-	sctp_assoc_t srs_assoc_id;
-	uint16_t srs_flags;
-	uint16_t srs_number_streams;	/* 0 == ALL */
-	uint16_t srs_stream_list[];	/* list if srs_num_streams is not 0 */
-};
-
-struct sctp_add_streams {
-	sctp_assoc_t sas_assoc_id;
-	uint16_t sas_instrms;
-	uint16_t sas_outstrms;
-};
-
-/* SCTP Stream schedulers */
-enum sctp_sched_type {
-	SCTP_SS_FCFS,
-	SCTP_SS_DEFAULT = SCTP_SS_FCFS,
-	SCTP_SS_PRIO,
-	SCTP_SS_RR,
-	SCTP_SS_MAX = SCTP_SS_RR
-};
-
-#endif /* _SCTP_H */
diff --git a/tools/cpp/CROSSTOOL b/tools/cpp/CROSSTOOL
index 20f47e3..c15eddf 100644
--- a/tools/cpp/CROSSTOOL
+++ b/tools/cpp/CROSSTOOL
@@ -914,7 +914,7 @@
   needsPic: true
   compiler_flag: "-target"
   compiler_flag: "armv7a-arm-linux-gnueabif"
-  compiler_flag: "--sysroot=external/linaro_linux_gcc_repo/arm-linux-gnueabihf/libc"
+  compiler_flag: "--sysroot=external/armhf_debian_rootfs"
   compiler_flag: "-mfloat-abi=hard"
   compiler_flag: "-mfpu=vfpv3-d16"
   compiler_flag: "-nostdinc"
@@ -931,7 +931,9 @@
   compiler_flag: "-isystem"
   compiler_flag: "external/linaro_linux_gcc_repo/include/c++/7.4.1"
   compiler_flag: "-isystem"
-  compiler_flag: "external/linaro_linux_gcc_repo/arm-linux-gnueabihf/libc/usr/include"
+  compiler_flag: "external/armhf_debian_rootfs/usr/include"
+  compiler_flag: "-isystem"
+  compiler_flag: "external/armhf_debian_rootfs/usr/include/arm-linux-gnueabihf"
   compiler_flag: "-isystem"
   compiler_flag: "external/org_frc971/third_party"
   compiler_flag: "-D__STDC_FORMAT_MACROS"
@@ -960,12 +962,14 @@
   compiler_flag: "-ggdb3"
   linker_flag: "-target"
   linker_flag: "armv7a-arm-linux-gnueabif"
-  linker_flag: "--sysroot=external/linaro_linux_gcc_repo/arm-linux-gnueabihf/libc"
+  linker_flag: "--sysroot=external/armhf_debian_rootfs"
   linker_flag: "-lstdc++"
   linker_flag: "-Ltools/cpp/linaro_linux_gcc/clang_more_libs"
-  linker_flag: "-Lexternal/linaro_linux_gcc_repo/arm-linux-gnueabihf/lib"
-  linker_flag: "-Lexternal/linaro_linux_gcc_repo/arm-linux-gnueabihf/libc/lib"
-  linker_flag: "-Lexternal/linaro_linux_gcc_repo/arm-linux-gnueabihf/libc/usr/lib"
+  linker_flag: "-Lexternal/armhf_debian_rootfs/usr/lib/gcc/arm-linux-gnueabihf/8"
+  linker_flag: "-Lexternal/armhf_debian_rootfs/lib/arm-linux-gnueabihf"
+  linker_flag: "-Lexternal/armhf_debian_rootfs/usr/lib/arm-linux-gnueabihf"
+  linker_flag: "-Lexternal/armhf_debian_rootfs/lib"
+  linker_flag: "-Lexternal/armhf_debian_rootfs/usr/lib"
   linker_flag: "-Lexternal/linaro_linux_gcc_repo/lib/gcc/arm-linux-gnueabihf/7.4.1"
   linker_flag: "-Bexternal/linaro_linux_gcc_repo/lib/gcc/arm-linux-gnueabihf/7.4.1"
   linker_flag: "-Bexternal/linaro_linux_gcc_repo/arm-linux-gnueabihf/bin"
@@ -990,12 +994,8 @@
     mode: DYNAMIC
   }
   cxx_builtin_include_directory: "%package(@linaro_linux_gcc_repo//include)%"
-  cxx_builtin_include_directory: "%package(@linaro_linux_gcc_repo//arm-linux-gnueabihf/libc/usr/include)%"
-  cxx_builtin_include_directory: "%package(@linaro_linux_gcc_repo//arm-linux-gnueabihf/libc/usr/lib/include)%"
-  cxx_builtin_include_directory: "%package(@linaro_linux_gcc_repo//arm-linux-gnueabihf/libc/lib/gcc/arm-linux-gnueabihf/7.4.1/include-fixed)%"
+  cxx_builtin_include_directory: "%package(@armhf_debian_rootfs//usr/include)%"
   cxx_builtin_include_directory: "%package(@linaro_linux_gcc_repo//include)%/c++/7.4.1"
-  cxx_builtin_include_directory: "%package(@linaro_linux_gcc_repo//arm-linux-gnueabihf/libc/lib/gcc/arm-linux-gnueabihf/7.4.1/include)%"
-  cxx_builtin_include_directory: "%package(@linaro_linux_gcc_repo//arm-linux-gnueabihf/libc/lib/gcc/arm-linux-gnueabihf/7.4.1/include-fixed)%"
   cxx_builtin_include_directory: "%package(@linaro_linux_gcc_repo//lib/gcc/arm-linux-gnueabihf/7.4.1/include)%"
   cxx_builtin_include_directory: "%package(@linaro_linux_gcc_repo//lib/gcc/arm-linux-gnueabihf/7.4.1/include-fixed)%"
   cxx_builtin_include_directory: "%package(@linaro_linux_gcc_repo//arm-linux-gnueabihf/include)%/c++/7.4.1"
diff --git a/tools/cpp/linaro_linux_gcc/BUILD b/tools/cpp/linaro_linux_gcc/BUILD
index b5ea5a5..7b7f3e5 100644
--- a/tools/cpp/linaro_linux_gcc/BUILD
+++ b/tools/cpp/linaro_linux_gcc/BUILD
@@ -1,13 +1,5 @@
 package(default_visibility = ["//tools/cpp:__pkg__"])
 
-cc_library(
-    name = "libpthread",
-    srcs = [
-        "clang_more_libs/libpthread.so",
-    ],
-    visibility = ["//visibility:public"],
-)
-
 filegroup(
     name = "gcc",
     srcs = [
diff --git a/tools/cpp/linaro_linux_gcc/clang_more_libs/libpthread.so b/tools/cpp/linaro_linux_gcc/clang_more_libs/libpthread.so
deleted file mode 100644
index 71f034f..0000000
--- a/tools/cpp/linaro_linux_gcc/clang_more_libs/libpthread.so
+++ /dev/null
@@ -1,5 +0,0 @@
-/* GNU ld script
-   Use the shared library, but some functions are only in
-   the static library, so try that secondarily.  */
-OUTPUT_FORMAT(elf32-littlearm)
-GROUP ( libpthread.so.0 libpthread_nonshared.a )
diff --git a/tools/cpp/static_crosstool.pb b/tools/cpp/static_crosstool.pb
index 8552dff..7261677 100644
--- a/tools/cpp/static_crosstool.pb
+++ b/tools/cpp/static_crosstool.pb
@@ -939,7 +939,7 @@
 
   compiler_flag: "-target"
   compiler_flag: "armv7a-arm-linux-gnueabif"
-  compiler_flag: "--sysroot=external/linaro_linux_gcc_repo/arm-linux-gnueabihf/libc"
+  compiler_flag: "--sysroot=external/armhf_debian_rootfs"
   compiler_flag: "-mfloat-abi=hard"
   compiler_flag: "-mfpu=vfpv3-d16"
 
@@ -957,27 +957,29 @@
   compiler_flag: "-isystem"
   compiler_flag: "external/linaro_linux_gcc_repo/include/c++/7.4.1"
   compiler_flag: "-isystem"
-  compiler_flag: "external/linaro_linux_gcc_repo/arm-linux-gnueabihf/libc/usr/include"
+  compiler_flag: "external/armhf_debian_rootfs/usr/include"
+  compiler_flag: "-isystem"
+  compiler_flag: "external/armhf_debian_rootfs/usr/include/arm-linux-gnueabihf"
+  compiler_flag: "-isystem"
+  compiler_flag: "external/org_frc971/third_party"
 
   cxx_builtin_include_directory: "%package(@linaro_linux_gcc_repo//include)%"
-  cxx_builtin_include_directory: "%package(@linaro_linux_gcc_repo//arm-linux-gnueabihf/libc/usr/include)%"
-  cxx_builtin_include_directory: "%package(@linaro_linux_gcc_repo//arm-linux-gnueabihf/libc/usr/lib/include)%"
-  cxx_builtin_include_directory: "%package(@linaro_linux_gcc_repo//arm-linux-gnueabihf/libc/lib/gcc/arm-linux-gnueabihf/7.4.1/include-fixed)%"
+  cxx_builtin_include_directory: "%package(@armhf_debian_rootfs//usr/include)%"
   cxx_builtin_include_directory: "%package(@linaro_linux_gcc_repo//include)%/c++/7.4.1"
-  cxx_builtin_include_directory: "%package(@linaro_linux_gcc_repo//arm-linux-gnueabihf/libc/lib/gcc/arm-linux-gnueabihf/7.4.1/include)%"
-  cxx_builtin_include_directory: "%package(@linaro_linux_gcc_repo//arm-linux-gnueabihf/libc/lib/gcc/arm-linux-gnueabihf/7.4.1/include-fixed)%"
   cxx_builtin_include_directory: "%package(@linaro_linux_gcc_repo//lib/gcc/arm-linux-gnueabihf/7.4.1/include)%"
   cxx_builtin_include_directory: "%package(@linaro_linux_gcc_repo//lib/gcc/arm-linux-gnueabihf/7.4.1/include-fixed)%"
   cxx_builtin_include_directory: "%package(@linaro_linux_gcc_repo//arm-linux-gnueabihf/include)%/c++/7.4.1"
 
   linker_flag: "-target"
   linker_flag: "armv7a-arm-linux-gnueabif"
-  linker_flag: "--sysroot=external/linaro_linux_gcc_repo/arm-linux-gnueabihf/libc"
+  linker_flag: "--sysroot=external/armhf_debian_rootfs"
   linker_flag: "-lstdc++"
   linker_flag: "-Ltools/cpp/linaro_linux_gcc/clang_more_libs"
-  linker_flag: "-Lexternal/linaro_linux_gcc_repo/arm-linux-gnueabihf/lib"
-  linker_flag: "-Lexternal/linaro_linux_gcc_repo/arm-linux-gnueabihf/libc/lib"
-  linker_flag: "-Lexternal/linaro_linux_gcc_repo/arm-linux-gnueabihf/libc/usr/lib"
+  linker_flag: "-Lexternal/armhf_debian_rootfs/usr/lib/gcc/arm-linux-gnueabihf/8"
+  linker_flag: "-Lexternal/armhf_debian_rootfs/lib/arm-linux-gnueabihf"
+  linker_flag: "-Lexternal/armhf_debian_rootfs/usr/lib/arm-linux-gnueabihf"
+  linker_flag: "-Lexternal/armhf_debian_rootfs/lib"
+  linker_flag: "-Lexternal/armhf_debian_rootfs/usr/lib"
   linker_flag: "-Lexternal/linaro_linux_gcc_repo/lib/gcc/arm-linux-gnueabihf/7.4.1"
   linker_flag: "-Bexternal/linaro_linux_gcc_repo/lib/gcc/arm-linux-gnueabihf/7.4.1"
   linker_flag: "-Bexternal/linaro_linux_gcc_repo/arm-linux-gnueabihf/bin"
diff --git a/y2014/BUILD b/y2014/BUILD
index b2617da..6ff529a 100644
--- a/y2014/BUILD
+++ b/y2014/BUILD
@@ -36,7 +36,7 @@
         "//aos/util:log_interval",
         "//frc971/autonomous:auto_fbs",
         "//frc971/control_loops/drivetrain:drivetrain_status_fbs",
-        "//frc971/queues:gyro",
+        "//frc971/queues:gyro_fbs",
         "//y2014/actors:shoot_action_lib",
         "//y2014/control_loops/claw:claw_goal_fbs",
         "//y2014/control_loops/claw:claw_status_fbs",
diff --git a/y2016/BUILD b/y2016/BUILD
index 520dd4b..a96ea5a 100644
--- a/y2016/BUILD
+++ b/y2016/BUILD
@@ -38,7 +38,7 @@
         "//frc971/autonomous:auto_fbs",
         "//frc971/control_loops/drivetrain:drivetrain_goal_fbs",
         "//frc971/control_loops/drivetrain:drivetrain_status_fbs",
-        "//frc971/queues:gyro",
+        "//frc971/queues:gyro_fbs",
         "//y2016/actors:autonomous_action_lib",
         "//y2016/actors:superstructure_action_lib",
         "//y2016/actors:vision_align_action_lib",
@@ -51,6 +51,9 @@
 )
 
 robot_downloader(
+    data = [
+        ":config.json",
+    ],
     dirs = [
         "//y2016/dashboard:www_files",
     ],
@@ -81,12 +84,17 @@
         "//y2016/control_loops/superstructure:superstructure_position_fbs",
         "//y2016/control_loops/superstructure:superstructure_status_fbs",
         "//y2016/queues:ball_detector_fbs",
-        "//y2017/vision:vision_fbs",
+        "//y2016/vision:vision_fbs",
+        "//y2019/control_loops/drivetrain:target_selector_fbs",
+        "//y2016/actors:vision_align_action_fbs",
+        "//y2016/actors:superstructure_action_fbs",
     ],
     visibility = ["//visibility:public"],
     deps = [
         "//aos/robot_state:config",
+        "//frc971/autonomous:config",
         "//frc971/control_loops/drivetrain:config",
+        "//frc971/wpilib:config",
     ],
 )
 
diff --git a/y2016/actors/autonomous_actor.cc b/y2016/actors/autonomous_actor.cc
index a665c28..0b11bdd 100644
--- a/y2016/actors/autonomous_actor.cc
+++ b/y2016/actors/autonomous_actor.cc
@@ -64,7 +64,7 @@
           actors::VisionAlignActor::MakeFactory(event_loop)),
       vision_status_fetcher_(
           event_loop->MakeFetcher<::y2016::vision::VisionStatus>(
-              "/superstructure")),
+              "/vision")),
       ball_detector_fetcher_(
           event_loop->MakeFetcher<::y2016::sensors::BallDetector>(
               "/superstructure")),
diff --git a/y2016/actors/vision_align_actor.cc b/y2016/actors/vision_align_actor.cc
index ae05c70..1761a72 100644
--- a/y2016/actors/vision_align_actor.cc
+++ b/y2016/actors/vision_align_actor.cc
@@ -24,7 +24,7 @@
           event_loop, "/vision_align_action"),
       vision_status_fetcher_(
           event_loop->MakeFetcher<::y2016::vision::VisionStatus>(
-              "/superstructure")),
+              "/vision")),
       drivetrain_goal_sender_(
           event_loop->MakeSender<::frc971::control_loops::drivetrain::Goal>(
               "/drivetrain")) {}
diff --git a/y2016/dashboard/dashboard.cc b/y2016/dashboard/dashboard.cc
index 8aec0de..3361e96 100644
--- a/y2016/dashboard/dashboard.cc
+++ b/y2016/dashboard/dashboard.cc
@@ -53,13 +53,13 @@
     : event_loop_(event_loop),
       vision_status_fetcher_(
           event_loop->MakeFetcher<::y2016::vision::VisionStatus>(
-              "/superstructure")),
+              "/vision")),
       ball_detector_fetcher_(
           event_loop->MakeFetcher<::y2016::sensors::BallDetector>(
               "/superstructure")),
       autonomous_mode_fetcher_(
           event_loop->MakeFetcher<::frc971::autonomous::AutonomousMode>(
-              "/aos")),
+              "/autonomous")),
       superstructure_status_fetcher_(
           event_loop
               ->MakeFetcher<::y2016::control_loops::superstructure::Status>(
diff --git a/y2016/vision/target_receiver.cc b/y2016/vision/target_receiver.cc
index e9a2e1d..93ec131 100644
--- a/y2016/vision/target_receiver.cc
+++ b/y2016/vision/target_receiver.cc
@@ -305,7 +305,7 @@
   ::aos::ShmEventLoop event_loop(&config.message());
 
   ::aos::Sender<::y2016::vision::VisionStatus> vision_status_sender =
-      event_loop.MakeSender<::y2016::vision::VisionStatus>("/superstructure");
+      event_loop.MakeSender<::y2016::vision::VisionStatus>("/vision");
 
   StereoGeometry stereo(constants::GetValues().vision_name);
   AOS_LOG(INFO, "calibration: %s\n",
diff --git a/y2016/vision/target_sender.cc b/y2016/vision/target_sender.cc
index 3e29085..41da462 100644
--- a/y2016/vision/target_sender.cc
+++ b/y2016/vision/target_sender.cc
@@ -226,7 +226,7 @@
   using namespace y2016::vision;
   StereoGeometry stereo("./stereo_rig.calib");
   ::aos::logging::Init();
-  ::aos::logging::AddImplementation(
+  ::aos::logging::SetImplementation(
       new ::aos::logging::StreamLogImplementation(stdout));
   std::thread cam0([stereo]() {
     RunCamera(0, GetCameraParams(stereo.calibration()),
diff --git a/y2016/wpilib_interface.cc b/y2016/wpilib_interface.cc
index dc828f3..5c2fcd6 100644
--- a/y2016/wpilib_interface.cc
+++ b/y2016/wpilib_interface.cc
@@ -153,7 +153,7 @@
                 "/superstructure")),
         auto_mode_sender_(
             event_loop->MakeSender<::frc971::autonomous::AutonomousMode>(
-                "/aos")),
+                "/autonomous")),
         shooter_position_sender_(
             event_loop->MakeSender<shooter::Position>("/shooter")),
         superstructure_position_sender_(
diff --git a/y2016/y2016.json b/y2016/y2016.json
index eb8ecd5..9ad4f20 100644
--- a/y2016/y2016.json
+++ b/y2016/y2016.json
@@ -45,10 +45,45 @@
       "name": "/superstructure",
       "type": "y2016.sensors.BallDetector",
       "frequency": 200
+    },
+    {
+      "name": "/vision",
+      "type": "y2016.vision.VisionStatus",
+      "frequency": 200
+    },
+    {
+      "name": "/superstructure_action",
+      "type": "aos.common.actions.Status"
+    },
+    {
+      "name": "/vision_align_action",
+      "type": "aos.common.actions.Status"
+    },
+    {
+      "name": "/autonomous",
+      "type": "aos.common.actions.Status"
+    },
+    {
+      "name": "/autonomous",
+      "type": "frc971.autonomous.Goal"
+    },
+    {
+      "name": "/drivetrain",
+      "type": "y2019.control_loops.drivetrain.TargetSelectorHint"
+    },
+    {
+      "name": "/vision_align_action",
+      "type": "y2016.actors.vision_align_action.Goal"
+    },
+    {
+      "name": "/superstructure_action",
+      "type": "y2016.actors.superstructure_action.Goal"
     }
   ],
   "imports": [
     "../aos/robot_state/robot_state_config.json",
-    "../frc971/control_loops/drivetrain/drivetrain_config.json"
+    "../frc971/control_loops/drivetrain/drivetrain_config.json",
+    "../frc971/autonomous/autonomous_config.json",
+    "../frc971/wpilib/wpilib_config.json"
   ]
 }
diff --git a/y2017/vision/target_sender.cc b/y2017/vision/target_sender.cc
index 261c397..88655c7 100644
--- a/y2017/vision/target_sender.cc
+++ b/y2017/vision/target_sender.cc
@@ -220,7 +220,7 @@
   using namespace y2017::vision;
 
   ::aos::logging::Init();
-  ::aos::logging::AddImplementation(
+  ::aos::logging::SetImplementation(
       new ::aos::logging::StreamLogImplementation(stdout));
   VisionConfig cfg;
   if (ReadConfiguration("ConfigFile.pb.ascii", &cfg)) {
diff --git a/y2018/control_loops/superstructure/intake/BUILD b/y2018/control_loops/superstructure/intake/BUILD
index ea44c7a..dde7c63 100644
--- a/y2018/control_loops/superstructure/intake/BUILD
+++ b/y2018/control_loops/superstructure/intake/BUILD
@@ -39,7 +39,6 @@
     visibility = ["//visibility:public"],
     deps = [
         ":intake_plants",
-        ":sensor_unwrap",
         "//aos:math",
         "//aos/controls:control_loop",
         "//frc971/control_loops:control_loops_fbs",
@@ -50,25 +49,3 @@
         "//y2018/control_loops/superstructure:superstructure_status_fbs",
     ],
 )
-
-cc_library(
-    name = "sensor_unwrap",
-    srcs = [
-        "sensor_unwrap.cc",
-    ],
-    hdrs = [
-        "sensor_unwrap.h",
-    ],
-    visibility = ["//visibility:public"],
-)
-
-cc_test(
-    name = "unwrap_test",
-    srcs = [
-        "unwrap_test.cc",
-    ],
-    deps = [
-        ":sensor_unwrap",
-        "//aos/testing:googletest",
-    ],
-)
diff --git a/y2018/control_loops/superstructure/intake/intake.h b/y2018/control_loops/superstructure/intake/intake.h
index ac4f971..e064c0e 100644
--- a/y2018/control_loops/superstructure/intake/intake.h
+++ b/y2018/control_loops/superstructure/intake/intake.h
@@ -5,11 +5,11 @@
 
 #include "aos/commonmath.h"
 #include "aos/controls/control_loop.h"
+#include "frc971/zeroing/wrap.h"
 #include "frc971/zeroing/zeroing.h"
 #include "y2018/constants.h"
 #include "y2018/control_loops/superstructure/intake/intake_delayed_plant.h"
 #include "y2018/control_loops/superstructure/intake/intake_plant.h"
-#include "y2018/control_loops/superstructure/intake/sensor_unwrap.h"
 #include "y2018/control_loops/superstructure/superstructure_output_generated.h"
 #include "y2018/control_loops/superstructure/superstructure_position_generated.h"
 #include "y2018/control_loops/superstructure/superstructure_status_generated.h"
@@ -121,7 +121,8 @@
     return ::y2018::constants::Values::kIntakeSpringRatio() * (2 * M_PI);
   }
 
-  UnwrapSensor spring_unwrap_{spring_offset_, spring_range()};
+  ::frc971::zeroing::UnwrapSensor spring_unwrap_{spring_offset_,
+                                                 spring_range()};
 
   State state_ = State::UNINITIALIZED;
 
diff --git a/y2018/control_loops/superstructure/intake/sensor_unwrap.cc b/y2018/control_loops/superstructure/intake/sensor_unwrap.cc
deleted file mode 100644
index c7d2a03..0000000
--- a/y2018/control_loops/superstructure/intake/sensor_unwrap.cc
+++ /dev/null
@@ -1,64 +0,0 @@
-#include "y2018/control_loops/superstructure/intake/sensor_unwrap.h"
-
-#include <cmath>
-
-namespace y2018 {
-namespace control_loops {
-namespace superstructure {
-namespace intake {
-
-UnwrapSensor::UnwrapSensor(double sensor_offset, double sensor_range)
-    : sensor_offset_(sensor_offset), sensor_range_(sensor_range) {
-  Reset();
-}
-
-double UnwrapSensor::Unwrap(double current_sensor_value) {
-  // First time the function is called it will use that value to initialize the
-  // wrap calculation. This catches cases where the offset and first value
-  // difference triggers an unwanted wrap at the first calculation.
-  if (uninitialized_ == true) {
-    uninitialized_ = false;
-  } else {
-    // Calculates the lower sensor value and set the max sensor range
-    // If offset is not 0, this will correct the zeroing offset
-    const double sensor_min_value = sensor_offset_;
-    const double sensor_max_value = sensor_range_ + sensor_min_value;
-
-    // Check if provided sensor value is within the range. This to prevent the
-    // function to get out of sync. Will not throw an error, but continue and
-    // return the value + wrapped factor and not process this value.
-    if (current_sensor_value < sensor_min_value ||
-        current_sensor_value > sensor_max_value) {
-      return current_sensor_value + (sensor_range_ * wrap_count_);
-    }
-
-    // Calculate the positive or negative movement
-    const double sensor_move = current_sensor_value - sensor_last_value_;
-
-    // Function assumes that a movement of more then 1/2 of the range
-    // indicates that we wrapped, instead of moved very fast.
-    if (std::abs(sensor_move) > (sensor_range_ / 2)) {
-      if (sensor_move >= 0) {
-        // sensor moved past the sensor_min_value
-        wrap_count_ -= 1;
-      } else {
-        // sensor moved past the sensor_max_value
-        wrap_count_ += 1;
-      }
-    }
-  }
-  sensor_last_value_ = current_sensor_value;
-  // return the unwrapped sensor value
-  return current_sensor_value + (sensor_range_ * wrap_count_);
-}
-
-void UnwrapSensor::Reset() {
-  wrap_count_ = 0;
-  sensor_last_value_ = sensor_offset_;
-  uninitialized_ = true;
-}
-
-}  // namespace intake
-}  // namespace superstructure
-}  // namespace control_loops
-}  // namespace y2018
diff --git a/y2018/control_loops/superstructure/intake/sensor_unwrap.h b/y2018/control_loops/superstructure/intake/sensor_unwrap.h
deleted file mode 100644
index ccb1b52..0000000
--- a/y2018/control_loops/superstructure/intake/sensor_unwrap.h
+++ /dev/null
@@ -1,48 +0,0 @@
-#ifndef Y2018_CONTROL_LOOPS_SUPERSTRUCTURE_INTAKE_SENSOR_UNWRAP_H_
-#define Y2018_CONTROL_LOOPS_SUPERSTRUCTURE_INTAKE_SENSOR_UNWRAP_H_
-
-namespace y2018 {
-namespace control_loops {
-namespace superstructure {
-namespace intake {
-
-// UnwrapSensor takes in a sensor value from a sensor that loops in a certain
-// interval. ex(the sensor moves from 0 to 10 and back to 0 while moving the
-// same direction) By checking for big gaps in sensor readings it assumes you
-// have wrapped either back or forwards and handles accordingly. It returns the
-// overall sensor value.
-
-class UnwrapSensor {
- public:
-  // The sensor_offset (+ or -) present the sensor value that is 'zero'
-  // The sensor_range presents the absolute value of the sensor range from 0 to
-  // sensor_range. This will be adjusted using the sensor_offset
-  UnwrapSensor(double sensor_offset, double sensor_range);
-
-  // Takes a wrapped sensor value and unwraps it to give you its total position.
-  double Unwrap(double current_sensor_value);
-
-  void Reset();
-
-  int sensor_wrapped() const { return wrap_count_; }
-
- private:
-  const double sensor_offset_, sensor_range_;
-
-  // The last value given from set_position, starts at offset
-  double sensor_last_value_ = sensor_offset_;
-
-  // Log if sensor is in wrapped state in either direction
-  int wrap_count_ = 0;
-
-  // function waits for first call with a value to set sensor_last_value_. Will
-  // start to calculate the spring unwrap at the second function call.
-  bool uninitialized_ = true;
-};
-
-}  // namespace intake
-}  // namespace superstructure
-}  // namespace control_loops
-}  // namespace y2018
-
-#endif  // Y2018_CONTROL_LOOPS_SUPERSTRUCTURE_INTAKE_SENSOR_UNWRAP_H_
diff --git a/y2018/vision/image_streamer.cc b/y2018/vision/image_streamer.cc
index b0c1f86..fa5f4b5 100644
--- a/y2018/vision/image_streamer.cc
+++ b/y2018/vision/image_streamer.cc
@@ -289,7 +289,7 @@
 int main(int argc, char ** argv) {
   gflags::ParseCommandLineFlags(&argc, &argv, false);
   ::aos::logging::Init();
-  ::aos::logging::AddImplementation(
+  ::aos::logging::SetImplementation(
       new ::aos::logging::StreamLogImplementation(stderr));
 
   TCPServer<MjpegDataSocket> tcp_server_(80);
diff --git a/y2019/image_streamer/image_streamer.cc b/y2019/image_streamer/image_streamer.cc
index 30bea8f..6f8027c 100644
--- a/y2019/image_streamer/image_streamer.cc
+++ b/y2019/image_streamer/image_streamer.cc
@@ -308,7 +308,7 @@
 int main(int argc, char **argv) {
   gflags::ParseCommandLineFlags(&argc, &argv, false);
   ::aos::logging::Init();
-  ::aos::logging::AddImplementation(
+  ::aos::logging::SetImplementation(
       new ::aos::logging::StreamLogImplementation(stderr));
   TCPServer<MjpegDataSocket> tcp_server_(80);
   aos::vision::CameraParams params0;
diff --git a/y2019/vision/debug_serial.cc b/y2019/vision/debug_serial.cc
index 857f20f..c707d1f 100644
--- a/y2019/vision/debug_serial.cc
+++ b/y2019/vision/debug_serial.cc
@@ -24,7 +24,7 @@
   using namespace frc971::jevois;
   // gflags::ParseCommandLineFlags(&argc, &argv, false);
   ::aos::logging::Init();
-  ::aos::logging::AddImplementation(
+  ::aos::logging::SetImplementation(
       new ::aos::logging::StreamLogImplementation(stderr));
 
   int flags = fcntl(0, F_GETFL, 0);
diff --git a/y2019/vision/global_calibration.cc b/y2019/vision/global_calibration.cc
index e2c4490..52a6e52 100644
--- a/y2019/vision/global_calibration.cc
+++ b/y2019/vision/global_calibration.cc
@@ -103,7 +103,7 @@
   };
 
   ::aos::logging::Init();
-  ::aos::logging::AddImplementation(
+  ::aos::logging::SetImplementation(
       new ::aos::logging::StreamLogImplementation(stderr));
 
   TargetFinder target_finder;
diff --git a/y2020/BUILD b/y2020/BUILD
new file mode 100644
index 0000000..17d3020
--- /dev/null
+++ b/y2020/BUILD
@@ -0,0 +1,125 @@
+load("//frc971:downloader.bzl", "robot_downloader")
+load("//aos:config.bzl", "aos_config")
+load("@com_google_protobuf//:protobuf.bzl", "cc_proto_library")
+load("@com_github_google_flatbuffers//:build_defs.bzl", "flatbuffer_cc_library")
+
+robot_downloader(
+    data = [
+        ":config.json",
+    ],
+    start_binaries = [
+        ":joystick_reader",
+        ":wpilib_interface",
+        "//y2020/control_loops/drivetrain:drivetrain",
+        "//y2020/control_loops/superstructure:superstructure",
+        "//y2020/actors:binaries",
+    ],
+)
+
+cc_library(
+    name = "constants",
+    srcs = [
+        "constants.cc",
+    ],
+    hdrs = [
+        "constants.h",
+    ],
+    visibility = ["//visibility:public"],
+    deps = [
+        "//aos/logging",
+        "//aos/mutex",
+        "//aos/network:team_number",
+        "//frc971:constants",
+        "//frc971/control_loops:pose",
+        "//frc971/control_loops:static_zeroing_single_dof_profiled_subsystem",
+        "//y2020/control_loops/drivetrain:polydrivetrain_plants",
+        "@com_google_absl//absl/base",
+    ],
+)
+
+cc_binary(
+    name = "wpilib_interface",
+    srcs = [
+        "wpilib_interface.cc",
+    ],
+    restricted_to = ["//tools:roborio"],
+    deps = [
+        ":constants",
+        "//aos:init",
+        "//aos:make_unique",
+        "//aos:math",
+        "//aos/controls:control_loop",
+        "//aos/events:shm_event_loop",
+        "//aos/logging",
+        "//aos/robot_state:robot_state_fbs",
+        "//aos/stl_mutex",
+        "//aos/time",
+        "//aos/util:log_interval",
+        "//aos/util:phased_loop",
+        "//aos/util:wrapping_counter",
+        "//frc971/autonomous:auto_mode_fbs",
+        "//frc971/control_loops:control_loops_fbs",
+        "//frc971/control_loops/drivetrain:drivetrain_position_fbs",
+        "//frc971/wpilib:ADIS16448",
+        "//frc971/wpilib:buffered_pcm",
+        "//frc971/wpilib:drivetrain_writer",
+        "//frc971/wpilib:encoder_and_potentiometer",
+        "//frc971/wpilib:interrupt_edge_counting",
+        "//frc971/wpilib:joystick_sender",
+        "//frc971/wpilib:logging_fbs",
+        "//frc971/wpilib:loop_output_handler",
+        "//frc971/wpilib:pdp_fetcher",
+        "//frc971/wpilib:sensor_reader",
+        "//frc971/wpilib:wpilib_interface",
+        "//frc971/wpilib:wpilib_robot_base",
+        "//third_party:phoenix",
+        "//third_party:wpilib",
+        "//y2020/control_loops/superstructure:superstructure_output_fbs",
+        "//y2020/control_loops/superstructure:superstructure_position_fbs",
+    ],
+)
+
+cc_binary(
+    name = "joystick_reader",
+    srcs = [
+        ":joystick_reader.cc",
+    ],
+    deps = [
+        "//aos:init",
+        "//aos/actions:action_lib",
+        "//aos/input:action_joystick_input",
+        "//aos/input:drivetrain_input",
+        "//aos/input:joystick_input",
+        "//aos/logging",
+        "//frc971/autonomous:auto_fbs",
+        "//frc971/autonomous:base_autonomous_actor",
+        "//frc971/control_loops:profiled_subsystem_fbs",
+        "//y2020/control_loops/drivetrain:drivetrain_base",
+        "//y2020/control_loops/superstructure:superstructure_goal_fbs",
+        "//y2020/control_loops/superstructure:superstructure_status_fbs",
+    ],
+)
+
+aos_config(
+    name = "config",
+    src = "y2020.json",
+    flatbuffers = [
+        "//y2020/control_loops/superstructure:superstructure_goal_fbs",
+        "//y2020/control_loops/superstructure:superstructure_output_fbs",
+        "//y2020/control_loops/superstructure:superstructure_position_fbs",
+        "//y2020/control_loops/superstructure:superstructure_status_fbs",
+    ],
+    visibility = ["//visibility:public"],
+    deps = [
+        "//aos/robot_state:config",
+        "//frc971/autonomous:config",
+        "//frc971/control_loops/drivetrain:config",
+        "//frc971/wpilib:config",
+    ],
+)
+
+py_library(
+    name = "python_init",
+    srcs = ["__init__.py"],
+    visibility = ["//visibility:public"],
+)
diff --git a/y2020/__init__.py b/y2020/__init__.py
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/y2020/__init__.py
diff --git a/y2020/actors/BUILD b/y2020/actors/BUILD
new file mode 100644
index 0000000..668c502
--- /dev/null
+++ b/y2020/actors/BUILD
@@ -0,0 +1,53 @@
+filegroup(
+    name = "binaries.stripped",
+    srcs = [
+        ":autonomous_action.stripped",
+    ],
+    visibility = ["//visibility:public"],
+)
+
+filegroup(
+    name = "binaries",
+    srcs = [
+        ":autonomous_action",
+    ],
+    visibility = ["//visibility:public"],
+)
+
+cc_library(
+    name = "autonomous_action_lib",
+    srcs = [
+        "auto_splines.cc",
+        "autonomous_actor.cc",
+    ],
+    hdrs = [
+        "auto_splines.h",
+        "autonomous_actor.h",
+    ],
+    deps = [
+        "//aos/events:event_loop",
+        "//aos/logging",
+        "//aos/util:phased_loop",
+        "//frc971/autonomous:base_autonomous_actor",
+        "//frc971/control_loops:control_loops_fbs",
+        "//frc971/control_loops:profiled_subsystem_fbs",
+        "//frc971/control_loops/drivetrain:drivetrain_config",
+        "//frc971/control_loops/drivetrain:localizer_fbs",
+        "//y2020/control_loops/drivetrain:drivetrain_base",
+        "//y2020/control_loops/superstructure:superstructure_goal_fbs",
+        "//y2020/control_loops/superstructure:superstructure_status_fbs",
+    ],
+)
+
+cc_binary(
+    name = "autonomous_action",
+    srcs = [
+        "autonomous_actor_main.cc",
+    ],
+    deps = [
+        ":autonomous_action_lib",
+        "//aos:init",
+        "//aos/events:shm_event_loop",
+        "//frc971/autonomous:auto_fbs",
+    ],
+)
diff --git a/y2020/actors/auto_splines.cc b/y2020/actors/auto_splines.cc
new file mode 100644
index 0000000..86eed14
--- /dev/null
+++ b/y2020/actors/auto_splines.cc
@@ -0,0 +1,103 @@
+#include "y2020/actors/auto_splines.h"
+
+#include "frc971/control_loops/control_loops_generated.h"
+
+namespace y2020 {
+namespace actors {
+
+void MaybeFlipSpline(
+    aos::Sender<frc971::control_loops::drivetrain::Goal>::Builder *builder,
+    flatbuffers::Offset<flatbuffers::Vector<float>> spline_y_offset,
+    bool is_left) {
+  flatbuffers::Vector<float> *spline_y =
+      GetMutableTemporaryPointer(*builder->fbb(), spline_y_offset);
+
+  if (!is_left) {
+    for (size_t i = 0; i < spline_y->size(); i++) {
+      spline_y->Mutate(i, -spline_y->Get(i));
+    }
+  }
+}
+
+flatbuffers::Offset<frc971::MultiSpline> AutonomousSplines::BasicSSpline(
+    aos::Sender<frc971::control_loops::drivetrain::Goal>::Builder *builder) {
+  flatbuffers::Offset<frc971::Constraint> longitudinal_constraint_offset;
+  flatbuffers::Offset<frc971::Constraint> lateral_constraint_offset;
+  flatbuffers::Offset<frc971::Constraint> voltage_constraint_offset;
+
+  {
+    frc971::Constraint::Builder longitudinal_constraint_builder =
+        builder->MakeBuilder<frc971::Constraint>();
+    longitudinal_constraint_builder.add_constraint_type(
+        frc971::ConstraintType::LONGITUDINAL_ACCELERATION);
+    longitudinal_constraint_builder.add_value(1.0);
+    longitudinal_constraint_offset = longitudinal_constraint_builder.Finish();
+  }
+
+  {
+    frc971::Constraint::Builder lateral_constraint_builder =
+        builder->MakeBuilder<frc971::Constraint>();
+    lateral_constraint_builder.add_constraint_type(
+        frc971::ConstraintType::LATERAL_ACCELERATION);
+    lateral_constraint_builder.add_value(1.0);
+    lateral_constraint_offset = lateral_constraint_builder.Finish();
+  }
+
+  {
+    frc971::Constraint::Builder voltage_constraint_builder =
+        builder->MakeBuilder<frc971::Constraint>();
+    voltage_constraint_builder.add_constraint_type(
+        frc971::ConstraintType::VOLTAGE);
+    voltage_constraint_builder.add_value(6.0);
+    voltage_constraint_offset = voltage_constraint_builder.Finish();
+  }
+
+  flatbuffers::Offset<
+      flatbuffers::Vector<flatbuffers::Offset<frc971::Constraint>>>
+      constraints_offset =
+          builder->fbb()->CreateVector<flatbuffers::Offset<frc971::Constraint>>(
+              {longitudinal_constraint_offset, lateral_constraint_offset,
+               voltage_constraint_offset});
+
+  const float startx = 0.4;
+  const float starty = 3.4;
+  flatbuffers::Offset<flatbuffers::Vector<float>> spline_x_offset =
+      builder->fbb()->CreateVector<float>({0.0f + startx, 0.6f + startx,
+                                           0.6f + startx, 0.4f + startx,
+                                           0.4f + startx, 1.0f + startx});
+  flatbuffers::Offset<flatbuffers::Vector<float>> spline_y_offset =
+      builder->fbb()->CreateVector<float>({starty - 0.0f, starty - 0.0f,
+                                           starty - 0.3f, starty - 0.7f,
+                                           starty - 1.0f, starty - 1.0f});
+
+  frc971::MultiSpline::Builder multispline_builder =
+      builder->MakeBuilder<frc971::MultiSpline>();
+
+  multispline_builder.add_spline_count(1);
+  multispline_builder.add_constraints(constraints_offset);
+  multispline_builder.add_spline_x(spline_x_offset);
+  multispline_builder.add_spline_y(spline_y_offset);
+
+  return multispline_builder.Finish();
+}
+
+flatbuffers::Offset<frc971::MultiSpline> AutonomousSplines::StraightLine(
+    aos::Sender<frc971::control_loops::drivetrain::Goal>::Builder *builder) {
+  flatbuffers::Offset<flatbuffers::Vector<float>> spline_x_offset =
+      builder->fbb()->CreateVector<float>(
+          {-12.3, -11.9, -11.5, -11.1, -10.6, -10.0});
+  flatbuffers::Offset<flatbuffers::Vector<float>> spline_y_offset =
+      builder->fbb()->CreateVector<float>({1.25, 1.25, 1.25, 1.25, 1.25, 1.25});
+
+  frc971::MultiSpline::Builder multispline_builder =
+      builder->MakeBuilder<frc971::MultiSpline>();
+
+  multispline_builder.add_spline_count(1);
+  multispline_builder.add_spline_x(spline_x_offset);
+  multispline_builder.add_spline_y(spline_y_offset);
+
+  return multispline_builder.Finish();
+}
+
+}  // namespace actors
+}  // namespace y2020
diff --git a/y2020/actors/auto_splines.h b/y2020/actors/auto_splines.h
new file mode 100644
index 0000000..471b7b9
--- /dev/null
+++ b/y2020/actors/auto_splines.h
@@ -0,0 +1,28 @@
+#ifndef y2020_ACTORS_AUTO_SPLINES_H_
+#define y2020_ACTORS_AUTO_SPLINES_H_
+
+#include "aos/events/event_loop.h"
+#include "frc971/control_loops/control_loops_generated.h"
+#include "frc971/control_loops/drivetrain/drivetrain_goal_generated.h"
+/*
+
+  The cooridinate system for the autonomous splines is the same as the spline
+  python generator and drivetrain spline systems.
+
+*/
+
+namespace y2020 {
+namespace actors {
+
+class AutonomousSplines {
+ public:
+  static flatbuffers::Offset<frc971::MultiSpline> BasicSSpline(
+      aos::Sender<frc971::control_loops::drivetrain::Goal>::Builder *builder);
+  static flatbuffers::Offset<frc971::MultiSpline> StraightLine(
+      aos::Sender<frc971::control_loops::drivetrain::Goal>::Builder *builder);
+};
+
+}  // namespace actors
+}  // namespace y2020
+
+#endif  // y2020_ACTORS_AUTO_SPLINES_H_
diff --git a/y2020/actors/autonomous_actor.cc b/y2020/actors/autonomous_actor.cc
new file mode 100644
index 0000000..d015c65
--- /dev/null
+++ b/y2020/actors/autonomous_actor.cc
@@ -0,0 +1,38 @@
+#include "y2020/actors/autonomous_actor.h"
+
+#include <inttypes.h>
+
+#include <chrono>
+#include <cmath>
+
+#include "aos/logging/logging.h"
+#include "frc971/control_loops/drivetrain/localizer_generated.h"
+#include "y2020/control_loops/drivetrain/drivetrain_base.h"
+
+namespace y2020 {
+namespace actors {
+
+using ::aos::monotonic_clock;
+using ::frc971::ProfileParametersT;
+using frc971::control_loops::drivetrain::LocalizerControl;
+namespace chrono = ::std::chrono;
+
+AutonomousActor::AutonomousActor(::aos::EventLoop *event_loop)
+    : frc971::autonomous::BaseAutonomousActor(
+          event_loop, control_loops::drivetrain::GetDrivetrainConfig()) {}
+
+void AutonomousActor::Reset() {
+  InitializeEncoders();
+  ResetDrivetrain();
+}
+
+bool AutonomousActor::RunAction(
+    const ::frc971::autonomous::AutonomousActionParams *params) {
+  Reset();
+
+  AOS_LOG(INFO, "Params are %d\n", params->mode());
+  return true;
+}
+
+}  // namespace actors
+}  // namespace y2020
diff --git a/y2020/actors/autonomous_actor.h b/y2020/actors/autonomous_actor.h
new file mode 100644
index 0000000..3bfa610
--- /dev/null
+++ b/y2020/actors/autonomous_actor.h
@@ -0,0 +1,27 @@
+#ifndef y2020_ACTORS_AUTONOMOUS_ACTOR_H_
+#define y2020_ACTORS_AUTONOMOUS_ACTOR_H_
+
+#include "aos/actions/actions.h"
+#include "aos/actions/actor.h"
+#include "frc971/autonomous/base_autonomous_actor.h"
+#include "frc971/control_loops/control_loops_generated.h"
+#include "frc971/control_loops/drivetrain/drivetrain_config.h"
+
+namespace y2020 {
+namespace actors {
+
+class AutonomousActor : public ::frc971::autonomous::BaseAutonomousActor {
+ public:
+  explicit AutonomousActor(::aos::EventLoop *event_loop);
+
+  bool RunAction(
+      const ::frc971::autonomous::AutonomousActionParams *params) override;
+
+ private:
+  void Reset();
+};
+
+}  // namespace actors
+}  // namespace y2020
+
+#endif  // y2020_ACTORS_AUTONOMOUS_ACTOR_H_
diff --git a/y2020/actors/autonomous_actor_main.cc b/y2020/actors/autonomous_actor_main.cc
new file mode 100644
index 0000000..99563ee
--- /dev/null
+++ b/y2020/actors/autonomous_actor_main.cc
@@ -0,0 +1,20 @@
+#include <stdio.h>
+
+#include "aos/events/shm_event_loop.h"
+#include "aos/init.h"
+#include "y2020/actors/autonomous_actor.h"
+
+int main(int /*argc*/, char * /*argv*/ []) {
+  ::aos::Init(-1);
+
+  aos::FlatbufferDetachedBuffer<aos::Configuration> config =
+      aos::configuration::ReadConfig("config.json");
+
+  ::aos::ShmEventLoop event_loop(&config.message());
+  ::y2020::actors::AutonomousActor autonomous(&event_loop);
+
+  event_loop.Run();
+
+  ::aos::Cleanup();
+  return 0;
+}
diff --git a/y2020/constants.cc b/y2020/constants.cc
new file mode 100644
index 0000000..70876c3
--- /dev/null
+++ b/y2020/constants.cc
@@ -0,0 +1,84 @@
+#include "y2020/constants.h"
+
+#include <inttypes.h>
+
+#include <map>
+
+#if __has_feature(address_sanitizer)
+#include "sanitizer/lsan_interface.h"
+#endif
+
+#include "absl/base/call_once.h"
+#include "aos/logging/logging.h"
+#include "aos/mutex/mutex.h"
+#include "aos/network/team_number.h"
+
+namespace y2020 {
+namespace constants {
+
+const int Values::kZeroingSampleSize;
+
+namespace {
+
+const uint16_t kCompTeamNumber = 971;
+const uint16_t kPracticeTeamNumber = 9971;
+const uint16_t kCodingRobotTeamNumber = 7971;
+
+const Values *DoGetValuesForTeam(uint16_t team) {
+  Values *const r = new Values();
+
+  switch (team) {
+    // A set of constants for tests.
+    case 1:
+      break;
+
+    case kCompTeamNumber:
+      break;
+
+    case kPracticeTeamNumber:
+      break;
+
+    case kCodingRobotTeamNumber:
+      break;
+
+    default:
+      AOS_LOG(FATAL, "unknown team #%" PRIu16 "\n", team);
+  }
+
+  return r;
+}
+
+void DoGetValues(const Values **result) {
+  uint16_t team = ::aos::network::GetTeamNumber();
+  AOS_LOG(INFO, "creating a Constants for team %" PRIu16 "\n", team);
+  *result = DoGetValuesForTeam(team);
+}
+
+}  // namespace
+
+const Values &GetValues() {
+  static absl::once_flag once;
+  static const Values *result;
+  absl::call_once(once, DoGetValues, &result);
+  return *result;
+}
+
+const Values &GetValuesForTeam(uint16_t team_number) {
+  static ::aos::Mutex mutex;
+  ::aos::MutexLocker locker(&mutex);
+
+  // IMPORTANT: This declaration has to stay after the mutex is locked to avoid
+  // race conditions.
+  static ::std::map<uint16_t, const Values *> values;
+
+  if (values.count(team_number) == 0) {
+    values[team_number] = DoGetValuesForTeam(team_number);
+#if __has_feature(address_sanitizer)
+    __lsan_ignore_object(values[team_number]);
+#endif
+  }
+  return *values[team_number];
+}
+
+}  // namespace constants
+}  // namespace y2020
diff --git a/y2020/constants.h b/y2020/constants.h
new file mode 100644
index 0000000..2e915f9
--- /dev/null
+++ b/y2020/constants.h
@@ -0,0 +1,44 @@
+#ifndef y2020_CONSTANTS_H_
+#define y2020_CONSTANTS_H_
+
+#include <math.h>
+#include <stdint.h>
+
+#include <array>
+
+#include "frc971/constants.h"
+#include "frc971/control_loops/pose.h"
+#include "frc971/control_loops/static_zeroing_single_dof_profiled_subsystem.h"
+#include "y2020/control_loops/drivetrain/drivetrain_dog_motor_plant.h"
+
+namespace y2020 {
+namespace constants {
+
+struct Values {
+  static const int kZeroingSampleSize = 200;
+
+  static constexpr double kDrivetrainCyclesPerRevolution() { return 512.0; }
+  static constexpr double kDrivetrainEncoderCountsPerRevolution() {
+    return kDrivetrainCyclesPerRevolution() * 4;
+  }
+  static constexpr double kDrivetrainEncoderRatio() { return (24.0 / 52.0); }
+  static constexpr double kMaxDrivetrainEncoderPulsesPerSecond() {
+    return control_loops::drivetrain::kFreeSpeed / (2.0 * M_PI) *
+           control_loops::drivetrain::kHighOutputRatio /
+           constants::Values::kDrivetrainEncoderRatio() *
+           kDrivetrainEncoderCountsPerRevolution();
+  }
+};
+
+// Creates (once) a Values instance for ::aos::network::GetTeamNumber() and
+// returns a reference to it.
+const Values &GetValues();
+
+// Creates Values instances for each team number it is called with and returns
+// them.
+const Values &GetValuesForTeam(uint16_t team_number);
+
+}  // namespace constants
+}  // namespace y2020
+
+#endif  // y2020_CONSTANTS_H_
diff --git a/y2020/control_loops/BUILD b/y2020/control_loops/BUILD
new file mode 100644
index 0000000..c0aa1ee
--- /dev/null
+++ b/y2020/control_loops/BUILD
@@ -0,0 +1,6 @@
+py_library(
+    name = "python_init",
+    srcs = ["__init__.py"],
+    visibility = ["//visibility:public"],
+    deps = ["//y2020:python_init"],
+)
diff --git a/y2020/control_loops/__init__.py b/y2020/control_loops/__init__.py
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/y2020/control_loops/__init__.py
diff --git a/y2020/control_loops/drivetrain/BUILD b/y2020/control_loops/drivetrain/BUILD
new file mode 100644
index 0000000..d03c1b9
--- /dev/null
+++ b/y2020/control_loops/drivetrain/BUILD
@@ -0,0 +1,83 @@
+load("@com_github_google_flatbuffers//:build_defs.bzl", "flatbuffer_cc_library")
+load("//tools/build_rules:select.bzl", "compiler_select", "cpu_select")
+
+genrule(
+    name = "genrule_drivetrain",
+    outs = [
+        "drivetrain_dog_motor_plant.h",
+        "drivetrain_dog_motor_plant.cc",
+        "kalman_drivetrain_motor_plant.h",
+        "kalman_drivetrain_motor_plant.cc",
+    ],
+    cmd = "$(location //y2020/control_loops/python:drivetrain) $(OUTS)",
+    tools = [
+        "//y2020/control_loops/python:drivetrain",
+    ],
+)
+
+genrule(
+    name = "genrule_polydrivetrain",
+    outs = [
+        "polydrivetrain_dog_motor_plant.h",
+        "polydrivetrain_dog_motor_plant.cc",
+        "polydrivetrain_cim_plant.h",
+        "polydrivetrain_cim_plant.cc",
+        "hybrid_velocity_drivetrain.h",
+        "hybrid_velocity_drivetrain.cc",
+    ],
+    cmd = "$(location //y2020/control_loops/python:polydrivetrain) $(OUTS)",
+    tools = [
+        "//y2020/control_loops/python:polydrivetrain",
+    ],
+)
+
+cc_library(
+    name = "polydrivetrain_plants",
+    srcs = [
+        "drivetrain_dog_motor_plant.cc",
+        "hybrid_velocity_drivetrain.cc",
+        "kalman_drivetrain_motor_plant.cc",
+        "polydrivetrain_dog_motor_plant.cc",
+    ],
+    hdrs = [
+        "drivetrain_dog_motor_plant.h",
+        "hybrid_velocity_drivetrain.h",
+        "kalman_drivetrain_motor_plant.h",
+        "polydrivetrain_dog_motor_plant.h",
+    ],
+    visibility = ["//visibility:public"],
+    deps = [
+        "//frc971/control_loops:hybrid_state_feedback_loop",
+        "//frc971/control_loops:state_feedback_loop",
+    ],
+)
+
+cc_library(
+    name = "drivetrain_base",
+    srcs = [
+        "drivetrain_base.cc",
+    ],
+    hdrs = [
+        "drivetrain_base.h",
+    ],
+    visibility = ["//visibility:public"],
+    deps = [
+        ":polydrivetrain_plants",
+        "//frc971:shifter_hall_effect",
+        "//frc971/control_loops/drivetrain:drivetrain_config",
+    ],
+)
+
+cc_binary(
+    name = "drivetrain",
+    srcs = [
+        "drivetrain_main.cc",
+    ],
+    visibility = ["//visibility:public"],
+    deps = [
+        ":drivetrain_base",
+        "//aos:init",
+        "//aos/events:shm_event_loop",
+        "//frc971/control_loops/drivetrain:drivetrain_lib",
+    ],
+)
diff --git a/y2020/control_loops/drivetrain/drivetrain_base.cc b/y2020/control_loops/drivetrain/drivetrain_base.cc
new file mode 100644
index 0000000..c3597f5
--- /dev/null
+++ b/y2020/control_loops/drivetrain/drivetrain_base.cc
@@ -0,0 +1,63 @@
+#include "y2020/control_loops/drivetrain/drivetrain_base.h"
+
+#include <chrono>
+
+#include "frc971/control_loops/drivetrain/drivetrain_config.h"
+#include "frc971/control_loops/state_feedback_loop.h"
+#include "y2020/control_loops/drivetrain/drivetrain_dog_motor_plant.h"
+#include "y2020/control_loops/drivetrain/hybrid_velocity_drivetrain.h"
+#include "y2020/control_loops/drivetrain/kalman_drivetrain_motor_plant.h"
+#include "y2020/control_loops/drivetrain/polydrivetrain_dog_motor_plant.h"
+
+using ::frc971::control_loops::drivetrain::DrivetrainConfig;
+
+namespace chrono = ::std::chrono;
+
+namespace y2020 {
+namespace control_loops {
+namespace drivetrain {
+
+using ::frc971::constants::ShifterHallEffect;
+
+const ShifterHallEffect kThreeStateDriveShifter{0.0, 0.0, 0.25, 0.75};
+
+const DrivetrainConfig<double> &GetDrivetrainConfig() {
+  static DrivetrainConfig<double> kDrivetrainConfig{
+      ::frc971::control_loops::drivetrain::ShifterType::SIMPLE_SHIFTER,
+      ::frc971::control_loops::drivetrain::LoopType::CLOSED_LOOP,
+      ::frc971::control_loops::drivetrain::GyroType::IMU_Z_GYRO,
+      ::frc971::control_loops::drivetrain::IMUType::IMU_FLIPPED_X,
+
+      drivetrain::MakeDrivetrainLoop,
+      drivetrain::MakeVelocityDrivetrainLoop,
+      drivetrain::MakeKFDrivetrainLoop,
+      drivetrain::MakeHybridVelocityDrivetrainLoop,
+
+      chrono::duration_cast<chrono::nanoseconds>(
+          chrono::duration<double>(drivetrain::kDt)),
+      drivetrain::kRobotRadius,
+      drivetrain::kWheelRadius,
+      drivetrain::kV,
+
+      drivetrain::kHighGearRatio,
+      drivetrain::kLowGearRatio,
+      drivetrain::kJ,
+      drivetrain::kMass,
+      kThreeStateDriveShifter,
+      kThreeStateDriveShifter,
+      true /* default_high_gear */,
+      0 /* down_offset if using constants use
+     constants::GetValues().down_error */
+      ,
+      0.7 /* wheel_non_linearity */,
+      1.2 /* quickturn_wheel_multiplier */,
+      1.2 /* wheel_multiplier */,
+      true /*pistol_grip_shift_enables_line_follow*/,
+  };
+
+  return kDrivetrainConfig;
+};
+
+}  // namespace drivetrain
+}  // namespace control_loops
+}  // namespace y2020
diff --git a/y2020/control_loops/drivetrain/drivetrain_base.h b/y2020/control_loops/drivetrain/drivetrain_base.h
new file mode 100644
index 0000000..c220088
--- /dev/null
+++ b/y2020/control_loops/drivetrain/drivetrain_base.h
@@ -0,0 +1,17 @@
+#ifndef y2020_CONTROL_LOOPS_DRIVETRAIN_DRIVETRAIN_BASE_H_
+#define y2020_CONTROL_LOOPS_DRIVETRAIN_DRIVETRAIN_BASE_H_
+
+#include "frc971/control_loops/drivetrain/drivetrain_config.h"
+
+namespace y2020 {
+namespace control_loops {
+namespace drivetrain {
+
+const ::frc971::control_loops::drivetrain::DrivetrainConfig<double>
+    &GetDrivetrainConfig();
+
+}  // namespace drivetrain
+}  // namespace control_loops
+}  // namespace y2020
+
+#endif  // y2020_CONTROL_LOOPS_DRIVETRAIN_DRIVETRAIN_BASE_H_
diff --git a/y2020/control_loops/drivetrain/drivetrain_main.cc b/y2020/control_loops/drivetrain/drivetrain_main.cc
new file mode 100644
index 0000000..66b9cc7
--- /dev/null
+++ b/y2020/control_loops/drivetrain/drivetrain_main.cc
@@ -0,0 +1,25 @@
+#include "aos/events/shm_event_loop.h"
+#include "aos/init.h"
+#include "frc971/control_loops/drivetrain/drivetrain.h"
+#include "y2020/control_loops/drivetrain/drivetrain_base.h"
+
+using ::frc971::control_loops::drivetrain::DrivetrainLoop;
+
+int main() {
+  ::aos::InitNRT(true);
+
+  aos::FlatbufferDetachedBuffer<aos::Configuration> config =
+      aos::configuration::ReadConfig("config.json");
+
+  ::aos::ShmEventLoop event_loop(&config.message());
+  ::frc971::control_loops::drivetrain::DeadReckonEkf localizer(
+      &event_loop, ::y2020::control_loops::drivetrain::GetDrivetrainConfig());
+  DrivetrainLoop drivetrain(
+      ::y2020::control_loops::drivetrain::GetDrivetrainConfig(), &event_loop,
+      &localizer);
+
+  event_loop.Run();
+
+  ::aos::Cleanup();
+  return 0;
+}
diff --git a/y2020/control_loops/python/BUILD b/y2020/control_loops/python/BUILD
new file mode 100644
index 0000000..edc72f5
--- /dev/null
+++ b/y2020/control_loops/python/BUILD
@@ -0,0 +1,57 @@
+package(default_visibility = ["//y2020:__subpackages__"])
+
+py_binary(
+    name = "drivetrain",
+    srcs = [
+        "drivetrain.py",
+    ],
+    legacy_create_init = False,
+    restricted_to = ["//tools:k8"],
+    deps = [
+        ":python_init",
+        "//external:python-gflags",
+        "//external:python-glog",
+        "//frc971/control_loops/python:drivetrain",
+    ],
+)
+
+py_binary(
+    name = "polydrivetrain",
+    srcs = [
+        "drivetrain.py",
+        "polydrivetrain.py",
+    ],
+    legacy_create_init = False,
+    restricted_to = ["//tools:k8"],
+    deps = [
+        ":python_init",
+        "//external:python-gflags",
+        "//external:python-glog",
+        "//frc971/control_loops/python:polydrivetrain",
+    ],
+)
+
+py_library(
+    name = "polydrivetrain_lib",
+    srcs = [
+        "drivetrain.py",
+        "polydrivetrain.py",
+    ],
+    restricted_to = ["//tools:k8"],
+    visibility = ["//visibility:public"],
+    deps = [
+        ":python_init",
+        "//external:python-gflags",
+        "//external:python-glog",
+        "//frc971/control_loops/python:controls",
+        "//frc971/control_loops/python:drivetrain",
+        "//frc971/control_loops/python:polydrivetrain",
+    ],
+)
+
+py_library(
+    name = "python_init",
+    srcs = ["__init__.py"],
+    visibility = ["//visibility:public"],
+    deps = ["//y2020/control_loops:python_init"],
+)
diff --git a/y2020/control_loops/python/__init__.py b/y2020/control_loops/python/__init__.py
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/y2020/control_loops/python/__init__.py
diff --git a/y2020/control_loops/python/drivetrain.py b/y2020/control_loops/python/drivetrain.py
new file mode 100644
index 0000000..54745dd
--- /dev/null
+++ b/y2020/control_loops/python/drivetrain.py
@@ -0,0 +1,42 @@
+#!/usr/bin/python
+
+from frc971.control_loops.python import drivetrain
+import sys
+
+import gflags
+import glog
+
+FLAGS = gflags.FLAGS
+
+gflags.DEFINE_bool('plot', False, 'If true, plot the loop response.')
+
+kDrivetrain = drivetrain.DrivetrainParams(
+    J=1.5,
+    mass=38.5,
+    robot_radius=0.45 / 2.0,
+    wheel_radius=4.0 * 0.0254 / 2.0,
+    G=9.0 / 52.0,
+    q_pos=0.14,
+    q_vel=1.30,
+    efficiency=0.80,
+    has_imu=True,
+    force=True,
+    kf_q_voltage=13.0,
+    controller_poles=[0.82, 0.82],
+    robot_cg_offset=0.0)
+
+
+def main(argv):
+    argv = FLAGS(argv)
+    glog.init()
+
+    if FLAGS.plot:
+        drivetrain.PlotDrivetrainMotions(kDrivetrain)
+    elif len(argv) != 5:
+        print "Expected .h file name and .cc file name"
+    else:
+        # Write the generated constants out to a file.
+        drivetrain.WriteDrivetrain(argv[1:3], argv[3:5], 'y2020', kDrivetrain)
+
+if __name__ == '__main__':
+    sys.exit(main(sys.argv))
diff --git a/y2020/control_loops/python/polydrivetrain.py b/y2020/control_loops/python/polydrivetrain.py
new file mode 100644
index 0000000..6481af1
--- /dev/null
+++ b/y2020/control_loops/python/polydrivetrain.py
@@ -0,0 +1,31 @@
+#!/usr/bin/python
+
+import sys
+from y2020.control_loops.python import drivetrain
+from frc971.control_loops.python import polydrivetrain
+
+import gflags
+import glog
+
+__author__ = 'Austin Schuh (austin.linux@gmail.com)'
+
+FLAGS = gflags.FLAGS
+
+try:
+  gflags.DEFINE_bool('plot', False, 'If true, plot the loop response.')
+except gflags.DuplicateFlagError:
+  pass
+
+def main(argv):
+  if FLAGS.plot:
+    polydrivetrain.PlotPolyDrivetrainMotions(drivetrain.kDrivetrain)
+  elif len(argv) != 7:
+    glog.fatal('Expected .h file name and .cc file name')
+  else:
+    polydrivetrain.WritePolyDrivetrain(argv[1:3], argv[3:5], argv[5:7], 'y2020',
+                                       drivetrain.kDrivetrain)
+
+if __name__ == '__main__':
+  argv = FLAGS(sys.argv)
+  glog.init()
+  sys.exit(main(argv))
diff --git a/y2020/control_loops/superstructure/BUILD b/y2020/control_loops/superstructure/BUILD
new file mode 100644
index 0000000..533a369
--- /dev/null
+++ b/y2020/control_loops/superstructure/BUILD
@@ -0,0 +1,78 @@
+package(default_visibility = ["//visibility:public"])
+
+load("@com_github_google_flatbuffers//:build_defs.bzl", "flatbuffer_cc_library")
+
+flatbuffer_cc_library(
+    name = "superstructure_goal_fbs",
+    srcs = [
+        "superstructure_goal.fbs",
+    ],
+    gen_reflections = 1,
+    includes = [
+        "//frc971/control_loops:control_loops_fbs_includes",
+        "//frc971/control_loops:profiled_subsystem_fbs_includes",
+    ],
+)
+
+flatbuffer_cc_library(
+    name = "superstructure_output_fbs",
+    srcs = [
+        "superstructure_output.fbs",
+    ],
+    gen_reflections = 1,
+)
+
+flatbuffer_cc_library(
+    name = "superstructure_status_fbs",
+    srcs = [
+        "superstructure_status.fbs",
+    ],
+    gen_reflections = 1,
+    includes = [
+        "//frc971/control_loops:control_loops_fbs_includes",
+        "//frc971/control_loops:profiled_subsystem_fbs_includes",
+    ],
+)
+
+flatbuffer_cc_library(
+    name = "superstructure_position_fbs",
+    srcs = [
+        "superstructure_position.fbs",
+    ],
+    gen_reflections = 1,
+    includes = [
+        "//frc971/control_loops:control_loops_fbs_includes",
+        "//frc971/control_loops:profiled_subsystem_fbs_includes",
+    ],
+)
+
+cc_library(
+    name = "superstructure_lib",
+    srcs = [
+        "superstructure.cc",
+    ],
+    hdrs = [
+        "superstructure.h",
+    ],
+    deps = [
+        ":superstructure_goal_fbs",
+        ":superstructure_output_fbs",
+        ":superstructure_position_fbs",
+        ":superstructure_status_fbs",
+        "//aos/controls:control_loop",
+        "//aos/events:event_loop",
+        "//y2020:constants",
+    ],
+)
+
+cc_binary(
+    name = "superstructure",
+    srcs = [
+        "superstructure_main.cc",
+    ],
+    deps = [
+        ":superstructure_lib",
+        "//aos:init",
+        "//aos/events:shm_event_loop",
+    ],
+)
diff --git a/y2020/control_loops/superstructure/superstructure.cc b/y2020/control_loops/superstructure/superstructure.cc
new file mode 100644
index 0000000..a3978a4
--- /dev/null
+++ b/y2020/control_loops/superstructure/superstructure.cc
@@ -0,0 +1,43 @@
+#include "y2020/control_loops/superstructure/superstructure.h"
+
+#include "aos/events/event_loop.h"
+
+namespace y2020 {
+namespace control_loops {
+namespace superstructure {
+
+using frc971::control_loops::AbsoluteEncoderProfiledJointStatus;
+using frc971::control_loops::PotAndAbsoluteEncoderProfiledJointStatus;
+
+Superstructure::Superstructure(::aos::EventLoop *event_loop,
+                               const ::std::string &name)
+    : aos::controls::ControlLoop<Goal, Position, Status, Output>(event_loop,
+                                                                 name) {
+      event_loop->SetRuntimeRealtimePriority(30);
+}
+
+void Superstructure::RunIteration(const Goal * /*unsafe_goal*/,
+                                  const Position * /*position*/,
+                                  aos::Sender<Output>::Builder *output,
+                                  aos::Sender<Status>::Builder *status) {
+  if (WasReset()) {
+    AOS_LOG(ERROR, "WPILib reset, restarting\n");
+  }
+
+
+  if (output != nullptr) {
+    OutputT output_struct;
+    output->Send(Output::Pack(*output->fbb(), &output_struct));
+  }
+
+  Status::Builder status_builder = status->MakeBuilder<Status>();
+
+  status_builder.add_zeroed(true);
+  status_builder.add_estopped(false);
+
+  status->Send(status_builder.Finish());
+}
+
+}  // namespace control_loops
+}  // namespace y2020
+}  // namespace y2020
diff --git a/y2020/control_loops/superstructure/superstructure.h b/y2020/control_loops/superstructure/superstructure.h
new file mode 100644
index 0000000..9aaca8e
--- /dev/null
+++ b/y2020/control_loops/superstructure/superstructure.h
@@ -0,0 +1,35 @@
+#ifndef y2020_CONTROL_LOOPS_SUPERSTRUCTURE_SUPERSTRUCTURE_H_
+#define y2020_CONTROL_LOOPS_SUPERSTRUCTURE_SUPERSTRUCTURE_H_
+
+#include "aos/controls/control_loop.h"
+#include "aos/events/event_loop.h"
+#include "y2020/constants.h"
+#include "y2020/control_loops/superstructure/superstructure_goal_generated.h"
+#include "y2020/control_loops/superstructure/superstructure_output_generated.h"
+#include "y2020/control_loops/superstructure/superstructure_position_generated.h"
+#include "y2020/control_loops/superstructure/superstructure_status_generated.h"
+
+namespace y2020 {
+namespace control_loops {
+namespace superstructure {
+
+class Superstructure
+    : public ::aos::controls::ControlLoop<Goal, Position, Status, Output> {
+ public:
+  explicit Superstructure(::aos::EventLoop *event_loop,
+                          const ::std::string &name = "/superstructure");
+
+ protected:
+  virtual void RunIteration(const Goal *unsafe_goal, const Position *position,
+                            aos::Sender<Output>::Builder *output,
+                            aos::Sender<Status>::Builder *status) override;
+
+ private:
+  DISALLOW_COPY_AND_ASSIGN(Superstructure);
+};
+
+}  // namespace superstructure
+}  // namespace control_loops
+}  // namespace y2020
+
+#endif  // y2020_CONTROL_LOOPS_SUPERSTRUCTURE_SUPERSTRUCTURE_H_
diff --git a/y2020/control_loops/superstructure/superstructure_goal.fbs b/y2020/control_loops/superstructure/superstructure_goal.fbs
new file mode 100644
index 0000000..8a38b95
--- /dev/null
+++ b/y2020/control_loops/superstructure/superstructure_goal.fbs
@@ -0,0 +1,9 @@
+include "frc971/control_loops/profiled_subsystem.fbs";
+
+namespace y2020.control_loops.superstructure;
+
+table Goal {
+
+}
+
+root_type Goal;
diff --git a/y2020/control_loops/superstructure/superstructure_main.cc b/y2020/control_loops/superstructure/superstructure_main.cc
new file mode 100644
index 0000000..101a0c7
--- /dev/null
+++ b/y2020/control_loops/superstructure/superstructure_main.cc
@@ -0,0 +1,20 @@
+#include "y2020/control_loops/superstructure/superstructure.h"
+
+#include "aos/events/shm_event_loop.h"
+#include "aos/init.h"
+
+int main(int /*argc*/, char * /*argv*/ []) {
+  ::aos::InitNRT(true);
+
+  aos::FlatbufferDetachedBuffer<aos::Configuration> config =
+      aos::configuration::ReadConfig("config.json");
+
+  ::aos::ShmEventLoop event_loop(&config.message());
+  ::y2020::control_loops::superstructure::Superstructure superstructure(
+      &event_loop);
+
+  event_loop.Run();
+
+  ::aos::Cleanup();
+  return 0;
+}
diff --git a/y2020/control_loops/superstructure/superstructure_output.fbs b/y2020/control_loops/superstructure/superstructure_output.fbs
new file mode 100644
index 0000000..3682db6
--- /dev/null
+++ b/y2020/control_loops/superstructure/superstructure_output.fbs
@@ -0,0 +1,7 @@
+namespace y2020.control_loops.superstructure;
+
+table Output {
+
+}
+
+root_type Output;
diff --git a/y2020/control_loops/superstructure/superstructure_position.fbs b/y2020/control_loops/superstructure/superstructure_position.fbs
new file mode 100644
index 0000000..c4b0a9a
--- /dev/null
+++ b/y2020/control_loops/superstructure/superstructure_position.fbs
@@ -0,0 +1,9 @@
+include "frc971/control_loops/control_loops.fbs";
+
+namespace y2020.control_loops.superstructure;
+
+table Position {
+
+}
+
+root_type Position;
diff --git a/y2020/control_loops/superstructure/superstructure_status.fbs b/y2020/control_loops/superstructure/superstructure_status.fbs
new file mode 100644
index 0000000..d0e9266
--- /dev/null
+++ b/y2020/control_loops/superstructure/superstructure_status.fbs
@@ -0,0 +1,15 @@
+include "frc971/control_loops/control_loops.fbs";
+include "frc971/control_loops/profiled_subsystem.fbs";
+
+namespace y2020.control_loops.superstructure;
+
+table Status {
+  // All subsystems know their location.
+  zeroed:bool;
+
+  // If true, we have aborted. This is the or of all subsystem estops.
+  estopped:bool;
+
+}
+
+root_type Status;
diff --git a/y2020/joystick_reader.cc b/y2020/joystick_reader.cc
new file mode 100644
index 0000000..105506e
--- /dev/null
+++ b/y2020/joystick_reader.cc
@@ -0,0 +1,79 @@
+#include <math.h>
+#include <stdio.h>
+#include <string.h>
+#include <unistd.h>
+
+#include "aos/actions/actions.h"
+#include "aos/init.h"
+#include "aos/input/action_joystick_input.h"
+#include "aos/input/driver_station_data.h"
+#include "aos/input/drivetrain_input.h"
+#include "aos/input/joystick_input.h"
+#include "aos/logging/logging.h"
+#include "aos/network/team_number.h"
+#include "aos/util/log_interval.h"
+#include "frc971/autonomous/base_autonomous_actor.h"
+#include "y2020/control_loops/drivetrain/drivetrain_base.h"
+#include "y2020/control_loops/superstructure/superstructure_goal_generated.h"
+#include "y2020/control_loops/superstructure/superstructure_status_generated.h"
+
+using aos::input::driver_station::ButtonLocation;
+using aos::input::driver_station::ControlBit;
+using aos::input::driver_station::JoystickAxis;
+using aos::input::driver_station::POVLocation;
+
+namespace y2020 {
+namespace input {
+namespace joysticks {
+
+namespace superstructure = y2020::control_loops::superstructure;
+
+class Reader : public ::aos::input::ActionJoystickInput {
+ public:
+  Reader(::aos::EventLoop *event_loop)
+      : ::aos::input::ActionJoystickInput(
+            event_loop,
+            ::y2020::control_loops::drivetrain::GetDrivetrainConfig(),
+            ::aos::input::DrivetrainInputReader::InputType::kPistol, {}),
+        superstructure_goal_sender_(
+            event_loop->MakeSender<superstructure::Goal>("/superstructure")),
+        superstructure_status_fetcher_(
+            event_loop->MakeFetcher<superstructure::Status>(
+                "/superstructure")) {}
+
+  void AutoEnded() override {
+    AOS_LOG(INFO, "Auto ended, assuming disc and have piece\n");
+  }
+
+  void HandleTeleop(
+      const ::aos::input::driver_station::Data & /*data*/) override {
+    superstructure_status_fetcher_.Fetch();
+    if (!superstructure_status_fetcher_.get()) {
+      AOS_LOG(ERROR, "Got no superstructure status message.\n");
+      return;
+    }
+  }
+
+ private:
+  ::aos::Sender<superstructure::Goal> superstructure_goal_sender_;
+
+  ::aos::Fetcher<superstructure::Status> superstructure_status_fetcher_;
+};
+
+}  // namespace joysticks
+}  // namespace input
+}  // namespace y2020
+
+int main() {
+  ::aos::InitNRT(true);
+
+  aos::FlatbufferDetachedBuffer<aos::Configuration> config =
+      aos::configuration::ReadConfig("config.json");
+
+  ::aos::ShmEventLoop event_loop(&config.message());
+  ::y2020::input::joysticks::Reader reader(&event_loop);
+
+  event_loop.Run();
+
+  ::aos::Cleanup();
+}
diff --git a/y2020/wpilib_interface.cc b/y2020/wpilib_interface.cc
new file mode 100644
index 0000000..4968c9f
--- /dev/null
+++ b/y2020/wpilib_interface.cc
@@ -0,0 +1,248 @@
+#include <inttypes.h>
+#include <stdio.h>
+#include <string.h>
+#include <unistd.h>
+
+#include <array>
+#include <chrono>
+#include <cmath>
+#include <functional>
+#include <mutex>
+#include <thread>
+
+#include "ctre/phoenix/CANifier.h"
+#include "frc971/wpilib/ahal/AnalogInput.h"
+#include "frc971/wpilib/ahal/Counter.h"
+#include "frc971/wpilib/ahal/DigitalGlitchFilter.h"
+#include "frc971/wpilib/ahal/DriverStation.h"
+#include "frc971/wpilib/ahal/Encoder.h"
+#include "frc971/wpilib/ahal/VictorSP.h"
+#undef ERROR
+
+#include "aos/commonmath.h"
+#include "aos/events/event_loop.h"
+#include "aos/events/shm_event_loop.h"
+#include "aos/init.h"
+#include "aos/logging/logging.h"
+#include "aos/make_unique.h"
+#include "aos/realtime.h"
+#include "aos/robot_state/robot_state_generated.h"
+#include "aos/time/time.h"
+#include "aos/util/log_interval.h"
+#include "aos/util/phased_loop.h"
+#include "aos/util/wrapping_counter.h"
+#include "ctre/phoenix/motorcontrol/can/TalonSRX.h"
+#include "frc971/autonomous/auto_mode_generated.h"
+#include "frc971/control_loops/drivetrain/drivetrain_position_generated.h"
+#include "frc971/wpilib/ADIS16448.h"
+#include "frc971/wpilib/buffered_pcm.h"
+#include "frc971/wpilib/buffered_solenoid.h"
+#include "frc971/wpilib/dma.h"
+#include "frc971/wpilib/drivetrain_writer.h"
+#include "frc971/wpilib/encoder_and_potentiometer.h"
+#include "frc971/wpilib/joystick_sender.h"
+#include "frc971/wpilib/logging_generated.h"
+#include "frc971/wpilib/loop_output_handler.h"
+#include "frc971/wpilib/pdp_fetcher.h"
+#include "frc971/wpilib/sensor_reader.h"
+#include "frc971/wpilib/wpilib_robot_base.h"
+#include "y2020/constants.h"
+#include "y2020/control_loops/superstructure/superstructure_output_generated.h"
+#include "y2020/control_loops/superstructure/superstructure_position_generated.h"
+
+using ::aos::monotonic_clock;
+using ::y2020::constants::Values;
+namespace superstructure = ::y2020::control_loops::superstructure;
+namespace chrono = ::std::chrono;
+using aos::make_unique;
+
+namespace y2020 {
+namespace wpilib {
+namespace {
+
+constexpr double kMaxBringupPower = 12.0;
+
+// TODO(Brian): Fix the interpretation of the result of GetRaw here and in the
+// DMA stuff and then removing the * 2.0 in *_translate.
+// The low bit is direction.
+
+// TODO(brian): Use ::std::max instead once we have C++14 so that can be
+// constexpr.
+template <typename T>
+constexpr T max(T a, T b) {
+  return (a > b) ? a : b;
+}
+
+template <typename T, typename... Rest>
+constexpr T max(T a, T b, T c, Rest... rest) {
+  return max(max(a, b), c, rest...);
+}
+
+double drivetrain_translate(int32_t in) {
+  return ((static_cast<double>(in) /
+           Values::kDrivetrainEncoderCountsPerRevolution()) *
+          (2.0 * M_PI)) *
+         Values::kDrivetrainEncoderRatio() *
+         control_loops::drivetrain::kWheelRadius;
+}
+
+double drivetrain_velocity_translate(double in) {
+  return (((1.0 / in) / Values::kDrivetrainCyclesPerRevolution()) *
+          (2.0 * M_PI)) *
+         Values::kDrivetrainEncoderRatio() *
+         control_loops::drivetrain::kWheelRadius;
+}
+
+constexpr double kMaxFastEncoderPulsesPerSecond =
+    Values::kMaxDrivetrainEncoderPulsesPerSecond();
+static_assert(kMaxFastEncoderPulsesPerSecond <= 1300000,
+              "fast encoders are too fast");
+constexpr double kMaxMediumEncoderPulsesPerSecond = kMaxFastEncoderPulsesPerSecond;
+
+static_assert(kMaxMediumEncoderPulsesPerSecond <= 400000,
+              "medium encoders are too fast");
+
+}  // namespace
+
+// Class to send position messages with sensor readings to our loops.
+class SensorReader : public ::frc971::wpilib::SensorReader {
+ public:
+  SensorReader(::aos::ShmEventLoop *event_loop)
+      : ::frc971::wpilib::SensorReader(event_loop),
+        auto_mode_sender_(
+            event_loop->MakeSender<::frc971::autonomous::AutonomousMode>(
+                "/autonomous")),
+        superstructure_position_sender_(
+            event_loop->MakeSender<superstructure::Position>(
+                "/superstructure")),
+        drivetrain_position_sender_(
+            event_loop
+                ->MakeSender<::frc971::control_loops::drivetrain::Position>(
+                    "/drivetrain")) {
+    // Set to filter out anything shorter than 1/4 of the minimum pulse width
+    // we should ever see.
+    UpdateFastEncoderFilterHz(kMaxFastEncoderPulsesPerSecond);
+    UpdateMediumEncoderFilterHz(kMaxMediumEncoderPulsesPerSecond);
+  }
+
+  // Auto mode switches.
+  void set_autonomous_mode(int i, ::std::unique_ptr<frc::DigitalInput> sensor) {
+    autonomous_modes_.at(i) = ::std::move(sensor);
+  }
+
+  void RunIteration() override {
+    {
+      auto builder = drivetrain_position_sender_.MakeBuilder();
+      frc971::control_loops::drivetrain::Position::Builder drivetrain_builder =
+          builder.MakeBuilder<frc971::control_loops::drivetrain::Position>();
+      drivetrain_builder.add_left_encoder(
+          drivetrain_translate(drivetrain_left_encoder_->GetRaw()));
+      drivetrain_builder.add_left_speed(
+          drivetrain_velocity_translate(drivetrain_left_encoder_->GetPeriod()));
+
+      drivetrain_builder.add_right_encoder(
+          -drivetrain_translate(drivetrain_right_encoder_->GetRaw()));
+      drivetrain_builder.add_right_speed(-drivetrain_velocity_translate(
+          drivetrain_right_encoder_->GetPeriod()));
+
+      builder.Send(drivetrain_builder.Finish());
+    }
+
+    {
+      auto builder = superstructure_position_sender_.MakeBuilder();
+      superstructure::Position::Builder position_builder =
+          builder.MakeBuilder<superstructure::Position>();
+      builder.Send(position_builder.Finish());
+    }
+
+    {
+      auto builder = auto_mode_sender_.MakeBuilder();
+
+      uint32_t mode = 0;
+      for (size_t i = 0; i < autonomous_modes_.size(); ++i) {
+        if (autonomous_modes_[i] && autonomous_modes_[i]->Get()) {
+          mode |= 1 << i;
+        }
+      }
+
+      auto auto_mode_builder =
+          builder.MakeBuilder<frc971::autonomous::AutonomousMode>();
+
+      auto_mode_builder.add_mode(mode);
+
+      builder.Send(auto_mode_builder.Finish());
+    }
+  }
+
+ private:
+  ::aos::Sender<::frc971::autonomous::AutonomousMode> auto_mode_sender_;
+  ::aos::Sender<superstructure::Position> superstructure_position_sender_;
+  ::aos::Sender<::frc971::control_loops::drivetrain::Position>
+      drivetrain_position_sender_;
+
+  ::std::array<::std::unique_ptr<frc::DigitalInput>, 2> autonomous_modes_;
+};
+
+class SuperstructureWriter
+    : public ::frc971::wpilib::LoopOutputHandler<superstructure::Output> {
+ public:
+  SuperstructureWriter(::aos::EventLoop *event_loop)
+      : ::frc971::wpilib::LoopOutputHandler<superstructure::Output>(
+            event_loop, "/superstructure") {}
+
+ private:
+  void Write(const superstructure::Output & /*output*/) override {}
+
+  void Stop() override { AOS_LOG(WARNING, "Superstructure output too old.\n"); }
+};
+
+class WPILibRobot : public ::frc971::wpilib::WPILibRobotBase {
+ public:
+  ::std::unique_ptr<frc::Encoder> make_encoder(int index) {
+    return make_unique<frc::Encoder>(10 + index * 2, 11 + index * 2, false,
+                                     frc::Encoder::k4X);
+  }
+
+  void Run() override {
+    aos::FlatbufferDetachedBuffer<aos::Configuration> config =
+        aos::configuration::ReadConfig("config.json");
+
+    // Thread 1.
+    ::aos::ShmEventLoop joystick_sender_event_loop(&config.message());
+    ::frc971::wpilib::JoystickSender joystick_sender(
+        &joystick_sender_event_loop);
+    AddLoop(&joystick_sender_event_loop);
+
+    // Thread 2.
+    ::aos::ShmEventLoop pdp_fetcher_event_loop(&config.message());
+    ::frc971::wpilib::PDPFetcher pdp_fetcher(&pdp_fetcher_event_loop);
+    AddLoop(&pdp_fetcher_event_loop);
+
+    // Thread 3.
+    ::aos::ShmEventLoop sensor_reader_event_loop(&config.message());
+    SensorReader sensor_reader(&sensor_reader_event_loop);
+    sensor_reader.set_drivetrain_left_encoder(make_encoder(0));
+    sensor_reader.set_drivetrain_right_encoder(make_encoder(1));
+
+    AddLoop(&sensor_reader_event_loop);
+
+    // Thread 4.
+    ::aos::ShmEventLoop output_event_loop(&config.message());
+    ::frc971::wpilib::DrivetrainWriter drivetrain_writer(&output_event_loop);
+    drivetrain_writer.set_left_controller0(
+        ::std::unique_ptr<::frc::VictorSP>(new ::frc::VictorSP(0)), true);
+    drivetrain_writer.set_right_controller0(
+        ::std::unique_ptr<::frc::VictorSP>(new ::frc::VictorSP(1)), false);
+
+    SuperstructureWriter superstructure_writer(&output_event_loop);
+
+    AddLoop(&output_event_loop);
+
+    RunLoops();
+  }
+};
+
+}  // namespace wpilib
+}  // namespace y2020
+
+AOS_ROBOT_CLASS(::y2020::wpilib::WPILibRobot);
diff --git a/y2020/y2020.json b/y2020/y2020.json
new file mode 100644
index 0000000..411e3ff
--- /dev/null
+++ b/y2020/y2020.json
@@ -0,0 +1,36 @@
+{
+  "channels":
+  [
+    {
+      "name": "/superstructure",
+      "type": "y2020.control_loops.superstructure.Goal",
+      "frequency": 200
+    },
+    {
+      "name": "/superstructure",
+      "type": "y2020.control_loops.superstructure.Status",
+      "frequency": 200
+    },
+    {
+      "name": "/superstructure",
+      "type": "y2020.control_loops.superstructure.Output",
+      "frequency": 200
+    },
+    {
+      "name": "/superstructure",
+      "type": "y2020.control_loops.superstructure.Position",
+      "frequency": 200
+    }
+  ],
+  "applications": [
+    {
+      "name": "drivetrain"
+    }
+  ],
+  "imports": [
+    "../aos/robot_state/robot_state_config.json",
+    "../frc971/control_loops/drivetrain/drivetrain_config.json",
+    "../frc971/autonomous/autonomous_config.json",
+    "../frc971/wpilib/wpilib_config.json"
+  ]
+}