# 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.

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 |
| **✕** | 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

| 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 (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.

---

## 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.
