ducky's telemetry library
Revision 0:80dd1516ad46, committed 2015-03-18
- Comitter:
- dnleek
- Date:
- Wed Mar 18 07:59:36 2015 +0000
- Commit message:
- Telemetry library from ducky
Changed in this revision
diff -r 000000000000 -r 80dd1516ad46 telemetry-data.cpp --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/telemetry-data.cpp Wed Mar 18 07:59:36 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 80dd1516ad46 telemetry-mbed-hal.cpp --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/telemetry-mbed-hal.cpp Wed Mar 18 07:59:36 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 80dd1516ad46 telemetry-mbed.h --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/telemetry-mbed.h Wed Mar 18 07:59:36 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 80dd1516ad46 telemetry.cpp --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/telemetry.cpp Wed Mar 18 07:59:36 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 80dd1516ad46 telemetry.h --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/telemetry.h Wed Mar 18 07:59:36 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