telemetry

Dependents:   Everything Sequential_Timing FixedPWM FixedPWMWill

telemetry.h

Committer:
vsutardja
Date:
2016-03-18
Revision:
0:aca5a32d2759

File content as of revision 0:aca5a32d2759:

#ifndef _TELEMETRY_H_
#define _TELEMETRY_H_

#include <stddef.h>
#include <stdint.h>

// Maximum number of Data objects a Telemetry object can hold.
// Default given here, but can be redefined with a compiler define.
#ifndef TELEMETRY_DATA_LIMIT
#define TELEMETRY_DATA_LIMIT 16
#endif

#ifndef TELEMETRY_SERIAL_RX_BUFFER_SIZE
#define TELEMETRY_SERIAL_RX_BUFFER_SIZE 256
#endif

namespace telemetry {
// Maximum number of Data 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;

// Time after which a partially received packet is discarded.
const uint32_t DECODER_TIMEOUT_MS = 100;

// Buffer size for received non-telemetry data.
const size_t SERIAL_RX_BUFFER_SIZE = TELEMETRY_SERIAL_RX_BUFFER_SIZE;
}

#ifdef ARDUINO
  #ifdef TELEMETRY_HAL
    #error "Multiple telemetry HALs defined"
  #endif
  #include "telemetry-arduino-hal.h"
#endif

#if defined(__MBED__)
  #ifdef TELEMETRY_HAL
    #error "Multiple telemetry HALs defined"
  #endif
  #include "telemetry-mbed-hal.h"
#endif

#ifndef TELEMETRY_HAL
  #error "No telemetry HAL defined"
#endif

#include "protocol.h"
#include "packet.h"
#include "queue.h"

namespace telemetry {
// 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(TransmitPacket& 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(TransmitPacket& 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 whether or not read_receive will return valid data.
  bool 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;

  Queue<uint8_t, SERIAL_RX_BUFFER_SIZE> rx_buffer;

  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
};

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) {
    data_id = telemetry_container.add_data(*this);
  }

  T operator = (T b) {
    value = b;
    telemetry_container.mark_data_updated(data_id);
    return b;
  }

  operator T() {
    return value;
  }

  Numeric<T>& set_limits(T min, T max) {
    min_val = min;
    max_val = max;
    return *this;
  }

  uint8_t get_data_type() { return protocol::DATATYPE_NUMERIC; }

  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
  }

  void write_header_kvrs(TransmitPacket& packet) {
    Data::write_header_kvrs(packet);
    packet.write_uint8(protocol::RECORDID_NUMERIC_SUBTYPE);
    packet.write_uint8(protocol::numeric_subtype<T>());
    packet.write_uint8(protocol::RECORDID_NUMERIC_LENGTH);
    packet.write_uint8(sizeof(value));
    packet.write_uint8(protocol::RECORDID_NUMERIC_LIMITS);
    serialize_data(min_val, packet);
    serialize_data(max_val, packet);
  }

  size_t get_payload_length() { return sizeof(value); }
  void write_payload(TransmitPacket& packet) { serialize_data(value, packet); }
  void set_from_packet(ReceivePacketBuffer& packet) {
    value = deserialize_data(packet);
    telemetry_container.mark_data_updated(data_id); }

  void serialize_data(T value, TransmitPacket& packet) {
    packet.write<T>(value);
  }
  T deserialize_data(ReceivePacketBuffer& packet) {
    return packet.read<T>();
  }


protected:
  Telemetry& telemetry_container;
  size_t data_id;
  T value;
  T min_val, max_val;
};

template <typename T, uint32_t array_count>
class NumericArrayAccessor;

template <typename T, uint32_t array_count>
class NumericArray : public Data {
  friend class NumericArrayAccessor<T, array_count>;
public:
  NumericArray(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) {
    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?
    return NumericArrayAccessor<T, array_count>(*this, index);
  }

  NumericArray<T, array_count>& set_limits(T min, T max) {
    min_val = min;
    max_val = max;
    return *this;
  }

  uint8_t get_data_type() { return protocol::DATATYPE_NUMERIC_ARRAY; }

  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
  }

  void write_header_kvrs(TransmitPacket& packet) {
    Data::write_header_kvrs(packet);
    packet.write_uint8(protocol::RECORDID_NUMERIC_SUBTYPE);
    packet.write_uint8(protocol::numeric_subtype<T>());
    packet.write_uint8(protocol::RECORDID_NUMERIC_LENGTH);
    packet.write_uint8(sizeof(value[0]));
    packet.write_uint8(protocol::RECORDID_ARRAY_COUNT);
    packet.write_uint32(array_count);
    packet.write_uint8(protocol::RECORDID_NUMERIC_LIMITS);
    serialize_data(min_val, packet);
    serialize_data(max_val, packet);
  }

  size_t get_payload_length() { return sizeof(value); }
  void write_payload(TransmitPacket& packet) {
    for (size_t i=0; i<array_count; i++) { serialize_data(this->value[i], packet); } }
  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); }

  void serialize_data(T data, TransmitPacket& packet) {
    packet.write<T>(data); }
  T deserialize_data(ReceivePacketBuffer& packet) {
    return packet.read<T>(); }

protected:
  Telemetry& telemetry_container;
  size_t data_id;
  T value[array_count];
  T min_val, max_val;
};

template <typename T, uint32_t array_count>
class NumericArrayAccessor {
public:
  NumericArrayAccessor(NumericArray<T, array_count>& container, size_t index) :
    container(container), index(index) { }

  T operator = (T b) {
    container.value[index] = b;
    container.telemetry_container.mark_data_updated(container.data_id);
    return b;
  }

  operator T() {
    return container.value[index];
  }

protected:
  NumericArray<T, array_count>& container;
  size_t index;
};

}

#endif