11 KiB
Architecture
This document describes the internal architecture of the MARTe2 Integrated Components.
1. Repository Overview
┌────────────────────────────────────────────────────────────────────┐
│ 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) │
└───────────────────────────────┘
┌────────────────────────────────────────────────────────────────────┐
│ MARTe2 Application │
│ ┌─────────────────────────────────────────────┐ │
│ │ UDPStreamer DataSource │ │
│ │ - Receives signals from GAMs via IOGAM │ │
│ │ - Serialises to UDPS binary protocol │ │
│ │ - Manages per-client sessions │ │
│ └──────────────────────┬──────────────────────┘ │
└─────────────────────────┼──────────────────────────────────────────┘
UDP 44500
│
┌───────────▼──────────────┐
│ Common/Client/go │
│ udpsprotocol package │
│ (decode CONFIG+DATA) │
└───────────┬──────────────┘
│ WebSocket
┌───────────▼──────────────┐
│ Browser oscilloscope │
│ (Chart.js plots) │
└──────────────────────────┘
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)
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 |
CONFIG Payload
Sent when the signal set changes or a client connects:
[uint32 numSigs]
numSigs × UDPSSignalDescriptor (136 bytes each, packed)
[uint8 publishMode]
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
When QuantizedType is set on a signal, the server maps [RangeMin, RangeMax] to the
full integer range of the quantized type before transmission. The Go client reverses this
using the Scale and Offset fields from UDPSSignalDescriptor.
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:
- Canonical:
<DataSourcePath>.<SignalName>(e.g.App.Data.DDB.Counter) - 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 bytracePushMutex(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:
- Maintains a list of registered client sessions (UDP source address + port)
- On each
Synchronise()call (once per RT cycle):- Serialises all configured signals into one or more UDPS DATA packets
- Sends to all connected clients
- Handles
CONNECT/DISCONNECT/ACKpackets from clients - Sends
CONFIGpackets when the signal list changes or a new client connects - Applies quantization (
QuantizedType,RangeMin,RangeMax) per signal
Packed Signals (Accumulate mode)
When a signal has NumberOfElements > 1 and SamplingRate is configured:
TimeMode = FirstSample: theTimeSignalvalue anchors the burst timestamp- The Go client interpolates per-sample timestamps as
t0 + e/SamplingRate * dt UDPStreamerpacks all elements contiguously with no per-element header
5. Go Client Packages
Common/Client/go/udpsprotocol
Pure Go decoder for UDPS packets:
Decoder— reassembles fragments; returns complete CONFIG or DATA payloadsSignalDescriptor— mirrorsUDPSSignalDescriptorfrom 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 servermartecontrol.go— TCP client toDebugService; routes commands from browserstatic/— Single-page application (HTML/JS/CSS) with Chart.js plots
6. Key Design Decisions
| Decision | Rationale |
|---|---|
Shared UDPSProtocol.h in Common/UDP/ |
Eliminates duplication between UDPStreamer and any future UDP 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++ components | MARTe2 coding convention; Vec<T> used instead of std::vector |
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 |
| Go for clients | Minimal dependencies, single binary, easy cross-compilation; no Node/npm toolchain required |