started cleanup up the socket mess

removed unused #include + dependency

more formatting fixes + fixed users of ReceiveSocket

cleaned more stuff up (converted from references to pointers is one)

wip. started rewriting everything, not quite finished

got everything except SensorOutput done (I think...)

got everything compiling except for missing SensorReceiver

worked on implementing the logic. didn't finish

made everything compile and finished implementing SensorReceiver

pulling over Austin's mock time stuff

added IncrementMockTime

finished up and started on tests

remembered something else
diff --git a/aos/crio/controls/ControlsManager.cpp b/aos/crio/controls/ControlsManager.cpp
index cfc429b..906394f 100644
--- a/aos/crio/controls/ControlsManager.cpp
+++ b/aos/crio/controls/ControlsManager.cpp
@@ -8,7 +8,6 @@
 #include "aos/crio/logging/crio_logging.h"
 #include "aos/common/Configuration.h"
 #include "aos/crio/aos_ctdt.h"
-#include "aos/crio/motor_server/CRIOControlLoopRunner.h"
 #include "aos/crio/motor_server/MotorServer.h"
 
 namespace aos {
@@ -29,20 +28,19 @@
   GetWatchdog().SetEnabled(false);
   LOG(INFO, "disabled watchdog\n");
 
-  RegisterControlLoops();
-  LOG(INFO, "registered control loops\n");
-
-  // CRIOControlLoopRunner calls part of MotorServer, so MotorServer has to get
-  // initialized first.
   MotorServer::Start();
   LOG(INFO, "MotorServer started\n");
-  CRIOControlLoopRunner::Start();
-  LOG(INFO, "cRIO control loops started\n");
 
   LOG(INFO, "calling init functions\n");
   aos_call_init_functions();
   LOG(INFO, "initialized\n");
 
+  RegisterControlLoops();
+  LOG(INFO, "registered control loops\n");
+
+  StartSensorBroadcasters();
+  LOG(INFO, "started sensor broadcasters\n");
+
   // Wait forever so that this task doesn't end to avoid confusing any brittle
   // FIRST code that might be hiding somewhere.
   while (true) {
diff --git a/aos/crio/controls/ControlsManager.h b/aos/crio/controls/ControlsManager.h
index 986fe02..aa78587 100644
--- a/aos/crio/controls/ControlsManager.h
+++ b/aos/crio/controls/ControlsManager.h
@@ -4,17 +4,24 @@
 namespace aos {
 namespace crio {
 
+// Designed for a subclass (that implements all of the pure virtual methods...)
+// to be passed to START_ROBOT_CLASS (a WPILib macro) to start all of the code.
 class ControlsManager : public RobotBase {
  public:
-  // Gets called when it is time to register all the control loops.
-  virtual void RegisterControlLoops() = 0;
   virtual void StartCompetition();
-  static inline ControlsManager &GetInstance() {
+
+  static ControlsManager &GetInstance() {
     return *static_cast<ControlsManager *>(&RobotBase::getInstance());
   }
-  inline DriverStation *GetDS() {
+  DriverStation *GetDS() {
     return m_ds;
   }
+
+ private:
+  // Hooks that subclasses have to implement to do the correct things at the
+  // correct times.
+  virtual void RegisterControlLoops() = 0;
+  virtual void StartSensorBroadcasters() = 0;
 };
 
 }  // namespace crio
diff --git a/aos/crio/controls/controls.gyp b/aos/crio/controls/controls.gyp
index 626cb89..ad253d0 100644
--- a/aos/crio/controls/controls.gyp
+++ b/aos/crio/controls/controls.gyp
@@ -11,7 +11,6 @@
         '<(EXTERNALS):WPILib',
         '<(AOS)/build/aos.gyp:logging',
         '<(AOS)/common/common.gyp:common',
-        '<(AOS)/crio/motor_server/motor_server.gyp:CRIOControlLoopRunner',
         '<(AOS)/crio/motor_server/motor_server.gyp:MotorServer',
         '<(AOS)/common/network/network.gyp:socket',
       ],
diff --git a/aos/crio/motor_server/CRIOControlLoopRunner.cc b/aos/crio/motor_server/CRIOControlLoopRunner.cc
deleted file mode 100644
index a4cf3ce..0000000
--- a/aos/crio/motor_server/CRIOControlLoopRunner.cc
+++ /dev/null
@@ -1,50 +0,0 @@
-#include "CRIOControlLoopRunner.h"
-
-#include "aos/common/logging/logging.h"
-#include "aos/crio/shared_libs/interrupt_bridge.h"
-#include "aos/crio/motor_server/MotorOutput.h"
-#include "aos/crio/motor_server/MotorServer.h"
-
-using ::aos::control_loops::SerializableControlLoop;
-
-namespace aos {
-namespace crio {
-
-bool CRIOControlLoopRunner::started_ = false;
-std::vector<SerializableControlLoop *> CRIOControlLoopRunner::loops_;
-Mutex CRIOControlLoopRunner::loops_lock;
-
-void CRIOControlLoopRunner::Start() {
-  if (started_) {
-    LOG(WARNING, "not going to Start twice!!\n");
-    return;
-  }
-  started_ = true;
-
-  // TODO(aschuh): Hold on to a handle to this...
-  (new WDInterruptNotifier<void>(Notify))->StartPeriodic(0.01);
-}
-
-void CRIOControlLoopRunner::AddControlLoop(SerializableControlLoop *loop) {
-  MutexLocker control_loop_goals_locker(&loops_lock);
-  loops_.push_back(loop);
-  MotorServer::RegisterControlLoopGoal(loop);
-}
-
-void CRIOControlLoopRunner::Notify(void *) {
-  // TODO(aschuh): Too many singletons/static classes!
-  SensorOutputs::UpdateAll();
-  // sensors get read first so it doesn't really matter if this takes a little bit
-  {
-    MutexLocker control_loop_goals_locker(
-        &MotorServer::control_loop_goals_lock);
-    for (auto it = loops_.begin(); it != loops_.end(); ++it) {
-      (*it)->Iterate();
-    }
-  }
-  MotorOutput::RunIterationAll();
-  MotorServer::WriteOutputs();
-}
-
-}  // namespace crio
-}  // namespace aos
diff --git a/aos/crio/motor_server/CRIOControlLoopRunner.h b/aos/crio/motor_server/CRIOControlLoopRunner.h
deleted file mode 100644
index efed120..0000000
--- a/aos/crio/motor_server/CRIOControlLoopRunner.h
+++ /dev/null
@@ -1,40 +0,0 @@
-#ifndef AOS_CRIO_MOTOR_SERVER_CRIO_CONTROL_LOOP_RUNNER_H_
-#define AOS_CRIO_MOTOR_SERVER_CRIO_CONTROL_LOOP_RUNNER_H_
-
-#include <vector>
-#include <semLib.h>
-
-#include "aos/common/control_loop/ControlLoop.h"
-#include "aos/common/mutex.h"
-
-namespace aos {
-namespace crio {
-
-// Runs crio-side control loops. Completely static because there is no reason
-// for multiple ones and it gets rid of the problem of passing an instance
-// around.
-class CRIOControlLoopRunner {
- public:
-  // Spawns a new Task that loops forever.
-  // No other functions should be called before this one returns.
-  static void Start();
-
-  // Adds a control loop to run.
-  // This class takes control of the instance.
-  static void AddControlLoop(control_loops::SerializableControlLoop *loop);
-
- private:
-  static bool started_;
-
-  static std::vector<control_loops::SerializableControlLoop *> loops_;
-  static Mutex loops_lock;
-
-  // Gets called by a WDInterruptNotifier on 0.01 second intervals.
-  static void Notify(void *);
-};
-
-
-}  // namespace crio
-}  // namespace aos
-
-#endif
diff --git a/aos/crio/motor_server/MotorServer.h b/aos/crio/motor_server/MotorServer.h
index 0126663..2f7fbde 100644
--- a/aos/crio/motor_server/MotorServer.h
+++ b/aos/crio/motor_server/MotorServer.h
@@ -27,13 +27,13 @@
 #include "aos/common/network/SendSocket.h"
 #include "aos/crio/motor_server/ControlLoopGoals.h"
 #include "aos/crio/motor_server/OutputDevice.h"
-#include "aos/crio/motor_server/SensorSender.h"
 #include "aos/crio/shared_libs/ByteBuffer.h"
 #include "aos/map_utils.h"
 
 namespace aos {
 namespace crio {
 
+template<class Values>
 class CRIOControlLoopRunner;
 class MotorServer {
  public:
@@ -46,7 +46,9 @@
   static const int32_t WORK_PRIORITY = 100;
 
  private:
+  template<class Values>
   friend class CRIOControlLoopRunner;
+
   // Counter for how many times new values come in. Used to stop all the
   // outputs if values stop.
   // Would take days to overflow.
diff --git a/aos/crio/motor_server/SensorOutput-tmpl.h b/aos/crio/motor_server/SensorOutput-tmpl.h
deleted file mode 100644
index d6b8b69..0000000
--- a/aos/crio/motor_server/SensorOutput-tmpl.h
+++ /dev/null
@@ -1,27 +0,0 @@
-#include "aos/common/input/SensorInput.h"
-
-namespace aos {
-
-template<class Values> std::vector<SensorOutput<Values> *> SensorOutput<Values>::output_running_;
-template<class Values> void SensorOutput<Values>::Run() {
-  semTake(lock_, WAIT_FOREVER);
-  output_running_.push_back(this);
-  outputs_running_.push_back(this);
-  semGive(lock_);
-}
-
-template<class Values> void SensorOutput<Values>::RunIterationAll(Values &vals) {
-  semTake(lock_, WAIT_FOREVER);
-  for (auto it = output_running_.begin(); it != output_running_.end(); ++it) {
-    (*it)->RunIteration(vals);
-  }
-  semGive(lock_);
-}
-template<class Values> void SensorOutput<Values>::Update() {
-  Values vals;
-  RunIteration(vals);
-  SensorInput<Values>::RunIterationAll(vals);
-}
-
-} // namespace aos
-
diff --git a/aos/crio/motor_server/SensorOutput.cc b/aos/crio/motor_server/SensorOutput.cc
deleted file mode 100644
index b887885..0000000
--- a/aos/crio/motor_server/SensorOutput.cc
+++ /dev/null
@@ -1,17 +0,0 @@
-#include "aos/crio/motor_server/SensorOutput.h"
-
-namespace aos {
-
-SEM_ID SensorOutputs::lock_ = semBCreate(SEM_Q_PRIORITY, SEM_FULL);
-std::vector<SensorOutputs *> SensorOutputs::outputs_running_;
-
-void SensorOutputs::UpdateAll() {
-  semTake(lock_, WAIT_FOREVER);
-  for (auto it = outputs_running_.begin(); it != outputs_running_.end(); ++it) {
-    (*it)->Update();
-  }
-  semGive(lock_);
-}
-
-} // namespace aos
-
diff --git a/aos/crio/motor_server/SensorOutput.h b/aos/crio/motor_server/SensorOutput.h
deleted file mode 100644
index 9e30cb9..0000000
--- a/aos/crio/motor_server/SensorOutput.h
+++ /dev/null
@@ -1,48 +0,0 @@
-#ifndef AOS_CRIO_MOTOR_SERVER_SENSOR_OUTPUT_H_
-#define AOS_CRIO_MOTOR_SERVER_SENSOR_OUTPUT_H_
-
-#include <semLib.h>
-#include <vector>
-
-namespace aos {
-
-// Keeps track of instances of all instantiations.
-class SensorOutputs {
- public:
-  // Calls RunIteration on all instances and then runs all SensorInput
-  // subclasses for that type.
-  static void UpdateAll();
- private:
-  static SEM_ID lock_;
-  static std::vector<SensorOutputs *> outputs_running_;
- protected:
-  // Calls RunIteration with a temporary Values instance and then runs all
-  // SensorInput subclasses with the same Values type.
-  virtual void Update() = 0;
-};
-
-// Class for implementing crio code that reads sensor values and puts them into
-// the sensor struct.
-template<class Values> class SensorOutput : public SensorOutputs {
- protected:
-  // Fills out vals with the current data.
-  // May not be called at anything close to consistent intervals and may be
-  // called simultaneously with different arguments, so it must be reentrant.
-  virtual void RunIteration(Values &vals) = 0;
- public:
-  // Sets it up so that RunIteration will get called when appropriate.
-  void Run();
-
-  // Calls RunIteration on all instances with vals.
-  static void RunIterationAll(Values &vals);
- private:
-  static std::vector<SensorOutput<Values> *> output_running_;
-  virtual void Update();
-};
-
-} // namespace aos
-
-#include "SensorOutput-tmpl.h"
-
-#endif
-
diff --git a/aos/crio/motor_server/SensorSender-tmpl.h b/aos/crio/motor_server/SensorSender-tmpl.h
deleted file mode 100644
index 0ec303c..0000000
--- a/aos/crio/motor_server/SensorSender-tmpl.h
+++ /dev/null
@@ -1,22 +0,0 @@
-#include "WPILib/Task.h"
-#include "WPILib/Timer.h"
-
-#include "aos/crio/motor_server/SensorOutput.h"
-#include "aos/common/network/SendSocket.h"
-#include "aos/common/Configuration.h"
-
-namespace aos {
-
-template<class Values> void SensorSender<Values>::Run() {
-  SendSocket sock(NetworkPort::kSensors,
-                  configuration::GetIPAddress(configuration::NetworkDevice::kAtom));
-  Values vals;
-  while (true) {
-    Wait(0.0015);
-    SensorOutput<Values>::RunIterationAll(vals);
-    sock.Send(&vals, sizeof(vals));
-  }
-}
-
-} // namespace aos
-
diff --git a/aos/crio/motor_server/SensorSender.h b/aos/crio/motor_server/SensorSender.h
deleted file mode 100644
index c42fbfb..0000000
--- a/aos/crio/motor_server/SensorSender.h
+++ /dev/null
@@ -1,22 +0,0 @@
-#ifndef AOS_CRIO_MOTOR_SERVER_SENSOR_SENDER_H_
-#define AOS_CRIO_MOTOR_SERVER_SENSOR_SENDER_H_
-
-namespace aos {
-
-// A class that handles sending all of the sensor values to the atom.
-// Designed for an instantiation (aos::SensorSender<X>) to be AOS_RUN_FORKed,
-// NOT a subclass.
-// Values is the type of the struct that will get sent out over the network.
-// Note: it should the same as the instance of TODO(brians) on the atom and any
-// SensorOutput instances that you want to feed into an instance of this.
-template<class Values> class SensorSender {
-	public:
-   // Loops forever.
-   void Run();
-};
-
-}  // namespace aos
-
-#include "SensorSender-tmpl.h"
-
-#endif  // AOS_CRIO_MOTOR_SERVER_SENSOR_SENDER_H_
diff --git a/aos/crio/motor_server/crio_control_loop_runner-tmpl.h b/aos/crio/motor_server/crio_control_loop_runner-tmpl.h
new file mode 100644
index 0000000..4bb448f
--- /dev/null
+++ b/aos/crio/motor_server/crio_control_loop_runner-tmpl.h
@@ -0,0 +1,35 @@
+#include "aos/common/logging/logging.h"
+
+#include "aos/crio/motor_server/MotorOutput.h"
+#include "aos/crio/motor_server/MotorServer.h"
+
+namespace aos {
+namespace crio {
+
+template<class Values>
+CRIOControlLoopRunner<Values>::CRIOControlLoopRunner(
+    sensors::SensorBroadcaster<Values> *broadcaster,
+    sensors::SensorUnpackerInterface<Values> *unpacker)
+    : broadcaster_(broadcaster),
+      unpacker_(unpacker) {}
+
+template<class Values>
+void CRIOControlLoopRunner<Values>::AddControlLoop(
+    ::aos::control_loops::SerializableControlLoop *loop) {
+  loops_.push_back(loop);
+  MotorServer::RegisterControlLoopGoal(loop);
+}
+
+template<class Values>
+void CRIOControlLoopRunner<Values>::Process(sensors::SensorData<Values> *data) {
+  unpacker_->UnpackFrom(&data.values);
+  for (auto it = loops_.begin(); it != loops_.end(); ++it) {
+    (*it)->Iterate();
+  }
+  // TODO(brians): make these nice objects too
+  MotorOutput::RunIterationAll();
+  MotorServer::WriteOutputs();
+}
+
+}  // namespace crio
+}  // namespace aos
diff --git a/aos/crio/motor_server/crio_control_loop_runner.h b/aos/crio/motor_server/crio_control_loop_runner.h
new file mode 100644
index 0000000..cea0852
--- /dev/null
+++ b/aos/crio/motor_server/crio_control_loop_runner.h
@@ -0,0 +1,47 @@
+#ifndef AOS_CRIO_MOTOR_SERVER_CRIO_CONTROL_LOOP_RUNNER_H_
+#define AOS_CRIO_MOTOR_SERVER_CRIO_CONTROL_LOOP_RUNNER_H_
+
+#include <vector>
+#include <semLib.h>
+
+#include "aos/common/control_loop/ControlLoop.h"
+#include "aos/common/mutex.h"
+#include "aos/common/sensors/sensor_sink.h"
+#include "aos/common/sensors/sensor_broadcaster.h"
+#include "aos/common/sensors/sensor_unpacker.h"
+#include "aos/common/macros.h"
+
+namespace aos {
+namespace crio {
+
+// Instances can run 0-N control loops on the cRIO.
+// See aos/common/sensors/sensors.h for an overview of where this fits in.
+template<class Values>
+class CRIOControlLoopRunner : public sensors::SensorSinkInterface<Values> {
+ public:
+  // Does not take ownership of broadcaster or unpacker.
+  // *broadcaster must not be started yet.
+  CRIOControlLoopRunner(sensors::SensorBroadcaster<Values> *broadcaster,
+                        sensors::SensorUnpackerInterface<Values> *unpacker);
+
+  // Adds a control loop to run.
+  // Must not be called after the broadcaster is started.
+  void AddControlLoop(control_loops::SerializableControlLoop *loop);
+
+  void Process(sensors::SensorData<Values> *data);
+
+ private:
+  std::vector<control_loops::SerializableControlLoop *> loops_;
+  sensors::SensorBroadcaster<Values> *const broadcaster_;
+  sensors::SensorUnpackerInterface<Values> *const unpacker_;
+
+  DISALLOW_COPY_AND_ASSIGN(CRIOControlLoopRunner<Values>);
+};
+
+
+}  // namespace crio
+}  // namespace aos
+
+#include "aos/crio/motor_server/crio_control_loop_runner-tmpl.h"
+
+#endif  // AOS_CRIO_MOTOR_SERVER_CRIO_CONTROL_LOOP_RUNNER_H_
diff --git a/aos/crio/motor_server/motor_server.gyp b/aos/crio/motor_server/motor_server.gyp
index 557dccb..09de2f3 100644
--- a/aos/crio/motor_server/motor_server.gyp
+++ b/aos/crio/motor_server/motor_server.gyp
@@ -1,24 +1,29 @@
 {
   'targets': [
     {
-      'target_name': 'CRIOControlLoopRunner',
+      'target_name': 'crio_control_loop_runner',
       'type': 'static_library',
       'sources': [
-        # 'ControlLoopGoals.h'
-        'CRIOControlLoopRunner.cc',
+        #'ControlLoopGoals.h'
+        #'crio_control_loop_runner-tmpl.h',
       ],
       'dependencies': [
         '<(EXTERNALS):WPILib',
-        '<(AOS)/common/common.gyp:mutex',
         '<(AOS)/common/common.gyp:controls',
-        '<(AOS)/crio/shared_libs/shared_libs.gyp:interrupt_notifier',
-        'output',
         '<(AOS)/build/aos.gyp:logging',
+        '<(AOS)/common/sensors/sensors.gyp:sensor_broadcaster',
+        '<(AOS)/common/sensors/sensors.gyp:sensor_sink',
+        'output',
+        'MotorServer',
       ],
       'export_dependent_settings': [
         '<(EXTERNALS):WPILib',
-        '<(AOS)/common/common.gyp:mutex',
         '<(AOS)/common/common.gyp:controls',
+        '<(AOS)/build/aos.gyp:logging',
+        '<(AOS)/common/sensors/sensors.gyp:sensor_broadcaster',
+        '<(AOS)/common/sensors/sensors.gyp:sensor_sink',
+        'output',
+        'MotorServer',
       ],
     },
     {
@@ -26,8 +31,6 @@
       'type': 'static_library',
       'sources': [
         # 'OutputDevice.h',
-        # 'SensorSender-tmpl.h',
-        'SensorOutput.cc',
         # 'SolenoidOutput.h'
         'MotorControllerOutput.cc',
         'MotorOutput.cc',
@@ -37,12 +40,14 @@
         '<(AOS)/common/network/network.gyp:socket',
         '<(AOS)/common/common.gyp:common',
         '<(AOS)/common/input/input.gyp:sensor_input',
+        '<(AOS)/crio/shared_libs/shared_libs.gyp:interrupt_notifier',
       ],
       'export_dependent_settings': [
         '<(EXTERNALS):WPILib',
         '<(AOS)/common/network/network.gyp:socket',
         '<(AOS)/common/common.gyp:common',
         '<(AOS)/common/input/input.gyp:sensor_input',
+        '<(AOS)/crio/shared_libs/shared_libs.gyp:interrupt_notifier',
       ],
     },
     {
@@ -57,7 +62,6 @@
         '<(AOS)/common/common.gyp:mutex',
         '<(AOS)/common/messages/messages.gyp:QueueHolder',
         '<(AOS)/common/network/network.gyp:socket',
-        'CRIOControlLoopRunner',
         '<(AOS)/crio/shared_libs/shared_libs.gyp:ByteBuffer',
         'output',
         '<(AOS)/common/network/network.gyp:socket',
@@ -68,7 +72,6 @@
         '<(AOS)/common/common.gyp:mutex',
         '<(AOS)/common/messages/messages.gyp:QueueHolder',
         '<(AOS)/common/network/network.gyp:socket',
-        'CRIOControlLoopRunner',
         '<(AOS)/crio/shared_libs/shared_libs.gyp:ByteBuffer',
         '<(AOS)/common/network/network.gyp:socket',
       ],
diff --git a/aos/crio/shared_libs/ByteBuffer.h b/aos/crio/shared_libs/ByteBuffer.h
index f1cf60a..9a926a7 100644
--- a/aos/crio/shared_libs/ByteBuffer.h
+++ b/aos/crio/shared_libs/ByteBuffer.h
@@ -14,7 +14,7 @@
    int m_i;
    char *m_buffer;
    bool recv_from_sock(ReceiveSocket *sock) {
-     m_length = sock->Recv(m_buffer, m_size, 40000);
+     m_length = sock->Receive(m_buffer, m_size, 40000);
      if (m_length < 0) {
        m_length = 0;
      }
diff --git a/aos/crio/shared_libs/interrupt_bridge-tmpl.h b/aos/crio/shared_libs/interrupt_bridge-tmpl.h
index fbd3e98..ef53dce 100644
--- a/aos/crio/shared_libs/interrupt_bridge-tmpl.h
+++ b/aos/crio/shared_libs/interrupt_bridge-tmpl.h
@@ -6,7 +6,6 @@
 
 #include "aos/common/logging/logging.h"
 #include "aos/crio/motor_server/MotorServer.h"
-#include "aos/common/time.h"
 
 extern "C" {
 // A really simple function implemented in a .c file because the header that
@@ -80,12 +79,14 @@
 PeriodicNotifier<T>::PeriodicNotifier(
     typename InterruptBridge<T>::Handler handler,
     T *param, int priority)
-    : InterruptBridge<T>(handler, param, priority) {}
+    : InterruptBridge<T>(handler, param, priority),
+      period_(-1, -1) {}
 
 template<typename T>
-void PeriodicNotifier<T>::StartPeriodic(double period) {
+void PeriodicNotifier<T>::StartPeriodic(time::Time period) {
+  period_ = period;
   this->StartTask();
-  StartNotifications(period);
+  StartNotifications();
 }
 
 template<typename T>
@@ -128,11 +129,11 @@
 }
 
 template<typename T>
-void TimerNotifier<T>::StartNotifications(double period) {
+void TimerNotifier<T>::StartNotifications() {
   itimerspec timer_spec;
   timer_spec.it_value.tv_sec = 0;
   timer_spec.it_value.tv_nsec = 1;  // 0 would mean to disarm the timer
-  timer_spec.it_interval = time::Time::InSeconds(period).ToTimespec();
+  timer_spec.it_interval = this->period().ToTimespec();
   if (timer_settime(timer_, 0, &timer_spec, NULL) != OK) {
     LOG(FATAL, "timer_settime(%p, 0, %p, NULL) failed with %d: %s\n",
         timer_, &timer_spec, errno, strerror(errno));
@@ -163,8 +164,8 @@
 }
 
 template<typename T>
-void WDInterruptNotifier<T>::StartNotifications(double period) {
-  delay_ = time::Time::InSeconds(period).ToTicks();
+void WDInterruptNotifier<T>::StartNotifications() {
+  delay_ = this->period().ToTicks();
 
   if (wdStart(wd_,
               1,  // run it really soon
diff --git a/aos/crio/shared_libs/interrupt_bridge.h b/aos/crio/shared_libs/interrupt_bridge.h
index f8f68b4..aedaaea 100644
--- a/aos/crio/shared_libs/interrupt_bridge.h
+++ b/aos/crio/shared_libs/interrupt_bridge.h
@@ -8,6 +8,7 @@
 
 #include "aos/common/scoped_ptr.h"
 
+#include "aos/common/time.h"
 #include "aos/common/macros.h"
 
 class Task;
@@ -61,8 +62,23 @@
 template<typename T>
 class PeriodicNotifier : public InterruptBridge<T> {
  public:
-  // Period is how much (in seconds) to wait between running the handler.
-  void StartPeriodic(double period);
+  // Period is how much to wait each time between running the handler.
+  void StartPeriodic(time::Time period);
+  // DEPRECATED(brians): use the overload that takes a time::Time
+  void StartPeriodic(double seconds) {
+    StartPeriodic(time::Time::InSeconds(seconds));
+  }
+  // After StartPeriodic is called, returns whether or not the subclass can
+  // actually call the callback exactly on those intervals or whether it will
+  // call it on some rounded amount.
+  //
+  // Default implementation assumes that the subclass has sysClockRateGet()
+  // resolution. Override if this is not true.
+  virtual bool IsExact() {
+    return period_ == time::Time::InTicks(period_.ToTicks());
+  }
+
+  time::Time period() { return period_; }
 
  protected:
   PeriodicNotifier(typename InterruptBridge<T>::Handler handler, T *param,
@@ -72,8 +88,10 @@
  private:
   virtual void StopNotifications() = 0;
   // Subclasses should do whatever they have to to start calling Notify() every
-  // period seconds.
-  virtual void StartNotifications(double period) = 0;
+  // period_.
+  virtual void StartNotifications() = 0;
+
+  time::Time period_;
 };
 
 // This one works accurately, but it has the potential to drift over time.
@@ -91,7 +109,7 @@
   // an instance. This function calls Notify() on that instance.
   static void StaticNotify(void *self_in);
   virtual void StopNotifications();
-  virtual void StartNotifications(double period);
+  virtual void StartNotifications();
 
   WDOG_ID wd_;
   int delay_;  // what to pass to wdStart
@@ -122,7 +140,7 @@
   // and calls Notify() on that instance.
   static void StaticNotify(int signum);
   virtual void StopNotifications();
-  virtual void StartNotifications(double period);
+  virtual void StartNotifications();
 
   timer_t timer_;
   // Which signal timer_ will notify on.