8.2 KiB
StreamHub WebSocket API
StreamHub exposes a single WebSocket server (default port 8090, any URL path).
Text frames carry JSON commands/events; binary frames carry data pushes and
trigger captures. The Go reference hub (Client/udpstreamer) implements the
identical protocol; both the browser SPA and the ImGui client work against either.
For the upstream UDPS wire format (source → hub) see Protocol.md.
All numbers in binary frames are little-endian. All timestamps are Unix wall-clock seconds (float64) — see Time base in StreamHub-Developer.md.
1. Commands (client → hub, JSON text frames)
Every command is a JSON object with a type field.
ping
{"type":"ping"}
Reply (unicast): {"type":"pong"}.
addSource
{"type":"addSource","label":"PSU","addr":"192.168.0.10:44500",
"multicastGroup":"239.0.0.1","dataPort":44503}
addr—host:portof the UDPStreamer control port.multicastGroup/dataPort— optional; if present the hub joins the multicast group for DATA and keeps a TCP control connection for CONNECT/CONFIG.- The hub assigns ids
s1,s2, … and broadcasts an updatedsourcesevent.
removeSource
{"type":"removeSource","id":"s1"}
saveSources
{"type":"saveSources"}
Persists the current dynamically-added source list to the hub's SourcesFile
(JSON array of {label,addr,multicastGroup,dataPort}); it is reloaded at startup.
getSources / getConfig / getStats
{"type":"getSources"}
{"type":"getConfig","sourceId":"s1"}
{"type":"getStats"}
Force a broadcast of the corresponding event.
setTrigger
{"type":"setTrigger","signal":"s1:Sine","edge":"rising","threshold":0.0,
"windowSec":0.1,"prePercent":20,"mode":"normal"}
signal— full keysrc:sig, orsrc:sig[i]to trigger on element i of a multi-element PACKET signal.edge—"rising","falling"or"both".windowSec— total capture window (clamped to 1e-4 … 10 s).preSec = windowSec * prePercent / 100,postSec = windowSec − preSec.mode—"normal"(auto-rearm ~200 ms after capture) or"single"(stays TRIGGERED untilrearm).
arm / disarm / rearm / trigStop
{"type":"arm"}
{"type":"disarm"}
{"type":"rearm"}
{"type":"trigStop","stopped":true}
arm— IDLE → ARMED.disarm— any state → IDLE.rearm— TRIGGERED → ARMED (single mode).trigStop— sets the stopped flag suppressing auto-rearm in normal mode; omitstoppedto toggle.
Every transition is broadcast as a triggerState event.
zoom
{"type":"zoom","reqId":17,"t0":1765370000.123,"t1":1765370000.223,
"n":2400,"signals":"s1:Sine,s2:Wave[0]"}
reqId— echoed in the reply; lets the client match request/response.t0/t1— Unix seconds window.n— target points per signal. Absent or<10→ 2400;n ≤ 0→ no decimation (raw ring contents).signals— comma-separated full keys; absent → all signals of all sources.- The reply is unicast to the requesting client only.
historyZoom
{"type":"historyZoom","reqId":42,"t0":1765360000.0,"t1":1765370000.0,
"n":2400,"signals":"s1:Sine,s2:Wave"}
- Reads from disk-backed history instead of the in-memory ring buffer.
Identical semantics to
zoombut queries the.shistfiles written byHistoryWriter. reqId,t0/t1,n,signals— same meaning aszoom.- If history is not enabled, the reply contains
"error":"history not enabled". - Reply is unicast (same shape as
zoomreply, but"type":"historyZoom").
historyInfo
{"type":"historyInfo"}
Request the hub to send a historyInfo event (unicast). Also sent automatically
on client connect.
setMaxPoints
{"type":"setMaxPoints","maxPoints":50000}
Resizes all ring buffers (applied safely inside the push loop; push cursors are
reset). Broadcasts maxPointsUpdated.
2. Events (hub → client, JSON text frames)
sources
{"type":"sources","sources":[
{"id":"s1","label":"PSU","addr":"192.168.0.10:44500","state":"streaming"}]}
Sent on client connect, after add/remove/getSources, and when a source first delivers its CONFIG.
config
{"type":"config","sourceId":"s1","publishMode":0,"signals":[
{"name":"Sine","typeCode":10,"quantType":0,"numDimensions":0,
"numRows":1,"numCols":1,"rangeMin":-1.0,"rangeMax":1.0,
"timeMode":0,"samplingRate":5000000.0,"timeSignalIdx":-1,"unit":"V"}]}
Field semantics follow the UDPS signal descriptor (Protocol.md).
numElements = numRows × numCols.
stats
Sent at StatsRate Hz (default 1 Hz):
{"type":"stats","sources":{"s1":{
"state":"streaming","totalReceived":1234,"totalLost":0,
"rateHz":100.1,"rateStdHz":0.3,
"fragsPerCycle":3.0,"bytesPerCycle":4200.0,
"cycleAvgMs":10.0,"cycleStdMs":0.1,"cycleMinMs":9.8,"cycleMaxMs":10.4,
"cycleHistMin":9.8,"cycleHistMax":10.4,"cycleHist":[0,1,5,"…(20 bins)"]}}}
triggerState
{"type":"triggerState","state":"triggered","mode":"normal",
"stopped":false,"trigTime":1765370000.1234567}
state ∈ idle | armed | collecting | triggered; trigTime present once a
trigger has fired.
zoom (reply)
{"type":"zoom","reqId":17,"signals":{
"s1:Sine":{"t":[1765370000.1230000,"…"],"v":[0.123456789,"…"]}}}
t is serialised with %.17g (full float64 precision — required for
µs windows at Unix-epoch magnitudes), v with %.9g.
historyInfo
Sent on client connect (if history is enabled) and on historyInfo command:
{"type":"historyInfo","enabled":true,"durationHours":1.0,"decimation":10,
"signals":{
"scalar:Sine1":{"t0":1765360000.0,"t1":1765370000.0,"count":360000,"capacity":360000},
"scalar:Sine2":{"t0":1765360000.0,"t1":1765370000.0,"count":360000,"capacity":360000}}}
enabled—trueif the+Historyconfig block is present and valid.durationHours— configured history duration.decimation— samples-to-disk decimation factor (1 = every sample).signals— per-signal metadata keyed by"sourceId:signalName":t0/t1— oldest/newest timestamp stored on disk (Unix seconds).count— number of valid entries currently in the circular file.capacity— total capacity of the circular file.
historyZoom (reply)
Same shape as zoom reply, but "type":"historyZoom":
{"type":"historyZoom","reqId":42,"signals":{
"s1:Sine":{"t":[1765360000.1230000,"…"],"v":[0.123456789,"…"]}}}
If history is not enabled: {"type":"historyZoom","error":"history not enabled"}.
maxPointsUpdated
{"type":"maxPointsUpdated","maxPoints":50000}
3. Binary frames (hub → client)
The first byte of every binary WS frame is a version discriminator.
Version 1 — data push
Sent at PushRate Hz per source. Contains only the samples that are new
since the previous push (per-signal cursors hub-side), LTTB-decimated to at
most MaxPushPoints (default 50) per signal.
[1] version = 1
[1] sourceIdLen (L)
[L] sourceId (UTF-8, no NUL)
[4] numSignals (uint32)
per signal:
[2] keyLen (uint16) (K)
[K] key = signal name; "name[i]" per element for multi-element PACKET signals
[4] pairCount (uint32) (N)
[N×8] t (float64, Unix seconds)
[N×8] v (float64, physical units)
Clients must append samples verbatim — there is no overlap between pushes.
Version 2 — trigger capture
Broadcast once per capture (FSM COLLECTING → TRIGGERED). Contains all
signals over [trigTime − preSec, trigTime + postSec], each LTTB-decimated
to ≤ 20 000 points.
[1] version = 2
[8] trigTime (float64, Unix seconds)
[8] preSec (float64)
[8] postSec (float64)
[4] numSignals (uint32)
per signal:
[2] keyLen (uint16) (K)
[K] fullKey = "src:sig" (UTF-8)
[4] pairCount (uint32) (N)
[N×8] t (float64, Unix seconds)
[N×8] v (float64)
4. Limits
| Limit | Value |
|---|---|
| Concurrent WS clients | 16 |
| UDPS source sessions | 32 |
| Max received WS payload | 64 KiB |
| Max sent WS payload | 4 MiB |