# 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` 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**: `.` (e.g. `App.Data.DDB.Counter`) 2. **GAM alias**: `.In.` or `.Out.` 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. ```cpp 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 ### Packed Signals (Accumulate mode) When a signal has `NumberOfElements > 1` and `SamplingRate` is configured: - `TimeMode = FirstSample`: the `TimeSignal` value anchors the burst timestamp - The Go client interpolates per-sample timestamps as `t0 + e/SamplingRate * dt` - `UDPStreamer` packs 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 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 --- ## 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` 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 |