b15e637f14
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>
368 lines
16 KiB
C++
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_ */
|