/** * @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 *
* +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
* }
* }
* }
*
*
* @par Example — multicast mode
*
* +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
* }
* }
* }
*
*/
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_ */