403 lines
20 KiB
C++
403 lines
20 KiB
C++
/**
|
||
* @file UDPStreamer.h
|
||
* @brief Header file for class UDPStreamer
|
||
* @date 13/05/2026
|
||
* @author Martino Ferrari
|
||
*
|
||
* @copyright Copyright 2015 F4E | European Joint Undertaking for ITER and
|
||
* the Development of Fusion Energy ('Fusion for Energy').
|
||
* Licensed under the EUPL, Version 1.1 or - as soon they will be approved
|
||
* by the European Commission - subsequent versions of the EUPL (the "Licence")
|
||
* You may not use this work except in compliance with the Licence.
|
||
* You may obtain a copy of the Licence at: http://ec.europa.eu/idabc/eupl
|
||
*
|
||
* @warning Unless required by applicable law or agreed to in writing,
|
||
* software distributed under the Licence is distributed on an "AS IS"
|
||
* basis, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express
|
||
* or implied. See the Licence permissions and limitations under the Licence.
|
||
*
|
||
* @details This header file contains the declaration of the class UDPStreamer
|
||
* with all of its public, protected and private members. It may also include
|
||
* definitions for inline methods which need to be visible to the compiler.
|
||
*/
|
||
|
||
#ifndef UDPSTREAMER_H_
|
||
#define UDPSTREAMER_H_
|
||
|
||
/*---------------------------------------------------------------------------*/
|
||
/* Standard header includes */
|
||
/*---------------------------------------------------------------------------*/
|
||
|
||
/*---------------------------------------------------------------------------*/
|
||
/* Project header includes */
|
||
/*---------------------------------------------------------------------------*/
|
||
#include "CompilerTypes.h"
|
||
#include "EmbeddedServiceMethodBinderI.h"
|
||
#include "EventSem.h"
|
||
#include "FastPollingMutexSem.h"
|
||
#include "MemoryDataSourceI.h"
|
||
#include "SingleThreadService.h"
|
||
#include "StreamString.h"
|
||
#include "UDPSServer.h"
|
||
|
||
/*---------------------------------------------------------------------------*/
|
||
/* Class declaration */
|
||
/*---------------------------------------------------------------------------*/
|
||
|
||
namespace MARTe {
|
||
|
||
/**
|
||
* @brief Publishing mode for the background sender thread.
|
||
*
|
||
* - Strict: send one packet every Synchronise() call (default).
|
||
* - Accumulate: buffer N successive snapshots and flush either when the next
|
||
* snapshot would exceed MaxPayloadSize or when 1/MinRefreshRate
|
||
* has elapsed. Scalars expand to arrays; the first scalar with
|
||
* Unit="us"/"ns" is auto-promoted as the FullArray time reference.
|
||
* - Decimate: send one packet every Ratio Synchronise() calls.
|
||
*/
|
||
typedef enum {
|
||
UDPStreamerPublishStrict = 0u, /**< Send on every RT cycle (default) */
|
||
UDPStreamerPublishAccumulate = 1u, /**< Accumulate until size/time limit; then flush */
|
||
UDPStreamerPublishDecimate = 2u /**< Send 1 packet every Ratio cycles */
|
||
} UDPStreamerPublishMode;
|
||
|
||
/**
|
||
* @brief Quantization types for float signals.
|
||
*/
|
||
typedef enum {
|
||
UDPStreamerQuantNone = 0u, /**< No quantization; raw wire format */
|
||
UDPStreamerQuantUint8 = 1u, /**< Quantize to uint8 [0, 255] */
|
||
UDPStreamerQuantInt8 = 2u, /**< Quantize to int8 [-127, 127] */
|
||
UDPStreamerQuantUint16 = 3u, /**< Quantize to uint16 [0, 65535] */
|
||
UDPStreamerQuantInt16 = 4u /**< Quantize to int16 [-32767, 32767] */
|
||
} UDPStreamerQuantType;
|
||
|
||
/**
|
||
* @brief Time reference modes for signals.
|
||
*/
|
||
typedef enum {
|
||
UDPStreamerTimePacket = 0u, /**< Use the packet-level timestamp (HRT at Synchronise time) */
|
||
UDPStreamerTimeFullArray = 1u, /**< Time signal has same NumberOfElements; one timestamp per element */
|
||
UDPStreamerTimeFirstSample = 2u, /**< Time signal is scalar = timestamp of first element */
|
||
UDPStreamerTimeLastSample = 3u /**< Time signal is scalar = timestamp of last element */
|
||
} UDPStreamerTimeMode;
|
||
|
||
/**
|
||
* @brief Per-signal metadata used at runtime for serialization.
|
||
*/
|
||
struct UDPStreamerSignalInfo {
|
||
StreamString name; /**< Signal name */
|
||
TypeDescriptor type; /**< MARTe2 type descriptor */
|
||
UDPStreamerQuantType quantType; /**< Quantization type (none = raw copy) */
|
||
uint8 numDimensions; /**< Number of dimensions (0=scalar, 1=array, 2=matrix) */
|
||
uint32 numElements; /**< Total number of elements (rows * cols) */
|
||
uint32 numRows; /**< Number of rows */
|
||
uint32 numCols; /**< Number of columns */
|
||
float64 rangeMin; /**< Min of physical range (for quantization) */
|
||
float64 rangeMax; /**< Max of physical range (for quantization) */
|
||
UDPStreamerTimeMode timeMode; /**< How time is encoded for this signal */
|
||
float64 samplingRate; /**< Hz, used for First/LastSample modes */
|
||
uint32 timeSignalIdx; /**< Index of the time-reference signal; 0xFFFFFFFFu = PacketTime */
|
||
StreamString unit; /**< Physical unit string */
|
||
uint32 srcByteSize; /**< Bytes in MARTe2 memory */
|
||
uint32 wireByteSize; /**< Bytes on the wire (may differ when quantized) */
|
||
uint32 bufferOffset; /**< Byte offset in the flat MemoryDataSourceI memory buffer */
|
||
bool accumulated; /**< True when this scalar was expanded to flushCount elements in Auto accumulation mode */
|
||
};
|
||
|
||
/**
|
||
* @brief A DataSource that streams MARTe2 signals to UDP clients.
|
||
*
|
||
* @details This output DataSource accepts signals from GAMs and forwards them
|
||
* asynchronously over the network. A dedicated background thread handles all
|
||
* network I/O so that the real-time thread is only blocked by a fast spinlock
|
||
* and a memcpy during Synchronise().
|
||
*
|
||
* Two operating modes are selected by the presence or absence of MulticastGroup:
|
||
*
|
||
* @par Unicast mode (default — no MulticastGroup)
|
||
* The server opens a single UDP socket on Port. The client initiates the session
|
||
* by sending a CONNECT packet to that port. The server replies with a CONFIG
|
||
* packet on the same socket and subsequently sends DATA packets directly to the
|
||
* client's address. Any number of clients may connect sequentially (one at a
|
||
* time); a new CONNECT evicts the previous client.
|
||
*
|
||
* @par Multicast mode (MulticastGroup specified)
|
||
* The server opens a TCP listener on Port for control traffic and a UDP socket
|
||
* aimed at MulticastGroup:DataPort for data traffic. The client:
|
||
* 1. Connects to Port via TCP and sends a CONNECT packet.
|
||
* 2. Receives the CONFIG packet over TCP.
|
||
* 3. Joins the multicast group (MulticastGroup:DataPort) to receive DATA packets.
|
||
* Multiple clients may receive data simultaneously by joining the same group.
|
||
* A new TCP CONNECT evicts the previous client from the control channel; the
|
||
* multicast data stream continues regardless.
|
||
*
|
||
* @par Packet protocol (both modes)
|
||
* - Client → Server: CONNECT (initiates session), DISCONNECT (terminates session),
|
||
* ACK (optional, for packet-loss monitoring).
|
||
* - Server → Client: CONFIG (signal metadata), DATA (signal values, possibly
|
||
* fragmented into multiple datagrams if payload exceeds MaxPayloadSize).
|
||
*
|
||
* @par Top-level configuration parameters
|
||
* | Parameter | Type | Default | Description |
|
||
* |-----------------|---------|---------|-------------|
|
||
* | Port | uint16 | 44500 | TCP control port (multicast) or UDP server port (unicast). Values ≤ 1024 produce a warning. |
|
||
* | MulticastGroup | string | *(absent)* | **Enables multicast mode.** IPv4 multicast address, e.g. `"239.0.0.1"`. Must be in 224.0.0.0/4. Absent or empty = unicast. |
|
||
* | DataPort | uint16 | Port+1 | UDP port for multicast DATA datagrams. Ignored in unicast mode. Must be non-zero and differ from Port. |
|
||
* | MaxPayloadSize | uint32 | 1400 | Maximum bytes of signal payload per UDP datagram (excluding the 17-byte header). Larger signals are fragmented. |
|
||
* | PublishingMode | string | Strict | `Strict`: send one packet every Synchronise() call. `Auto`: rate-limited; flush only when MinRefreshRate interval has elapsed. |
|
||
* | MinRefreshRate | float64 | — | Required when PublishingMode = Auto. Flush frequency in Hz (e.g. 120.0). |
|
||
* | MaxBatchSize | uint32 | 1 | Optional when PublishingMode = Auto. Number of RT cycles to accumulate before flushing one packet. Scalar signals are expanded to arrays of MaxBatchSize elements; the first scalar with Unit="us" or "ns" is auto-promoted as the per-sample FullArray timestamp reference for all other scalars. When omitted or 1, the most-recent single value is sent at MinRefreshRate. |
|
||
* | CPUMask | uint32 | 0xFFFFFFFF | CPU affinity bitmask for the background thread. |
|
||
* | StackSize | uint32 | (MARTe2 default) | Stack size in bytes for the background thread. |
|
||
*
|
||
* @par Per-signal configuration parameters
|
||
* | Parameter | Type | Default | Description |
|
||
* |------------------|---------|-------------|-------------|
|
||
* | Type | string | — | MARTe2 type name (uint8, int16, float32, float64, …). Mandatory. |
|
||
* | NumberOfDimensions | uint8 | 0 | 0 = scalar, 1 = array, 2 = matrix. |
|
||
* | NumberOfElements | uint32 | 1 | Total element count (rows × cols for matrices). |
|
||
* | Unit | string | "" | Physical unit label, e.g. `"Pa"` or `"m/s"`. Transmitted in CONFIG for display purposes. |
|
||
* | RangeMin | float64 | 0.0 | Minimum of the physical range. Required when QuantizedType is not `none`. |
|
||
* | RangeMax | float64 | 1.0 | Maximum of the physical range. Required when QuantizedType is not `none`. |
|
||
* | QuantizedType | string | none | Wire quantization for float signals: `none` \| `uint8` \| `int8` \| `uint16` \| `int16`. Reduces wire bandwidth at the cost of precision. Only valid for float32/float64 signals. |
|
||
* | TimeMode | string | PacketTime | How the signal's time axis is encoded (see below). |
|
||
* | TimeSignal | string | — | Name of the signal that carries timestamps. Required when TimeMode ≠ PacketTime. |
|
||
* | SamplingRate | float64 | — | Signal sampling rate in Hz. Required when TimeMode = FirstSample or LastSample. |
|
||
*
|
||
* @par TimeMode values
|
||
* | Value | Description |
|
||
* |--------------|-------------|
|
||
* | PacketTime | Uses the HRT counter captured at Synchronise() time as the single packet timestamp. No dedicated time signal needed. |
|
||
* | FullArray | TimeSignal has the same NumberOfElements as this signal; one timestamp per element. |
|
||
* | FirstSample | TimeSignal is a scalar = timestamp of the first element; subsequent elements are spaced by 1/SamplingRate. |
|
||
* | LastSample | TimeSignal is a scalar = timestamp of the last element; elements are spaced backwards by 1/SamplingRate. |
|
||
*
|
||
* @par Example — unicast mode
|
||
* <pre>
|
||
* +Streamer = {
|
||
* Class = UDPStreamer
|
||
* Port = 44500
|
||
* MaxPayloadSize = 1400
|
||
* PublishingMode = "Strict"
|
||
* Signals = {
|
||
* Time = {
|
||
* Type = uint64
|
||
* }
|
||
* Pressure = {
|
||
* Type = float32
|
||
* NumberOfDimensions = 1
|
||
* NumberOfElements = 100
|
||
* Unit = "Pa"
|
||
* RangeMin = 0.0
|
||
* RangeMax = 1000000.0
|
||
* QuantizedType = uint16
|
||
* TimeMode = LastSample
|
||
* TimeSignal = Time
|
||
* SamplingRate = 10000.0
|
||
* }
|
||
* Temperature = {
|
||
* Type = float64
|
||
* Unit = "degC"
|
||
* TimeMode = PacketTime
|
||
* }
|
||
* }
|
||
* }
|
||
* </pre>
|
||
*
|
||
* @par Example — multicast mode
|
||
* <pre>
|
||
* +Streamer = {
|
||
* Class = UDPStreamer
|
||
* Port = 44500 // TCP control port
|
||
* MulticastGroup = "239.0.0.1" // Enables multicast mode
|
||
* DataPort = 44501 // UDP data port (default: Port+1)
|
||
* MaxPayloadSize = 1400
|
||
* PublishingMode = "Auto"
|
||
* MinRefreshRate = 60
|
||
* Signals = {
|
||
* Time = {
|
||
* Type = uint64
|
||
* }
|
||
* Voltage = {
|
||
* Type = float32
|
||
* NumberOfDimensions = 1
|
||
* NumberOfElements = 1000
|
||
* Unit = "V"
|
||
* RangeMin = -10.0
|
||
* RangeMax = 10.0
|
||
* QuantizedType = int16
|
||
* TimeMode = FirstSample
|
||
* TimeSignal = Time
|
||
* SamplingRate = 100000.0
|
||
* }
|
||
* }
|
||
* }
|
||
* </pre>
|
||
*/
|
||
class UDPStreamer : public MemoryDataSourceI, public EmbeddedServiceMethodBinderI {
|
||
public:
|
||
CLASS_REGISTER_DECLARATION()
|
||
|
||
/**
|
||
* @brief Constructor. Initialises all members to safe defaults.
|
||
*/
|
||
UDPStreamer();
|
||
|
||
/**
|
||
* @brief Destructor. Stops the background thread and frees allocated memory.
|
||
*/
|
||
virtual ~UDPStreamer();
|
||
|
||
/**
|
||
* @brief Parses top-level configuration parameters.
|
||
* @details Reads Port, MaxPayloadSize, CPUMask, StackSize, PublishingMode,
|
||
* MinRefreshRate, MulticastGroup, and DataPort from the configuration node.
|
||
* Sets useMulticast and dataPort when MulticastGroup is present and valid.
|
||
* @return true if all mandatory parameters are valid and consistent.
|
||
*/
|
||
virtual bool Initialise(StructuredDataI &data);
|
||
|
||
/**
|
||
* @brief Validates signal types and builds the per-signal metadata array.
|
||
* @details Reads per-signal custom fields (Unit, RangeMin, RangeMax, QuantizedType,
|
||
* TimeMode, TimeSignal, SamplingRate) from signalsDatabase and populates signalInfos[].
|
||
* @return true if all signal configurations are valid.
|
||
*/
|
||
virtual bool SetConfiguredDatabase(StructuredDataI &data);
|
||
|
||
/**
|
||
* @brief Allocates signal memory (via parent) plus readyBuffer, scratchBuffer, wireBuffer.
|
||
* @return true if all memory allocations succeed.
|
||
*/
|
||
virtual bool AllocateMemory();
|
||
|
||
/**
|
||
* @brief Returns the broker name for the given signal direction.
|
||
* @return "MemoryMapSynchronisedOutputBroker" for OutputSignals; "" otherwise.
|
||
*/
|
||
virtual const char8 *GetBrokerName(StructuredDataI &data,
|
||
const SignalDirection direction);
|
||
|
||
/**
|
||
* @brief Called by the RT thread after all GAMs have written output signals.
|
||
* @details Copies signal memory to readyBuffer under a fast spinlock, then posts
|
||
* the dataSem to wake the background thread. Returns immediately.
|
||
* @return true always.
|
||
*/
|
||
virtual bool Synchronise();
|
||
|
||
/**
|
||
* @brief Opens the server socket and starts the background thread.
|
||
* @return true if the socket and thread start successfully.
|
||
*/
|
||
virtual bool PrepareNextState(const char8 *const currentStateName,
|
||
const char8 *const nextStateName);
|
||
|
||
/**
|
||
* @brief Background thread entry point.
|
||
* @details Handles client CONNECT/DISCONNECT/ACK and sends DATA packets.
|
||
*/
|
||
virtual ErrorManagement::ErrorType Execute(ExecutionInfo &info);
|
||
|
||
/**
|
||
* @brief Returns the listening port number.
|
||
*/
|
||
uint16 GetPort() const;
|
||
|
||
/**
|
||
* @brief Returns the maximum payload size per UDP datagram.
|
||
*/
|
||
uint32 GetMaxPayloadSize() const;
|
||
|
||
/**
|
||
* @brief Returns true if a client is currently connected.
|
||
*/
|
||
bool IsClientConnected() const;
|
||
|
||
/**
|
||
* @brief Returns true when multicast mode is active (MulticastGroup was set in config).
|
||
*/
|
||
bool IsMulticast() const;
|
||
|
||
private:
|
||
/**
|
||
* @brief Serializes the CONFIG payload into buf and sets payloadSize.
|
||
* @return true if serialization succeeds.
|
||
*/
|
||
bool BuildConfigPayload(uint8 *buf, uint32 bufSize, uint32 &payloadSize);
|
||
|
||
/**
|
||
* @brief Quantizes and serializes all signals from srcBuf into wireBuffer.
|
||
* @param[in] srcBuf Flat source buffer (signal data in MARTe2 format).
|
||
* @param[in] timestamp HRT counter to embed as packet timestamp.
|
||
*/
|
||
void QuantizeAndSerialize(const uint8 *srcBuf, uint64 timestamp);
|
||
|
||
/**
|
||
* @brief Serializes an accumulated batch into wireBuffer.
|
||
* @param src Flat buffer [numSamples × totalSrcBytes], slot 0 = oldest.
|
||
* @param timestamps HRT counters per slot; timestamps[0] is the packet timestamp.
|
||
* @param numSamples Actual number of filled slots to serialize.
|
||
*/
|
||
void SerializeAccumulated(const uint8 *src, const uint64 *timestamps, uint32 numSamples);
|
||
|
||
/**
|
||
* @brief Maps a MARTe2 TypeDescriptor to the UDPS_TYPECODE_* constants.
|
||
*/
|
||
static uint8 TypeDescriptorToCode(TypeDescriptor td);
|
||
|
||
/* Configuration parameters */
|
||
uint16 port; /**< UDP server port */
|
||
uint32 maxPayloadSize; /**< Max payload bytes per UDP packet (excluding header) */
|
||
uint32 cpuMask; /**< Background thread CPU affinity */
|
||
uint32 stackSize; /**< Background thread stack size */
|
||
UDPStreamerPublishMode publishMode; /**< Strict or Auto publishing mode */
|
||
float64 minRefreshRate; /**< Minimum flush rate (Hz) for Auto mode */
|
||
uint64 flushPeriodTicks; /**< HRT ticks per flush interval (computed from minRefreshRate) */
|
||
/* Accumulate mode — dynamic batch parameters */
|
||
uint32 maxBatchCount; /**< Max snapshots that fit in MaxPayloadSize (Accumulate) */
|
||
uint32 singleCycleWireBytes; /**< Wire bytes for all accumulated signals per snapshot */
|
||
uint32 fixedWireBytes; /**< Wire bytes for non-accumulated signals (arrays, once per packet) */
|
||
volatile uint64 lastPublishTs; /**< HRT counter of last successful flush (Accumulate mode) */
|
||
uint8 *accumBuffer; /**< Heap: [maxBatchCount × totalSrcBytes] linear fill */
|
||
uint64 *accumTimestamps; /**< Heap: [maxBatchCount] HRT counter per snapshot */
|
||
uint32 accumFill; /**< Slots filled in accumBuffer (0..maxBatchCount) */
|
||
uint64 *readyTimestamps; /**< Heap: [maxBatchCount] HRT for completed ready batch */
|
||
uint64 *scratchTimestamps; /**< Heap: [maxBatchCount] background-thread local copy */
|
||
uint32 readyFill; /**< Snapshot count in the ready batch */
|
||
/* Decimate mode */
|
||
uint32 decimateRatio; /**< Send 1 packet every decimateRatio Synchronise() calls */
|
||
uint32 decimateCounter; /**< Current decimate cycle counter */
|
||
|
||
/* Signal metadata */
|
||
uint32 numSigs; /**< Number of signals */
|
||
UDPStreamerSignalInfo *signalInfos; /**< Per-signal metadata array */
|
||
|
||
/* Double-buffering for RT safety */
|
||
uint8 *readyBuffer; /**< Protected copy of signal memory for background thread */
|
||
uint8 *scratchBuffer; /**< Local copy used during serialization (avoids holding lock) */
|
||
uint32 totalSrcBytes; /**< Total bytes of signal data (= stateMemorySize) */
|
||
FastPollingMutexSem bufMutex; /**< Protects readyBuffer against concurrent access */
|
||
EventSem dataSem; /**< Wakes background thread when new data is ready */
|
||
volatile uint64 syncTimestamp; /**< HRT counter captured in Synchronise() */
|
||
|
||
/* Wire serialization buffer */
|
||
uint8 *wireBuffer; /**< Pre-allocated buffer for the serialized DATA payload */
|
||
uint32 totalWireBytes; /**< Total bytes of all signals after quantization + 8-byte timestamp prefix */
|
||
|
||
/* Networking — handled by UDPSServer */
|
||
UDPSServer server; /**< Multi-client session manager (unicast and multicast) */
|
||
|
||
/* Thread management */
|
||
SingleThreadService executor; /**< Background thread service */
|
||
|
||
/* Packet sequencing */
|
||
uint32 packetCounter; /**< Incremented for each DATA update sent */
|
||
};
|
||
|
||
} /* namespace MARTe */
|
||
|
||
#endif /* UDPSTREAMER_H_ */
|