241 lines
11 KiB
Markdown
241 lines
11 KiB
Markdown
# 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 |
|