# StreamHub — User Guide

StreamHub turns signals streamed by MARTe2 `UDPStreamer` DataSources into a
multi-user oscilloscope. One hub process aggregates any number of sources and
serves up to 16 simultaneous clients: a browser SPA and/or the native ImGui
desktop client.

```
MARTe2 app (UDPStreamer) ──UDP 44500──▶ StreamHub ──WebSocket 8090──▶ browser / ImGui
```

## 1. Quick start (demo stack)

```bash
source env.sh
./run_streamhub.sh -w -g
```

This builds everything, starts a demo MARTe2 application with three streamers
(scalars over multicast, two array streamers), a StreamHub on port 8090, the
web UI server on port 8080 (`-w`) and the ImGui client (`-g`). Then open:

```
http://localhost:8080/?hub=localhost:8090
```

Useful flags: `-p <port>` WS port, `-n <pts>` ring size, `-s` skip rebuild,
`-h` full help (including the list of ports used).

## 2. Running against your own application

1. Add a `UDPStreamer` DataSource to your MARTe2 configuration
   (see [UDPStreamer.md](UDPStreamer.md)).
2. Write a hub configuration, e.g. `hub.cfg`:

   ```
   WSPort   = 8090
   Sources = {
       MyApp = { Label = "MyApp" Addr = "192.168.0.10" Port = 44500 }
   }
   ```

   For multicast streamers add `MulticastGroup = "239.0.0.1"` and
   `DataPort = 44503`. All keys and defaults:
   [StreamHub-Developer.md](StreamHub-Developer.md) §6.

3. Run the hub and a client:

   ```bash
   source env.sh
   ./Build/x86-linux/StreamHub/StreamHub.ex -cfg hub.cfg

   # web UI
   cd Client/webui && go build -o streamhub-webui . && ./streamhub-webui
   # then browse http://localhost:8080/?hub=<hub-host>:8090

   # or native client
   ./Client/streamhub/build/StreamHubClient -host <hub-host> -port 8090
   ```

Sources can also be added at runtime from either client ("Add source": label +
`host:port`, optional multicast group/data port). "Save sources" persists the
runtime list to `streamhub_sources.json` so the hub reloads it on restart.

## 3. Using the oscilloscope (web UI and ImGui)

Both clients offer the same workflow:

- **Plotting** — drag signals from the source sidebar onto a plot. Choose a
  grid layout (1×1 … 4×1 / 2×2 / 1×4). Live plots scroll with wall-clock time;
  pick the time window (1/5/10/30/60 s).
- **Zoom** — scroll/drag to zoom. When zoomed in, the client automatically
  fetches high-resolution data from the hub for the visible window. *Back*
  steps through the zoom history, *Fit* frames the data, *Reset/Live* returns
  to the scrolling live view.
- **Cursors** — enable cursors A/B and drag them; the readout shows ΔT and the
  value of each trace at both cursors.
- **V-scale modes** — *normal*, *digital* (booleans stacked as logic traces),
  *mixed*.
- **Signal styling** — per-trace colour, line width, markers.
- **Stats** — per source: packet rate ± std, lost packets, fragments and bytes
  per cycle, cycle-time min/avg/max/std and a 20-bin cycle-time histogram.

## 4. Trigger

The trigger runs **in the hub**, so a capture is identical in every connected
client and uses full-rate ring data (not the decimated live stream).

1. Pick the trigger **signal** (for array signals: a specific element), the
   **edge** (rising/falling/both) and the **threshold**.
2. Pick the capture **window** (100 µs … 10 s) and the **pre-trigger**
   percentage (how much of the window precedes the trigger instant).
3. Mode **normal** re-arms automatically ~200 ms after each capture
   (*Stop* pauses re-arming); mode **single** captures once and waits for
   *Rearm*.
4. Press **Arm**. Badge states: `IDLE → ARMED → COLLECTING → TRIGGERED`.

On capture, the trigger view shows all plotted signals relative to the trigger
instant (marker at t = 0) over `[-pre, +post]`.

## 5. History (disk-backed storage)

StreamHub can store signal data to disk, allowing you to browse hours of past
data even after the in-memory ring buffers have wrapped.

### Enabling history

Add a `+History` block to the hub configuration:

```
Hub = {
    WSPort = 8090
    +History = {
        Directory      = "/data/streamhub_history"
        DurationHours  = 1
        Decimation     = 10
        FlushIntervalSec = 5
        MinDiskFreeMB  = 200
    }
    Sources = { … }
}
```

| Key | Default | Description |
|-----|---------|-------------|
| `Directory` | *(required)* | Root directory for `.shist` files. Created automatically. Each source gets a subdirectory; each signal a separate file. |
| `DurationHours` | 1 | How many hours of data to keep per signal. The file size is fixed at creation: `duration × samplingRate / decimation × 16 bytes`. |
| `Decimation` | 1 | Write every N-th sample (1 = no decimation). Useful for high-rate signals where full-rate disk writes are impractical. |
| `FlushIntervalSec` | 5 | How often file headers are flushed to disk (data is written immediately via `pwrite`). |
| `MinDiskFreeMB` | 500 | Writing pauses when free disk space drops below this threshold and resumes when space is available again. |

### Disk usage

Each sample on disk is 16 bytes (8 bytes timestamp + 8 bytes value). Example:

- 10 signals at 1 ksps, decimation 10, duration 1 hour:
  `10 × (1000/10) × 3600 × 16 bytes = 57.6 MB`
- 10 signals at 1 Msps, decimation 1000, duration 1 hour:
  `10 × (1e6/1000) × 3600 × 16 bytes = 576 MB`

Files are pre-allocated at creation and never grow. The directory structure is:

```
/data/streamhub_history/
├── scalar/
│   ├── Sine1.shist
│   └── Sine2.shist
├── med/
│   ├── Ch1.shist
│   └── Ch2.shist
└── fast/
    ├── Ch3.shist
    └── Ch4.shist
```

### Using history in clients

When history is enabled, both the web SPA and ImGui client show a **HIST**
indicator. History data is fetched transparently:

- **Zoom**: when you zoom or pan into a time range that extends beyond the
  in-memory ring buffer, the client issues a `historyZoom` request in parallel
  with the regular `zoom` request and merges the results — history data fills
  the older part of the window, ring data the recent part.
- **Status**: the web SPA displays a history badge in the status bar showing
  the configured duration and decimation factor. The ImGui client shows
  "HIST" in the plot header when history data is being displayed.

History files survive hub restarts. If a `.shist` file exists with a matching
capacity, the hub reopens it and continues writing where it left off.

## 6. Troubleshooting

| Symptom | Check |
|---------|-------|
| Source stuck in *connecting* | Streamer host/port reachable? For multicast: group/data port match the streamer, and routing allows IGMP |
| No data but source *streaming* | `getStats` rate ≈ RT cycle rate? Firewall on UDP data port? |
| Web UI loads but no signals | Did you pass `?hub=host:8090` (the SPA talks WS directly to the hub, not the web server)? |
| Trigger never fires | Threshold inside the signal's actual range? Edge direction correct? |
| Time axis jumps once at start | Normal: first-packet wall-clock calibration; it also re-anchors after a source restart |

Hub-side logs go to stdout; the demo scripts write
`/tmp/streamhub_e2e_{marte,hub}.log`.

## 7. Further reading

- [StreamHub-API.md](StreamHub-API.md) — WebSocket protocol (write your own client)
- [StreamHub-Developer.md](StreamHub-Developer.md) — internals, build, tests
- [UDPStreamer.md](UDPStreamer.md) — configuring the MARTe2 DataSource
- [Protocol.md](Protocol.md) — UDPS wire format
