Eight review findings on FrameDecoder's accumulated-scalar rule. The squeeze that pulls a leading timeline back was expressed as a fraction of the NOMINAL burst width, which cannot converge: inside one timestamps() call the wall clock is frozen, so any positive step raises the lead measured at that instant, and the lead only falls because the wall advances between packets. At the kMinBleedFactor floor the timeline still gained 0.05 * nominal per packet, so a declared SamplingRate of 30 against a producer really flushing 10 samples at 1 kHz ran away without bound (667 s of lead after 1000 s of stream). Cap the burst's total advance at half the wall time really elapsed since this signal's previous burst instead, and the lead strictly falls for any declared rate. SigState gained lastEmittedWall for that reference; lastPacketWall could not be reused because it belongs to packetBurst. The hrt branch contributed zero elapsed for a late datagram but still wrote the hrt reference back to it, so the next packet's delta spanned two intervals and fabricated a whole extra packet of producer time — permanently, since the monotonic clamp discards the correction ClockOffset would have made. Reordering is reachable in production: udps_client.c only counts counter gaps. Simply never regressing the reference is not the fix either, because a producer restart would then freeze the signal forever, so the two are now separated by the size of the backward jump. The hrt branch's clamp was also one-directional, reintroducing on that branch exactly the defect the declared branch's squeeze exists to prevent: a backward wall step (NTP, suspend/resume) left a permanent lead. It now shares the same wall-elapsed cap. Also: anchor an hrt-branch burst's LAST element on arrival, matching the declared branch, so two accumulated scalars in one scope do not sit a burst apart on the shared X axis; treat a non-finite samplingRate off the wire as undeclared, since +inf produced a 0.0/0.0 factor the floor could not catch and turned every stamp NaN; write lastCounter on the hrt branch so duplicate datagrams are dropped there too; and correct two comments that argued for the current code with claims that are false (a counter-gap clamp reaches the opposite outcome, not the same one earlier, and the squeeze's steady state is a sawtooth, not a fixed offset). Seven new tests, each proven non-vacuous by sabotage; 54 pass. Plan document Task 4 re-synced and its stale test count and "agree on the same stream" claim corrected.
MARTe2 Integrated Components
A unified MARTe2 library providing real-time signal streaming and on-the-fly debugging for control applications built with MARTe2.
Overview
This repository integrates two complementary capabilities:
| Capability | Component | Purpose |
|---|---|---|
| Signal streaming | UDPStreamer DataSource |
Continuously stream selected signals to a browser-based oscilloscope over UDP |
| Signal debugging | DebugService Interface |
On-demand signal tracing, value forcing, and conditional breakpoints — zero application code changes required |
| Sine generation | SineArrayGAM |
Generate continuous sine-wave arrays for testing and simulation |
| Time stamping | TimeArrayGAM |
Provide time-reference arrays aligned to an RT cycle |
| Log forwarding | TCPLogger Interface |
Forward REPORT_ERROR log events to TCP clients in real time |
| Integrated client | Common/Client/go |
Go packages for UDPS protocol and WebSocket hub |
| Debug web client | Client/debugger |
Browser-based debug UI communicating with DebugService |
Repository Structure
MARTe_Integrated_components/
├── Common/
│ ├── UDP/ UDPS binary protocol header (shared by all components)
│ └── Client/go/ Go packages: udpsprotocol, wshub
├── Source/Components/
│ ├── DataSources/UDPStreamer/ Real-time UDP signal streaming DataSource
│ ├── GAMs/SineArrayGAM/ Sine-wave array generator GAM
│ ├── GAMs/TimeArrayGAM/ Time-reference array GAM
│ ├── Interfaces/DebugService/ Signal tracing/forcing/breakpoint Interface
│ └── Interfaces/TCPLogger/ TCP log-forwarding LoggerConsumerI
├── Test/
│ ├── GTest/ GTest harness for UDPStreamer unit tests
│ ├── Integration/ Integration tests for DebugService
│ ├── Components/DataSources/UDPStreamer/ UDPStreamer unit tests
│ └── Configurations/ MARTe2 config files for tests and demos
├── Client/debugger/ Go web client for DebugService
└── Docs/ Documentation
Components
UDPStreamer DataSource
Streams MARTe2 signals over UDP using the UDPS binary protocol. Clients register by
sending a CONNECT packet; the server then sends CONFIG (signal metadata) and continuous
DATA packets. Features:
- Optional 16-bit quantization (configurable per signal:
QuantizedType) - Packed high-frequency bursts (
NumberOfElements > 1withSamplingRate) - Automatic packet fragmentation for payloads exceeding
MaxPayloadSize
See Docs/UDPStreamer.md and Docs/Protocol.md.
SineArrayGAM
Generates a continuous float32 sine-wave array every RT cycle. Used as a signal
source for testing and demo applications. Configurable: Frequency, Amplitude,
Phase, SamplingRate, NumberOfElements.
See Docs/SineArrayGAM.md.
TimeArrayGAM
Generates a time-reference uint64 array. Each element holds the timestamp of the
corresponding sample in a packed burst, computed from the RT cycle timestamp and the
configured SamplingRate.
Anchor selects how the burst is placed in time:
Anchor |
out[k] |
|---|---|
FirstSample |
input + k · period |
LastSample |
input − (N−1−k) · period |
Continuous |
input(first cycle) + (n + k) · period |
FirstSample/LastSample re-read the timer each cycle, so a lost RT cycle
(LinuxTimer re-phases with counter += nCycles) punches a whole-period hole
into the time base even though only one array of samples was produced. Use
Continuous when the data signal is itself contiguous (SineArrayGAM never
skips phase): it latches the timer once and then advances an internal sample
counter by N per cycle, like an acquisition card running off its own clock.
DebugService Interface
Instruments a running MARTe2 application without modifying its source code. On
Initialise() it patches the ClassRegistryDatabase to wrap all standard
MemoryMap*Broker types. When RealTimeApplication::ConfigureApplication() runs
afterward the application transparently uses the wrapped brokers.
Capabilities accessible over TCP (port 8080 by default):
DISCOVER— enumerate all signals with type and alias metadataTRACE— enable/disable high-speed UDP telemetry per signal (with decimation)FORCE/UNFORCE— inject persistent values into signals on the RT pathBREAK— set conditional breakpoints (>,<,==, etc.)PAUSE/RESUME/STEP— execution steppingTREE/INFO/LS— live ORD navigationVALUE— read current signal value on demandMSG— send MARTe2Messageto any ORD object
High-speed signal telemetry is streamed as UDP binary datagrams (port 8081).
Logs are forwarded via TcpLogger (port 8082).
See Docs/DebugService.md.
TCPLogger Interface
A LoggerConsumerI that forwards every MARTe2 REPORT_ERROR call to up to 8 TCP
clients on a configurable port. Works as a sidecar to DebugService.
StreamHub Application
Headless C++ hub (Source/Applications/StreamHub/) that aggregates multiple
UDPStreamer sources and serves them to oscilloscope clients over WebSocket
(port 8090): ring buffers with wall-clock time calibration, LTTB decimation,
hub-side trigger engine, per-window zoom. Clients: browser SPA
(Client/webui + Client/udpstreamer/static) and native ImGui desktop client
(Client/streamhub). Demo: ./run_streamhub.sh -w -g; E2E test:
Test/E2E/suite/run_e2e.sh.
See Docs/StreamHub-UserGuide.md, Docs/StreamHub-API.md and
Docs/StreamHub-Developer.md.
UDPS Protocol
The Common/UDP/UDPSProtocol.h header defines the shared binary wire format used by
both UDPStreamer and DebugService. It is intentionally free of MARTe2-specific
dependencies so it can also be used by Go clients (via Common/Client/go/udpsprotocol).
See Docs/Protocol.md.
Build
Prerequisites
- MARTe2 built and installed (set
MARTe2_DIR) - MARTe2-components built and installed (set
MARTe2_Components_DIR) - GCC toolchain, make
- Go 1.21+ (for clients)
Setup
# Edit env.sh to set MARTe2_DIR and MARTe2_Components_DIR, then:
source env.sh
Build all C++ components
make -f Makefile.gcc core
Build and run tests
make -f Makefile.gcc test
source env.sh
./Build/x86-linux/Test/Integration/IntegrationTests
Build Go clients
# Integrated UDPS web client
cd Common/Client/go && go build ./...
# Debug web client
cd Client/debugger && go build ./...
Clean
make -f Makefile.gcc clean
Quick Start
1. Stream signals with UDPStreamer
Add to your MARTe2 config:
+Streamer = {
Class = UDPStreamer
Port = 44500
MaxPayloadSize = 1400
Signals = {
Voltage = { Type = float32; Unit = "V" }
}
}
Launch the web client:
cd Common/Client/go
./udpstreamer-webui --streamer 127.0.0.1:44500 --listen :8080
Open http://localhost:8080 and drag signals onto plots.
2. Debug a running application with DebugService
Add to your MARTe2 config (as a sibling of +App):
+DebugService = {
Class = DebugService
ControlPort = 8080
UdpPort = 8081
LogPort = 8082
}
+Logger = { Class = TcpLogger; Port = 8082 }
Launch the debug client:
cd Client/debugger
./debugger --listen :9090
Open http://localhost:9090, explore the object tree, trace signals, force values.
Documentation
| Document | Contents |
|---|---|
Docs/Protocol.md |
UDPS binary wire protocol specification |
Docs/UDPStreamer.md |
UDPStreamer DataSource configuration reference |
Docs/UDPS-C-Client.md |
Standalone C/C++ UDPS receiver library (Common/Client/c) |
Docs/SineArrayGAM.md |
SineArrayGAM configuration reference |
Docs/DebugService.md |
DebugService TCP API and architecture |
Docs/Tutorial.md |
Step-by-step tutorial covering both components |
Docs/WebUI.md |
Web client user guide |
Docs/StreamHub-UserGuide.md |
StreamHub oscilloscope user guide (web + ImGui clients) |
Docs/StreamHub-API.md |
StreamHub WebSocket protocol (commands, events, binary frames) |
Docs/StreamHub-Developer.md |
StreamHub internals, threading, time base, build & E2E tests |
ARCHITECTURE.md |
System architecture overview |
License
Licensed under the EUPL v1.1 (see individual source files for copyright notices).