/** * @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 "BasicUDPSocket.h" #include "CompilerTypes.h" #include "EmbeddedServiceMethodBinderI.h" #include "EventSem.h" #include "FastPollingMutexSem.h" #include "MemoryDataSourceI.h" #include "SingleThreadService.h" #include "StreamString.h" /*---------------------------------------------------------------------------*/ /* Class declaration */ /*---------------------------------------------------------------------------*/ namespace MARTe { /** * @brief Publishing mode for the background sender thread. * * - Strict: send one UDP packet on every Synchronise() call (legacy behaviour). * - Auto: buffer successive ticks and only flush when the HRT-based flush * interval has elapsed. Controlled by MinRefreshRate (Hz). */ typedef enum { UDPStreamerPublishStrict = 0u, /**< Send on every RT cycle (default) */ UDPStreamerPublishAuto = 1u /**< Rate-limited: flush at MinRefreshRate Hz */ } 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 */ }; /** * @brief Magic number for UDPStreamer packets: 'UDPS' in little-endian. */ static const uint32 UDPS_MAGIC = 0x53504455u; /** * @brief Packet type codes. */ static const uint8 UDPS_TYPE_DATA = 0u; /**< Server → Client: signal data */ static const uint8 UDPS_TYPE_CONFIG = 1u; /**< Server → Client: signal configuration */ static const uint8 UDPS_TYPE_ACK = 2u; /**< Client → Server: acknowledge counter */ static const uint8 UDPS_TYPE_CONNECT = 3u; /**< Client → Server: connect request */ static const uint8 UDPS_TYPE_DISCONNECT = 4u; /**< Client → Server: disconnect */ /** * @brief Wire packet header (17 bytes, packed). */ struct UDPSPacketHeader { uint32 magic; /**< Must equal UDPS_MAGIC */ uint8 type; /**< One of UDPS_TYPE_* */ uint32 counter; /**< Sequence counter (per data update, not per fragment) */ uint16 fragmentIdx; /**< 0-based index of this fragment */ uint16 totalFragments; /**< Total fragments for this update (1 = no fragmentation) */ uint32 payloadBytes; /**< Bytes of payload immediately following this header */ } __attribute__((packed)); /** * @brief Signal type codes transmitted in the CONFIG packet. * These are simplified type codes independent of MARTe2 internals. */ static const uint8 UDPS_TYPECODE_UINT8 = 0u; static const uint8 UDPS_TYPECODE_INT8 = 1u; static const uint8 UDPS_TYPECODE_UINT16 = 2u; static const uint8 UDPS_TYPECODE_INT16 = 3u; static const uint8 UDPS_TYPECODE_UINT32 = 4u; static const uint8 UDPS_TYPECODE_INT32 = 5u; static const uint8 UDPS_TYPECODE_UINT64 = 6u; static const uint8 UDPS_TYPECODE_INT64 = 7u; static const uint8 UDPS_TYPECODE_FLOAT32 = 8u; static const uint8 UDPS_TYPECODE_FLOAT64 = 9u; static const uint8 UDPS_TYPECODE_UNKNOWN = 255u; /** * @brief A DataSource that streams MARTe2 signals to a single UDP client. * * @details This output DataSource accepts signals from GAMs and forwards them * asynchronously to a connected UDP client. A dedicated background thread handles * all network I/O so that the real-time thread is only blocked for a fast spinlock * and a memcpy during Synchronise(). * * Protocol: * - Client sends CONNECT → server replies with CONFIG describing all signals. * - Server sends DATA packets (fragmented if needed) on every RT cycle. * - Client sends ACK packets (optional, for monitoring loss). * - Client sends DISCONNECT to terminate the session. * * Signal configuration syntax: *
* +UDPStreamer1 = {
* Class = UDPStreamer
* Port = 44500 // Optional (default 44500)
* MaxPayloadSize = 1400 // Optional (default 1400); max payload bytes per UDP datagram
* CPUMask = 0x2 // Optional, affinity for background thread
* StackSize = 1048576 // Optional, stack size for background thread
* PublishingMode = "Auto" // Optional: "Strict" (default) | "Auto"
* MinRefreshRate = 120 // Optional (Hz); only used when PublishingMode = "Auto"
* Signals = {
* Time = {
* Type = uint64
* }
* Pressure = {
* Type = float32
* NumberOfDimensions = 1
* NumberOfElements = 100
* Unit = "Pa" // Optional
* RangeMin = 0.0 // Optional (required for quantization)
* RangeMax = 1000000.0 // Optional (required for quantization)
* QuantizedType = uint16 // Optional: none|uint8|int8|uint16|int16
* TimeMode = LastSample // Optional: PacketTime|FullArray|FirstSample|LastSample
* TimeSignal = Time // Required when TimeMode != PacketTime
* SamplingRate = 10000.0 // Required when TimeMode = FirstSample or LastSample
* }
* Temperature = {
* Type = float64
* Unit = "K"
* TimeMode = PacketTime
* }
* }
* }
*
*
* Notes:
* - QuantizedType is only valid for float32/float64 signals.
* - TimeMode = PacketTime uses the HRT counter captured in Synchronise().
* - TimeMode = FullArray requires TimeSignal to have the same NumberOfElements.
* - TimeMode = FirstSample/LastSample requires a scalar TimeSignal and SamplingRate.
*/
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 (Port, MaxPayloadSize, CPUMask, StackSize).
* @return true if all mandatory parameters are valid.
*/
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;
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 Sends payload as one or more fragmented UDP datagrams to the connected client.
* @return true if all datagrams were sent without error.
*/
bool SendFragmented(uint8 type, uint32 counter, const uint8 *payload, uint32 payloadSize);
/**
* @brief Handles a single received command packet from a client.
*/
void HandleClientCommand(const uint8 *buf, uint32 size);
/**
* @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) */
/* 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 */
BasicUDPSocket serverSocket; /**< Bound to port; receives client commands */
BasicUDPSocket clientSocket; /**< Configured to send to the connected client */
volatile bool clientConnected; /**< True when a client has successfully connected */
/* Thread management */
SingleThreadService executor; /**< Background thread service */
/* Packet sequencing */
uint32 packetCounter; /**< Incremented for each DATA update sent */
};
} /* namespace MARTe */
#endif /* UDPSTREAMER_H_ */