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