Files
MARTe_IO_Components/Source/Components/DataSources/UDPStreamer/UDPStreamer.h
T
Martino Ferrari b15e637f14 Add TCP control + UDP multicast mode to UDPStreamer
DataSource (C++):
- New optional config: MulticastGroup (e.g. "239.0.0.1") and DataPort
  (default Port+1); when set, enables multicast mode; absent = unicast
- Control plane: BasicTCPSocket listener on Port; CONNECT received over
  TCP triggers HandleTCPConnect() which sends CONFIG via the same TCP
  connection
- Data plane: BasicUDPSocket aimed at MulticastGroup:DataPort; all DATA
  fragments sent via dataSocket.Write() so any joined client receives them
- useMulticast is the single branch point; every unicast code path is
  unchanged when MulticastGroup is absent
- New methods: HandleTCPConnect(), IsMulticast()
- 5 new unit tests (ports 44710-44729); all 38 tests passing

WebUI hub (Go):
- SourceConfig gains MulticastGroup and DataPort fields (JSON, optional)
- UDPClient gains multicastGroup/dataPort; Run() routes to new
  runMulticastSession() when multicastGroup is non-empty
- runMulticastSession(): TCP dial for CONNECT/CONFIG, net.ListenMulticastUDP
  for DATA, background goroutine watches TCP for DISCONNECT/close
- All existing sm.Add() call sites updated (unicast callers pass "", 0)

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-26 22:56:27 +02:00

368 lines
16 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 "BasicTCPSocket.h"
#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:
* <pre>
* +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
* }
* }
* }
* </pre>
*
* 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;
/**
* @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 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 (unicast mode).
*/
void HandleClientCommand(const uint8 *buf, uint32 size);
/**
* @brief Handles a CONNECT received on the TCP control socket (multicast mode).
* @details Validates magic+type, sets clientConnected, builds and sends CONFIG via tcpClient.
*/
void HandleTCPConnect(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 — unicast mode */
BasicUDPSocket serverSocket; /**< Bound to port; receives client commands (unicast) */
BasicUDPSocket clientSocket; /**< Configured to send to the connected client (unicast) */
volatile bool clientConnected; /**< True when a client has successfully connected */
/* Networking — multicast mode (only used when useMulticast == true) */
StreamString multicastGroup; /**< Multicast group IP, e.g. "239.0.0.1"; empty = unicast */
uint16 dataPort; /**< UDP port for DATA datagrams in multicast mode */
bool useMulticast; /**< Derived from multicastGroup.Size() > 0 in Initialise() */
BasicTCPSocket tcpListener; /**< TCP server socket: accepts control connections */
BasicTCPSocket *tcpClient; /**< Accepted TCP control connection (heap by WaitConnection) */
BasicUDPSocket dataSocket; /**< UDP socket aimed at multicastGroup:dataPort */
/* Thread management */
SingleThreadService executor; /**< Background thread service */
/* Packet sequencing */
uint32 packetCounter; /**< Incremented for each DATA update sent */
};
} /* namespace MARTe */
#endif /* UDPSTREAMER_H_ */