/** * @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_ */