setup

Files at this revision

API Documentation at this revision

Comitter:
cheryl_he
Date:
Tue May 05 00:43:17 2015 +0000
Commit message:
telemetry setup

Changed in this revision

telemetry-master/.gitignore Show annotated file Show diff for this revision Revisions of this file
telemetry-master/README.md Show annotated file Show diff for this revision Revisions of this file
telemetry-master/client-py/.gitignore Show annotated file Show diff for this revision Revisions of this file
telemetry-master/client-py/.pydevproject Show annotated file Show diff for this revision Revisions of this file
telemetry-master/client-py/console.py Show annotated file Show diff for this revision Revisions of this file
telemetry-master/client-py/plotter.py Show annotated file Show diff for this revision Revisions of this file
telemetry-master/client-py/telemetry/__init__.py Show annotated file Show diff for this revision Revisions of this file
telemetry-master/client-py/telemetry/parser.py Show annotated file Show diff for this revision Revisions of this file
telemetry-master/docs/.gitignore Show annotated file Show diff for this revision Revisions of this file
telemetry-master/docs/protocol.tex Show annotated file Show diff for this revision Revisions of this file
telemetry-master/docs/quickstart.tex Show annotated file Show diff for this revision Revisions of this file
telemetry-master/server-cpp/.gitignore Show annotated file Show diff for this revision Revisions of this file
telemetry-master/server-cpp/telemetry-arduino-hal.cpp Show annotated file Show diff for this revision Revisions of this file
telemetry-master/server-cpp/telemetry-arduino.h Show annotated file Show diff for this revision Revisions of this file
telemetry-master/server-cpp/telemetry-data.cpp Show annotated file Show diff for this revision Revisions of this file
telemetry-master/server-cpp/telemetry-mbed-hal.cpp Show annotated file Show diff for this revision Revisions of this file
telemetry-master/server-cpp/telemetry-mbed.h Show annotated file Show diff for this revision Revisions of this file
telemetry-master/server-cpp/telemetry.cpp Show annotated file Show diff for this revision Revisions of this file
telemetry-master/server-cpp/telemetry.h Show annotated file Show diff for this revision Revisions of this file
diff -r 000000000000 -r fc22cf8cc478 telemetry-master/.gitignore
diff -r 000000000000 -r fc22cf8cc478 telemetry-master/README.md
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/telemetry-master/README.md	Tue May 05 00:43:17 2015 +0000
@@ -0,0 +1,2 @@
+# telemetry
+Telemetry protocol with spec, transmit-side library, and visualization client
diff -r 000000000000 -r fc22cf8cc478 telemetry-master/client-py/.gitignore
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/telemetry-master/client-py/.gitignore	Tue May 05 00:43:17 2015 +0000
@@ -0,0 +1,2 @@
+*.pyc
+*.csv
diff -r 000000000000 -r fc22cf8cc478 telemetry-master/client-py/.pydevproject
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/telemetry-master/client-py/.pydevproject	Tue May 05 00:43:17 2015 +0000
@@ -0,0 +1,8 @@
+<?xml version="1.0" encoding="UTF-8" standalone="no"?>
+<?eclipse-pydev version="1.0"?><pydev_project>
+<pydev_pathproperty name="org.python.pydev.PROJECT_SOURCE_PATH">
+<path>/${PROJECT_DIR_NAME}</path>
+</pydev_pathproperty>
+<pydev_property name="org.python.pydev.PYTHON_PROJECT_VERSION">python 3.0</pydev_property>
+<pydev_property name="org.python.pydev.PYTHON_PROJECT_INTERPRETER">Python3.4</pydev_property>
+</pydev_project>
diff -r 000000000000 -r fc22cf8cc478 telemetry-master/client-py/console.py
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/telemetry-master/client-py/console.py	Tue May 05 00:43:17 2015 +0000
@@ -0,0 +1,37 @@
+from __future__ import print_function
+import time
+
+import serial
+
+from telemetry.parser import TelemetrySerial
+
+if __name__ == "__main__":
+  import argparse
+  parser = argparse.ArgumentParser(description='Telemetry packet parser example.')
+  parser.add_argument('port', metavar='p', help='serial port to receive on')
+  parser.add_argument('--baud', metavar='b', type=int, default=38400,
+                      help='serial baud rate')
+  args = parser.parse_args()
+  
+  telemetry = TelemetrySerial(serial.Serial(args.port, baudrate=args.baud))
+  
+  while True:
+    telemetry.process_rx()
+    time.sleep(0.1)
+
+    while True:
+      next_packet = telemetry.next_rx_packet()
+      if not next_packet:
+        break
+      print('')
+      print(next_packet)
+    
+    while True:
+      next_byte = telemetry.next_rx_byte()
+      if next_byte is None:
+        break
+      try:
+        print(chr(next_byte), end='')
+      except UnicodeEncodeError:
+        pass
+          
\ No newline at end of file
diff -r 000000000000 -r fc22cf8cc478 telemetry-master/client-py/plotter.py
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/telemetry-master/client-py/plotter.py	Tue May 05 00:43:17 2015 +0000
@@ -0,0 +1,383 @@
+from __future__ import print_function
+import ast
+from collections import deque
+import csv
+import datetime 
+import sys
+
+if sys.version_info.major < 3:
+  from Tkinter import *
+  import tkSimpleDialog as simpledialog
+else:
+  from tkinter import *
+  import tkinter.simpledialog as simpledialog
+  
+from matplotlib import pyplot as plt
+import matplotlib.animation as animation
+import numpy as np
+import serial
+
+from telemetry.parser import TelemetrySerial, DataPacket, HeaderPacket, NumericData, NumericArray
+
+class BasePlot(object):
+  """Base class / interface definition for telemetry plotter plots with a
+  dependent variable vs. an scrolling independent variable (like time).
+  """
+  def __init__(self, subplot, indep_def, dep_def, indep_span):
+    """Constructor.
+    
+    Arguments:
+    subplot -- matplotlib subplot to draw stuff onto
+    indep_def -- TelemetryData of the independent variable
+    dep_def -- TelemetryData of the dependent variable
+    indep_span -- the span of the independent variable to keep track of
+    """
+    self.indep_span = indep_span
+    self.indep_id = indep_def.data_id
+    self.dep_id = dep_def.data_id
+    
+    self.dep_def = dep_def
+    
+    if dep_def.limits[0] != dep_def.limits[1]:
+      self.limits = dep_def.limits
+    else:
+      self.limits = None
+      
+    self.subplot = subplot
+    
+  def update_from_packet(self, packet):
+    """Updates my internal data structures from a received packet. Should not do
+    rendering - this may be called multiple times per visible update.
+    """
+    raise NotImplementedError 
+
+  def update_show(self, packet):
+    """Render my data. This is separated from update_from_packet to allow
+    multiple packets to be processed while doing only one time-consuming render. 
+    """
+    raise NotImplementedError
+
+  def get_name(self):
+    return self.dep_def.display_name
+
+  def get_dep_def(self):
+    return self.dep_def
+
+class NumericPlot(BasePlot):
+  """A plot of a single numeric dependent variable vs. a single independent
+  variable
+  """
+  def __init__(self, subplot, indep_def, dep_def, indep_span):
+    super(NumericPlot, self).__init__(subplot, indep_def, dep_def, indep_span)
+    self.line, = subplot.plot([0])
+    
+    self.indep_data = deque()
+    self.dep_data = deque()
+  
+  def update_from_packet(self, packet):
+    assert isinstance(packet, DataPacket)
+    indep_val = packet.get_data_by_id(self.indep_id)
+    dep_val = packet.get_data_by_id(self.dep_id)
+    
+    if indep_val is not None and dep_val is not None:
+      self.indep_data.append(indep_val)
+      self.dep_data.append(dep_val)
+      
+      indep_cutoff = indep_val - self.indep_span
+      
+      while self.indep_data[0] < indep_cutoff or self.indep_data[0] > indep_val:
+        self.indep_data.popleft()
+        self.dep_data.popleft()
+        
+  def update_show(self):
+      self.line.set_xdata(self.indep_data)
+      self.line.set_ydata(self.dep_data)
+      
+      if self.limits is not None: 
+        minlim = self.limits[0]
+        maxlim = self.limits[1] 
+      else:
+        if not self.dep_data:
+          return
+        minlim = min(self.dep_data)
+        maxlim = max(self.dep_data)
+        if minlim < 0 and maxlim < 0:
+          maxlim = 0
+        elif minlim > 0 and maxlim > 0:
+          minlim = 0
+      rangelim = maxlim - minlim
+      minlim -= rangelim / 20 # TODO make variable padding
+      maxlim += rangelim / 20
+      self.subplot.set_ylim(minlim, maxlim)
+
+class WaterfallPlot(BasePlot):
+  def __init__(self, subplot, indep_def, dep_def, indep_span):
+    super(WaterfallPlot, self).__init__(subplot, indep_def, dep_def, indep_span)
+    self.count = dep_def.count
+    
+    self.x_mesh = [0] * (self.count + 1)
+    self.x_mesh = np.array([self.x_mesh])
+    
+    self.y_array = range(0, self.count + 1)
+    self.y_array = list(map(lambda x: x - 0.5, self.y_array))
+    self.y_mesh = np.array([self.y_array])
+    
+    self.data_array = None
+    self.indep_data = deque()
+    self.quad = None
+  
+  def update_from_packet(self, packet):
+    assert isinstance(packet, DataPacket)
+    indep_val = packet.get_data_by_id(self.indep_id)
+    dep_val = packet.get_data_by_id(self.dep_id)
+    
+    if indep_val is not None and dep_val is not None:
+      self.x_mesh = np.vstack([self.x_mesh, np.array([[indep_val] * (self.count + 1)])])
+      self.y_mesh = np.vstack([self.y_mesh, np.array(self.y_array)])
+      if self.data_array is None:
+        self.data_array = np.array([dep_val])
+      else:
+        self.data_array = np.vstack([self.data_array, dep_val])
+        
+      indep_cutoff = indep_val - self.indep_span
+      
+      while self.x_mesh[0][0] < indep_cutoff or self.x_mesh[0][0] > indep_val:
+        self.x_mesh = np.delete(self.x_mesh, (0), axis=0)
+        self.y_mesh = np.delete(self.y_mesh, (0), axis=0)
+        self.data_array = np.delete(self.data_array, (0), axis=0)
+
+  def update_show(self):
+    if self.quad is not None:
+      self.quad.remove()
+      del self.quad
+    
+    if self.limits is not None:
+      self.quad = self.subplot.pcolorfast(self.x_mesh, self.y_mesh, self.data_array,
+          cmap='gray', vmin=self.limits[0], vmax=self.limits[1],
+          interpolation='None')
+    else:  
+      self.quad = self.subplot.pcolorfast(self.x_mesh, self.y_mesh, self.data_array,
+          cmap='gray', interpolation='None')
+
+plot_registry = {}
+plot_registry[NumericData] = NumericPlot
+plot_registry[NumericArray] = WaterfallPlot
+
+
+def subplots_from_header(packet, figure, indep_def, indep_span=10000):
+  """Instantiate subplots and plots from a received telemetry HeaderPacket.
+  The default implementation creates a new plot for each dependent variable,
+  but you can customize it to do better things.
+  
+  Arguments:
+  packet -- HeaderPacket object
+  figure -- matplotlib figure to draw on 
+  indep_name -- internal_name of the independent variable.
+  indep_span -- span of the independent variable to display.
+  
+  Returns: a dict of matplotlib subpolots to list of contained BasePLot objects. 
+  """
+  assert isinstance(packet, HeaderPacket)
+  
+  if indep_def is None:
+    print("No independent variable")
+    return [], []
+
+  data_defs = []
+  for _, data_def in reversed(sorted(packet.get_data_defs().items())):
+    if data_def != indep_def:
+      data_defs.append(data_def)
+
+  plots_dict = {}
+
+  for plot_idx, data_def in enumerate(data_defs):
+    ax = figure.add_subplot(len(data_defs), 1, len(data_defs)-plot_idx)
+    ax.set_title("%s: %s (%s)"           
+        % (data_def.internal_name, data_def.display_name, data_def.units))
+    if plot_idx != 0:
+      plt.setp(ax.get_xticklabels(), visible=False)
+    
+    assert data_def.__class__ in plot_registry, "Unable to handle TelemetryData type %s" % data_def.__class__.__name__ 
+    plot = plot_registry[data_def.__class__](ax, indep_def, data_def, 
+                                             indep_span)
+    plots_dict[ax] = [plot]
+    
+    print("Found dependent data %s" % data_def.internal_name)
+
+  return plots_dict
+
+class CsvLogger(object):
+  def __init__(self, name, header_packet):
+    csv_header = []
+    internal_name_dict = {}
+    display_name_dict = {}
+    units_dict = {}
+        
+    for _, data_def in sorted(header_packet.get_data_defs().items()):
+      csv_header.append(data_def.data_id)
+      internal_name_dict[data_def.data_id] = data_def.internal_name
+      display_name_dict[data_def.data_id] = data_def.display_name
+      units_dict[data_def.data_id] = data_def.units
+    csv_header.append("data")  # for non-telemetry data
+                         
+    if sys.version_info.major < 3:
+      self.csv_file = open(name, 'wb')
+    else:
+      self.csv_file = open(name, 'w', newline='')
+    self.csv_writer = csv.DictWriter(self.csv_file, fieldnames=csv_header)
+
+    self.csv_writer.writerow(internal_name_dict)
+    self.csv_writer.writerow(display_name_dict)
+    self.csv_writer.writerow(units_dict)
+    
+    self.pending_data = ""
+    
+  def add_char(self, data):
+    if data == '\n' or data == '\r':
+      if self.pending_data:
+        self.csv_writer.writerow({'data': self.pending_data})
+        self.pending_data = ''
+    else:
+      self.pending_data += data
+    
+  def write_data(self, data_packet):
+    if self.pending_data:
+      self.csv_writer.writerow({'data': self.pending_data})
+      self.pending_data = ""
+    self.csv_writer.writerow(data_packet.get_data_dict())
+        
+  def finish(self):
+    if self.pending_data:
+      self.csv_writer.writerow({'data': self.pending_data})
+    self.csv_file.close()
+  
+if __name__ == "__main__":
+  import argparse
+  parser = argparse.ArgumentParser(description='Telemetry data plotter.')
+  parser.add_argument('port', metavar='p', help='serial port to receive on')
+  parser.add_argument('--baud', metavar='b', type=int, default=38400,
+                      help='serial baud rate')
+  parser.add_argument('--indep_name', metavar='i', default='time',
+                      help='internal name of independent axis')
+  parser.add_argument('--span', metavar='s', type=int, default=10000,
+                      help='independent variable axis span')
+  parser.add_argument('--log_prefix', metavar='f', default='telemetry',
+                      help='filename prefix for logging output, set to empty to disable logging')
+  args = parser.parse_args()
+ 
+  telemetry = TelemetrySerial(serial.Serial(args.port, args.baud))
+  
+  fig = plt.figure()
+  
+  # note: mutable elements are in lists to allow access from nested functions
+  indep_def = [None]  # note: data ID 0 is invalid
+  latest_indep = [0]
+  plots_dict = [[]]
+  
+  csv_logger = [None]
+  
+  def update(data):
+    telemetry.process_rx()
+    plot_updated = False
+    
+    while True:
+      packet = telemetry.next_rx_packet()
+      if not packet:
+        break
+      
+      if isinstance(packet, HeaderPacket):
+        fig.clf()
+        
+        # get independent variable data ID
+        indep_def[0] = None
+        for _, data_def in packet.get_data_defs().items():
+          if data_def.internal_name == args.indep_name:
+            indep_def[0] = data_def
+        # TODO warn on missing indep id or duplicates
+        
+        # instantiate plots
+        plots_dict[0] = subplots_from_header(packet, fig, indep_def[0], args.span)
+        
+        # prepare CSV file and headers
+        timestring = datetime.datetime.now().strftime("%Y%m%d-%H%M%S")
+        filename = '%s-%s.csv' %  (args.log_prefix, timestring)
+        if csv_logger[0] is not None:
+          csv_logger[0].finish()
+        if args.log_prefix:
+          csv_logger[0] = CsvLogger(filename, packet)
+        
+      elif isinstance(packet, DataPacket):
+        if indep_def[0] is not None:
+          indep_value = packet.get_data_by_id(indep_def[0].data_id)
+          if indep_value is not None:
+            latest_indep[0] = indep_value
+            for plot_list in plots_dict[0].values():
+              for plot in plot_list:
+                plot.update_from_packet(packet)
+            plot_updated = True
+        
+        if csv_logger[0]:
+          csv_logger[0].write_data(packet)
+        
+      else:
+        raise Exception("Unknown received packet %s" % repr(packet))
+      
+    while True:
+      next_byte = telemetry.next_rx_byte()
+      if next_byte is None:
+        break
+      try:
+        print(chr(next_byte), end='')
+        if csv_logger[0]:
+          csv_logger[0].add_char(chr(next_byte))
+      except UnicodeEncodeError:
+        pass
+
+    if plot_updated:
+      for plot_list in plots_dict[0].values():
+        for plot in plot_list:
+          plot.update_show()  
+      for subplot in plots_dict[0].keys():
+        subplot.set_xlim([latest_indep[0] - args.span, latest_indep[0]])
+        
+  def set_plot_dialog(plot):
+    def set_plot_dialog_inner():
+      got = plot.get_dep_def().get_latest_value()
+      error = ""
+      while True:
+        got = simpledialog.askstring("Set remote value",
+                                     "Set %s\n%s" % (plot.get_name(), error),
+                                     initialvalue=got)
+        if got is None:
+          break
+        try:
+          parsed = ast.literal_eval(got)
+          telemetry.transmit_set_packet(plot.get_dep_def(), parsed)
+          break
+        except ValueError as e:
+          error = str(e)
+        except SyntaxError as e:
+          error = str(e)
+                  
+    return set_plot_dialog_inner
+        
+  def on_click(event):
+    ax = event.inaxes
+    if event.dblclick:
+      if ax is None or ax not in plots_dict[0]:
+        return
+      plots_list = plots_dict[0][ax]
+      if len(plots_list) > 1:
+        menu = Menu(fig.canvas.get_tk_widget(), tearoff=0)
+        for plot in plots_list: 
+          menu.add_command(label="Set %s" % plot.get_name(),
+                           command=set_plot_dialog(plot))
+        menu.post(event.guiEvent.x_root, event.guiEvent.y_root)
+      elif len(plots_list) == 1:
+        set_plot_dialog(plots_list[0])()
+      else:
+        return
+        
+  fig.canvas.mpl_connect('button_press_event', on_click)
+  ani = animation.FuncAnimation(fig, update, interval=30)
+  plt.show()
diff -r 000000000000 -r fc22cf8cc478 telemetry-master/client-py/telemetry/__init__.py
diff -r 000000000000 -r fc22cf8cc478 telemetry-master/client-py/telemetry/parser.py
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/telemetry-master/client-py/telemetry/parser.py	Tue May 05 00:43:17 2015 +0000
@@ -0,0 +1,517 @@
+from collections import deque
+from numbers import Number
+import struct
+import time
+
+# TODO: MASSIVE REFACTORING EVERYWHERE
+
+# lifted from https://stackoverflow.com/questions/36932/how-can-i-represent-an-enum-in-python
+def enum(*sequential, **named):
+    enums = dict(zip(sequential, range(len(sequential))), **named)
+    return type('Enum', (), enums)
+
+# Global constants defined by the telemetry protocol.
+# TODO: parse constants from cpp header
+SOF_BYTE = [0x05, 0x39]
+
+OPCODE_HEADER = 0x81
+OPCODE_DATA = 0x01
+
+DATAID_TERMINATOR = 0x00
+
+DATATYPE_NUMERIC = 0x01
+DATATYPE_NUMERIC_ARRAY = 0x02
+
+NUMERIC_SUBTYPE_UINT = 0x01
+NUMERIC_SUBTYPE_SINT = 0x02
+NUMERIC_SUBTYPE_FLOAT = 0x03
+
+RECORDID_TERMINATOR = 0x00
+
+# Deserialization functions that (destructively) reads data from the input "stream".
+def deserialize_uint8(byte_stream):
+# TODO: handle overflow
+  return byte_stream.popleft()
+
+def deserialize_bool(byte_stream):
+# TODO: handle overflow
+  return not(byte_stream.popleft() == 0)
+
+def deserialize_uint16(byte_stream):
+  # TODO: handle overflow
+  return byte_stream.popleft() << 8 | byte_stream.popleft()
+
+def deserialize_uint32(byte_stream):
+  # TODO: handle overflow
+  return (byte_stream.popleft() << 24 
+         | byte_stream.popleft() << 16
+         | byte_stream.popleft() << 8
+         | byte_stream.popleft())
+  
+def deserialize_float(byte_stream):
+  # TODO: handle overflow
+  packed = bytearray([byte_stream.popleft(), 
+                      byte_stream.popleft(),
+                      byte_stream.popleft(),
+                      byte_stream.popleft()])
+  return struct.unpack('!f', packed)[0]
+
+def deserialize_numeric(byte_stream, subtype, length):
+  if subtype == NUMERIC_SUBTYPE_UINT:
+    value = 0
+    remaining = length
+    while remaining > 0:
+      value = value << 8 | deserialize_uint8(byte_stream)
+      remaining -= 1
+    return value
+    # TODO: add support for sint
+  elif subtype == NUMERIC_SUBTYPE_FLOAT:
+    if length == 4:
+      return deserialize_float(byte_stream)
+    else:
+      raise UnknownNumericSubtype("Unknown float length %02x" % length)
+  else:
+    raise UnknownNumericSubtype("Unknown subtype %02x" % subtype)
+
+def deserialize_numeric_from_def(data_def, count=None):
+  def deserialize_numeric_inner(byte_stream):
+    # these should have already been decoded
+    assert hasattr(data_def, 'subtype')
+    assert hasattr(data_def, 'length')
+    if count is not None:
+      inner_count = count
+      out = []
+      while inner_count > 0:
+        out.append(deserialize_numeric(byte_stream, data_def.subtype, data_def.length))
+        inner_count -= 1
+      return out
+    else:
+      return deserialize_numeric(byte_stream, data_def.subtype, data_def.length)
+  return deserialize_numeric_inner
+
+def deserialize_string(byte_stream):
+  # TODO: handle overflow
+  outstr = ""
+  data = byte_stream.popleft()
+  while data:
+    outstr += chr(data)
+    data = byte_stream.popleft()
+  return outstr
+
+
+
+def serialize_uint8(value):
+  if (not isinstance(value, int)) or (value < 0 or value > 255):
+    raise ValueError("Invalid uint8: %s" % value)
+  return struct.pack('!B', value)
+
+def serialize_uint16(value):
+  if (not isinstance(value, int)) or (value < 0 or value > 65535):
+    raise ValueError("Invalid uint16: %s" % value) 
+  return struct.pack('!H', value)
+
+def serialize_uint32(value):
+  if (not isinstance(value, int)) or (value < 0 or value > 2 ** 32 - 1): 
+    raise ValueError("Invalid uint32: %s" % value)
+  return struct.pack('!L', value)
+
+def serialize_float(value):
+  if not isinstance(value, Number):
+    raise ValueError("Invalid uintfloat: %s" % value)
+  return struct.pack('!f', value)
+
+def serialize_numeric(value, subtype, length):
+  if subtype == NUMERIC_SUBTYPE_UINT:
+    if length == 1:
+      return serialize_uint8(value)
+    elif length == 2:
+      return serialize_uint16(value)
+    elif length == 4:
+      return serialize_uint32(value)
+    else:
+      raise ValueError("Unknown uint length %02x" % length)
+  elif subtype == NUMERIC_SUBTYPE_FLOAT:
+    if length == 4:
+      return serialize_float(value)
+    else:
+      raise ValueError("Unknown float length %02x" % length)
+  else:
+    raise ValueError("Unknown subtype %02x" % subtype)
+
+
+
+PACKET_LENGTH_BYTES = 2 # number of bytes in the packet length field
+
+class TelemetryDeserializationError(Exception):
+  pass
+
+class NoRecordIdError(TelemetryDeserializationError):
+  pass
+class MissingKvrError(TelemetryDeserializationError):
+  pass
+
+datatype_registry = {}
+class TelemetryData(object):
+  """Abstract base class for telemetry data ID definitions.
+  """
+  def __repr__(self):
+    out = self.__class__.__name__
+    for _, record_desc in self.get_kvrs_dict().items():
+      record_name, _ = record_desc
+      out += " %s=%s" % (record_name, repr(getattr(self, record_name)))
+    return out
+  
+  def get_kvrs_dict(self):
+    """Returns a dict of record id => (record name, deserialization function) known by this class.
+    The record name is used as an instance variable name if the KVR is read in.
+    """
+    return {
+      0x01: ('internal_name', deserialize_string),
+      0x02: ('display_name', deserialize_string),
+      0x03: ('units', deserialize_string),
+      # TODO: make more robust by allowing defaults / partial parses
+      # add record 0x08 = freeze
+    }
+  
+  @staticmethod
+  def decode_header(data_id, byte_stream):
+    """Decodes a data header from the telemetry stream, automatically detecting and returning
+    the correct TelemetryData subclass object.
+    """
+    opcode = byte_stream[0]
+    if opcode not in datatype_registry:
+      raise NoOpcodeError("No datatype %02x" % opcode)
+    data_cls = datatype_registry[opcode]
+    return data_cls(data_id, byte_stream)
+  
+  def __init__(self, data_id, byte_stream):
+    self.data_id = data_id
+    self.data_type = deserialize_uint8(byte_stream)
+    
+    self.internal_name = "%02x" % data_id
+    self.display_name = self.internal_name
+    self.units = ""
+    
+    self.latest_value = None
+    
+    self.decode_kvrs(byte_stream)
+    
+  def decode_kvrs(self, byte_stream):
+    """Destructively reads in a sequence of KVRs from the input stream, writing
+    the known ones as instance variables and throwing exceptions on unknowns.
+    """
+    kvrs_dict = self.get_kvrs_dict()
+    while True:
+      record_id = deserialize_uint8(byte_stream)
+      if record_id == RECORDID_TERMINATOR:
+        break
+      elif record_id not in kvrs_dict:
+        raise NoRecordIdError("No RecordId %02x in %s" % (record_id, self.__class__.__name__))
+      record_name, record_deserializer = kvrs_dict[record_id]
+      setattr(self, record_name, record_deserializer(byte_stream))
+      
+    # check that all KVRs have been read in / defaulted
+    for record_id, record_desc in kvrs_dict.items():
+      record_name, _ = record_desc
+      if not hasattr(self, record_name):
+        raise NoRecordIdError("%s missing RecordId %02x (%s) in header" % (self.__class__.__name__, record_id, record_name))
+
+  def deserialize_data(self, byte_stream):
+    """Destructively reads in the data of this type from the input stream.
+    """
+    raise NotImplementedError
+  
+  def serialize_data(self, value):
+    """Returns the serialized version (as bytes) of this data given a value.
+    Can raise a ValueError if there is a conversion issue.
+    """
+    raise NotImplementedError
+  
+  def get_latest_value(self):
+    return self.latest_value
+  
+  def set_latest_value(self, value):
+    self.latest_value = value
+  
+  
+  
+class UnknownNumericSubtype(Exception):
+  pass
+  
+class NumericData(TelemetryData):
+  def get_kvrs_dict(self):
+    newdict = super(NumericData, self).get_kvrs_dict().copy()
+    newdict.update({ 
+      0x40: ('subtype', deserialize_uint8),
+      0x41: ('length', deserialize_uint8),
+      0x42: ('limits', deserialize_numeric_from_def(self, count=2)),
+    })
+    return newdict
+    
+  def deserialize_data(self, byte_stream):
+    return deserialize_numeric(byte_stream, self.subtype, self.length)
+  
+  def serialize_data(self, value):
+    return serialize_numeric(value, self.subtype, self.length)
+  
+datatype_registry[DATATYPE_NUMERIC] = NumericData
+
+class NumericArray(TelemetryData):
+  def get_kvrs_dict(self):
+    newdict = super(NumericArray, self).get_kvrs_dict().copy()
+    newdict.update({ 
+      0x40: ('subtype', deserialize_uint8),
+      0x41: ('length', deserialize_uint8),
+      0x42: ('limits', deserialize_numeric_from_def(self, count=2)),
+      0x50: ('count', deserialize_uint32),
+    })
+    return newdict 
+  
+  def deserialize_data(self, byte_stream):
+    out = []
+    for _ in range(self.count):
+      out.append(deserialize_numeric(byte_stream, self.subtype, self.length))
+    return out
+  
+  def serialize_data(self, value):
+    if len(value) != self.count:
+      raise ValueError("Length mismatch: got %i, expected %i"
+                       % (len(value), self.count))
+    out = bytes()
+    for elt in value:
+      out += serialize_numeric(elt, self.subtype, self.length)
+    return out
+  
+datatype_registry[DATATYPE_NUMERIC_ARRAY] = NumericArray
+
+class PacketSizeError(TelemetryDeserializationError):
+  pass
+class NoOpcodeError(TelemetryDeserializationError):
+  pass
+class DuplicateDataIdError(TelemetryDeserializationError):
+  pass
+class UndefinedDataIdError(TelemetryDeserializationError):
+  pass
+
+opcodes_registry = {}
+class TelemetryPacket(object):
+  """Abstract base class for telemetry packets.
+  """
+  @staticmethod
+  def decode(byte_stream, context):
+    opcode = byte_stream[0]
+    if opcode not in opcodes_registry:
+      raise NoOpcodeError("No opcode %02x" % opcode)
+    packet_cls = opcodes_registry[opcode]
+    return packet_cls(byte_stream, context)
+  
+  def __init__(self, byte_stream, context):
+    self.opcode = deserialize_uint8(byte_stream)
+    self.sequence = deserialize_uint8(byte_stream)
+    self.decode_payload(byte_stream, context)
+    if len(byte_stream) > 0:
+      raise PacketSizeError("%i unused bytes in packet" % len(byte_stream))
+      
+  def decode_payload(self, byte_stream, context):
+    raise NotImplementedError
+
+class HeaderPacket(TelemetryPacket):
+  def __repr__(self):
+    return "[%i]Header: %s" % (self.sequence, repr(self.data))
+    
+  def decode_payload(self, byte_stream, context):
+    self.data = {}
+    while True:
+      data_id = deserialize_uint8(byte_stream)
+      if data_id == DATAID_TERMINATOR:
+        break
+      elif data_id in self.data:
+        raise DuplicateDataIdError("Duplicate DataId %02x" % data_id)
+      self.data[data_id] = TelemetryData.decode_header(data_id, byte_stream)
+      
+  def get_data_defs(self):
+    """Returns the data defs defined in this header as a dict of data ID to
+    TelemetryData objects.
+    """
+    return self.data
+  
+  def get_data_names(self):
+    data_names = []
+    for data_def in self.data.values():
+      data_names.append(data_def.internal_name)
+    return data_names
+      
+opcodes_registry[OPCODE_HEADER] = HeaderPacket
+
+class DataPacket(TelemetryPacket):
+  def __repr__(self):
+    return "[%i]Data: %s" % (self.sequence, repr(self.data))
+    
+  def decode_payload(self, byte_stream, context):
+    self.data = {}
+    while True:
+      data_id = deserialize_uint8(byte_stream)
+      if data_id == DATAID_TERMINATOR:
+        break
+      data_def = context.get_data_def(data_id)
+      if not data_def:
+        raise UndefinedDataIdError("Received DataId %02x not defined in header" % data_id)
+      data_value = data_def.deserialize_data(byte_stream)
+      data_def.set_latest_value(data_value)
+      self.data[data_def.data_id] = data_value 
+
+  def get_data_dict(self):
+    return self.data
+
+  def get_data_by_id(self, data_id):
+    if data_id in self.data:
+      return self.data[data_id]
+    else:
+      return None
+
+opcodes_registry[OPCODE_DATA] = DataPacket  
+
+
+
+class TelemetryContext(object):
+  """Context for telemetry communications, containing the setup information in
+  the header.
+  """
+  def __init__(self, data_defs):
+    self.data_defs = data_defs
+    
+  def get_data_def(self, data_id):
+    if data_id in self.data_defs:
+      return self.data_defs[data_id]
+    else:
+      return None
+
+
+
+class TelemetrySerial(object):
+  """Telemetry serial receiver state machine. Separates out telemetry packets
+  from the rest of the stream.
+  """
+  DecoderState = enum('SOF', 'LENGTH', 'DATA', 'DATA_DESTUFF', 'DATA_DESTUFF_END')
+  PACKET_TIMEOUT_THRESHOLD = 0.1  # seconds
+  
+  def __init__(self, serial):
+    self.serial = serial
+    
+    self.rx_packets = deque()  # queued decoded packets
+    
+    self.context = TelemetryContext([])
+    
+    # decoder state machine variables
+    self.decoder_state = self.DecoderState.SOF;  # expected next byte
+    self.decoder_pos = 0; # position within decoder_State
+    self.packet_length = 0;  # expected packet length
+    self.packet_buffer = deque()
+    
+    self.data_buffer = deque()
+    
+    # decoder packet timeout variables
+    self.last_loop_received = False
+    self.last_receive_time = time.time()
+
+  def process_rx(self):
+    if ((not self.last_loop_received)
+        and (time.time() - self.last_receive_time > self.PACKET_TIMEOUT_THRESHOLD)
+        and (self.decoder_state != self.DecoderState.SOF and self.decoder_pos > 0)):
+      self.decoder_state = self.DecoderState.SOF
+      self.decoder_pos = 0
+      self.packet_buffer = deque()
+      print("Packet timed out; dropping")
+      
+    self.last_loop_received = False
+    
+    while self.serial.inWaiting():
+      self.last_loop_received = True
+      self.last_receive_time = time.time()
+
+      rx_byte = ord(self.serial.read())
+      if self.decoder_state == self.DecoderState.SOF:
+        self.packet_buffer.append(rx_byte)
+        
+        if rx_byte == SOF_BYTE[self.decoder_pos]:
+          self.decoder_pos += 1
+          if self.decoder_pos == len(SOF_BYTE):
+            self.packet_length = 0
+            self.decoder_pos = 0
+            self.decoder_state = self.DecoderState.LENGTH
+        else:
+          self.data_buffer.extend(self.packet_buffer)
+          self.packet_buffer = deque()
+          self.decoder_pos = 0
+      elif self.decoder_state == self.DecoderState.LENGTH:
+        self.packet_length = self.packet_length << 8 | rx_byte
+        self.decoder_pos += 1
+        if self.decoder_pos == PACKET_LENGTH_BYTES:
+          self.packet_buffer = deque()
+          self.decoder_pos = 0
+          self.decoder_state = self.DecoderState.DATA
+      elif self.decoder_state == self.DecoderState.DATA:
+        self.packet_buffer.append(rx_byte)
+        self.decoder_pos += 1
+        if self.decoder_pos == self.packet_length:
+          try:
+            decoded = TelemetryPacket.decode(self.packet_buffer, self.context)
+          
+            if isinstance(decoded, HeaderPacket):
+              self.context = TelemetryContext(decoded.get_data_defs())
+            
+            self.rx_packets.append(decoded)
+          except TelemetryDeserializationError as e:
+            print("Deserialization error: %s" % repr(e)) # TODO prettier cleaner
+          
+          self.packet_buffer = deque()
+      
+          self.decoder_pos = 0
+          if rx_byte == SOF_BYTE[0]:
+            self.decoder_state = self.DecoderState.DATA_DESTUFF_END
+          else:
+            self.decoder_state = self.DecoderState.SOF
+        elif rx_byte == SOF_BYTE[0]:
+          self.decoder_state = self.DecoderState.DATA_DESTUFF
+      elif self.decoder_state == self.DecoderState.DATA_DESTUFF:
+        self.decoder_state = self.DecoderState.DATA
+      elif self.decoder_state == self.DecoderState.DATA_DESTUFF_END:
+        self.decoder_state = self.DecoderState.SOF
+      else:
+        raise RuntimeError("Unknown DecoderState")
+
+  def transmit_set_packet(self, data_def, value):
+    packet = bytearray()
+    packet += serialize_uint8(OPCODE_DATA)
+    packet += serialize_uint8(data_def.data_id)
+    packet += data_def.serialize_data(value)
+    packet += serialize_uint8(DATAID_TERMINATOR)
+    self.transmit_packet(packet)
+  
+  def transmit_packet(self, packet):
+    header = bytearray()
+    for elt in SOF_BYTE:
+      header += serialize_uint8(elt)
+    header += serialize_uint16(len(packet))
+    
+    modified_packet = bytearray()
+    for packet_byte in packet:
+      modified_packet.append(packet_byte)
+      if packet_byte == SOF_BYTE[0]:
+        modified_packet.append(0x00)
+    
+    self.serial.write(header + modified_packet)
+
+    # TODO: add CRC support
+      
+  def next_rx_packet(self):
+    if self.rx_packets:
+      return self.rx_packets.popleft()
+    else:
+      return None
+    
+  def next_rx_byte(self):
+    if self.data_buffer:
+      return self.data_buffer.popleft()
+    else:
+      return None
diff -r 000000000000 -r fc22cf8cc478 telemetry-master/docs/.gitignore
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/telemetry-master/docs/.gitignore	Tue May 05 00:43:17 2015 +0000
@@ -0,0 +1,7 @@
+*.aux
+*.log
+*.pdf
+*.synctex.gz
+*.out
+*.*~
+
diff -r 000000000000 -r fc22cf8cc478 telemetry-master/docs/protocol.tex
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/telemetry-master/docs/protocol.tex	Tue May 05 00:43:17 2015 +0000
@@ -0,0 +1,142 @@
+\documentclass[border=10pt,png]{article}
+\usepackage{bytefield}
+\usepackage{color}
+
+\begin{document}
+
+\section{Introduction}
+
+\textcolor{red}{WARNING! This spec is still in BETA and is subject to change.}
+
+
+\subsection{Big Ideas}
+Each piece of data is assigned an ID
+
+\subsection{General Notes}
+All multi-byte words are defined to be in network ordering (RFC 1700, big endian).
+
+\section{Packet Layer}
+The packet layer structures data provided by the underlying link layer into an organized packet. Currently, there is only one implementation of this layer.
+
+\subsection{In-band Signaling over UART Serial Terminal}
+This provides embedding telemetry data in a UART serial terminal stream. It is assumed that no ASCII control characters will be transmitted on the same stream as they will be used to delimit telemetry data.
+
+While not required by the spec, the telemetry transmitter may preface each telemetry frame with ANSI commands to prevent serial terminals from displaying the telemetry (gibberish) contents. ANSI conceal and invisible may be used.
+
+To prevent terminals from interpreting the telemetry data as ANSI commands, anytime an ANSI escape character (\texttt{0x1b}) appears in the data stream, a null character (\texttt{0x00}) should be added directly afterwards. This should be handled by the packet layer only - the byte will be stuffed at this layer at the transmitter and destuffed at the corresponding layer at the receiver. The data length should not include stuffed bytes.
+
+\begin{bytefield}{32}
+  \bitheader{0, 15, 16, 31} \\
+  \bitbox{16}{Start of Frame \\ \tiny{0x05 0x39}}
+  & \bitbox{16}{Data Length \\ \tiny{(2 bytes, number of bytes in the data)}} \\
+  \wordbox[lrt]{1}{Data \\ \tiny{payload of bytes equal to length above}} \\
+  \skippedwords \\
+  \wordbox[lrb]{1}{} \\
+  \bitbox{16}{CRC (unimplemented)}
+\end{bytefield}
+
+\section{Telemetry Protocol Layer}
+
+Each telemetry packet is structured as follows:
+
+\begin{bytefield}{16}
+  \bitheader{0, 7, 8, 15} \\
+  \bitbox{8}{Opcode}
+  & \bitbox{8}{Sequence \#} \\
+  \wordbox[lrt]{1}{Payload \\ \tiny{length dependent on payload opcode}} \\
+  \skippedwords \\
+  \wordbox[lrb]{1}{}
+\end{bytefield}
+
+The sequence number is incremeneted by 1 per transmitted packet (rolling over at the maximum of 255) and should start at zero. This is used to detect network errors like dropped packets. No ARQ protocol is currently specified.
+
+\subsection{Payload format for opcode 0x81: Data Definition}
+This is sent to the telemetry client to configure the display. This should be the first telemetry packet sent and must be comprehensive (all data IDs are sent, populated with all immutable fields). After initial configuration, this packet may be transmitted (in either direction) to update mutable fields.
+
+\begin{bytefield}{16}
+  \bitheader{0, 7, 8, 15} \\
+  \wordbox[lrt]{1}{Data headers} \\
+  \skippedwords \\
+  \wordbox[lrb]{1}{} \\
+  \bitbox{8}{0x00 \\ \tiny{terminator ``record''}} \\
+\end{bytefield}
+
+Each data header is defined as:
+
+\begin{bytefield}{16}
+  \bitheader{0, 7, 8, 15} \\
+  \bitbox{8}{Data ID}
+  \bitbox{8}{Data type} \\
+  \wordbox[lrt]{1}{KV records} \\
+  \skippedwords \\
+  \wordbox[lrb]{1}{} \\
+  \bitbox{8}{0x00 \\ \tiny{terminator ``record''}} \\
+\end{bytefield}
+
+Data IDs may not exceed 127 (allowing varints to be used in future implementations, as necessary). The Data ID of 0 is reserved as a terminator.
+
+Each KV record is defined as:
+
+\begin{bytefield}{16}
+  \bitheader{0, 7, 8, 15} \\
+  \bitbox{8}{Record ID} \\
+  \wordbox[lrt]{1}{Record value} \\
+  \skippedwords \\
+  \wordbox[lrb]{1}{} \\
+\end{bytefield}
+
+Record IDs may not exceed 127 (allowing varints to be used in future implementations, as necessary). The Record ID of 0 is reserved as a terminator. \\
+Record ID meanings are different for each data type. The length and format of the record value is dependent on the record meaning. \\
+Record IDs 1-63 (0x01-0x3f) are reserved for common global records definitions. IDs from 64 onwards are free to be defined per data type.
+
+\subsubsection{Global Records IDs}
+Record values are immutable (can't be updated after the initial header is sent), unless otherwise noted.
+
+0x00: reserved (terminator) \\
+0x01: internal name, as a null-terminated string, default to data ID \\
+0x02: display (user-friendly) name, as a null-terminated string, default to internal name \\
+0x03: units, as a null-terminated string, default "" \\
+
+0x08: freeze on/off, one byte as a boolean flag (nonzero is TRUE), if TRUE prevents the value from being updated on the client, default is 0, mutable \\
+
+\subsection{Data format for opcode 0x01: Data}
+Data is usually sent from server to the client. However, a packet from the client to the server means to set the corresponding data object.
+
+\begin{bytefield}{16}
+  \bitheader{0, 7, 8, 15} \\
+  \wordbox[lrt]{1}{Data records} \\
+  \skippedwords \\
+  \wordbox[lrb]{1}{} \\
+  \bitbox{8}{0x00 \\ \tiny{terminator ``record''}} \\
+\end{bytefield}
+
+Each data record is defined as:
+
+\begin{bytefield}{16}
+  \bitheader{0, 7, 8, 15} \\
+  \bitbox{8}{Data ID} \\
+  \wordbox[lrt]{1}{Data value} \\
+  \skippedwords \\
+  \wordbox[lrb]{1}{} \\
+\end{bytefield}
+
+The data value length and format is dependent on the data type, which is defined by the data ID in the header.
+
+\section{Data Types}
+
+\subsection{Numeric: Data type 1}
+\subsubsection{KV Records}
+Record ID 0x40, uint8: data sub-type: 0x01 indicates unsigned integer, 0x02 indicates signed integer, 0x03 indicates floating-point \\
+Record ID 0x41, uint8: data length (in bytes) \\
+Record ID 0x42, uint8: range limits (in data type, obviously must be transmitted after sub-type and length) \\
+\subsubsection{Data format}
+Raw data in network order.
+
+\subsection{Numeric Array: Data type 2}
+\subsubsection{KV Records}
+This includes all the records in the numeric type (for element type), along with: \\
+Record ID 0x50, uint32: array count (in number of elements)
+\subsubsection{Data format}
+Raw data in network order.
+
+\end{document}
diff -r 000000000000 -r fc22cf8cc478 telemetry-master/docs/quickstart.tex
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/telemetry-master/docs/quickstart.tex	Tue May 05 00:43:17 2015 +0000
@@ -0,0 +1,214 @@
+\documentclass[11pt]{article}
+\usepackage{graphicx}
+\usepackage{hyperref}
+\usepackage{xcolor}
+\usepackage{listings}
+
+\newcommand{\todo}[1]{\textcolor{red}{\textbf{TODO:} #1}}
+
+% lifted from http://timmurphy.org/2014/01/27/displaying-code-in-latex-documents/
+\lstset{
+    basicstyle=\ttfamily\footnotesize,
+    breaklines=true,
+    frame=tb, % draw a frame at the top and bottom of the code block
+    tabsize=2, % tab space width
+    showstringspaces=false, % don't mark spaces in strings
+    numbers=left, % display line numbers on the left
+    commentstyle=\color{green}, % comment color
+    keywordstyle=\color{blue}, % keyword color
+    stringstyle=\color{red} % string color
+}
+
+\title{\textbf{Telemetry Quick Start \& Reference}}
+\author{Ducky}
+\date{\today}
+\begin{document}
+
+\maketitle
+
+\section{Introduction}
+The telemetry library is designed to provide a simple and efficient way to get data off an embedded platform and both visualized and logged on a PC. The intended use case is streaming data from an embedded system to a PC through an UART interface (possibly on top of either a USB or Bluetooth transport layer). Since data definitions are transmitted, user-side code changes can be limited to the data source side, eliminating the need to keep both the source-side and sink-side code in sync.
+
+The server-side (data source) code is written in C++ and designed with embedded constraints in mind (mainly, no dynamic memory allocation is done). Templated classes are used to support most native data types and a hardware abstraction layer allows multiple platforms (including Arduino and mBed) to be supported.
+
+The client-side (data sink) code is written in Python. A basic telemetry protocol parser is provided that separates and interprets telemetry packets and other data from the received stream. A matplotlib-based GUI is also provided on top of the parser that visualizes numeric data as a line plot and array-numeric data as a waterfall / spectrogram style plot.
+
+A protocol spec is also given allowing other implementations of either the server and client. It short, it defines a binary wire format along with packet structures for data and headers.
+
+\section{Prerequisites}
+The following software is necessary for a basic telemetry install:
+\begin{itemize}
+  \item \href{https://www.python.org/downloads/}{Python} (this document was written for 3.4, but may work on 2.7)
+\end{itemize}
+
+The following software is necessary for the telemetry plotter GUI:
+\begin{itemize}
+  \item \href{http://sourceforge.net/projects/numpy/files/NumPy/}{NumPy (1.9.2 or later)}
+  \begin{itemize}
+    \item \href{http://sourceforge.net/projects/numpy/files/NumPy/1.9.2/numpy-1.9.2-win32-superpack-python3.4.exe/download}{executable for Windows, Python 3.4 32-bit}
+  \end{itemize}
+  \item \href{http://matplotlib.org/downloads.html}{matplotlib (1.4.3 or later)}
+  \begin{itemize}
+    \item \href{http://sourceforge.net/projects/matplotlib/files/matplotlib/matplotlib-1.4.3/windows/matplotlib-1.4.3.win32-py3.4.exe/download}{executable for Windows, Python 3.4 32-bit}
+  \end{itemize}
+\end{itemize}
+
+\subsection{Windows Install}
+\begin{enumerate}
+  \item Install the prerequisite software above.
+  \item Install the required Python packages via \texttt{pip}:
+  \begin{verbatim}
+    cd C:\Python34\Scripts
+    pip install pyserial 
+  \end{verbatim}
+  \item If using the plotter GUI, also install these Python dependencies for matplotlib via \texttt{pip}:
+  \begin{verbatim}
+    cd C:\Python34\Scripts
+    pip install six python-dateutil pyparsing
+  \end{verbatim}
+\end{enumerate}
+
+\subsection{Linux Install}
+\todo{to be written}
+
+\section{Known Issues}
+\subsection{Telemetry framework}
+\begin{itemize}
+  \item No CRC support yet.
+  \item Packets do not time out on the receiver side yet.
+  \item Receivers can't request that the header packet be re-sent.
+\end{itemize}
+
+\subsection{Plotter GUI}
+\begin{itemize}
+  \item Waterfall / spectrogram-style plotting is highly inefficient (but serviceable).
+\end{itemize}
+
+\subsection{mBed HAL}
+\begin{itemize}
+  \item MODSERIAL and Serial putc is highly inefficient for multi-byte data, having high overhead (at 1 Mbaud, the overhead is about 3x the time to transmit the byte). Using MODDMA may increase efficiency.
+\end{itemize}
+
+\subsection{Arduino HAL}
+\begin{itemize}
+  \item None yet
+\end{itemize}
+
+\section{Transmit-side Quick Start Guide}
+All the transmit-side code is contained in (repository base)\texttt{/server-cpp}.
+
+\subsection{Build system setup}
+\begin{itemize}
+  \item Add all the \texttt{.h} header files to your compiler's include path.
+  \item Add all the \texttt{.cpp} source files to your build path - these should end up compiled into your code.
+\end{itemize}
+
+\subsection{Code setup}
+\begin{itemize}
+  \item Include the telemetry header in your code:
+  \begin{lstlisting}[language=C++]
+#include "telemetry.h"
+  \end{lstlisting}
+  \item Include the HAL for your platform: either \texttt{telemetry-arduino.h} or \texttt{telemetry-mbed.h}
+\end{itemize}
+
+\subsection{Usage}
+\begin{itemize}
+  \item Instantiate both a telemetry HAL (again, for your platform) and a \texttt{Telemetry} object: \\
+  Arduino example using \texttt{Serial1} as the UART pins:
+  \begin{lstlisting}[language=C++]
+telemetry::ArduinoHalInterface telemetry_hal(Serial1);
+telemetry::Telemetry telemetry_obj(telemetry_hal);
+  \end{lstlisting}  
+  mBed example using \texttt{PTA2} as UART transmit and \texttt{PTA1} as UART receive:
+  \begin{lstlisting}[language=C++]
+MODSERIAL telemetry_serial(PTA2, PTA1);
+telemetry::MbedHal telemetry_hal(telemetry_serial);
+telemetry::Telemetry telemetry_obj(telemetry_hal);
+  \end{lstlisting}
+  \item Instantiate telemetry data objects:
+\begin{lstlisting}[language=C++]
+telemetry::Numeric<uint32_t> tele_time_ms(telemetry_obj, "time", "Time", "ms", 0);
+telemetry::NumericArray<uint16_t, 128> tele_linescan(telemetry_obj, "linescan", "Linescan", "ADC", 0);  
+telemetry::Numeric<float> tele_motor_pwm(telemetry_obj, "motor", "Motor PWM", "%DC", 0);
+\end{lstlisting}
+  The constructor signatures are:
+  \begin{itemize}
+    \item \texttt{template <typename T>\\ Numeric(Telemetry\& telemetry\_container, const char* internal\_name, const char* display\_name, const char* units, T init\_value)} \\
+    \texttt{Numeric} describes numeric data of type \texttt{T}. Only 8-, 16- and 32-bit unsigned integers and single-precision floats are currently supported (but why would you use double-precision on a dinky embedded processor?!).
+    \begin{itemize}
+      \item \texttt{telemetry\_container}: a reference to a \texttt{Telemetry} object to associate this data with.
+      \item \texttt{internal\_name}: a string giving this object an internal name to be referenced in code.
+      \item \texttt{display\_name}: a string giving this object a human-friendly name.
+      \item \texttt{units}: units this data record is in (not currently used, but may be useful for automation later).
+      \item \texttt{init\_value}: initial value.
+    \end{itemize}
+    \item \texttt{template <typename T, uint32\_t array\_count>\\ NumericArray(Telemetry\& telemetry\_container, const char* internal\_name, const char* display\_name, const char* units, T elem\_init\_value)} \\
+    \texttt{NumericArray} describes an array of numeric objects of type \texttt{T}. Same constraints apply as from Numeric. The array size \texttt{array\_count} is a template parameter (constant of sorts) to avoid dynamic memory allocations.
+    \begin{itemize}
+      \item \texttt{telemetry\_container}: a reference to a \texttt{Telemetry} object to associate this data with.
+      \item \texttt{internal\_name}: a string giving this object an internal name to be referenced in code.
+      \item \texttt{display\_name}: a string giving this object a human-friendly name.
+      \item \texttt{units}: units this data record is in (not currently used, but may be useful for automation later).
+      \item \texttt{elem\_init\_value}: initial value of array elements.
+    \end{itemize}
+  \end{itemize}
+  \item You can optionally specify additional parameters on the data objects. The only one is the numerical value limits, which can be used to set manual plot bounds on a data visualizer.
+\begin{lstlisting}[language=C++]
+telemetry::Numeric<float> tele_motor_pwm(telemetry_obj, "motor", "Motor PWM", "%DC", 0);
+tele_motor_pwm.set_limits(0, 1);
+\end{lstlisting}
+  Note that this (currently) does not affect the embedded code at all - the values will not be clipped nor will warnings / errors be thrown if an out-of-bounds value if set.
+  \item Transmit the data definitions once at the beginning of your code, after you have finished defining all the data.
+  \begin{lstlisting}[language=C++]
+telemetry_obj.transmit_header();
+  \end{lstlisting}
+  \item Load data to be transmitted into the telemetry objects. \\
+  These objects contain the code necessary to transmit and receive telemetry data, but otherwise behave similarly to their template types. For example, you can use the \texttt{tele\_linescan} object as an array:
+\begin{lstlisting}[language=C++]
+telemetry::NumericArray<uint16_t, 128> tele_linescan(telemetry_obj, "linescan", "Linescan", "ADC", 0);  
+...
+uint16_t* data = cam1.read();
+for (uint16_t i=0; i<CAMERA_PIXEL_COUNT; i++) {
+  tele_linescan[i] = data[i];
+}
+\end{lstlisting}
+  Writing to the objects flags the new data to be transmitted on the next telemetry IO operation.
+  \item Regularly call \texttt{Telemetry}'s \texttt{do\_io} operation to transmit any updated data and handle received data (like remote set commands).
+\begin{lstlisting}[language=C++]
+telemetry_obj.do_io();
+\end{lstlisting}
+  \textbf{Note that the code may not be thread-safe.} It scans through all the telemetry data objects, checking for and writing updated values to the output stream.
+  \item Telemetry data objects may have their values set remotely, though they will not latch (i.e. setting it on the embedded-side causes the remotely-set value to be overwritten). As an example, if \texttt{tele\_motor\_pwm} is not set anywhere else, this code allows the motor PWM duty cycle to be set remotely.
+\begin{lstlisting}[language=C++]
+PwmOut MotorOutput(PTA4);
+telemetry::Numeric<float> tele_motor_pwm(telemetry_obj, "motor", "Motor PWM", "%DC", 0);
+...
+telemetry_obj.do_io(); // remote set commands to tele_motor_pwm are processed here
+MotorOutput.write(tele_motor_pwm);
+\end{lstlisting}
+  \item Note that you may continue to use the UART to transmit other data (like \texttt{printf}s) as long as this is not done during the middle of a \texttt{Telemetry} \texttt{do\_io} operation or contains the start-of-frame sequence (\texttt{0x05 0x39}).
+\end{itemize}
+
+\section{Receive-side Quick Start Guide}
+All the receive-side code is contained in (repository base)\texttt{/client-py}.
+\subsection{Plotter}
+Run the plotter by going into (repository base)\texttt{/client-py} and running \texttt{python plotter.py} and passing in the serial port, baud rate (optional, defaults to 38400), independent variable (optional, defaults to \texttt{time}), and independent variable timespan (optional, defaults to 10000). The arguments list can be obtained through running \texttt{python plotter.py --help}
+
+The plotter must be running when the header is transmitted, otherwise it will fail to decode the data packets (and notify you of such). The plotter will automatically reinitialize upon receiving a new header.
+
+This simple plotter graphs all the data against a selected independent variable (like time). Numeric data is plotted as a line graph and array-numeric data is plotted as a waterfall / spectrograph-style graph in real-time. Regular UART data (like from \texttt{printf}s) is also written to the console in real-time. All received data, including from \texttt{printf}s, is logged to a CSV. A new CSV is created each time a new header packet is received, with a timestamped filename.
+
+You can double-click a plot to inspect its latest value numerically and optionally remotely set it to a new value.
+
+If you feel really adventurous, you can also try to mess with the code to plot things in different styles. For example, the plot instantiation function from a received header packet is in \texttt{subplots\_from\_header}. The default just creates a line plot for numeric data and a waterfall plot for array-numeric data. You can make it do fancier things, like overlay a numerical detected track position on the raw camera waterfall plot.
+
+\subsection{Console Demo}
+The console demo is also in (repository base)\texttt{/client-py} and can be run with \texttt{python console.py}. This simply prints the parsed received packets and can be useful if trying to learn the parser API.
+
+\subsection{Parser Library}
+The parser code is in (repository base)\texttt{/client-py/telemetry/parser.py}. No official documentation is currently available, but docstrings are scattered in important places and you can reference the console demo or plotter code.
+\\
+\\
+That's all, folks!
+\end{document}
diff -r 000000000000 -r fc22cf8cc478 telemetry-master/server-cpp/.gitignore
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/telemetry-master/server-cpp/.gitignore	Tue May 05 00:43:17 2015 +0000
@@ -0,0 +1,1 @@
+/Debug/
diff -r 000000000000 -r fc22cf8cc478 telemetry-master/server-cpp/telemetry-arduino-hal.cpp
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/telemetry-master/server-cpp/telemetry-arduino-hal.cpp	Tue May 05 00:43:17 2015 +0000
@@ -0,0 +1,36 @@
+/*
+ * telemetry-arduino-hal.cpp
+ *
+ *  Created on: Mar 2, 2015
+ *      Author: Ducky
+ *
+ * Telemetry HAL for Serial on Arduino.
+ */
+
+#ifdef ARDUINO
+
+#include "telemetry-arduino.h"
+
+namespace telemetry {
+
+void ArduinoHalInterface::transmit_byte(uint8_t data) {
+  serial.write(data);
+}
+
+size_t ArduinoHalInterface::rx_available() {
+  return serial.available();
+}
+
+uint8_t ArduinoHalInterface::receive_byte() {
+  // TODO: handle -1 case
+  return serial.read();
+}
+
+void ArduinoHalInterface::do_error(const char* msg) {
+  // TODO: use side channel?
+  serial.println(msg);
+}
+
+}
+
+#endif // ifdef ARDUINO
diff -r 000000000000 -r fc22cf8cc478 telemetry-master/server-cpp/telemetry-arduino.h
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/telemetry-master/server-cpp/telemetry-arduino.h	Tue May 05 00:43:17 2015 +0000
@@ -0,0 +1,25 @@
+#ifdef ARDUINO
+
+#include "telemetry.h"
+#include <Stream.h>
+
+namespace telemetry {
+
+class ArduinoHalInterface : public HalInterface {
+public:
+  ArduinoHalInterface(Stream& serial) :
+    serial(serial) {}
+
+  virtual void transmit_byte(uint8_t data);
+  virtual size_t rx_available();
+  virtual uint8_t receive_byte();
+
+  virtual void do_error(const char* message);
+
+protected:
+  Stream& serial;
+};
+
+}
+
+#endif // ifdef ARDUINO
diff -r 000000000000 -r fc22cf8cc478 telemetry-master/server-cpp/telemetry-data.cpp
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/telemetry-master/server-cpp/telemetry-data.cpp	Tue May 05 00:43:17 2015 +0000
@@ -0,0 +1,90 @@
+/*
+ * telemetry-data.cpp
+ *
+ *  Created on: Mar 2, 2015
+ *      Author: Ducky
+ *
+ * Implementation for Telemetry Data classes.
+ */
+#include <telemetry.h>
+#include <string.h>
+
+namespace telemetry {
+void packet_write_string(TransmitPacketInterface& packet, const char* str) {
+  // TODO: move into HAL for higher performance?
+  while (*str != '\0') {
+    packet.write_uint8(*str);
+    str++;
+  }
+  packet.write_uint8('\0');
+}
+
+size_t Data::get_header_kvrs_length() {
+  return 1 + strlen(internal_name) + 1
+      + 1 + strlen(display_name) + 1
+      + 1 + strlen(units) + 1;
+}
+
+void Data::write_header_kvrs(TransmitPacketInterface& packet) {
+  packet.write_uint8(RECORDID_INTERNAL_NAME);
+  packet_write_string(packet, internal_name);
+  packet.write_uint8(RECORDID_DISPLAY_NAME);
+  packet_write_string(packet, display_name);
+  packet.write_uint8(RECORDID_UNITS);
+  packet_write_string(packet, units);
+}
+
+template<>
+uint8_t Numeric<uint8_t>::get_subtype() {
+  return NUMERIC_SUBTYPE_UINT;
+}
+template<>
+void Numeric<uint8_t>::serialize_data(uint8_t value, TransmitPacketInterface& packet) {
+  packet.write_uint8(value);
+}
+template<>
+uint8_t Numeric<uint8_t>::deserialize_data(ReceivePacketBuffer& packet) {
+  return packet.read_uint8();
+}
+
+template<>
+uint8_t Numeric<uint16_t>::get_subtype() {
+  return NUMERIC_SUBTYPE_UINT;
+}
+template<>
+void Numeric<uint16_t>::serialize_data(uint16_t value, TransmitPacketInterface& packet) {
+  packet.write_uint16(value);
+}
+template<>
+uint16_t Numeric<uint16_t>::deserialize_data(ReceivePacketBuffer& packet) {
+  return packet.read_uint16();
+}
+
+template<>
+uint8_t Numeric<uint32_t>::get_subtype() {
+  return NUMERIC_SUBTYPE_UINT;
+}
+template<>
+void Numeric<uint32_t>::serialize_data(uint32_t value, TransmitPacketInterface& packet) {
+  packet.write_uint32(value);
+}
+template<>
+uint32_t Numeric<uint32_t>::deserialize_data(ReceivePacketBuffer& packet) {
+  return packet.read_uint32();
+}
+
+// TODO: move into HAL
+template<>
+uint8_t Numeric<float>::get_subtype() {
+  return NUMERIC_SUBTYPE_FLOAT;
+}
+template<>
+void Numeric<float>::serialize_data(float value, TransmitPacketInterface& packet) {
+  packet.write_float(value);
+}
+template<>
+float Numeric<float>::deserialize_data(ReceivePacketBuffer& packet) {
+  return packet.read_float();
+}
+
+}
diff -r 000000000000 -r fc22cf8cc478 telemetry-master/server-cpp/telemetry-mbed-hal.cpp
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/telemetry-master/server-cpp/telemetry-mbed-hal.cpp	Tue May 05 00:43:17 2015 +0000
@@ -0,0 +1,39 @@
+/*
+ * telemetry-mbedo-hal.cpp
+ *
+ *  Created on: Mar 4, 2015
+ *      Author: Ducky
+ *
+ * Telemetry HAL for Serial on mBed.
+ */
+
+#ifdef __ARMCC_VERSION
+
+#include "telemetry-mbed.h"
+
+namespace telemetry {
+
+void MbedHal::transmit_byte(uint8_t data) {
+  // TODO: optimize with DMA
+  serial.putc(data);
+}
+
+size_t MbedHal::rx_available() {
+  return serial.rxBufferGetCount();
+}
+
+uint8_t MbedHal::receive_byte() {
+  return serial.getc();
+}
+
+void MbedHal::do_error(const char* msg) {
+  serial.printf("%s\r\n", msg);
+}
+
+uint32_t MbedHal::get_time_ms() {
+  return timer.read_ms();
+}
+
+}
+
+#endif // ifdef MBED
diff -r 000000000000 -r fc22cf8cc478 telemetry-master/server-cpp/telemetry-mbed.h
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/telemetry-master/server-cpp/telemetry-mbed.h	Tue May 05 00:43:17 2015 +0000
@@ -0,0 +1,32 @@
+// Make this less hacky and detect properly
+#ifdef __ARMCC_VERSION
+
+#include "telemetry.h"
+#include "mbed.h"
+#include "MODSERIAL.h"
+
+namespace telemetry {
+
+class MbedHal : public HalInterface {
+public:
+  MbedHal(MODSERIAL& serial) :
+    serial(serial) {
+	  timer.start();
+  }
+
+  virtual void transmit_byte(uint8_t data);
+  virtual size_t rx_available();
+  virtual uint8_t receive_byte();
+
+  virtual void do_error(const char* message);
+
+  virtual uint32_t get_time_ms();
+
+protected:
+  MODSERIAL& serial;
+  Timer timer;
+};
+
+}
+
+#endif // ifdef MBED
diff -r 000000000000 -r fc22cf8cc478 telemetry-master/server-cpp/telemetry.cpp
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/telemetry-master/server-cpp/telemetry.cpp	Tue May 05 00:43:17 2015 +0000
@@ -0,0 +1,332 @@
+/*
+ * telemetry.cpp
+ *
+ *  Created on: Mar 2, 2015
+ *      Author: Ducky
+ *
+ * Implementation for the base Telemetry class.
+ */
+
+#include <telemetry.h>
+
+namespace telemetry {
+
+size_t Telemetry::add_data(Data& new_data) {
+  if (data_count >= MAX_DATA_PER_TELEMETRY) {
+    do_error("MAX_DATA_PER_TELEMETRY limit reached.");
+    return 0;
+  }
+  if (header_transmitted) {
+    do_error("Cannot add new data after header transmitted.");
+    return 0;
+  }
+  data[data_count] = &new_data;
+  data_updated[data_count] = true;
+  data_count++;
+  return data_count - 1;
+}
+
+void Telemetry::mark_data_updated(size_t data_id) {
+  data_updated[data_id] = true;
+}
+
+void Telemetry::transmit_header() {
+  if (header_transmitted) {
+    do_error("Cannot retransmit header.");
+    return;
+  }
+
+  size_t packet_legnth = 2; // opcode + sequence
+  for (int data_idx = 0; data_idx < data_count; data_idx++) {
+    packet_legnth += 2; // data ID, data type
+    packet_legnth += data[data_idx]->get_header_kvrs_length();
+    packet_legnth += 1; // terminator record id
+  }
+  packet_legnth++;  // terminator "record"
+
+  FixedLengthTransmitPacket packet(hal, packet_legnth);
+
+  packet.write_uint8(OPCODE_HEADER);
+  packet.write_uint8(packet_tx_sequence);
+  for (int data_idx = 0; data_idx < data_count; data_idx++) {
+    packet.write_uint8(data_idx+1);
+    packet.write_uint8(data[data_idx]->get_data_type());
+    data[data_idx]->write_header_kvrs(packet);
+    packet.write_uint8(RECORDID_TERMINATOR);
+  }
+  packet.write_uint8(DATAID_TERMINATOR);
+
+  packet.finish();
+
+  packet_tx_sequence++;
+  header_transmitted = true;
+}
+
+void Telemetry::do_io() {
+  transmit_data();
+  process_received_data();
+}
+
+void Telemetry::transmit_data() {
+  if (!header_transmitted) {
+    do_error("Must transmit header before transmitting data.");
+    return;
+  }
+
+  // Keep a local copy to make it more thread-safe
+  bool data_updated_local[MAX_DATA_PER_TELEMETRY];
+
+  size_t packet_legnth = 2; // opcode + sequence
+  for (int data_idx = 0; data_idx < data_count; data_idx++) {
+    data_updated_local[data_idx] = data_updated[data_idx];
+    data_updated[data_idx] = 0;
+    if (data_updated_local[data_idx]) {
+      packet_legnth += 1; // data ID
+      packet_legnth += data[data_idx]->get_payload_length();
+    }
+  }
+  packet_legnth++;  // terminator "record"
+
+  FixedLengthTransmitPacket packet(hal, packet_legnth);
+
+  packet.write_uint8(OPCODE_DATA);
+  packet.write_uint8(packet_tx_sequence);
+  for (int data_idx = 0; data_idx < data_count; data_idx++) {
+    if (data_updated_local[data_idx]) {
+      packet.write_uint8(data_idx+1);
+      data[data_idx]->write_payload(packet);
+    }
+  }
+  packet.write_uint8(DATAID_TERMINATOR);
+
+  packet.finish();
+
+  packet_tx_sequence++;
+}
+
+void Telemetry::process_received_data() {
+  uint32_t current_time = hal.get_time_ms();
+
+  if (decoder_last_receive_ms <= current_time) {
+	if (!decoder_last_received && decoder_state != SOF && decoder_pos != 0
+		&& (decoder_last_receive_ms - current_time > DECODER_TIMEOUT_MS)) {
+	  decoder_pos = 0;
+	  packet_length = 0;
+	  decoder_state = SOF;
+	  hal.do_error("RX timeout");
+	}
+  } else {
+	// timer overflowed, do nothing
+  }
+  decoder_last_receive_ms = current_time;
+
+  decoder_last_received = false;
+  while (hal.rx_available()) {
+	decoder_last_received = true;
+
+    uint8_t rx_byte = hal.receive_byte();
+
+    if (decoder_state == SOF) {
+      if (rx_byte == SOF_SEQ[decoder_pos]) {
+        decoder_pos++;
+        if (decoder_pos >= (sizeof(SOF_SEQ) / sizeof(SOF_SEQ[0]))) {
+          decoder_pos = 0;
+          packet_length = 0;
+          decoder_state = LENGTH;
+        }
+      } else {
+        decoder_pos = 0;
+        // TODO: pass rest of data through
+      }
+    } else if (decoder_state == LENGTH) {
+      packet_length = (packet_length << 8) | rx_byte;
+      decoder_pos++;
+      if (decoder_pos >= LENGTH_SIZE) {
+        decoder_pos = 0;
+        decoder_state = DATA;
+      }
+    } else if (decoder_state == DATA) {
+      received_packet.add_byte(rx_byte);
+      decoder_pos++;
+      if (decoder_pos >= packet_length) {
+        process_received_packet();
+
+        decoder_pos = 0;
+        if (rx_byte == SOF_SEQ[0]) {
+          decoder_state = DATA_DESTUFF_END;
+        } else {
+          decoder_state = SOF;
+        }
+      } else {
+        if (rx_byte == SOF_SEQ[0]) {
+          decoder_state = DATA_DESTUFF;
+        }
+      }
+    } else if (decoder_state == DATA_DESTUFF) {
+      decoder_state = DATA;
+    } else if (decoder_state == DATA_DESTUFF_END) {
+      decoder_state = SOF;
+    }
+  }
+}
+
+void Telemetry::process_received_packet() {
+  uint8_t opcode = received_packet.read_uint8();
+  if (opcode == OPCODE_DATA) {
+    uint8_t data_id = received_packet.read_uint8();
+    while (data_id != DATAID_TERMINATOR) {
+      if (data_id < data_count + 1) {
+        data[data_id - 1]->set_from_packet(received_packet);
+      } else {
+        hal.do_error("Unknown data ID");
+      }
+      data_id = received_packet.read_uint8();
+    }
+  } else {
+    hal.do_error("Unknown opcode");
+  }
+}
+
+size_t Telemetry::receive_available() {
+  // TODO: implement me
+  return 0;
+}
+
+uint8_t read_receive() {
+  // TODO: implement me
+  return 0;
+}
+
+FixedLengthTransmitPacket::FixedLengthTransmitPacket(HalInterface& hal,
+    size_t length) :
+        hal(hal),
+        length(length),
+        count(0) {
+  hal.transmit_byte(SOF1);
+  hal.transmit_byte(SOF2);
+
+  hal.transmit_byte((length >> 8) & 0xff);
+  hal.transmit_byte((length >> 0) & 0xff);
+
+  valid = true;
+}
+
+void FixedLengthTransmitPacket::write_byte(uint8_t data) {
+  if (!valid) {
+    hal.do_error("Writing to invalid packet");
+    return;
+  } else if (count + 1 > length) {
+    hal.do_error("Writing over packet length");
+    return;
+  }
+  hal.transmit_byte(data);
+  if (data == SOF1) {
+    hal.transmit_byte(0x00);  // TODO: proper abstraction and magic numbers
+  }
+  count++;
+}
+
+void FixedLengthTransmitPacket::write_uint8(uint8_t data) {
+  write_byte(data);
+}
+
+void FixedLengthTransmitPacket::write_uint16(uint16_t data) {
+  write_byte((data >> 8) & 0xff);
+  write_byte((data >> 0) & 0xff);
+}
+
+void FixedLengthTransmitPacket::write_uint32(uint32_t data) {
+  write_byte((data >> 24) & 0xff);
+  write_byte((data >> 16) & 0xff);
+  write_byte((data >> 8) & 0xff);
+  write_byte((data >> 0) & 0xff);
+}
+
+void FixedLengthTransmitPacket::write_float(float data) {
+  // TODO: THIS IS ENDIANNESS DEPENDENT, ABSTRACT INTO HAL?
+  uint8_t *float_array = (uint8_t*) &data;
+  write_byte(float_array[3]);
+  write_byte(float_array[2]);
+  write_byte(float_array[1]);
+  write_byte(float_array[0]);
+}
+
+void FixedLengthTransmitPacket::finish() {
+  if (!valid) {
+    hal.do_error("Finish invalid packet");
+    return;
+  } else if (count != length) {
+    hal.do_error("TX packet under length");
+    return;
+  }
+
+  // TODO: add CRC check here
+}
+
+ReceivePacketBuffer::ReceivePacketBuffer(HalInterface& hal) :
+    hal(hal) {
+  new_packet();
+}
+
+void ReceivePacketBuffer::new_packet() {
+  packet_length = 0;
+  read_loc = 0;
+}
+
+void ReceivePacketBuffer::add_byte(uint8_t byte) {
+  if (packet_length >= MAX_RECEIVE_PACKET_LENGTH) {
+    hal.do_error("RX packet over length");
+    return;
+  }
+
+  data[packet_length] = byte;
+  packet_length++;
+}
+
+uint8_t ReceivePacketBuffer::read_uint8() {
+  if (read_loc + 1 > packet_length) {
+    hal.do_error("Read uint8 over length");
+    return 0;
+  }
+  read_loc += 1;
+  return data[read_loc - 1];
+}
+
+uint16_t ReceivePacketBuffer::read_uint16() {
+  if (read_loc + 2 > packet_length) {
+    hal.do_error("Read uint16 over length");
+    return 0;
+  }
+  read_loc += 2;
+  return ((uint16_t)data[read_loc - 2] << 8)
+       | ((uint16_t)data[read_loc - 1] << 0);
+}
+
+uint32_t ReceivePacketBuffer::read_uint32() {
+  if (read_loc + 4 > packet_length) {
+    hal.do_error("Read uint32 over length");
+    return 0;
+  }
+  read_loc += 4;
+  return ((uint32_t)data[read_loc - 4] << 24)
+       | ((uint32_t)data[read_loc - 3] << 16)
+       | ((uint32_t)data[read_loc - 2] << 8)
+       | ((uint32_t)data[read_loc - 1] << 0);
+}
+
+float ReceivePacketBuffer::read_float() {
+  if (read_loc + 4 > packet_length) {
+    hal.do_error("Read float over length");
+    return 0;
+  }
+  read_loc += 4;
+  float out = 0;
+  uint8_t* out_array = (uint8_t*)&out;
+  out_array[0] = data[read_loc - 1];
+  out_array[1] = data[read_loc - 2];
+  out_array[2] = data[read_loc - 3];
+  out_array[3] = data[read_loc - 4];
+  return out;
+}
+
+}
diff -r 000000000000 -r fc22cf8cc478 telemetry-master/server-cpp/telemetry.h
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/telemetry-master/server-cpp/telemetry.h	Tue May 05 00:43:17 2015 +0000
@@ -0,0 +1,524 @@
+#ifndef _TELEMETRY_H_
+#define _TELEMETRY_H_
+
+#include <stddef.h>
+#include <stdint.h>
+
+namespace telemetry {
+
+#ifndef TELEMETRY_DATA_LIMIT
+#define TELEMETRY_DATA_LIMIT 16
+#endif
+
+// Maximum number of DataInterface objects a Telemetry object can hold.
+// Used for array sizing.
+const size_t MAX_DATA_PER_TELEMETRY = TELEMETRY_DATA_LIMIT;
+
+// Maximum payload size for a received telemetry packet.
+const size_t MAX_RECEIVE_PACKET_LENGTH = 255;
+
+// Various wire protocol constants.
+const uint8_t SOF1 = 0x05;  // start of frame byte 1
+const uint8_t SOF2 = 0x39;  // start of frame byte 2
+const uint8_t SOF_SEQ[] = {0x05, 0x39};
+
+const size_t LENGTH_SIZE = 2;
+
+// TODO: make these length independent
+
+const uint8_t OPCODE_HEADER = 0x81;
+const uint8_t OPCODE_DATA = 0x01;
+
+const uint8_t DATAID_TERMINATOR = 0x00;
+
+const uint8_t DATATYPE_NUMERIC = 0x01;
+const uint8_t DATATYPE_NUMERIC_ARRAY = 0x02;
+
+const uint8_t RECORDID_TERMINATOR = 0x00;
+const uint8_t RECORDID_INTERNAL_NAME = 0x01;
+const uint8_t RECORDID_DISPLAY_NAME = 0x02;
+const uint8_t RECORDID_UNITS = 0x03;
+
+const uint8_t RECORDID_OVERRIDE_CTL = 0x08;
+const uint8_t RECORDID_OVERRIDE_DATA = 0x08;
+
+const uint8_t RECORDID_NUMERIC_SUBTYPE = 0x40;
+const uint8_t RECORDID_NUMERIC_LENGTH = 0x41;
+const uint8_t RECORDID_NUMERIC_LIMITS = 0x42;
+const uint8_t RECORDID_ARRAY_COUNT = 0x50;
+
+const uint8_t NUMERIC_SUBTYPE_UINT = 0x01;
+const uint8_t NUMERIC_SUBTYPE_SINT = 0x02;
+const uint8_t NUMERIC_SUBTYPE_FLOAT = 0x03;
+
+const uint32_t DECODER_TIMEOUT_MS = 100;
+
+// Hardware abstraction layer for the telemetry server.
+class HalInterface {
+public:
+  virtual ~HalInterface() {}
+
+  // Write a byte to the transmit buffer.
+  virtual void transmit_byte(uint8_t data) = 0;
+  // Returns the number of bytes available in the receive buffer.
+  virtual size_t rx_available() = 0;
+  // Returns the next byte in the receive stream. rx_available must return > 0.
+  virtual uint8_t receive_byte() = 0;
+
+  // TODO: more efficient block transmit operations?
+
+  // Called on a telemetry error.
+  virtual void do_error(const char* message) = 0;
+
+  // Return the current time in milliseconds. May overflow at any time.
+  virtual uint32_t get_time_ms() = 0;
+};
+
+// Abstract base class for building a packet to be transmitted.
+// Implementation is unconstrained - writes may either be buffered or passed
+// directly to the hardware transmit buffers.
+class TransmitPacketInterface {
+public:
+  virtual ~TransmitPacketInterface() {}
+
+  // Writes a 8-bit unsigned integer to the packet stream.
+  virtual void write_byte(uint8_t data) = 0;
+    
+  // Writes a 8-bit unsigned integer to the packet stream.
+  virtual void write_uint8(uint8_t data) = 0;
+  // Writes a 16-bit unsigned integer to the packet stream.
+  virtual void write_uint16(uint16_t data) = 0;
+  // Writes a 32-bit unsigned integer to the packet stream.
+  virtual void write_uint32(uint32_t data) = 0;
+  // Writes a float to the packet stream.
+  virtual void write_float(float data) = 0;
+
+  // Finish the packet and writes data to the transmit stream (if not already
+  // done). No more data may be written afterwards.
+  virtual void finish() = 0;
+};
+
+class ReceivePacketBuffer {
+public:
+  ReceivePacketBuffer(HalInterface& hal);
+
+  // Starts a new packet, resetting the packet length and read pointer.
+  void new_packet();
+
+  // Appends a new byte onto this packet, advancing the packet length
+  void add_byte(uint8_t byte);
+
+  // Reads a 8-bit unsigned integer from the packet stream, advancing buffer.
+  uint8_t read_uint8();
+  // Reads a 16-bit unsigned integer from the packet stream, advancing buffer.
+  uint16_t read_uint16();
+  // Reads a 32-bit unsigned integer from the packet stream, advancing buffer.
+  uint32_t read_uint32();
+  // Reads a float from the packet stream, advancing buffer.
+  float read_float();
+
+protected:
+  HalInterface& hal;
+
+  size_t packet_length;
+  size_t read_loc;
+  uint8_t data[MAX_RECEIVE_PACKET_LENGTH];
+};
+
+// Abstract base class for telemetry data objects.
+class Data {
+public:
+  Data(const char* internal_name, const char* display_name,
+      const char* units):
+      internal_name(internal_name),
+      display_name(display_name),
+      units(units) {};
+
+  virtual ~Data() {}
+
+  // Returns the data type code.
+  virtual uint8_t get_data_type() = 0;
+
+  // Returns the length of the header KVRs, in bytes. Does not include the
+  // terminator header.
+  virtual size_t get_header_kvrs_length();
+  // Writes the header KVRs to the transmit packet. Does not write the
+  // terminiator header.
+  virtual void write_header_kvrs(TransmitPacketInterface& packet);
+
+  // Returns the length of the payload, in bytes. Should be "fast".
+  virtual size_t get_payload_length() = 0;
+  // Writes the payload to the transmit packet. Should be "fast".
+  virtual void write_payload(TransmitPacketInterface& packet) = 0;
+
+  // Sets my value from the received packet, interpreting the current packet
+  // read position as my data type.
+  virtual void set_from_packet(ReceivePacketBuffer& packet) = 0;
+
+protected:
+  const char* internal_name;
+  const char* display_name;
+  const char* units;
+};
+
+// Telemetry Server object.
+class Telemetry {
+public:
+  Telemetry(HalInterface& hal) :
+    hal(hal),
+    data_count(0),
+    received_packet(ReceivePacketBuffer(hal)),
+    decoder_state(SOF),
+    decoder_pos(0),
+    packet_length(0),
+	decoder_last_received(false),
+	decoder_last_receive_ms(0),
+    header_transmitted(false),
+    packet_tx_sequence(0),
+    packet_rx_sequence(0) {};
+
+  // Associates a DataInterface with this object, returning the data ID.
+  size_t add_data(Data& new_data);
+
+  // Marks a data ID as updated, to be transmitted in the next packet.
+  void mark_data_updated(size_t data_id);
+
+  // Transmits header data. Must be called after all add_data calls are done
+  // and before and IO is done.
+  void transmit_header();
+
+  // Does IO, including transmitting telemetry packets. Should be called on
+  // a regular basis. Since this does IO, this may block depending on the HAL
+  // semantics.
+  void do_io();
+
+  // TODO: better docs defining in-band receive.
+  // Returns the number of bytes available in the receive stream.
+  size_t receive_available();
+  // Returns the next byte in the receive stream.
+  uint8_t read_receive();
+
+  // Calls the HAL's error function if some condition is false.
+  void do_error(const char* message) {
+    hal.do_error(message);
+  }
+
+protected:
+  // Transmits any updated data.
+  void transmit_data();
+
+  // Handles received data, splitting regular UART data from in-band packet
+  // data and processing received telemetry packets.
+  void process_received_data();
+
+  // Handles a received packet in received_packet.
+  void process_received_packet();
+
+  HalInterface& hal;
+
+  // Array of associated DataInterface objects. The index+1 is the
+  // DataInterface's data ID field.
+  Data* data[MAX_DATA_PER_TELEMETRY];
+  // Whether each data has been updated or not.
+  bool data_updated[MAX_DATA_PER_TELEMETRY];
+  // Count of associated DataInterface objects.
+  size_t data_count;
+
+  // Buffer holding the receive packet being assembled / parsed.
+  ReceivePacketBuffer received_packet;
+
+  enum DecoderState {
+    SOF,    // reading start-of-frame sequence (or just non-telemetry data)
+    LENGTH, // reading packet length
+    DATA,   // reading telemetry packet data
+    DATA_DESTUFF,     // reading a stuffed byte
+    DATA_DESTUFF_END  // last stuffed byte in a packet
+  } decoder_state;
+
+  size_t decoder_pos;
+  size_t packet_length;
+  bool decoder_last_received;
+  uint32_t decoder_last_receive_ms;
+
+  bool header_transmitted;
+
+  // Sequence number of the next packet to be transmitted.
+  uint8_t packet_tx_sequence;
+  uint8_t packet_rx_sequence; // TODO use this somewhere
+};
+
+// A telemetry packet with a length known before data is written to it.
+// Data is written directly to the hardware transmit buffers without packet
+// buffering. Assumes transmit buffers won't fill up.
+class FixedLengthTransmitPacket : public TransmitPacketInterface {
+public:
+  FixedLengthTransmitPacket(HalInterface& hal, size_t length);
+
+  virtual void write_byte(uint8_t data);
+
+  virtual void write_uint8(uint8_t data);
+  virtual void write_uint16(uint16_t data);
+  virtual void write_uint32(uint32_t data);
+  virtual void write_float(float data);
+
+  virtual void finish();
+
+protected:
+  HalInterface& hal;
+
+  // Predetermined length, in bytes, of this packet's payload, for sanity check.
+  size_t length;
+
+  // Current length, in bytes, of this packet's payload.
+  size_t count;
+
+  // Is the packet valid?
+  bool valid;
+};
+
+template <typename T> 
+class Numeric : public Data {
+public:
+  Numeric(Telemetry& telemetry_container, 
+      const char* internal_name, const char* display_name,
+      const char* units, T init_value):
+      Data(internal_name, display_name, units),
+      telemetry_container(telemetry_container),
+      value(init_value), min_val(init_value), max_val(init_value),
+      frozen(false) {
+    data_id = telemetry_container.add_data(*this);
+  }
+
+  T operator = (T b) {
+    if (!frozen) {
+      value = b;
+      telemetry_container.mark_data_updated(data_id);
+    }
+    return value;
+  }
+
+  operator T() {
+    return value;
+  }
+  
+  Numeric<T>& set_limits(T min, T max) {
+    min_val = min;
+    max_val = max;
+    return *this;
+  }
+  
+  virtual uint8_t get_data_type() { return DATATYPE_NUMERIC; }
+
+  virtual size_t get_header_kvrs_length() {
+    return Data::get_header_kvrs_length()
+        + 1 + 1   // subtype
+        + 1 + 1   // data length
+        + 1 + sizeof(value) + sizeof(value);  // limits
+  }
+
+  virtual void write_header_kvrs(TransmitPacketInterface& packet) {
+    Data::write_header_kvrs(packet);
+    packet.write_uint8(RECORDID_NUMERIC_SUBTYPE);
+    packet.write_uint8(get_subtype());
+    packet.write_uint8(RECORDID_NUMERIC_LENGTH);
+    packet.write_uint8(sizeof(value));
+    packet.write_uint8(RECORDID_NUMERIC_LIMITS);
+    serialize_data(min_val, packet);
+    serialize_data(max_val, packet);
+  }
+
+  uint8_t get_subtype();
+
+  virtual size_t get_payload_length() { return sizeof(value); }
+  virtual void write_payload(TransmitPacketInterface& packet) { serialize_data(value, packet); }
+  virtual void set_from_packet(ReceivePacketBuffer& packet) {
+    value = deserialize_data(packet);
+    telemetry_container.mark_data_updated(data_id); }
+  
+  void serialize_data(T data, TransmitPacketInterface& packet);
+  T deserialize_data(ReceivePacketBuffer& packet);
+
+protected:
+  Telemetry& telemetry_container;
+  size_t data_id;
+  T value;
+  T min_val, max_val;
+  bool frozen;
+};
+
+template <typename T, uint32_t array_count>
+class NumericArrayAccessor;
+
+// TODO: fix this partial specialization inheritance nightmare
+template <typename T, uint32_t array_count> 
+class NumericArrayBase : public Data {
+  friend class NumericArrayAccessor<T, array_count>;
+public:
+  NumericArrayBase(Telemetry& telemetry_container, 
+      const char* internal_name, const char* display_name,
+      const char* units, T elem_init_value):
+      Data(internal_name, display_name, units),
+      telemetry_container(telemetry_container),
+      min_val(elem_init_value), max_val(elem_init_value),
+      frozen(false) {
+    for (size_t i=0; i<array_count; i++) {
+      value[i] = elem_init_value;
+    }
+    data_id = telemetry_container.add_data(*this);
+  }
+
+  NumericArrayAccessor<T, array_count> operator[] (const int index) {
+    // TODO: add bounds checking here
+    // TODO: add "frozen" check
+    return NumericArrayAccessor<T, array_count>(*this, index);
+  }
+
+  NumericArrayBase<T, array_count>& set_limits(T min, T max) {
+    min_val = min;
+    max_val = max;
+    return *this;
+  }
+  
+  virtual uint8_t get_data_type() { return DATATYPE_NUMERIC_ARRAY; }
+
+  virtual size_t get_header_kvrs_length() {
+    return Data::get_header_kvrs_length()
+        + 1 + 1   // subtype
+        + 1 + 1   // data length
+        + 1 + 4   // array length
+        + 1 + sizeof(value[0]) + sizeof(value[0]);  // limits
+  }
+
+  virtual void write_header_kvrs(TransmitPacketInterface& packet) {
+    Data::write_header_kvrs(packet);
+    packet.write_uint8(RECORDID_NUMERIC_SUBTYPE);
+    packet.write_uint8(get_subtype());
+    packet.write_uint8(RECORDID_NUMERIC_LENGTH);
+    packet.write_uint8(sizeof(value[0]));
+    packet.write_uint8(RECORDID_ARRAY_COUNT);
+    packet.write_uint32(array_count);
+    packet.write_uint8(RECORDID_NUMERIC_LIMITS);
+    serialize_data(min_val, packet);
+    serialize_data(max_val, packet);
+  }
+
+  virtual uint8_t get_subtype() = 0;
+
+  virtual size_t get_payload_length() { return sizeof(value); }
+  virtual void write_payload(TransmitPacketInterface& packet) {
+    for (size_t i=0; i<array_count; i++) { serialize_data(this->value[i], packet); } }
+  virtual void set_from_packet(ReceivePacketBuffer& packet) {
+    for (size_t i=0; i<array_count; i++) { value[i] = deserialize_data(packet); }
+    telemetry_container.mark_data_updated(data_id); }
+
+  virtual void serialize_data(T data, TransmitPacketInterface& packet) = 0;
+  virtual T deserialize_data(ReceivePacketBuffer& packet) = 0;
+  
+protected:
+  Telemetry& telemetry_container;
+  size_t data_id;
+  T value[array_count];
+  T min_val, max_val;
+  bool frozen;
+};
+
+template <typename T, uint32_t array_count>
+class NumericArrayAccessor {
+public:
+  NumericArrayAccessor(NumericArrayBase<T, array_count>& container, size_t index) :
+    container(container), index(index) { }
+
+  T operator = (T b) {
+    if (!container.frozen) {
+      container.value[index] = b;
+      container.telemetry_container.mark_data_updated(container.data_id);
+    }
+    return container.value[index];
+  }
+
+  operator T() {
+    return container.value[index];
+  }
+
+protected:
+  NumericArrayBase<T, array_count>& container;
+  size_t index;
+};
+
+template <typename T, uint32_t array_count> 
+class NumericArray : public NumericArrayBase<T, array_count> {
+  NumericArray(Telemetry& telemetry_container, 
+      const char* internal_name, const char* display_name,
+      const char* units, T elem_init_value);
+  virtual uint8_t get_subtype();
+  virtual void write_payload(TransmitPacketInterface& packet);
+  virtual T deserialize_data(ReceivePacketBuffer& packet);
+};
+
+template <uint32_t array_count> 
+class NumericArray<uint8_t, array_count> : public NumericArrayBase<uint8_t, array_count> {
+public:
+  NumericArray(Telemetry& telemetry_container, 
+      const char* internal_name, const char* display_name,
+      const char* units, uint8_t elem_init_value):
+      NumericArrayBase<uint8_t, array_count>(
+          telemetry_container, internal_name, display_name,
+          units, elem_init_value) {};
+  virtual uint8_t get_subtype() {return NUMERIC_SUBTYPE_UINT; }
+  virtual void serialize_data(uint8_t data, TransmitPacketInterface& packet) {
+    packet.write_uint8(data); }
+  virtual uint8_t deserialize_data(ReceivePacketBuffer& packet) {
+    return packet.read_uint8(); }
+};
+
+template <uint32_t array_count> 
+class NumericArray<uint16_t, array_count> : public NumericArrayBase<uint16_t, array_count> {
+public:
+  NumericArray(Telemetry& telemetry_container, 
+      const char* internal_name, const char* display_name,
+      const char* units, uint16_t elem_init_value):
+      NumericArrayBase<uint16_t, array_count>(
+          telemetry_container, internal_name, display_name,
+          units, elem_init_value) {};
+  virtual uint8_t get_subtype() {return NUMERIC_SUBTYPE_UINT; }
+  virtual void serialize_data(uint16_t data, TransmitPacketInterface& packet) {
+    packet.write_uint16(data); }
+  virtual uint16_t deserialize_data(ReceivePacketBuffer& packet) {
+    return packet.read_uint16(); }
+};
+
+template <uint32_t array_count> 
+class NumericArray<uint32_t, array_count> : public NumericArrayBase<uint32_t, array_count> {
+public:
+  NumericArray(Telemetry& telemetry_container, 
+      const char* internal_name, const char* display_name,
+      const char* units, uint32_t elem_init_value):
+      NumericArrayBase<uint32_t, array_count>(
+          telemetry_container, internal_name, display_name,
+          units, elem_init_value) {};
+  virtual uint8_t get_subtype() {return NUMERIC_SUBTYPE_UINT; }
+  virtual void serialize_data(uint32_t data, TransmitPacketInterface& packet) {
+    packet.write_uint32(data); }
+  virtual uint32_t deserialize_data(ReceivePacketBuffer& packet) {
+    return packet.read_uint32(); }
+};
+
+template <uint32_t array_count> 
+class NumericArray<float, array_count> : public NumericArrayBase<float, array_count> {
+public:
+  NumericArray(Telemetry& telemetry_container, 
+      const char* internal_name, const char* display_name,
+      const char* units, float elem_init_value):
+      NumericArrayBase<float, array_count>(
+          telemetry_container, internal_name, display_name,
+          units, elem_init_value) {};
+  virtual uint8_t get_subtype() {return NUMERIC_SUBTYPE_FLOAT; }
+  virtual void serialize_data(float data, TransmitPacketInterface& packet) {
+    packet.write_float(data); }
+  virtual float deserialize_data(ReceivePacketBuffer& packet) {
+    return packet.read_float(); }
+};
+
+}
+
+#endif