Files
MARTe-Integrated-Components/Docs/WebUI.md
T
Martino FerrariandClaude Sonnet 4.6 686fc2ce7d docs: fix three review findings in calibration documentation
- Finding 1 (Critical): replace "16 chars" with "16 UTF-8 bytes" in the
  setCalibration unit field description (StreamHub-API.md) and the Cal·Unit
  toolbar row (WebUI.md); note that multi-byte characters consume more than
  one byte and that truncation never splits a character.

- Finding 2 (Important): correct the claim that a sources broadcast after
  reloadConfig is conditional on new sources being added — that is true only
  of the Go hub. The C++ hub calls BroadcastSources() unconditionally on
  success. Both the reloadConfig command description and the configReloaded
  event description in StreamHub-API.md are updated; the Reload bullet in
  WebUI.md is updated with a brief note. Clients must tolerate an unsolicited
  sources frame after any reload.

- Finding 3 (Minor): the configSaved failure example used "no SourcesFile
  configured", which matches neither hub. Corrected to the C++ form
  "no sources file configured" and added a note that the exact error text
  is not part of the protocol contract (Go uses "no sources-file configured").

Source evidence: calibration.go (maxUnitLen, len(), rune-repair loop),
StreamHub.cpp (kMaxUnitLen, byte strncpy, HandleReloadConfig unconditional
BroadcastSources, HandleSaveSources error string).

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-08-17 07:34:48 +02:00

338 lines
14 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.
# WebUI Client
The WebUI is a Go binary that acts as a bridge between UDPStreamer and any web browser.
It receives UDP packets from UDPStreamer, reassembles fragmented data, and re-publishes
decoded signal values over a WebSocket to the browser. The browser renders live plots
using [uPlot](https://github.com/leeoniya/uPlot).
```
MARTe2 RT app
│ UDP (binary protocol)
udpstreamer-webui (Go)
│ WebSocket (binary frames)
Browser (index.html + uPlot)
```
---
## Building
```bash
cd Client/WebUI
go build -o udpstreamer-webui ./...
```
Requires Go ≥ 1.21. The only external dependency is `gorilla/websocket`
(declared in `go.mod`; fetched automatically by `go build`).
---
## Running
```bash
./udpstreamer-webui \
--streamer 127.0.0.1:44500 \ # address:port of the UDPStreamer server
--listen :8080 \ # HTTP / WebSocket listen address
--clientport 44900 # local UDP port for receiving from streamer
```
Open `http://localhost:8080` in any modern browser.
### Flags
| Flag | Default | Description |
|------|---------|-------------|
| `--streamer` | `127.0.0.1:44500` | UDP address of the UDPStreamer DataSource |
| `--listen` | `:8080` | HTTP server bind address |
| `--clientport` | `44900` | Local UDP port for receiving data |
---
## Browser UI
### Layout
```
┌───────────────────────────────────────────────────────────────────────────────┐
│ ☰ UDP Scope │ ⊞ 1×1 ▾ │ A: — B: — ΔT: — │ Window: [5s▾] Fit ⬇ CSV ⚡ Trigger ⏸ Pause │
├──────────────────┬────────────────────────────────────────────────────────────┤
│ Signals │ [Plot title] ○ ○ ○ ← signal badges │
│──────────────────│────────────────────────────────────────────────────────── │
│ Counter · u32 │ ┌─────────────────────┐ ┌─────────────────────────────┐ │
│ Time · f64 │ │ Plot 1 │ │ Plot 2 │ │
│ Sine1 · f32 │ │ (drop signals here) │ │ (drop signals here) │ │
│ Sine2 · f32 │ │ │ │ │ │
│ Ch1 · [1000] f32 │ └─────────────────────┘ └─────────────────────────────┘ │
└──────────────────┴────────────────────────────────────────────────────────────┘
│ ● Streaming [📊 Stats] v1.0.0 │
└───────────────────────────────────────────────────────────────────────────────┘
```
### Signal Sidebar
Signals received in the CONFIG packet are listed in the sidebar:
- **Scalar signals** — appear as single draggable items (e.g. `Sine1 · f32`).
- **Temporal arrays** — high-frequency burst signals with `TimeMode ≠ PacketTime`.
Displayed as a single draggable item showing element count: `Ch1 · [1000] f32`.
- **Spatial arrays** — `TimeMode = PacketTime` arrays are shown as an expandable
group; individual elements (`Ch1[0]`, `Ch1[1]`, …) can be dragged independently.
The unit badge next to each signal shows the calibration's unit override when one
is set, and the streamer's own unit otherwise.
At the bottom of the sidebar, the collapsible **Sources & Config** section holds:
- the `host:port`, label, multicast group and data port inputs plus **Connect**,
which adds a source at runtime;
- **Save** — writes the source list and the whole calibration table to the hub's
config file;
- **Reload** — re-reads that file. Calibration is replaced wholesale (so unsaved
edits are discarded), sources present in the file but not running are added,
and no running source is stopped or reconnected. The hub may send an updated
`sources` list even when nothing changed (see the API doc for the per-hub
difference);
- a status line showing the written path on success or the hub's error text on
failure.
Click the sidebar toggle button (☰) to collapse/expand the signal list.
### Adding Plots
1. Select a layout from the layout menu (⊞ button in the top bar).
2. Drag a signal from the sidebar onto a plot panel.
3. Multiple signals can be overlaid on the same plot.
4. Click the **×** inside a signal badge to remove a trace.
### Plot Layout
The plot grid supports multiple layouts selectable from the layout menu (⊞):
| Layout | Description |
|--------|-------------|
| 1×1 | Single plot |
| 2×1 | Two columns |
| 1×2 | Two rows |
| 2×2 | Four plots |
| 3×1 | Three columns |
| … | More layouts available |
**Resizing plots**: When multiple plots are shown, drag the dividers between them
to resize. Vertical dividers resize column widths; horizontal dividers resize row
heights. Sizes are stored as fractional grid units (fr).
### Plot Configuration
Click the **plot title** to open the configuration toolbar:
- **Title** — edit the plot's display name.
- **Mode** — select the plot display mode:
- **Normal** — standard time-series oscilloscope view (default).
- **Mixed** — signals are arranged in horizontal bands (like digital mode), but
each signal can independently be displayed as analog (auto-scaled within its band)
or digital (quantized to high/low within its band).
- **Digital** — logic-analyzer style; each signal occupies a fixed horizontal band
and is quantized to high/low based on its data range midpoint as threshold.
### Signal Badges and Selection
Each signal assigned to a plot appears as a colored badge in the plot header.
- **Click a badge** — selects the signal and opens the V-Scale toolbar for that
signal. The selected signal is drawn on top with increased line width.
- **Click again** — deselects the signal and closes the V-Scale toolbar.
- **Right-click a badge** — opens the style context menu (color, line width, dash
style, markers).
- **Click ×** on a badge — removes the signal from the plot.
### V-Scale Toolbar
When a signal is selected (via badge click), an inline toolbar appears below the
plot header showing per-signal vertical scale controls:
| Control | Description |
|---------|-------------|
| **Auto** | Automatically fits the signal vertically |
| **Range** | Shows V/div and Pos controls for manual positioning |
| **Manual** | Fixed V/div with free positioning |
| **V/div** | Volts (or units) per division |
| **Pos (div)** | Screen position in divisions (draggable offset marker on Y axis) |
| **Type** (Mixed mode only) | Toggle between **Analog** and **Digital** for this signal |
| **Cal · Scale** | Data calibration gain. `value = raw × Scale + Offset` |
| **Cal · Offset** | Data calibration bias, in calibrated units |
| **Cal · Unit** | Overrides the unit reported by the streamer (max 16 UTF-8 bytes; a multi-byte character such as `°C` counts as more than one byte) |
| **Reset** | Clears this signal's calibration (`Scale = 1`, `Offset = 0`, no unit override) |
| **✕** | Close the toolbar and deselect the signal |
Offset markers (small triangles on the Y axis) show each signal's position and can
be dragged to reposition signals without opening the toolbar.
**Calibration vs. V/div and Offset.** They are different things. V/div and Offset
are a *display* transform: they move and stretch the trace on screen. Calibration
changes *the value itself* — the plot, the Y-axis tick labels, the cursor and
hover readouts, the CSV export and the trigger threshold all report
`raw × Scale + Offset` in the calibrated unit. V/div is then read as "calibrated
units per division" and Offset as "the calibrated value at screen centre".
The calibration header names the **base** signal and its element count, because
one entry covers every element of an array — opening the toolbar on `Adc[3]` and
editing the calibration moves all of `Adc`.
Calibration is keyed by the source's **label**, is shared with every other
browser connected to the same hub, and is not persisted until you press **Save**
in the Sources & Config section. It is mirrored to `localStorage` so it survives
a page reload even against a hub with no config file.
### Plot Controls
| Control | Action |
|---------|--------|
| **⏸ / ▶** (per plot) | Pause / resume that plot; paused plots allow zoom and pan |
| **⬇** (per plot) | Export all visible traces to CSV |
| **🗑** (per plot) | Delete the plot |
| **⏸ Pause** (top bar) | Pause all plots simultaneously |
| **↺ Auto** (top bar) | Resume all paused plots and snap back to live view |
| **Window** (top bar) | Adjust the rolling time window (1 s 60 s) |
| **Fit** (top bar) | Fit all plots to their current data range |
| **⬇ CSV** (top bar) | Export all signals from all plots to CSV |
| Layout button | Switch between grid layouts |
| Sidebar **☰** | Toggle the signal list panel |
### Cursor System
Two vertical cursors (A and B) can be placed on plots:
- Enable with the **Cursor** button (shown when a plot is paused/zoomed).
- The top bar readout shows **A**, **B**, and **ΔT** (time between cursors).
- Cursors follow the mouse within the plot area.
### Zoom
- **Drag** left or right on a paused plot to zoom into a time range.
- **← Back** button steps back through zoom history.
- **Fit** returns to the full data view.
- When zooming into a region with few or no data points, the nearest data points
outside the zoom window are included so that connecting lines are drawn across
the view rather than showing blank space.
### Trigger System
Click **⚡ Trigger** in the top bar to open the trigger bar:
| Control | Description |
|---------|-------------|
| **Signal** | Select the trigger source signal |
| **Edge** | Rising ↑, Falling ↓, or Both ↕ |
| **Threshold** | Trigger level |
| **Window** | Capture duration after trigger (100 µs 10 s) |
| **Pre** | Pre-trigger buffer percentage (0100%) |
| **Mode** | **Normal** (re-arms automatically) or **Single** (fires once) |
| **Rearm** | Manually re-arm the trigger |
| **Stop** | Cancel waiting trigger |
For array signals, clicking the trigger signal selector prompts for the element
index to use as the trigger source.
### Status Bar
The status bar at the bottom shows:
- **LED indicator**: red (disconnected), orange pulsing (connected, no data), green pulsing (streaming).
- **Status text**: connection state and data age.
- **📊 Stats** button: opens the source statistics panel (packet counts, rates, errors).
- **Build version**: server build tag.
---
## Data Buffering
Signal buffers are sized based on the signal's configured sampling rate from the
CONFIG packet:
```
capacity = min(600 000, ceil(samplingRate × 60 s × 1.5))
```
This ensures up to 60 seconds of history is retained for any signal, regardless of
sample rate. If a signal's sampling rate is not configured, a default capacity of
100 000 samples is used. Temporal arrays (burst signals) use a fixed capacity of
500 000 samples.
Buffers grow automatically during streaming if a higher effective sample rate is
detected. Existing data is preserved during growth.
---
## Architecture (Go side)
### Goroutines
```
main()
├── hub.Run() ← event loop: register/unregister WS clients, batch data at 30 Hz
├── udpClient.Run() ← reconnects on silence; parses UDP packets; feeds hub.dataCh
└── http.ListenAndServe()
├── GET / ← serves embedded static files
└── GET /ws ← upgrades to WebSocket; launches per-client read/write pumps
```
### WebSocket Message Format
Two message types are sent from server to browser.
#### `config` message (JSON)
Sent immediately when a browser client connects (if a CONFIG has been received from
UDPStreamer) and whenever UDPStreamer sends a new CONFIG packet.
```json
{
"type": "config",
"signals": [
{
"name": "Sine1",
"typeCode": 8,
"quantType": 3,
"numDimensions": 0,
"numRows": 1,
"numCols": 1,
"rangeMin": -10.0,
"rangeMax": 10.0,
"timeMode": 0,
"samplingRate": 1000.0,
"timeSignalIdx": 4294967295,
"unit": "V"
}
]
}
```
#### `data` message (binary)
Sent at ≤ 30 Hz, batching all UDP packets received since the last tick.
Uses a compact binary format: a fixed header followed by per-signal blocks.
Each signal block contains:
- Signal name (length-prefixed)
- Number of samples
- Timestamp array (float64 LE, Unix seconds)
- Value array (float64 LE)
For **temporal arrays**, each packet contributes `N` samples (one per array element).
For **spatial arrays**, keys are `"Ch1[0]"`, `"Ch1[1]"`, etc.
---
## Reconnection Behaviour
- **UDP reconnect:** The Go client reconnects to UDPStreamer automatically after 5 s
of silence. This handles MARTe2 restarts transparently.
- **WebSocket keepalive:** The server sends a WebSocket ping every 30 s. The browser
auto-responds; if no pong is received within 10 s the connection is closed and the
browser reconnects with exponential backoff (starting at 1 s, capped at 30 s).
- **Buffer preservation:** Browser-side signal buffers are only reset when the signal
layout changes (name, type, or dimensions differ). A reconnect with the same CONFIG
keeps existing data visible in the plots.