Files
MARTe-Integrated-Components/ARCHITECTURE.md
T
Martino Ferrari 617b5bd712 Initial release
2026-05-29 13:29:59 +02:00

241 lines
11 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# 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:
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.
```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<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 |