191 lines
7.3 KiB
Markdown
191 lines
7.3 KiB
Markdown
# 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
|