docs: document per-signal calibration and config save/reload
Update Docs/StreamHub-API.md (new setCalibration/reloadConfig commands, calibration/configSaved/configReloaded events, §4 config file format), ARCHITECTURE.md §6 (updated command/event tables and Config File Format subsection), Docs/WebUI.md (Cal row in V-Scale Toolbar, Sources & Config sidebar section). Also corrects the spec's Validation sentence to match the shipped cal-invalid border behaviour instead of silent field revert. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Sonnet 4.6
parent
e37eec0276
commit
d26b78b7f6
+109
-3
@@ -46,8 +46,57 @@ Reply (unicast): `{"type":"pong"}`.
|
||||
```json
|
||||
{"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.
|
||||
Writes the hub's `SourcesFile`: the current dynamically-added source list **and**
|
||||
the calibration table, as one flat JSON array (see [§4](#4-config-file-format)).
|
||||
The hub replies with [`configSaved`](#configsaved). Despite the name, this
|
||||
command persists the whole config, not just the sources.
|
||||
|
||||
### `setCalibration`
|
||||
|
||||
```json
|
||||
{"type":"setCalibration","source":"wave","signal":"Adc","scale":0.00030518,"offset":-1.25,"unit":"V"}
|
||||
```
|
||||
Records an affine calibration `value = raw × scale + offset` for one signal,
|
||||
keyed by the source's **label** (not its runtime id) and the **base** signal
|
||||
name — one entry covers every element of an array signal.
|
||||
|
||||
| Field | Type | Default | Validation |
|
||||
|---|---|---|---|
|
||||
| `source` | string | — | non-empty after trimming |
|
||||
| `signal` | string | — | non-empty after trimming; any trailing `[i]` is stripped |
|
||||
| `scale` | number | `1` | finite and non-zero |
|
||||
| `offset` | number | `0` | finite |
|
||||
| `unit` | string | `""` | trimmed, truncated to 16 chars; empty = use the streamer's own unit |
|
||||
|
||||
Calibration is **metadata only**: the hub stores and redistributes it but never
|
||||
applies it. Ring buffers, recorded history, the `zoom` reply, both binary frames
|
||||
and the trigger comparator all stay in raw units — a client that ignores
|
||||
calibration behaves exactly as before.
|
||||
|
||||
An entry that reduces to the identity (`scale = 1`, `offset = 0`, `unit = ""`) is
|
||||
**deleted** rather than stored, so a reset leaves no residue in the config file.
|
||||
|
||||
On acceptance the hub broadcasts [`calibration`](#calibration) to every client. A
|
||||
rejected entry produces **no** broadcast, so the offending client reverts to the
|
||||
last value it was told.
|
||||
|
||||
### `reloadConfig`
|
||||
|
||||
```json
|
||||
{"type":"reloadConfig"}
|
||||
```
|
||||
Re-reads `SourcesFile` and then:
|
||||
|
||||
- **replaces** the calibration table wholesale with the file's contents;
|
||||
- **adds** any source in the file that is not already active;
|
||||
- **never** removes, restarts or reconnects a live source.
|
||||
|
||||
The asymmetry is deliberate: calibration is cheap to reapply, whereas a source is
|
||||
a live UDP session that must not be interrupted. An unsaved source the user added
|
||||
keeps streaming.
|
||||
|
||||
The hub replies with [`configReloaded`](#configreloaded), followed on success by a
|
||||
`calibration` broadcast and a `sources` broadcast.
|
||||
|
||||
### `getSources` / `getConfig` / `getStats`
|
||||
|
||||
@@ -219,6 +268,37 @@ If history is not enabled: `{"type":"historyZoom","error":"history not enabled"}
|
||||
{"type":"maxPointsUpdated","maxPoints":50000}
|
||||
```
|
||||
|
||||
### `calibration`
|
||||
|
||||
```json
|
||||
{"type":"calibration","cal":[
|
||||
{"source":"wave","signal":"Adc","scale":0.00030518,"offset":-1.25,"unit":"V"}
|
||||
]}
|
||||
```
|
||||
The complete calibration table. Broadcast when a client connects (as an empty
|
||||
array when nothing is calibrated), after every accepted `setCalibration`, and
|
||||
after a successful `reloadConfig`. It is a separate frame rather than a field on
|
||||
`sources` because `sources` is serialised into a fixed 4 KiB buffer.
|
||||
|
||||
### `configSaved`
|
||||
|
||||
```json
|
||||
{"type":"configSaved","ok":true,"path":"/etc/streamhub/sources.json"}
|
||||
{"type":"configSaved","ok":false,"path":"","error":"no SourcesFile configured"}
|
||||
```
|
||||
Broadcast in reply to `saveSources`. `path` is always present (empty when the hub
|
||||
has no config file configured); `error` only when `ok` is false.
|
||||
|
||||
### `configReloaded`
|
||||
|
||||
```json
|
||||
{"type":"configReloaded","ok":true,"path":"/etc/streamhub/sources.json"}
|
||||
{"type":"configReloaded","ok":false,"path":"/etc/streamhub/sources.json","error":"cannot read sources file"}
|
||||
```
|
||||
Broadcast in reply to `reloadConfig`; same shape as `configSaved`. On success it
|
||||
is followed by a `calibration` broadcast and, if the file added any source, a
|
||||
`sources` broadcast.
|
||||
|
||||
---
|
||||
|
||||
## 3. Binary frames (hub → client)
|
||||
@@ -270,7 +350,33 @@ per signal:
|
||||
|
||||
---
|
||||
|
||||
## 4. Limits
|
||||
## 4. Config file format
|
||||
|
||||
`SourcesFile` (C++ `SourcesFile` config key, Go `-sources-file` flag) is a flat
|
||||
JSON array of flat objects. A block containing `addr` is a source; a block
|
||||
containing `signal` is a calibration entry; anything else is skipped with a
|
||||
warning.
|
||||
|
||||
```json
|
||||
[
|
||||
{"label": "wave", "addr": "127.0.0.1:44500"},
|
||||
{"label": "mc", "addr": "127.0.0.1:44501", "multicastGroup": "239.0.0.1", "dataPort": 44502},
|
||||
{"source": "wave", "signal": "Adc", "scale": 0.00030518, "offset": -1.25, "unit": "V"}
|
||||
]
|
||||
```
|
||||
|
||||
**Every object must stay flat.** The C++ `StreamHub::LoadSourcesFile` parser
|
||||
takes each `{` up to the next `}` as one object, so a nested object anywhere in
|
||||
the file would truncate the parse at the inner brace. A nested
|
||||
`"calibration": {…}` inside a source entry is therefore not an option, and this
|
||||
is why calibration entries are siblings of sources rather than children.
|
||||
|
||||
Files written by hub versions predating calibration load unchanged, and a file
|
||||
written by either hub loads in the other.
|
||||
|
||||
---
|
||||
|
||||
## 5. Limits
|
||||
|
||||
| Limit | Value |
|
||||
|-------|-------|
|
||||
|
||||
@@ -80,6 +80,21 @@ Signals received in the CONFIG packet are listed in the sidebar:
|
||||
- **Spatial arrays** — `TimeMode = PacketTime` arrays are shown as an expandable
|
||||
group; individual elements (`Ch1[0]`, `Ch1[1]`, …) can be dragged independently.
|
||||
|
||||
The unit badge next to each signal shows the calibration's unit override when one
|
||||
is set, and the streamer's own unit otherwise.
|
||||
|
||||
At the bottom of the sidebar, the collapsible **Sources & Config** section holds:
|
||||
|
||||
- the `host:port`, label, multicast group and data port inputs plus **Connect**,
|
||||
which adds a source at runtime;
|
||||
- **Save** — writes the source list and the whole calibration table to the hub's
|
||||
config file;
|
||||
- **Reload** — re-reads that file. Calibration is replaced wholesale (so unsaved
|
||||
edits are discarded), sources present in the file but not running are added,
|
||||
and no running source is stopped or reconnected;
|
||||
- a status line showing the written path on success or the hub's error text on
|
||||
failure.
|
||||
|
||||
Click the sidebar toggle button (☰) to collapse/expand the signal list.
|
||||
|
||||
### Adding Plots
|
||||
@@ -143,11 +158,31 @@ plot header showing per-signal vertical scale controls:
|
||||
| **V/div** | Volts (or units) per division |
|
||||
| **Pos (div)** | Screen position in divisions (draggable offset marker on Y axis) |
|
||||
| **Type** (Mixed mode only) | Toggle between **Analog** and **Digital** for this signal |
|
||||
| **Cal · Scale** | Data calibration gain. `value = raw × Scale + Offset` |
|
||||
| **Cal · Offset** | Data calibration bias, in calibrated units |
|
||||
| **Cal · Unit** | Overrides the unit reported by the streamer (max 16 chars) |
|
||||
| **Reset** | Clears this signal's calibration (`Scale = 1`, `Offset = 0`, no unit override) |
|
||||
| **✕** | Close the toolbar and deselect the signal |
|
||||
|
||||
Offset markers (small triangles on the Y axis) show each signal's position and can
|
||||
be dragged to reposition signals without opening the toolbar.
|
||||
|
||||
**Calibration vs. V/div and Offset.** They are different things. V/div and Offset
|
||||
are a *display* transform: they move and stretch the trace on screen. Calibration
|
||||
changes *the value itself* — the plot, the Y-axis tick labels, the cursor and
|
||||
hover readouts, the CSV export and the trigger threshold all report
|
||||
`raw × Scale + Offset` in the calibrated unit. V/div is then read as "calibrated
|
||||
units per division" and Offset as "the calibrated value at screen centre".
|
||||
|
||||
The calibration header names the **base** signal and its element count, because
|
||||
one entry covers every element of an array — opening the toolbar on `Adc[3]` and
|
||||
editing the calibration moves all of `Adc`.
|
||||
|
||||
Calibration is keyed by the source's **label**, is shared with every other
|
||||
browser connected to the same hub, and is not persisted until you press **Save**
|
||||
in the Sources & Config section. It is mirrored to `localStorage` so it survives
|
||||
a page reload even against a hub with no config file.
|
||||
|
||||
### Plot Controls
|
||||
|
||||
| Control | Action |
|
||||
|
||||
Reference in New Issue
Block a user