feat(tcp): TCPStreamer DataSources, in-process TCPStreamService, stream multiplexing and TCPStreamJoin

Add TCPStreamer/TCPStreamerClient (TCP transport behind the UDPSServerI/UDPSClientI
seam), an optional single in-process TCPStreamService serving many streams over
worker threads, multiplexed SUBSCRIBE (comma list, stream index in fragmentIdx),
and TCPStreamJoin, which merges several streams into one sample-and-hold stream
paced by a master. StreamHub takes a comma list of streams over one connection.
Includes GTests, E2E scenarios s58-s73 and docs.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
Martino FerrariandClaude Opus 4.6 committed 2026-11-06 13:41:27 +01:00
1 parent 5562877c99
commit 50d85363a3
79 files changed
+12348 -138

No files matched your search

@@ -131,7 +131,8 @@ static float64 DequantValue(float64 raw, uint8 quantType,
UDPStreamerClient::UDPStreamerClient() :
MemoryDataSourceI(),
UDPSClientListener(),
client() {
client(NULL_PTR(UDPSClientI *)) {
client = new UDPSClient();
serverAddress = UDPS_CLIENT_DEFAULT_ADDR;
port = UDPS_CLIENT_DEFAULT_PORT;
maxPayloadSize = UDPS_CLIENT_DEFAULT_MAX_PAYLOAD;
@@ -160,9 +161,13 @@ UDPStreamerClient::UDPStreamerClient() :
UDPStreamerClient::~UDPStreamerClient() {
(void) dataSem.Post();
if (receiverUp) {
(void) client.Stop();
receiverUp = false;
if (client != NULL_PTR(UDPSClientI *)) {
if (receiverUp) {
(void) client->Stop();
receiverUp = false;
}
delete client;
client = NULL_PTR(UDPSClientI *);
}
HeapI *heap = GlobalObjectsDatabase::Instance()->GetStandardHeap();
@@ -258,36 +263,49 @@ bool UDPStreamerClient::Initialise(StructuredDataI &data) {
}
}
/* Forward the transport parameters to the shared UDPSClient receiver,
* translating the DataSource parameter names to the UDPSClient keys. */
if (ok) {
ConfigurationDatabase cdb;
ok = cdb.Write("ServerAddr", serverAddress);
if (ok) { ok = cdb.Write("Port", static_cast<uint32>(port)); }
if (ok && useMulticast) {
ok = cdb.Write("MulticastGroup", multicastGroup);
if (ok) { ok = cdb.Write("DataPort", static_cast<uint32>(dataPort)); }
if (ok && (multicastInterface.Size() > 0u)) {
ok = cdb.Write("Interface", multicastInterface);
}
}
if (ok) { ok = cdb.Write("MaxPayloadSize", maxPayloadSize); }
if (ok) { ok = cdb.Write("KeepAliveInterval", keepAliveInterval); }
if (ok) { ok = cdb.Write("SilenceTimeout", silenceTimeout); }
if (ok) { ok = cdb.Write("CPUMask", cpuMask); }
if (ok) { ok = cdb.Write("StackSize", stackSize); }
if (ok) { ok = cdb.MoveToRoot(); }
if (ok) { ok = client.Initialise(cdb); }
if (!ok) {
REPORT_ERROR(ErrorManagement::ParametersError,
"Could not configure the UDPSClient receiver.");
}
client.SetListener(this);
ok = InitialiseTransport(data);
}
return ok;
}
bool UDPStreamerClient::InitialiseTransport(StructuredDataI &data) {
/* Forward the transport parameters to the shared UDPSClient receiver,
* translating the DataSource parameter names to the UDPSClient keys. */
bool ok = true;
ConfigurationDatabase cdb;
ok = cdb.Write("ServerAddr", serverAddress);
if (ok) { ok = cdb.Write("Port", static_cast<uint32>(port)); }
if (ok && useMulticast) {
ok = cdb.Write("MulticastGroup", multicastGroup);
if (ok) { ok = cdb.Write("DataPort", static_cast<uint32>(dataPort)); }
if (ok && (multicastInterface.Size() > 0u)) {
ok = cdb.Write("Interface", multicastInterface);
}
}
if (ok) { ok = cdb.Write("MaxPayloadSize", maxPayloadSize); }
if (ok) { ok = cdb.Write("KeepAliveInterval", keepAliveInterval); }
if (ok) { ok = cdb.Write("SilenceTimeout", silenceTimeout); }
if (ok) { ok = cdb.Write("CPUMask", cpuMask); }
if (ok) { ok = cdb.Write("StackSize", stackSize); }
if (ok) { ok = cdb.MoveToRoot(); }
if (ok) { ok = client->Initialise(cdb); }
if (!ok) {
REPORT_ERROR(ErrorManagement::ParametersError,
"Could not configure the UDPSClient receiver.");
}
client->SetListener(this);
return ok;
}
void UDPStreamerClient::SetClient(UDPSClientI *newClient) {
if (client != NULL_PTR(UDPSClientI *)) {
delete client;
}
client = newClient;
}
bool UDPStreamerClient::SetConfiguredDatabase(StructuredDataI &data) {
bool ok = MemoryDataSourceI::SetConfiguredDatabase(data);
if (!ok) {
@@ -377,7 +395,7 @@ bool UDPStreamerClient::PrepareNextState(const char8 *const currentStateName,
const char8 *const nextStateName) {
bool ok = true;
if (!receiverUp) {
ok = client.Start();
ok = client->Start();
if (ok) {
receiverUp = true;
}
@@ -160,6 +160,27 @@ public:
*/
virtual void OnUDPSDisconnected();
protected:
/**
* @brief Configures the transport from the already-parsed members and
* registers this object as its listener.
* @details The default implementation configures the UDPSClient (unicast /
* multicast). A subclass that installs another transport with SetClient()
* overrides this to configure it instead.
*/
virtual bool InitialiseTransport(StructuredDataI &data);
/**
* @brief Replaces the transport. Takes ownership; only valid before Start().
*/
void SetClient(UDPSClientI *newClient);
/* Configuration parameters shared with transport subclasses */
StreamString serverAddress; /**< Server IP address. */
uint16 port; /**< Server port. */
uint32 cpuMask; /**< Background thread CPU affinity. */
uint32 stackSize; /**< Background thread stack size. */
private:
/**
* @brief Decodes a single snapshot from a DATA payload into @p dst.
@@ -171,13 +192,9 @@ private:
void DecodeSnapshot(const uint8 *payload, uint32 size, uint8 *dst);
/* Configuration parameters */
StreamString serverAddress; /**< Server IP address. */
uint16 port; /**< Server port. */
uint32 maxPayloadSize; /**< Max payload bytes per datagram. */
uint32 keepAliveInterval; /**< Seconds between unicast keepalive ACKs (0 disables). */
float32 silenceTimeout; /**< Seconds of no data before reconnect (sub-second allowed, 0 disables). */
uint32 cpuMask; /**< Background thread CPU affinity. */
uint32 stackSize; /**< Background thread stack size. */
StreamString multicastGroup; /**< Multicast group IP; empty = unicast. */
StreamString multicastInterface; /**< Local IPv4 address for multicast join; empty = INADDR_ANY. */
uint16 dataPort; /**< UDP port for DATA datagrams (multicast). */
@@ -191,7 +208,7 @@ private:
uint8 publishMode; /**< UDPS_PUBLISH_* from CONFIG. */
/* Shared receiver (same code base as the StreamHub hub) */
UDPSClient client; /**< Transport, reassembly and reconnect. */
UDPSClientI *client; /**< Owned transport (UDPSClient by default); see SetClient(). */
bool receiverUp; /**< True once the receiver thread is started. */
/* Double-buffering for RT safety */
@@ -136,4 +136,5 @@
/home/martino/workspace/MARTe2/Source/Core/Scheduler/L3Services/EmbeddedServiceMethodBinderI.h \
/home/martino/workspace/MARTe2/Source/Core/Scheduler/L3Services/EmbeddedServiceMethodBinderT.h \
/home/martino/workspace/MARTe2/Source/Core/Scheduler/L3Services/EmbeddedThreadI.h \
/home/martino/workspace/MARTe2/Source/Core/Scheduler/L3Services/EmbeddedThread.h
/home/martino/workspace/MARTe2/Source/Core/Scheduler/L3Services/EmbeddedThread.h \
../../../..//Source/Components/Interfaces/UDPStream/UDPSTransportI.h
@@ -136,4 +136,5 @@ UDPStreamerClient.o: UDPStreamerClient.cpp \
/home/martino/workspace/MARTe2/Source/Core/Scheduler/L3Services/EmbeddedServiceMethodBinderI.h \
/home/martino/workspace/MARTe2/Source/Core/Scheduler/L3Services/EmbeddedServiceMethodBinderT.h \
/home/martino/workspace/MARTe2/Source/Core/Scheduler/L3Services/EmbeddedThreadI.h \
/home/martino/workspace/MARTe2/Source/Core/Scheduler/L3Services/EmbeddedThread.h
/home/martino/workspace/MARTe2/Source/Core/Scheduler/L3Services/EmbeddedThread.h \
../../../..//Source/Components/Interfaces/UDPStream/UDPSTransportI.h