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/common/sensors/sensor_broadcaster-tmpl.h b/aos/common/sensors/sensor_broadcaster-tmpl.h
new file mode 100644
index 0000000..fa3f2fd
--- /dev/null
+++ b/aos/common/sensors/sensor_broadcaster-tmpl.h
@@ -0,0 +1,52 @@
+#include "aos/common/Configuration.h"
+
+namespace aos {
+namespace sensors {
+
+template<class Values>
+SensorBroadcaster<Values>::SensorBroadcaster(
+ SensorPackerInterface<Values> *packer)
+ : packer_(packer),
+ notifier_(StaticNotify, this),
+ socket_(NetworkPort::kSensors,
+ configuration::GetIPAddress(
+ configuration::NetworkDevice::kAtom)),
+ crio_control_loop_runner_(NULL) {
+ static_assert(shm_ok<SensorData<Values>>::value,
+ "it is going to get sent over a socket");
+ data_.count = 0;
+}
+
+template<class Values>
+void SensorBroadcaster<Values>::Start() {
+ notifier_.StartPeriodic(kSensorSendFrequency);
+ if (!notifier_.IsExact()) {
+ LOG(FATAL, "bad choice for kSensorSendFrequency\n");
+ }
+}
+
+template<class Values>
+void SensorBroadcaster<Values>::RegisterControlLoopRunner(
+ SensorSinkInterface<Values> *crio_control_loop_runner) {
+ if (crio_control_loop_runner_ != NULL) {
+ LOG(FATAL, "trying to register loop runner %p but already have %p\n",
+ crio_control_loop_runner, crio_control_loop_runner_);
+ }
+ crio_control_loop_runner_ = crio_control_loop_runner;
+}
+
+template<class Values>
+void SensorBroadcaster<Values>::Notify() {
+ packer_->PackInto(&data_.values);
+ socket_.Send(&data_, sizeof(data_));
+ ++data_.count;
+
+ if ((data_.count % kSendsPerCycle) == 0) {
+ if (crio_control_loop_runner_ != NULL) {
+ crio_control_loop_runner_->Process(&data_);
+ }
+ }
+}
+
+} // namespace sensors
+} // namespace aos
diff --git a/aos/common/sensors/sensor_broadcaster.h b/aos/common/sensors/sensor_broadcaster.h
new file mode 100644
index 0000000..1e61208
--- /dev/null
+++ b/aos/common/sensors/sensor_broadcaster.h
@@ -0,0 +1,57 @@
+#ifndef AOS_COMMON_SENSORS_SENSOR_BROADCASTER_H_
+#define AOS_COMMON_SENSORS_SENSOR_BROADCASTER_H_
+
+#include "aos/crio/shared_libs/interrupt_bridge.h"
+#include "aos/common/network/SendSocket.h"
+#include "aos/common/sensors/sensors.h"
+#include "aos/common/sensors/sensor_packer.h"
+#include "aos/common/sensors/sensor_sink.h"
+#include "aos/common/macros.h"
+
+namespace aos {
+namespace crio {
+
+template<class Values>
+class CRIOControlLoopRunner;
+
+} // namespace crio
+namespace sensors {
+
+// A class that handles sending sensor values to the atom.
+// See sensors.h for an overview of where this fits in.
+template<class Values>
+class SensorBroadcaster {
+ public:
+ // Does not take ownership of packer.
+ SensorBroadcaster(SensorPackerInterface<Values> *packer);
+
+ void Start();
+
+ private:
+ // So that it can access RegisterControlLoopRunner.
+ friend class crio::CRIOControlLoopRunner<Values>;
+
+ // Registers an object to get run on control loop timing. Only designed to be
+ // called by crio::CRIOControlLoopRunner.
+ // Does not take ownership of crio_control_loop_runner.
+ void RegisterControlLoopRunner(
+ SensorSinkInterface<Values> *crio_control_loop_runner);
+
+ static void StaticNotify(SensorBroadcaster<Values> *self) { self->Notify(); }
+ void Notify();
+
+ SensorPackerInterface<Values> *const packer_;
+ crio::WDInterruptNotifier<SensorBroadcaster<Values>> notifier_;
+ SendSocket socket_;
+ SensorData<Values> data_;
+ SensorSinkInterface<Values> *crio_control_loop_runner_;
+
+ DISALLOW_COPY_AND_ASSIGN(SensorBroadcaster<Values>);
+};
+
+} // namespace sensors
+} // namespace aos
+
+#include "sensor_broadcaster-tmpl.h"
+
+#endif // AOS_COMMON_SENSORS_SENSOR_BROADCASTER_H_
diff --git a/aos/common/sensors/sensor_packer.h b/aos/common/sensors/sensor_packer.h
new file mode 100644
index 0000000..cfcf045
--- /dev/null
+++ b/aos/common/sensors/sensor_packer.h
@@ -0,0 +1,22 @@
+#ifndef AOS_COMMON_SENSORS_SENSOR_PACKER_H_
+#define AOS_COMMON_SENSORS_SENSOR_PACKER_H_
+
+namespace aos {
+namespace sensors {
+
+// An interface that handles reading input data and putting it into the sensor
+// values struct.
+// See sensors.h for an overview of where this fits in.
+template<class Values>
+class SensorPackerInterface {
+ public:
+ virtual ~SensorPackerInterface() {}
+
+ // Reads the inputs (from WPILib etc) and writes the data into *values.
+ virtual void PackInto(Values *values) = 0;
+};
+
+} // namespace sensors
+} // namespace aos
+
+#endif // AOS_COMMON_SENSORS_SENSOR_PACKER_H_
diff --git a/aos/common/sensors/sensor_receiver-tmpl.h b/aos/common/sensors/sensor_receiver-tmpl.h
new file mode 100644
index 0000000..9d37b8c
--- /dev/null
+++ b/aos/common/sensors/sensor_receiver-tmpl.h
@@ -0,0 +1,154 @@
+#include "aos/common/Configuration.h"
+#include "aos/common/inttypes.h"
+
+namespace aos {
+namespace sensors {
+
+template<class Values>
+const time::Time SensorReceiver<Values>::kJitterDelay =
+ time::Time::InSeconds(0.0025);
+
+template<class Values>
+SensorReceiver<Values>::SensorReceiver(
+ SensorUnpackerInterface<Values> *unpacker)
+ : unpacker_(unpacker),
+ synchronized_(false) {}
+
+template<class Values>
+void SensorReceiver<Values>::RunIteration() {
+ if (synchronized_) {
+ if (ReceiveData()) {
+ LOG(DEBUG, "receive said to try a reset\n");
+ synchronized_ = false;
+ return;
+ }
+ if (GoodPacket()) {
+ unpacker_->UnpackFrom(&data_.values);
+ }
+ } else {
+ LOG(INFO, "resetting to try receiving data\n");
+ Reset();
+ if (Synchronize()) {
+ LOG(INFO, "synchronized successfully\n");
+ synchronized_ = true;
+ }
+ }
+}
+
+template<class Values>
+bool SensorReceiver<Values>::GoodPacket() {
+ // If it's a multiple of kSensorSendFrequency from start_count_.
+ if (((data_.count - start_count_) % kSendsPerCycle) == 0) {
+ return true;
+ } else {
+ return false;
+ }
+}
+
+// Looks for when the timestamps transition from before where we want to after
+// and then picks whichever one was closer. After that, reads kTestCycles and
+// makes sure that at most 1 is bad.
+template<class Values>
+bool SensorReceiver<Values>::Synchronize() {
+ time::Time old_received_time(0, 0);
+ time::Time start_time = time::Time::Now();
+ // When we want to send out the next set of values.
+ time::Time goal_time = (start_time / kLoopFrequency.ToNSec()) *
+ kLoopFrequency.ToNSec() +
+ kLoopFrequency - kJitterDelay;
+ while (true) {
+ if (ReceiveData()) return false;
+ time::Time received_time = time::Time::Now();
+ if (received_time > goal_time) {
+ // If this was the very first one we got, try again.
+ if (old_received_time == time::Time(0, 0)) return false;
+
+ assert(old_received_time < goal_time);
+
+ // If the most recent one is closer than the last one.
+ if ((received_time - goal_time).abs() <
+ (old_received_time - goal_time).abs()) {
+ start_count_ = data_.count;
+ } else {
+ start_count_ = data_.count - 1;
+ }
+
+ int bad_count = 0;
+ for (int i = 0; i < kTestCycles;) {
+ ReceiveData();
+ received_time = time::Time::Now();
+ if (GoodPacket()) {
+ LOG(DEBUG, "checking packet count=%"PRId32
+ " received at %"PRId32"s%"PRId32"ns\n",
+ data_.count, received_time.sec(), received_time.nsec());
+ // If |the difference between the goal time for this numbered packet
+ // and the time we actually got this one| is too big.
+ if (((goal_time +
+ kSensorSendFrequency * (data_.count - start_count_)) -
+ received_time).abs() > kSensorSendFrequency) {
+ LOG(INFO, "rejected time of the last good packet\n");
+ ++bad_count;
+ }
+ ++i;
+ }
+ if (bad_count > 1) {
+ LOG(WARNING, "got multiple packets with bad timestamps\n");
+ return false;
+ }
+ }
+
+ return true;
+ }
+
+ old_received_time = received_time;
+ }
+}
+
+template<class Values>
+bool SensorReceiver<Values>::ReceiveData() {
+ int old_count = data_.count;
+ DoReceiveData();
+ if (data_.count < 0) {
+ LOG(FATAL, "data count overflowed. currently %"PRId32"\n", data_.count);
+ }
+ if (data_.count < old_count) {
+ LOG(INFO, "count reset. ws %"PRId32", now %"PRId32"\n",
+ old_count, data_.count);
+ return true;
+ }
+ return false;
+}
+
+template<class Values>
+const time::Time NetworkSensorReceiver<Values>::kWarmupTime =
+ time::Time::InSeconds(0.125);
+
+template<class Values>
+NetworkSensorReceiver<Values>::NetworkSensorReceiver(
+ SensorUnpackerInterface<Values> *unpacker)
+ : SensorReceiver<Values>(unpacker),
+ socket_(NetworkPort::kSensors) {}
+
+template<class Values>
+void NetworkSensorReceiver<Values>::Reset() {
+ LOG(INFO, "beginning warm up\n");
+ time::Time start = time::Time::Now();
+ while ((time::Time::Now() - start) < kWarmupTime) {
+ socket_.Receive(this->data(), sizeof(*this->data()));
+ }
+ LOG(INFO, "done warming up\n");
+}
+
+template<class Values>
+void NetworkSensorReceiver<Values>::DoReceiveData() {
+ while (true) {
+ if (socket_.Receive(this->data(), sizeof(*this->data())) ==
+ sizeof(*this->data())) {
+ return;
+ }
+ LOG(WARNING, "received incorrect amount of data\n");
+ }
+}
+
+} // namespace sensors
+} // namespace aos
diff --git a/aos/common/sensors/sensor_receiver.h b/aos/common/sensors/sensor_receiver.h
new file mode 100644
index 0000000..1fe3003
--- /dev/null
+++ b/aos/common/sensors/sensor_receiver.h
@@ -0,0 +1,98 @@
+#ifndef AOS_COMMON_SENSORS_SENSOR_RECEIVER_H_
+#define AOS_COMMON_SENSORS_SENSOR_RECEIVER_H_
+
+#include "aos/common/sensors/sensor_unpacker.h"
+#include "aos/common/network/ReceiveSocket.h"
+#include "aos/common/sensors/sensors.h"
+#include "aos/common/time.h"
+#include "aos/common/gtest_prod.h"
+
+namespace aos {
+namespace sensors {
+namespace testing {
+
+FORWARD_DECLARE_TEST_CASE(SensorReceiverTest, Simple);
+
+} // namespace testing
+
+// A class that handles receiving sensor values from the cRIO.
+// See sensors.h for an overview of where this fits in.
+//
+// Abstract class to make testing the complex logic for choosing which data to
+// use easier.
+template<class Values>
+class SensorReceiver {
+ public:
+ // Does not take ownership of unpacker.
+ SensorReceiver(SensorUnpackerInterface<Values> *unpacker);
+
+ void RunIteration();
+
+ protected:
+ SensorData<Values> *data() { return &data_; }
+
+ private:
+ // How long before the control loops run to aim for receiving sensor data (to
+ // prevent jitter if some packets arrive up to this much later).
+ static const time::Time kJitterDelay;
+ // How many cycles not to send data out to make sure that we're in phase
+ // (during this time, the code verifies that <= 1 cycle is not within 1
+ // cycle's time of kJitterDelay).
+ static const int kTestCycles = 10;
+
+ FRIEND_TEST_NAMESPACE(SensorReceiverTest, Simple, testing);
+
+ // Subclasses need to implement this to read 1 set of data (blocking until it
+ // is available) into data().
+ virtual void DoReceiveData() = 0;
+
+ // Optional: if subclasses can do anything to reinitialize after there are
+ // problems, they should do it here.
+ // This will be called right before calling DoReceiveData() the first time.
+ virtual void Reset() {}
+
+ // Returns whether the current packet looks like a good one to use.
+ bool GoodPacket();
+
+ // Synchronizes with incoming packets and sets start_count_ to where we
+ // started reading.
+ // Returns whether it succeeded in locking on.
+ bool Synchronize();
+ // Receives a set of values and makes sure that it's sane.
+ // Returns whether to start over again with timing.
+ bool ReceiveData();
+
+ SensorUnpackerInterface<Values> *const unpacker_;
+ SensorData<Values> data_;
+ // The count that we started out (all other sent packets will be multiples of
+ // this).
+ int32_t start_count_;
+ bool synchronized_;
+
+ DISALLOW_COPY_AND_ASSIGN(SensorReceiver<Values>);
+};
+
+// A SensorReceiver that receives data from a SensorBroadcaster.
+template<class Values>
+class NetworkSensorReceiver : public SensorReceiver<Values> {
+ public:
+ NetworkSensorReceiver(SensorUnpackerInterface<Values> *unpacker);
+
+ private:
+ // How long to read data as fast as possible for (to clear out buffers etc).
+ static const time::Time kWarmupTime;
+
+ virtual void DoReceiveData();
+ virtual void Reset();
+
+ ReceiveSocket socket_;
+
+ DISALLOW_COPY_AND_ASSIGN(NetworkSensorReceiver<Values>);
+};
+
+} // namespace sensors
+} // namespace aos
+
+#include "aos/common/sensors/sensor_receiver-tmpl.h"
+
+#endif // AOS_COMMON_SENSORS_SENSOR_RECEIVER_H_
diff --git a/aos/common/sensors/sensor_receiver_test.cc b/aos/common/sensors/sensor_receiver_test.cc
new file mode 100644
index 0000000..5a66432
--- /dev/null
+++ b/aos/common/sensors/sensor_receiver_test.cc
@@ -0,0 +1,84 @@
+#include "aos/common/sensors/sensor_receiver.h"
+
+#include "gtest/gtest.h"
+
+#include "aos/common/sensors/sensors.h"
+#include "aos/common/time.h"
+#include "aos/common/queue_testutils.h"
+
+using ::aos::time::Time;
+
+namespace aos {
+namespace sensors {
+namespace testing {
+
+struct TestValues {
+ int count;
+ int more_data;
+};
+class TestSensorReceiver : public SensorReceiver<TestValues>,
+ public SensorUnpackerInterface<TestValues> {
+ public:
+ TestSensorReceiver()
+ : SensorReceiver<TestValues>(this),
+ resets_(0),
+ unpacks_(0) {
+ data()->count = 0;
+ }
+
+ void Reset() {
+ LOG(DEBUG, "reset for the %dth time\n", ++resets_);
+ }
+ void DoReceiveData() {
+ last_received_count_ = ++data()->count;
+ data()->values.count = last_received_count_;
+ Time::IncrementMockTime(kSensorSendFrequency);
+ }
+
+ int resets() { return resets_; }
+ int unpacks() { return unpacks_; }
+ using SensorReceiver<TestValues>::data;
+
+ void UnpackFrom(TestValues *data) {
+ // Make sure that it didn't lost one that we gave it.
+ EXPECT_EQ(last_received_count_, data->count);
+ ++unpacks_;
+ }
+
+ private:
+ int resets_;
+ int unpacks_;
+ int last_received_count_;
+};
+
+class SensorReceiverTest : public ::testing::Test {
+ protected:
+ SensorReceiverTest() {
+ ::aos::common::testing::EnableTestLogging();
+ Time::EnableMockTime(Time(971, 254));
+ }
+
+ TestSensorReceiver &receiver() { return receiver_; }
+
+ private:
+ TestSensorReceiver receiver_;
+};
+
+TEST_F(SensorReceiverTest, Simple) {
+ static const int kIterations = 53;
+ for (int i = 0; i < kIterations; ++i) {
+ receiver().RunIteration();
+ }
+ EXPECT_EQ(1, receiver().resets());
+ // expected value is kIterations/kSendsPerCycle (rounded up) (the number of
+ // times that it should get a good one) - 1 (to compensate for the iteration
+ // when it synced itself up)
+ EXPECT_EQ((kIterations + kSendsPerCycle - 1) / kSendsPerCycle - 1,
+ receiver().unpacks());
+}
+
+// TODO(brians) finish writing tests
+
+} // namespace testing
+} // namespace sensors
+} // namespace aos
diff --git a/aos/common/sensors/sensor_sink.h b/aos/common/sensors/sensor_sink.h
new file mode 100644
index 0000000..81bc491
--- /dev/null
+++ b/aos/common/sensors/sensor_sink.h
@@ -0,0 +1,21 @@
+#ifndef AOS_COMMON_SENSORS_SENSOR_SINK_H_
+#define AOS_COMMON_SENSORS_SENSOR_SINK_H_
+
+#include "aos/common/sensors/sensors.h"
+
+namespace aos {
+namespace sensors {
+
+// Generic class for something that can do something with sensor data.
+template<class Values>
+class SensorSinkInterface {
+ public:
+ virtual ~SensorSinkInterface() {}
+
+ void Process(SensorData<Values> *data);
+};
+
+} // namespace sensors
+} // namespace aos
+
+#endif // AOS_COMMON_SENSORS_SENSOR_SINK_H_
diff --git a/aos/common/sensors/sensor_unpacker.h b/aos/common/sensors/sensor_unpacker.h
new file mode 100644
index 0000000..b19e0c6
--- /dev/null
+++ b/aos/common/sensors/sensor_unpacker.h
@@ -0,0 +1,22 @@
+#ifndef AOS_COMMON_SENSORS_SENSOR_UNPACKER_H_
+#define AOS_COMMON_SENSORS_SENSOR_UNPACKER_H_
+
+namespace aos {
+namespace sensors {
+
+// An interface that handles taking data from the sensor Values struct and
+// putting it into queues (for control loops etc).
+// See sensors.h for an overview of where this fits in.
+template<class Values>
+class SensorUnpackerInterface {
+ public:
+ virtual ~SensorUnpackerInterface() {}
+
+ // Takes the data in *values and writes it out into queues etc.
+ virtual void UnpackFrom(Values *values) = 0;
+};
+
+} // namespace sensors
+} // namespace aos
+
+#endif // AOS_COMMON_SENSORS_SENSOR_UNPACKER_H_
diff --git a/aos/common/sensors/sensors.cc b/aos/common/sensors/sensors.cc
new file mode 100644
index 0000000..8b774d2
--- /dev/null
+++ b/aos/common/sensors/sensors.cc
@@ -0,0 +1,10 @@
+#include "aos/common/sensors/sensors.h"
+
+namespace aos {
+namespace sensors {
+
+const time::Time kSensorSendFrequency =
+ ::aos::control_loops::kLoopFrequency / kSendsPerCycle;
+
+} // namespace sensors
+} // namespace aos
diff --git a/aos/common/sensors/sensors.gyp b/aos/common/sensors/sensors.gyp
new file mode 100644
index 0000000..2c49369
--- /dev/null
+++ b/aos/common/sensors/sensors.gyp
@@ -0,0 +1,87 @@
+{
+ 'targets': [
+ {
+ 'target_name': 'sensor_sink',
+ 'type': 'static_library',
+ 'sources': [
+ ],
+ 'dependencies': [
+ 'sensors',
+ ],
+ 'export_dependent_settings': [
+ 'sensors',
+ ],
+ },
+ {
+ 'target_name': 'sensors',
+ 'type': 'static_library',
+ 'sources': [
+ 'sensors.cc'
+ ],
+ 'dependencies': [
+ '<(AOS)/common/common.gyp:time',
+ '<(AOS)/common/common.gyp:controls',
+ ],
+ 'export_dependent_settings': [
+ '<(AOS)/common/common.gyp:time',
+ '<(AOS)/common/common.gyp:controls',
+ ],
+ },
+ {
+ 'target_name': 'sensor_receiver',
+ 'type': 'static_library',
+ 'sources': [
+ #'sensor_receiver-tmpl.h'
+ ],
+ 'dependencies': [
+ '<(AOS)/common/network/network.gyp:socket',
+ '<(AOS)/common/common.gyp:common',
+ 'sensors',
+ '<(AOS)/common/common.gyp:time',
+ '<(AOS)/common/common.gyp:gtest_prod',
+ ],
+ 'export_dependent_settings': [
+ '<(AOS)/common/network/network.gyp:socket',
+ '<(AOS)/common/common.gyp:common',
+ 'sensors',
+ '<(AOS)/common/common.gyp:time',
+ '<(AOS)/common/common.gyp:gtest_prod',
+ ],
+ },
+ {
+ 'target_name': 'sensor_receiver_test',
+ 'type': 'executable',
+ 'sources': [
+ 'sensor_receiver_test.cc',
+ ],
+ 'dependencies': [
+ '<(EXTERNALS):gtest',
+ 'sensor_receiver',
+ '<(AOS)/common/common.gyp:time',
+ 'sensors',
+ '<(AOS)/common/common.gyp:queue_testutils',
+ ],
+ },
+ {
+ 'target_name': 'sensor_broadcaster',
+ 'type': 'static_library',
+ 'sources': [
+ #'sensor_broadcaster-tmpl.h',
+ ],
+ 'dependencies': [
+ '<(AOS)/crio/shared_libs/shared_libs.gyp:interrupt_notifier',
+ '<(AOS)/common/network/network.gyp:socket',
+ '<(AOS)/common/common.gyp:common',
+ 'sensors',
+ 'sensor_sink',
+ ],
+ 'export_dependent_settings': [
+ '<(AOS)/crio/shared_libs/shared_libs.gyp:interrupt_notifier',
+ '<(AOS)/common/network/network.gyp:socket',
+ '<(AOS)/common/common.gyp:common',
+ 'sensors',
+ 'sensor_sink',
+ ],
+ },
+ ],
+}
diff --git a/aos/common/sensors/sensors.h b/aos/common/sensors/sensors.h
new file mode 100644
index 0000000..06f5253
--- /dev/null
+++ b/aos/common/sensors/sensors.h
@@ -0,0 +1,48 @@
+#ifndef AOS_COMMON_SENSORS_SENSORS_H_
+#define AOS_COMMON_SENSORS_SENSORS_H_
+
+#include "aos/common/time.h"
+
+#include "aos/common/control_loop/ControlLoop.h"
+
+namespace aos {
+// This namespace contains all of the stuff for dealing with reading sensors and
+// communicating it to everything that needs it. There are 4 main classes whose
+// instances actually process the data. They must all be registered in the
+// appropriate ::aos::crio::ControlsManager hooks.
+//
+// SensorPackers get run on the cRIO to read inputs (from WPILib or elsewhere)
+// and put the values into the Values struct (which is templated for all of the
+// classes that use it).
+// SensorUnpackers get run on both the atom and the cRIO to take the data from
+// the Values struct and put them into queues for control loops etc.
+// SensorBroadcasters (on the cRIO) send the data to a SensorReceiver (on the
+// atom) to pass to its SensorUnpacker there.
+// CRIOControlLoopRunners register with a SensorBroadcaster to get called right
+// after reading the sensor data so that they can immediately pass it so a
+// SensorUnpacker and then run their control loops.
+// The actual SensorPacker and SensorUnpacker classes have the Interface suffix
+// on them.
+namespace sensors {
+
+// How many times per ::aos::control_loops::kLoopFrequency sensor
+// values get sent out by the cRIO.
+// This must evenly divide that frequency into multiples of sysClockRateGet().
+const int kSendsPerCycle = 10;
+// ::aos::control_loops::kLoopFrequency / kSendsPerCycle for
+// convenience.
+extern const time::Time kSensorSendFrequency;
+using ::aos::control_loops::kLoopFrequency;
+
+// This is the struct that actually gets sent over the UDP socket.
+template<class Values>
+struct SensorData {
+ Values values;
+ // Starts at 0 and goes up.
+ int32_t count;
+} __attribute__((packed));
+
+} // namespace sensors
+} // namespace aos
+
+#endif // AOS_COMMON_SENSORS_SENSORS_H_