# 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 [Plotly.js](https://plotly.com/javascript/).

```
MARTe2 RT app
    │ UDP (binary protocol)
    ▼
udpstreamer-webui (Go)
    │ WebSocket (JSON)
    ▼
Browser (index.html + Plotly.js)
```

---

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

```
┌─────────────────────────────────────────────────────────┐
│ 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]│                                              │
└──────────┴──────────────────────────────────────────────┘
```

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

### Adding Plots

1. Click **+ Add Plot** in the toolbar.
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.

### 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 All** (top bar) | Pause all plots simultaneously |
| **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 |

### Status LED

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

---

## 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 index.html
      └── 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:

#### `config` message

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": 0.0,
      "timeSignalIdx": 4294967295,
      "unit": "V"
    }
  ]
}
```

#### `data` message

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.

```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, ...] }
  }
}
```

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

---

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