Files
MARTe-Integrated-Components/ARCHITECTURE.md
2026-06-26 09:11:10 +02:00

30 KiB
Raw Permalink Blame History

Architecture

This document describes the internal architecture of the MARTe2 Integrated Components.


1. Repository Overview

DebugService path (legacy introspection / breakpoints)

┌────────────────────────────────────────────────────────────────────┐
│                       MARTe2 Application                           │
│  ┌──────────┐   ┌───────────────────────┐   ┌──────────────────┐   │
│  │  GAM(s)  │   │  DebugBrokerWrapper   │   │  FastScheduler   │   │
│  │          │◄──│  (Registry-patched)   │   │  (unmodified)    │   │
│  └──────────┘   └──────────┬────────────┘   └──────────────────┘   │
│                            │ RT-path API                           │
└────────────────────────────┼───────────────────────────────────────┘
                             │
                    ┌────────▼────────┐
                    │  DebugServiceI  │  ← Abstract singleton interface
                    └────────┬────────┘
                    ┌────────▼────────┐
                    │  DebugService   │
                    │  TCP/UDP        │
                    └────────┬────────┘
             ┌───────────────┼──────────────┐
             │               │              │
        TCP 8080         UDP 8081      TCP 8082
        (commands)      (telemetry)    (TcpLogger)
             │               │
    ┌────────▼───────────────▼──────┐
    │     Client/debugger           │
    │     Go web client (browser)   │
    └───────────────────────────────┘

UDPStreamer / StreamHub path (high-throughput signal streaming)

┌────────────────────────────────────────────────────────────────────┐
│                       MARTe2 Application                           │
│  ┌─────────────────────────────────────────────┐                   │
│  │  UDPStreamer DataSource                     │                   │
│  │  - Receives signals from GAMs via IOGAM     │                   │
│  │  - Serialises to UDPS binary protocol       │                   │
│  │  - Manages per-client sessions              │                   │
│  └──────────────────────┬──────────────────────┘                   │
└─────────────────────────┼──────────────────────────────────────────┘
                    UDP 44500 (unicast or multicast)
                          │
          ┌───────────────▼───────────────┐
          │       StreamHub               │
          │  Source/Applications/StreamHub│
          │  - N simultaneous sources     │
          │  - Ring buffers per signal    │
          │  - LTTB decimation            │
          │  - Trigger engine (FSM)       │
          │  - M WebSocket clients        │
          └──────────────┬────────────────┘
                   WebSocket 8090
                         │
          ┌──────────────┴──────────────┐
          │                             │
  ┌───────▼──────────┐     ┌────────────▼──────────────┐
  │ Browser (any)    │     │ ImGui Desktop Client       │
  │ Client/udpstreamer│    │ Client/streamhub/          │
  │ Go web server +  │     │ C++ native window          │
  │ JS oscilloscope  │     │ (SDL2 + OpenGL + ImPlot)   │
  └──────────────────┘     └───────────────────────────┘

2. UDPS Binary Protocol

Defined in Common/UDP/UDPSProtocol.h and shared between:

  • UDPStreamer (C++ producer)
  • DebugService (C++ producer for trace packets)
  • Common/Client/go/udpsprotocol (Go decoder)
  • Source/Components/Interfaces/UDPStream/UDPSClient (C++ consumer)

Packet Header (17 bytes, little-endian, packed)

Offset Size Type Field Description
0 4 uint32 magic Always 0x53504455 ('UDPS' LE)
4 1 uint8 type Packet type (DATA=0, CONFIG=1, ACK=2, CONNECT=3, DISCONNECT=4)
5 4 uint32 counter Per-update sequence number (same across all fragments)
9 2 uint16 fragmentIdx 0-based fragment index
11 2 uint16 totalFragments Total fragments for this update
13 4 uint32 payloadBytes Bytes of payload following this header

Signal Descriptor (136 bytes, packed)

Field Type Description
name char[64] Null-terminated signal name
typeCode uint8 UDPS_TYPECODE_* (0=u8, 1=i8, … 9=f32, 10=f64)
quantType uint8 UDPS_QUANT_* (0=none, 1=u8, 2=i8, 3=u16, 4=i16)
numDimensions uint8 0=scalar, 1=array, 2=matrix
numRows uint32 Element count for 1D; rows for 2D
numCols uint32 1 for scalar/1D; columns for 2D
rangeMin float64 Physical range minimum (for dequantisation)
rangeMax float64 Physical range maximum (for dequantisation)
timeMode uint8 UDPS_TIMEMODE_* (0=packet, 1=first, 2=last, 3=full_array)
samplingRate float64 Hz; used for first/last sample interpolation
timeSignalIdx uint32 Index of time-ref signal; 0xFFFFFFFF if none
unit char[32] Null-terminated physical unit string

CONFIG Payload

Sent when the signal set changes or a client connects:

[uint32 numSigs]
numSigs × UDPSSignalDescriptor (136 bytes each, packed)
[uint8 publishMode]   0=Strict/Decimate, 1=Accumulate

DATA Payload (Strict / Decimate modes)

[uint64 HRT timestamp]
per-signal data in CONFIG order (quantised or raw, no inter-signal padding)

DATA Payload (Accumulate mode)

[uint64 HRT timestamp]
[uint32 numSamples]
for each signal: if scalar → numSamples elements; else → NumElements once

Quantization / Dequantization

When quantType != QUANT_NONE, the server maps [rangeMin, rangeMax] to the full integer range. Clients reverse with:

quantType Formula
QUANT_UINT8 rangeMin + (q / 255.0) × (rangeMax rangeMin)
QUANT_INT8 rangeMin + ((q + 127.0) / 254.0) × (rangeMax rangeMin)
QUANT_UINT16 rangeMin + (q / 65535.0) × (rangeMax rangeMin)
QUANT_INT16 rangeMin + ((q + 32767.0) / 65534.0) × (rangeMax rangeMin)

3. DebugService Architecture

3.1 Registry Patching (Zero-Code-Change Instrumentation)

DebugService::PatchRegistry() replaces the ObjectBuilder for all MemoryMap*Broker types in the MARTe2 ClassRegistryDatabase. Any subsequent ConfigureApplication() call will instantiate DebugBrokerWrapper<T> objects instead of the originals. No application source changes are needed.

Wrapped types: MemoryMapInputBroker, MemoryMapOutputBroker, MemoryMapSynchronisedInputBroker, MemoryMapSynchronisedOutputBroker, MemoryMapMultiBufferBroker, MemoryMapMultiBufferOutputBroker, MemoryMapAsynchronousInputBroker, MemoryMapAsynchronousOutputBroker, MemoryMapInterpolatedInputBroker, MemoryMapStatefulOutputBroker, MemoryMapStatefulInputBroker.

3.2 Signal Registration

ConfigureApplication()
  └─► DebugBrokerWrapper::Init()
        └─► DebugBrokerHelper::InitSignals()
              ├─► DebugServiceI::RegisterSignal()   (canonical + GAM alias)
              └─► DebugServiceI::RegisterBroker()

Each signal is registered twice:

  1. Canonical: <DataSourcePath>.<SignalName> (e.g. App.Data.DDB.Counter)
  2. GAM alias: <GAMPath>.In.<SignalName> or <GAMPath>.Out.<SignalName>

Both map to the same DebugSignalInfo*. AliasMatch() in DebugServiceBase.cpp performs bidirectional suffix matching so short unqualified names work in commands.

3.3 RT Hot Path

RealTimeThread::Execute()
  └─► DebugBrokerWrapper::Execute()
        ├─► Base::Execute()                  (actual data movement)
        └─► DebugBrokerHelper::Process()
              ├─► For each active signal:
              │     └─► DebugServiceI::ProcessSignal()
              │           ├─► if isForcing: memcpy forcedValue → signal memory
              │           ├─► if isTracing & decimation fires: push to TraceRingBuffer
              │           └─► if breakOp set: evaluate condition → SetPaused(true)
              └─► (output brokers only) ConsumeStepIfNeeded()

3.4 TraceRingBuffer

Single-producer/single-consumer circular byte buffer (4 MB default). Entry format: [ID:4][Timestamp:8][Size:4][Data:N].

  • Push() serialised by tracePushMutex (multiple RT threads may write)
  • Pop() called exclusively by the Streamer thread
  • Corrupt entries detected by size >= bufferSize; discarded gracefully

3.5 DebugService Threads

Thread Role
Server() Accepts one TCP client; reads text commands; writes JSON/text responses
Streamer() Drains TraceRingBuffer; assembles/sends UDP datagrams; polls monitored signals

3.6 DebugServiceI Abstraction

DebugServiceI.h defines a pure-virtual singleton so transport implementations (DebugService TCP/UDP, WebDebugService HTTP/SSE) share the same broker injection layer.

DebugServiceI::SetInstance(this);           // concrete implementation registers itself
DebugServiceI *svc = DebugServiceI::GetInstance(); // broker wrapper retrieves it

4. UDPStreamer DataSource

UDPStreamer is a standard MARTe2 DataSourceI that:

  1. Maintains a list of registered client sessions (UDP source address + port)
  2. On each Synchronise() call (once per RT cycle):
    • Serialises all configured signals into one or more UDPS DATA packets
    • Sends to all connected clients
  3. Handles CONNECT / DISCONNECT / ACK packets from clients
  4. Sends CONFIG packets when the signal list changes or a new client connects
  5. Applies quantization (QuantizedType, RangeMin, RangeMax) per signal
  6. Supports unicast and multicast UDP delivery (configurable per instance)

Packed Signals (Accumulate mode)

When a signal has NumberOfElements > 1 and SamplingRate is configured:

  • TimeMode = FirstSample: the TimeSignal value anchors the burst timestamp
  • Clients interpolate per-sample timestamps as t0 + e/SamplingRate
  • UDPStreamer packs all elements contiguously with no per-element header

5. StreamHub C++ Application

StreamHub (Source/Applications/StreamHub/) is a headless standalone process that bridges N MARTe2 UDPStreamer sources to M simultaneous WebSocket clients. It replaces the Go-based Client/udpstreamer/ hub for deployments where a Go toolchain is unavailable or where the hub must run as a C++ service.

Components

File Purpose
main.cpp Entry point; parses -cfg, -port, -maxPoints flags
StreamHub.h/.cpp Top-level orchestrator; push loop; WS command dispatch
UDPSourceSession.h/.cpp One UDPS source: UDPSClient + ring buffers + wall-clock calibration + stats
TriggerEngine.h/.cpp FSM: IDLE / ARMED / COLLECTING / TRIGGERED (web-client semantics)
WSServer.h/.cpp RFC 6455 WebSocket server; multi-client accept+read threads
WSFrame.h WebSocket frame encode/decode helpers
SignalRingBuffer.h Thread-safe fixed-capacity circular buffer (float64 t, v)
UDPSourceStats.h Per-source statistics struct
LTTB.h Largest Triangle Three Buckets decimation (header-only)
SHA1.h Inline SHA-1 for WebSocket handshake Accept key
Base64.h Inline Base64 encoder for WebSocket handshake

Time Base (wall-clock calibration)

All ring-buffer timestamps are Unix wall-clock seconds (float64). Each UDPSourceSession calibrates source time to the hub's CLOCK_REALTIME on the first packet (offset = wallNow sourceTime) and re-anchors automatically if the computed time drifts more than 2 s from arrival time (source restart). Per-sample time depends on the signal's UDPS timeMode:

timeMode Per-sample timestamp
FirstSample / LastSample calibrated time-signal anchor ± k/samplingRate
FullArray calibrated per-element time-signal array
Packet (scalar) packet arrival wall time
Packet (array, n>1) elements span (lastPktWall, wallNow], interpolated from the inter-packet gap

Push Loop

Runs at PushRate Hz (default 30 Hz):

  1. For each configured source:
    • On first CONFIG: broadcast sources + config, reset push cursors
    • For each signal: read only the samples written since the last tick (per-signal monotonic cursor into the ring; overrun clamps to oldest)
    • LTTB-decimate temporal signals to MaxPushPoints (default 50); scalars and packet-timed arrays go verbatim
    • Serialise one binary v1 frame per source; broadcast to all WS clients
    • Cursors advance even with zero clients (no backlog burst on connect)
  2. Trigger servicing (capture finalisation, auto-rearm, state events)
  3. Every PushRate / StatsRate ticks: broadcast JSON stats frame

Pushing only-new samples is essential: re-reading overlapping windows and re-LTTBing them picks different points per window and corrupts the traces.

Trigger Engine

Hub-side trigger with the web client's semantics (config: signal key "src:sig" or "src:sig[i]", edge rising/falling/both, threshold, window 0.1 ms10 s, pre-trigger percent, mode normal/single):

IDLE       →[arm]→                ARMED
ARMED      →[edge crossing]→      COLLECTING   (latches trigTime, pre/postSec)
COLLECTING →[post window + margin elapsed]→ TRIGGERED  (broadcast binary v2 capture)
TRIGGERED  →[auto-rearm (normal, ~200 ms) | rearm (single)]→ ARMED
any        →[disarm]→             IDLE

CheckSample() is called from the UDPSClient receive thread for every decoded sample of the configured signal. The capture is assembled in the push loop from ReadSignalRange(trigTimepreSec, trigTime+postSec) over all signals, LTTB-capped at 20 000 points/signal, and broadcast as a binary version-2 frame. A stopped flag (trigStop) freezes auto-rearm.

Configuration File (MARTe2 cfg format)

Hub = {
    WSPort        = 8090
    MaxPoints     = 20000      // zoom/live window cap (setMaxPoints)
    PushRate      = 30         // push loop Hz
    MaxPushPoints = 50         // LTTB cap per signal per tick
    StatsRate     = 1          // stats broadcast Hz
    RingTemporal  = 1000000    // ring capacity (points) for multi-element signals
    RingScalar    = 100000     // ring capacity (points) for scalar signals
    SourcesFile   = "streamhub_sources.json"  // dynamic-source persistence
    Sources = {
        App1 = {
            Label = "MARTe2 App 1"
            Addr  = "127.0.0.1"
            Port  = 44500
        }
        App2 = {
            Label          = "MARTe2 App 2"
            Addr           = "192.168.1.10"
            Port           = 44501
            MulticastGroup = "239.0.0.1"
            DataPort       = 44504
        }
    }
}

Sources added at runtime (addSource) get generated ids s1, s2, …; saveSources persists them to SourcesFile (JSON array of {label, addr, multicastGroup?, dataPort?}), reloaded at start-up.

Build

MARTe2_DIR=/path/to/MARTe2 \
  make -C Source/Applications/StreamHub -f Makefile.gcc TARGET=x86-linux
# Produces: Build/x86-linux/StreamHub/StreamHub.ex

6. StreamHub WebSocket Protocol

All client↔hub communication uses WebSocket (RFC 6455). Text frames carry JSON; binary frames carry data push payloads.

Commands (client → hub, JSON text frames)

type field Additional fields Action
ping Hub replies {"type":"pong"}
addSource label, addr ("host:port"), multicastGroup?, dataPort? Connect to a new UDPS source; hub assigns id s1, s2, …
removeSource id Disconnect and remove a source
saveSources Persist the current dynamic source list to SourcesFile (JSON)
getSources Trigger sources broadcast
getConfig sourceId Trigger config broadcast for one source
getStats Trigger stats broadcast
setTrigger signal ("src:sig" or "src:sig[i]"), edge ("rising"|"falling"|"both"), threshold, windowSec, prePercent, mode ("normal"|"single") Configure the hub-side trigger
arm IDLE → ARMED
disarm Any state → IDLE
rearm TRIGGERED → ARMED (used in single mode)
trigStop stopped? (bool; absent = toggle) Suppress auto-rearm in normal mode
zoom reqId, t0, t1, n?, signals? (comma-separated "src:sig" keys; absent = all) Hi-res window read; reply is unicast to the requester. n absent → 2400; n≤0 → no decimation
setMaxPoints maxPoints Resize all ring buffers (applied in push loop; cursors reset)

Events (hub → client, JSON text frames)

type field Fields When sent
sources sources:[{id, label, addr:"host:port", state}] On connect; after add/remove/getSources; on first CONFIG
config sourceId, publishMode, signals:[{name, typeCode, quantType, numDimensions, numRows, numCols, rangeMin, rangeMax, timeMode, samplingRate, timeSignalIdx, unit}] After CONFIG received from source
stats sources:{id:{state, totalReceived, totalLost, rateHz, rateStdHz, fragsPerCycle, bytesPerCycle, cycleAvgMs, cycleStdMs, cycleMinMs, cycleMaxMs, cycleHistMin, cycleHistMax, cycleHist:[20]}} At StatsRate Hz
triggerState state ("idle"|"armed"|"collecting"|"triggered"), mode, stopped, trigTime? On any trigger FSM transition
zoom reqId, signals:{"src:sig":{t:[…], v:[…]}} (t printed %.17g, v %.9g) Unicast reply to zoom
maxPointsUpdated maxPoints After ring buffer resize
pong In reply to ping

Binary Push Frame (version 1, hub → client, binary WS frame)

Little-endian throughout. Sent at PushRate Hz per source; contains only samples new since the previous push (per-signal cursors), decimated to MaxPushPoints per signal.

[1]  version = 1
[1]  sourceIdLen (L)
[L]  sourceId (UTF-8, no null terminator)
[4]  numSignals (uint32 LE)

Per signal:
  [2]  keyLen (uint16 LE) (K)
  [K]  key = signal name (UTF-8; "name[i]" per element for multi-element PACKET signals)
  [4]  pairCount (uint32 LE) (N)
  [N×8] time array (float64 LE, Unix wall-clock seconds)
  [N×8] value array (float64 LE, physical units)

Binary Capture Frame (version 2, hub → client, binary WS frame)

Broadcast once per trigger capture (FSM COLLECTING → TRIGGERED). Each signal is LTTB-decimated to at most 20 000 points.

[1]  version = 2
[8]  trigTime (float64 LE, Unix seconds)
[8]  preSec   (float64 LE)
[8]  postSec  (float64 LE)
[4]  numSignals (uint32 LE)

Per signal:
  [2]  keyLen (uint16 LE) (K)
  [K]  fullKey = "src:sig" (UTF-8)
  [4]  pairCount (uint32 LE) (N)
  [N×8] time array (float64 LE, Unix seconds)
  [N×8] value array (float64 LE)

7. ImGui Desktop Client

Client/streamhub/ is a native C++ desktop oscilloscope that connects directly to StreamHub via WebSocket and provides the same feature set as the browser client.

Technology Stack

Component Library Notes
UI framework Dear ImGui (v1.91.x) Fetched by CMake FetchContent
Time-series plots ImPlot (v0.17.x) Fetched by CMake FetchContent
Window + input SDL2 System package (sdl2)
GPU rendering OpenGL 3.3 core System package
WebSocket client Custom (POSIX) Reuses WSFrame.h, SHA1.h, Base64.h from StreamHub

Features

Feature Implementation
Source sidebar ImGui::TreeNodeEx per source; draggable signal leaves
Drag & drop signals ImGui::BeginDragDropSourceImGui::BeginDragDropTarget on plots
Multi-plot layout 1×1, 1×2, 2×1, 1×3, 3×1, 2×2, 1×4, 4×1 grid; ImGui::BeginTable
Live data streaming Binary v1 frame parser → per-signal ring buffer → ImPlot; live x-axis follows wall clock
LTTB decimation Client-side, before plotting
Pause / resume Per-plot toggle; frozen data still rendered
Zoom ImPlot scroll+drag with history stack (Back / Fit / Reset-to-live); hub WS zoom for hi-res when zoomed in
Cursor A / B ImPlot DragLineX annotations + ΔT and per-trace readouts
Trigger panel Signal/edge/threshold/window/pre%/mode controls; Arm/Disarm/Rearm/Stop; badge (IDLE/ARMED/COLLECTING/TRIGGERED)
Trigger capture view Binary v2 frames rendered relative to trigTime in [-preSec, +postSec], marker at 0
V-scale modes normal / digital / mixed normalisation (parity with web client)
Stats panel Per-source rate/cycle stats + 20-bin cycle-time histogram (ImPlot::PlotBars)
Add / remove source Modal dialog; sends addSource / removeSource JSON
Signal styles Per-signal color picker, line width, markers
Auto-reconnect WSClient receive thread retries every 3 s

Build

# Install SDL2 (Arch Linux)
sudo pacman -S sdl2

cd Client/streamhub
cmake -B build -DCMAKE_BUILD_TYPE=Release
cmake --build build -j$(nproc)
# Produces: build/StreamHubClient

# Run (StreamHub must already be running on port 8090)
./build/StreamHubClient -host 127.0.0.1 -port 8090

7b. Qt Desktop Client

Client/streamhub-qt/ is a native Qt Widgets desktop oscilloscope — a feature/UX-equivalent alternative to the ImGui client, speaking the identical StreamHub WebSocket protocol. It targets deployments that prefer a system Qt runtime over bundled ImGui/SDL2/OpenGL, and it builds against either Qt6 (preferred) or Qt5 for old-Linux back-compatibility.

Technology Stack

Component Library Notes
UI framework Qt Widgets Autodetect Qt6 → Qt5 via find_package(QT NAMES Qt6 Qt5 ...), then Qt${QT_VERSION_MAJOR}:: targets
Time-series plots Custom QPainter (PlotWidget) No QtCharts/QCustomPlot — for ImPlot parity, zero extra deps, EUPL-clean
WebSocket client QtWebSockets QWebSocket (WsClient) Signals delivered on the GUI thread
Wire layer Reused verbatim from ../streamhub/ Protocol.{h,cpp}, SignalBuffer.h (framework-free C++17)

Threading & keyword model

  • Single GUI thread. QWebSocket text/binary signals arrive on the GUI thread, so no locks are needed (unlike the ImGui client's background receive thread + drain). A 60 Hz QTimer (16 ms) drives repaint and panel refresh.
  • QT_NO_KEYWORDS. The reused Protocol.h/Model.h structs have members named signals (e.g. ZoomResponse::signals), which collide with Qt's signals/slots/emit macros. The build defines QT_NO_KEYWORDS; all Qt classes here use Q_SIGNALS: / Q_SLOTS: / Q_EMIT instead. This keeps the shared wire layer unmodified.

Components

File Responsibility
Hub.{h,cpp} Domain model + command builders; owns WsClient; re-emits parsed events as Qt signals
WsClient.{h,cpp} QWebSocket wrapper; auto-reconnect (3 s timer)
PlotWidget.{h,cpp} One QPainter plot; live/stored/trigger modes, cursors, zoom cache
PlotGrid.{h,cpp} Persistent pool of 8 PlotWidgets mounted into nested QSplitters per layout
SourceSidebar.{h,cpp} QTreeWidget of sources/signals; drag source = mime application/x-shq-signal carrying qint32[2] {srcIdx, sigIdx} (LittleEndian)
TriggerBar.{h,cpp} Trigger config/arm controls + state badge
StatsDialog.{h,cpp} Per-source stats table + 20-bin QPainter histogram
HistoryBar.{h,cpp} Live / pan / jump-ago / show-all history navigation
MainWindow.{h,cpp} Toolbars, docks, layout menu, connection controls, 60 Hz tick

Build & run

cd Client/streamhub-qt
cmake -B build              # autodetects Qt6, falls back to Qt5
cmake --build build -j$(nproc)
# Produces: build/StreamHubQtClient

# Run (StreamHub must already be running on port 8090)
./build/StreamHubQtClient --host 127.0.0.1 --port 8090

Use long --host/--port (or short -H/-p). A single-dash -host is misparsed by QCommandLineParser as clustered short flags.

Verified: builds and links cleanly against both Qt6 (6.11) and Qt5 (5.15); the Qt6 binary connects to a live StreamHub (server logs "WebSocket client connected") and runs without error.


8. Go Client Packages

Common/Client/go/udpsprotocol

Pure Go decoder for UDPS packets:

  • Decoder — reassembles fragments; returns complete CONFIG or DATA payloads
  • SignalDescriptor — mirrors UDPSSignalDescriptor from C++
  • DataPacket — decoded signal values with per-sample timestamps

Common/Client/go/wshub

WebSocket broadcast hub:

  • Multiple browser clients share one UDP → WebSocket relay
  • JSON-encodes signal samples and broadcasts to all connected browsers

Client/debugger

Go HTTP server providing the debug web UI:

  • main.go — CLI entry point; starts TCP relay and HTTP server
  • martecontrol.go — TCP client to DebugService; routes commands from browser
  • static/ — Single-page application (HTML/JS/CSS) with Chart.js plots

Client/udpstreamer

Go HTTP server providing the UDP streamer oscilloscope web UI:

  • main.go — CLI entry point; connects to one or more UDPStreamer ports
  • static/ — Full-featured SPA (uPlot plots, LTTB decimation, trigger UI, zoom)
  • Compatible with StreamHub's WebSocket protocol (identical binary frame format)

9. Key Design Decisions

Decision Rationale
Shared UDPSProtocol.h in Common/UDP/ Eliminates duplication between UDPStreamer, UDPSClient, and any future UDPS producer; Go packages decode the same binary layout
DebugServiceI abstract interface Allows DebugService (TCP/UDP) and WebDebugService (HTTP/SSE) to share broker injection without coupling
No STL in C++ MARTe2 components MARTe2 coding convention; FastPollingMutexSem and fixed arrays used throughout
STL allowed in ImGui client Client/streamhub/ is not a MARTe2 component; C++17 + STL reduce boilerplate without any architectural conflict
FastPollingMutexSem on hot path Lowest-latency synchronisation primitive available in MARTe2; avoids OS scheduler involvement
Separate TCPLogger component Decoupled from DebugService; can be used with any MARTe2 application independently
C++ StreamHub instead of Go hub Single binary, no Go toolchain required on deployment host; reuses MARTe2 UDPSClient directly; embeds trigger engine
ImPlot for time-series in native client Best-in-class ImGui plotting extension; handles millions of points with GPU acceleration; native zoom/pan without custom code
Reuse WSFrame.h / SHA1.h / Base64.h in ImGui client Avoids duplicating the WebSocket framing implementation; CMake include path points directly at Source/Applications/StreamHub/
POSIX WebSocket client (no external library) Keeps the client self-contained; RFC 6455 frame parsing is already implemented in WSFrame.h; only handshake SHA-1 and Base64 are needed
Go for browser-based clients Minimal dependencies, single binary, easy cross-compilation; no Node/npm toolchain required for the web UI path