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

494 lines
13 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.
# Tutorial: MARTe2 Integrated Components
This guide covers two complementary use-cases:
- **Part A** — Real-time signal streaming with `UDPStreamer` and the web client
- **Part B** — Signal tracing, forcing, and breakpoints with `DebugService`
---
## Part A: Streaming Signals with UDPStreamer
### A.1 Setting up the environment
2. Running the demo application
3. Visualising signals in the browser
4. Adding UDPStreamer to your own MARTe2 application
5. Streaming high-frequency packed signals
**Prerequisites:** MARTe2 and MARTe2-components must already be built.
See the [MARTe2 installation guide](https://vcis.f4e.europa.eu/marte2-docs/) if needed.
---
## 1. Environment Setup
Edit `marte_env.sh` in the repository root to point at your MARTe2 installations:
```bash
# marte_env.sh (key variables)
export MARTe2_DIR="$HOME/workspace/MARTe2"
export MARTe2_Components_DIR="$HOME/workspace/MARTe2-components"
```
Then source it in your shell:
```bash
cd /path/to/MARTe_IO_components
source marte_env.sh
```
Verify the environment is correct:
```bash
echo $MARTe2_DIR
ls $MARTe2_DIR/Build/x86-linux/App/MARTeApp.ex # should exist
```
---
## 2. Running the Demo Application
The demo is in `Test/MARTeApp/`. It runs a 10 kHz MARTe2 application that streams:
- **Counter** and **Time** — scalar counters from the Linux timer
- **Sine1** — 1 Hz sine wave (float32, amplitude 10, quantized to uint16 on wire)
- **Sine2** — 0.3 Hz sine wave (float32, amplitude 5, raw float32 on wire)
- **Ch1**, **Ch2** — 1 kHz sine bursts packed as 1000 samples/packet (10 MSps)
Start everything with one command:
```bash
cd Test/MARTeApp
./run.sh --webui
```
The script will:
1. Build the UDPStreamer shared library.
2. Build the Go WebUI binary (first run only).
3. Start the WebUI relay on `http://localhost:8080`.
4. Launch the MARTe2 application.
Press `Ctrl+C` to stop both processes.
---
## 3. Visualising Signals in the Browser
Open `http://localhost:8080` in any modern browser.
### Add your first plot
1. Click **+ Add Plot** in the toolbar.
2. A blank plot panel appears with a "Drop signals here" hint.
### Plot a signal
1. In the left sidebar find **Sine1** (listed as `Sine1 · f32`).
2. Click and drag it onto the plot panel.
3. The sine wave appears immediately.
### Overlay multiple signals
Drag **Sine2** onto the same plot — it is added as a second trace.
### Adjust the time window
Use the **Window** dropdown in the top bar to change the rolling display window
(1 s, 5 s, 10 s, 30 s, 60 s).
### Plot layout
Use the layout buttons (`1×1`, `2×1`, `2×2`, …) to split the screen into multiple
plot panels. Each panel is independent — drag different signals onto each.
### High-frequency signals
Drag **Ch1** (shown as `Ch1 · [1000] f32`) onto a plot. Each UDP packet carries
1000 samples at 10 MSps; the WebUI reconstructs per-sample timestamps and displays
the continuous waveform.
### Export data
Click **⬇** on any plot to download the visible window as a CSV file.
---
## 4. Adding UDPStreamer to Your Own Application
### Step 1 — Declare the DataSource
Add UDPStreamer to the `+Data` section of your MARTe2 configuration:
```
+Data = {
Class = ReferenceContainer
DefaultDataSource = DDB
+DDB = { Class = GAMDataSource }
+Streamer = {
Class = UDPStreamer
Port = 44500
MaxPayloadSize = 1400
Signals = {
Voltage = {
Type = float32
Unit = "V"
RangeMin = -10.0
RangeMax = 10.0
QuantizedType = uint16 // 16-bit quantized on wire
}
Current = {
Type = float32
Unit = "A"
}
}
}
+Timings = { Class = TimingDataSource }
}
```
### Step 2 — Route signals with IOGAM
Use IOGAM to copy signals from your inter-GAM DDB into the Streamer:
```
+StreamerGAM = {
Class = IOGAM
InputSignals = {
Voltage = { DataSource = DDB; Type = float32 }
Current = { DataSource = DDB; Type = float32 }
}
OutputSignals = {
Voltage = { DataSource = Streamer; Type = float32 }
Current = { DataSource = Streamer; Type = float32 }
}
}
```
Add `StreamerGAM` at the **end** of the thread's `Functions` list so it runs after
your control GAMs have written their outputs.
### Step 3 — Add the library to LD_LIBRARY_PATH
In your run script, add the UDPStreamer build directory:
```bash
export LD_LIBRARY_PATH="/path/to/Build/x86-linux/Components/DataSources/UDPStreamer:$LD_LIBRARY_PATH"
```
### Step 4 — Start the WebUI and connect
```bash
# From the Client/WebUI directory:
./udpstreamer-webui --streamer 127.0.0.1:44500 --listen :8080 --clientport 44900
```
Open `http://localhost:8080`, drag your signals onto a plot, and you're done.
---
## 5. Streaming High-Frequency Packed Signals
This section shows how to stream 1000 samples per RT cycle at 1 MSps.
### Overview
At 1 kHz RT rate with 1000 samples per cycle the effective sample rate is 1 MSps.
Each UDP packet carries a burst of 1000 samples; the client reconstructs timestamps
using the anchor timestamp and `SamplingRate`.
### Step 1 — Generate burst data with SineArrayGAM
`SineArrayGAM` (bundled in `UDPStreamer.so`) produces a continuous float32 array:
```
+Ch1GAM = {
Class = SineArrayGAM
Frequency = 1000.0 // 1 kHz signal
Amplitude = 1.0
Phase = 0.0
SamplingRate = 1000000.0 // must match Streamer config below
OutputSignals = {
Ch1 = {
DataSource = DDB
Type = float32
NumberOfDimensions = 1
NumberOfElements = 1000
}
}
}
```
### Step 2 — Add a time reference signal
Add a scalar time signal that will anchor the first sample's timestamp:
```
+TimerGAM = {
Class = IOGAM
InputSignals = {
Time = { DataSource = Timer; Type = uint32; Frequency = 1000 }
}
OutputSignals = {
Time = { DataSource = DDB; Type = uint32 }
}
}
```
### Step 3 — Configure UDPStreamer for packed signals
```
+Streamer = {
Class = UDPStreamer
Port = 44500
MaxPayloadSize = 1400
Signals = {
Time = { Type = uint32; Unit = "us" } // time reference (scalar)
Ch1 = {
Type = float32
NumberOfDimensions = 1
NumberOfElements = 1000
Unit = "V"
TimeMode = FirstSample // Time = timestamp of first sample
TimeSignal = Time
SamplingRate = 1000000.0 // Hz
}
}
}
```
### Step 4 — Wire everything in the thread
```
+Thread1 = {
Class = RealTimeThread
Functions = { TimerGAM Ch1GAM StreamerGAM }
}
```
Where `StreamerGAM` is the IOGAM that copies `Time` and `Ch1` from DDB to Streamer.
### Step 5 — Fragmentation note
A single 1000-element float32 channel plus a uint32 time signal produces:
```
payload = 8 B (HRT) + 4 B (Time/uint32) + 4000 B (float32×1000) = 4012 B
```
With `MaxPayloadSize = 1400`:
```
fragments = ceil(4012 / 1383) = 3 datagrams per cycle
```
At 1 kHz that is 3000 UDP datagrams/second per channel — well within typical LAN capacity.
### Step 6 — Create the SineArrayGAM symlink
MARTe2 tries to `dlopen("SineArrayGAM.so")` the first time it encounters the class.
Create the symlink in your build directory:
```bash
UDPSTREAMER_LIB=/path/to/Build/x86-linux/Components/DataSources/UDPStreamer
ln -sf "${UDPSTREAMER_LIB}/UDPStreamer.so" "${UDPSTREAMER_LIB}/SineArrayGAM.so"
```
---
## 6. Writing a Custom UDP Client
A minimal Python client that receives and prints signal data:
```python
import socket, struct
MAGIC = 0x53504455
HDR = struct.Struct('<IBHHI') # 17 bytes: magic, type, counter, fragIdx, total, payloadBytes
SERVER = ('127.0.0.1', 44500)
MY_PORT = 44900
sock = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
sock.bind(('', MY_PORT))
sock.settimeout(5.0)
# Send CONNECT
sock.sendto(HDR.pack(MAGIC, 3, 0, 0, 1, 0), SERVER)
print("CONNECT sent")
reassembly = {}
while True:
data, _ = sock.recvfrom(65536)
if len(data) < 17:
continue
magic, ptype, counter, frag_idx, total_frags, payload_bytes = HDR.unpack_from(data)
if magic != MAGIC:
continue
payload = data[17:17 + payload_bytes]
# Accumulate fragments
bucket = reassembly.setdefault((ptype, counter), {})
bucket[frag_idx] = payload
if len(bucket) < total_frags:
continue
full = b''.join(bucket[i] for i in range(total_frags))
del reassembly[(ptype, counter)]
if ptype == 1: # CONFIG
num_sigs = struct.unpack_from('<I', full)[0]
print(f"CONFIG: {num_sigs} signals")
elif ptype == 0: # DATA
hrt = struct.unpack_from('<Q', full)[0]
print(f"DATA counter={counter} hrt={hrt} payload={len(full)}B")
```
For a full-featured client with CONFIG parsing and dequantization see
[`Client/WebUI/protocol.go`](../Client/WebUI/protocol.go) (Go)
and the [Protocol reference](Protocol.md).
---
---
## Part B: Debugging with DebugService
### B.1 Add DebugService to Your Config
Add it as a **sibling** of the `+App` node (not inside it):
```text
+DebugService = {
Class = DebugService
ControlPort = 8080
UdpPort = 8081
LogPort = 8082
}
+Logger = {
Class = TcpLogger
Port = 8082
}
+App = {
Class = RealTimeApplication
...
}
```
Call `SetFullConfig(cdb)` after `ConfigureApplication()` to enable the `CONFIG` and `INFO` commands.
### B.2 Start the Debug Web Client
```bash
cd Client/debugger
go build ./...
./debugger --listen :9090
```
Open `http://localhost:9090` in any browser.
### B.3 Exploring the Object Tree
The **Application Tree** panel on the left mirrors your live MARTe2 `ObjectRegistryDatabase`.
1. Expand `Root → App → Data` to find data sources.
2. Click **Info** next to any node to see its class, config, and signals.
3. Click **List** to show immediate children.
### B.4 Real-Time Signal Tracing
1. Locate a signal, e.g. `Root.App.Data.Timer.Counter`.
2. Click **Trace**. The signal appears in the **Traced Signals** list with its live last value.
3. Click **Plot** to open it in the real-time graph. Use **Follow** to keep the time axis scrolling.
4. To set decimation (e.g. every 10th sample):
```
TRACE App.Data.Timer.Counter 1 10
```
5. Click **Trace** again (or send `TRACE … 0`) to stop.
### B.5 Signal Forcing
1. Find a signal, e.g. `Root.App.Data.DDB.Counter`.
2. Click **Force**, enter a value (e.g. `9999`), click **Apply**.
3. The signal is locked at that value every RT cycle.
4. Click **Unforce** to release.
### B.6 Conditional Breakpoints
1. Click **Break** next to a signal.
2. Select an operator (`>`, `<`, `==`, `>=`, `<=`, `!=`) and enter a threshold.
3. When the condition fires, the application pauses. The status bar shows **PAUSED**.
4. Use **Step** to advance one cycle at a time, or **Resume** to continue.
5. Click **Break OFF** to clear.
### B.7 Execution Stepping
While paused (after a breakpoint or manual **Pause**):
1. Enter a step count (e.g. `5`) and click **Step**.
2. The RT loop runs exactly 5 output-broker cycles, then pauses again.
3. The status SSE event (`{"type":"status","remaining":…}`) keeps the UI updated.
### B.8 Scripted / Programmatic Access
Both `DebugService` and the web client accept plain-text TCP commands:
```bash
# Direct TCP
echo -e "DISCOVER\nTRACE App.Data.DDB.Counter 1" | nc localhost 8080
# Via web client API
curl -s -X POST http://localhost:9090/api/command \
-H "Content-Type: text/plain" \
-d "DISCOVER"
```
See `Docs/DebugService.md` for the full command reference.
---
## Part C: Using UDPStreamer and DebugService Together
Both can run simultaneously in the same application:
```text
+DebugService = { Class = DebugService; ControlPort = 8080; UdpPort = 8081; LogPort = 8082 }
+Logger = { Class = TcpLogger; Port = 8082 }
+App = {
Class = RealTimeApplication
+Data = {
Class = ReferenceContainer
DefaultDataSource = DDB
+DDB = { Class = GAMDataSource }
+Streamer = { Class = UDPStreamer; Port = 44500; MaxPayloadSize = 1400; ... }
+Timings = { Class = TimingDataSource }
}
...
}
```
- `UDPStreamer` provides continuous high-speed streaming of selected signals.
- `DebugService` provides on-demand tracing, forcing, and breakpoints for any signal.
- Both use the UDPS binary protocol format (see `Docs/Protocol.md`).
---
## Troubleshooting
| Symptom | Cause | Fix |
|---------|-------|-----|
| `Failed dlopen(): UDPStreamer.so` | Library not in `LD_LIBRARY_PATH` | Source `env.sh`; add build dir |
| `Failed dlopen(): SineArrayGAM.so` | Symlink missing | `ln -sf UDPStreamer.so SineArrayGAM.so` in build dir |
| WebUI shows "No data" | UDPStreamer not running / wrong port | Check port numbers; check MARTe2 logs |
| Plots only show ~167 ms of HF data | Browser buffer too small | Reduce decimation or increase `TEMPORAL_CAP` in JS |
| Fragmentation error / missing data | MTU too small | Reduce `MaxPayloadSize` to 1200 or smaller |
| DebugService: DISCOVER returns empty | `PatchRegistry` called too late | Ensure `DebugService` is initialised before `ConfigureApplication()` |
| Integration tests timeout | MARTe2 libs not on `LD_LIBRARY_PATH` | Source `env.sh` before running tests |