#pragma once #include #include #include #include #include #include // ─── Protocol constants (matching UDPStreamer wire format) ────────── constexpr uint32_t UDPS_MAGIC = 0x53504455u; // 'UDPS' LE constexpr uint8_t UDPS_TYPE_DATA = 0u; constexpr uint8_t UDPS_TYPE_CONFIG = 1u; constexpr uint8_t UDPS_TYPE_ACK = 2u; constexpr uint8_t UDPS_TYPE_CONNECT = 3u; constexpr uint8_t UDPS_TYPE_DISCONNECT = 4u; constexpr size_t UDPS_HEADER_SIZE = 17; constexpr size_t UDPS_SIGDESC_SIZE = 136; // Type codes (matching Go protocol.go) constexpr uint8_t TC_UINT8 = 0u; constexpr uint8_t TC_INT8 = 1u; constexpr uint8_t TC_UINT16 = 2u; constexpr uint8_t TC_INT16 = 3u; constexpr uint8_t TC_UINT32 = 4u; constexpr uint8_t TC_INT32 = 5u; constexpr uint8_t TC_UINT64 = 6u; constexpr uint8_t TC_INT64 = 7u; constexpr uint8_t TC_FLOAT32 = 8u; constexpr uint8_t TC_FLOAT64 = 9u; // Quantisation types constexpr uint8_t QUANT_NONE = 0u; constexpr uint8_t QUANT_UINT8 = 1u; constexpr uint8_t QUANT_INT8 = 2u; constexpr uint8_t QUANT_UINT16 = 3u; constexpr uint8_t QUANT_INT16 = 4u; // Time modes constexpr uint8_t TM_PACKET = 0u; constexpr uint8_t TM_FULL_ARRAY = 1u; constexpr uint8_t TM_FIRST_SAMPLE = 2u; constexpr uint8_t TM_LAST_SAMPLE = 3u; constexpr uint32_t NO_TIME_SIGNAL = 0xFFFFFFFFu; #pragma pack(push, 1) struct UDPSPacketHeader { uint32_t magic; uint8_t type; uint32_t counter; uint16_t fragment_idx; uint16_t total_fragments; uint32_t payload_bytes; }; struct UDPSSignalInfo { char name[64]; uint8_t type_code; uint8_t quant_type; uint8_t num_dimensions; uint32_t num_rows; uint32_t num_cols; double range_min; double range_max; uint8_t time_mode; double sampling_rate; uint32_t time_signal_idx; char unit[32]; }; #pragma pack(pop) static_assert(sizeof(UDPSPacketHeader) == 17, "Header must be 17 bytes"); static_assert(sizeof(UDPSSignalInfo) == 136, "Signal desc must be 136 bytes"); // ─── Parsed signal metadata ───────────────────────────────────────── struct SignalDef { std::string name; uint8_t type_code = 0; uint8_t quant_type = 0; uint32_t num_elements = 0; // rows * cols uint32_t num_rows = 1; uint32_t num_cols = 1; double range_min = 0.0; double range_max = 0.0; uint8_t time_mode = TM_PACKET; double sampling_rate = 0.0; uint32_t time_signal_idx = NO_TIME_SIGNAL; std::string unit; int element_byte_size() const; }; // ─── One DATA packet worth of samples ─────────────────────────────── struct DataPacket { uint64_t hrt_timestamp = 0; // Per-signal float values (always float64 after dequantisation) std::vector> values; }; // ─── Parser functions ─────────────────────────────────────────────── /** Validate a UDP datagram header. Returns false if magic or size is wrong. */ bool parse_header(const uint8_t* buf, size_t len, UDPSPacketHeader& hdr); /** Parse a reassembled CONFIG payload into a signal list. */ std::vector parse_config(const uint8_t* payload, size_t len); /** Parse a reassembled DATA payload into float64 values per signal. */ DataPacket parse_data(const uint8_t* payload, size_t len, const std::vector& sig_defs); /** Build a 17-byte CONNECT packet. */ std::vector build_connect(); /** Build a 17-byte DISCONNECT packet. */ std::vector build_disconnect();