WebUI: resizable plots, digital/mixed modes, buffer fix, zoom brackets, updated docs
- Resizable plot grid: drag handles between plots adjust column/row fr sizes - Plot configuration toolbar (click title): edit title, select Normal/Mixed/Digital mode - Digital mode: logic-analyzer banded layout, signals quantized to hi/lo per band - Mixed mode: banded layout where each signal is independently analog or digital - Per-signal vscale toolbar embedded inline below plot header (badge click to open) - Active signal highlighted in foreground with increased line width - Signal offset markers draggable on Y axis; per-plot vscale state isolation - Buffer sizing based on signal sampling rate (up to 60s @ configured rate) - growBuffer: live buffer expansion without data loss on window/rate change - Zoom bracket lines: nearest out-of-range points included for continuity - Updated Docs/WebUI.md to reflect current uPlot-based implementation Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
+159
-47
@@ -3,16 +3,16 @@
|
||||
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 [Plotly.js](https://plotly.com/javascript/).
|
||||
using [uPlot](https://github.com/leeoniya/uPlot).
|
||||
|
||||
```
|
||||
MARTe2 RT app
|
||||
│ UDP (binary protocol)
|
||||
▼
|
||||
udpstreamer-webui (Go)
|
||||
│ WebSocket (JSON)
|
||||
│ WebSocket (binary frames)
|
||||
▼
|
||||
Browser (index.html + Plotly.js)
|
||||
Browser (index.html + uPlot)
|
||||
```
|
||||
|
||||
---
|
||||
@@ -55,18 +55,19 @@ Open `http://localhost:8080` in any modern browser.
|
||||
### Layout
|
||||
|
||||
```
|
||||
┌─────────────────────────────────────────────────────────┐
|
||||
│ MARTe2 UDP Streamer ● Streaming Window: [5 s▾] ⏸ │ ← top bar
|
||||
├──────────┬──────────────────────────────────────────────┤
|
||||
│ Signals │ Plots [1×1][2×1][2×2]… [+Add] │
|
||||
│──────────│──────────────────────────────────────────────│
|
||||
│ Counter │ ┌─────────────────┐ ┌──────────────────┐ │
|
||||
│ Time │ │ Plot 1 │ │ Plot 2 │ │
|
||||
│ Sine1 │ │ (drop signals) │ │ (drop signals) │ │
|
||||
│ Sine2 │ │ │ │ │ │
|
||||
│ Ch1[1000]│ └─────────────────┘ └──────────────────┘ │
|
||||
│ Ch2[1000]│ │
|
||||
└──────────┴──────────────────────────────────────────────┘
|
||||
┌───────────────────────────────────────────────────────────────────────────────┐
|
||||
│ ☰ 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
|
||||
@@ -79,12 +80,73 @@ Signals received in the CONFIG packet are listed in the sidebar:
|
||||
- **Spatial arrays** — `TimeMode = PacketTime` arrays are shown as an expandable
|
||||
group; individual elements (`Ch1[0]`, `Ch1[1]`, …) can be dragged independently.
|
||||
|
||||
Click the sidebar toggle button (☰) to collapse/expand the signal list.
|
||||
|
||||
### Adding Plots
|
||||
|
||||
1. Click **+ Add Plot** in the toolbar.
|
||||
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 **×** badge to remove a trace.
|
||||
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 |
|
||||
| **✕** | 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.
|
||||
|
||||
### Plot Controls
|
||||
|
||||
@@ -93,18 +155,76 @@ Signals received in the CONFIG packet are listed in the sidebar:
|
||||
| **⏸ / ▶** (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 All** (top bar) | Pause all plots simultaneously |
|
||||
| **⏸ 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) |
|
||||
| Layout buttons | Switch between 1×1, 2×1, 1×2, 2×2, 3×1, … grid layouts |
|
||||
| Sidebar **←** | Collapse the signal list to maximise plot area |
|
||||
| **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 |
|
||||
|
||||
### Status LED
|
||||
### Cursor System
|
||||
|
||||
| Colour | Meaning |
|
||||
|--------|---------|
|
||||
| Red (solid) | No WebSocket connection to browser |
|
||||
| Orange (pulsing) | WebSocket connected but no data received in > 1 s |
|
||||
| Green (pulsing) | Data is being received normally |
|
||||
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 (0–100%) |
|
||||
| **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.
|
||||
|
||||
---
|
||||
|
||||
@@ -117,15 +237,15 @@ 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 index.html
|
||||
├── GET / ← serves embedded static files
|
||||
└── GET /ws ← upgrades to WebSocket; launches per-client read/write pumps
|
||||
```
|
||||
|
||||
### WebSocket Message Format
|
||||
|
||||
All messages are JSON. Two types are sent from server to browser:
|
||||
Two message types are sent from server to browser.
|
||||
|
||||
#### `config` message
|
||||
#### `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.
|
||||
@@ -144,7 +264,7 @@ UDPStreamer) and whenever UDPStreamer sends a new CONFIG packet.
|
||||
"rangeMin": -10.0,
|
||||
"rangeMax": 10.0,
|
||||
"timeMode": 0,
|
||||
"samplingRate": 0.0,
|
||||
"samplingRate": 1000.0,
|
||||
"timeSignalIdx": 4294967295,
|
||||
"unit": "V"
|
||||
}
|
||||
@@ -152,27 +272,19 @@ UDPStreamer) and whenever UDPStreamer sends a new CONFIG packet.
|
||||
}
|
||||
```
|
||||
|
||||
#### `data` message
|
||||
#### `data` message (binary)
|
||||
|
||||
Sent at ≤ 30 Hz, batching all UDP packets received since the last tick.
|
||||
Each signal carries its own time axis to support mixed scalar + temporal-array signals.
|
||||
Uses a compact binary format: a fixed header followed by per-signal blocks.
|
||||
|
||||
```json
|
||||
{
|
||||
"type": "data",
|
||||
"signals": {
|
||||
"Sine1": { "t": [1747123456.001, 1747123456.002], "v": [3.14, 2.71] },
|
||||
"Counter": { "t": [1747123456.001, 1747123456.002], "v": [12340, 12341] },
|
||||
"Ch1": { "t": [1747123456.000, 1747123456.0001, ...], "v": [0.12, 0.13, ...] }
|
||||
}
|
||||
}
|
||||
```
|
||||
Each signal block contains:
|
||||
- Signal name (length-prefixed)
|
||||
- Number of samples
|
||||
- Timestamp array (float64 LE, Unix seconds)
|
||||
- Value array (float64 LE)
|
||||
|
||||
- `t` — Unix timestamp in seconds (float64) for each sample.
|
||||
- `v` — physical value (after dequantization if applicable).
|
||||
- For **temporal arrays**, `t` and `v` have `N × batchSize` entries
|
||||
(up to 2000 per 30 Hz tick after server-side decimation).
|
||||
- For **spatial arrays** the keys are `"Ch1[0]"`, `"Ch1[1]"`, etc.
|
||||
For **temporal arrays**, each packet contributes `N` samples (one per array element).
|
||||
For **spatial arrays**, keys are `"Ch1[0]"`, `"Ch1[1]"`, etc.
|
||||
|
||||
---
|
||||
|
||||
|
||||
Reference in New Issue
Block a user