improving doc

This commit is contained in:
Martino Ferrari committed 2026-10-05 00:28:10 +02:00
1 parent 02c53bb7a0
commit 1fc2a3afe2
11 files changed
+678 -76

No files matched your search

+201
View File
@@ -0,0 +1,201 @@
# udpscope — user guide
A viewer for a UDPStreamer source: it connects, decodes the CONFIG, plots the signals you enable,
and lets you freeze, measure and derive from them. This is the *user* guide; `README.md` documents
what was built and why, and `SPECS.md` is the requirement document.
![the main window](img/main-window.png)
*The main window with a source streaming: the signal panel on the right, the plot in the middle, the
status line directly under it, and the toolbar above. The window control — `2 s` — is the first thing
on the left.*
## 1. Connecting
```sh
udpscope # asks which source to open
udpscope --server 10.0.0.5 --port 8000 # connect straight away
udpscope --server 239.1.2.3 --port 8000 --multicast 239.1.2.3 --interface 10.0.0.9
```
With no `--server` the app shows a chooser: the sessions it has stored, each naming its source, plus
a form for a new host. A source opened from the list reconnects to the multicast group and interface
it was stored with.
**A unicast source serves one client at a time.** A second viewer's `CONNECT` displaces the first, and
they take turns — the status line says so when it happens. Watching one source from several instances
needs `--multicast <group>` with `--interface <local address>`.
Every launch is its own process, so running two scopes side by side is fine.
## 2. The status line
Directly under the plot, it reads `● <state> · <rate> · <window>`, and the ⓘ beside it opens the
detail: packets, frames, gaps, per-channel sample counts, the buffer's span and its decimation.
- **The state word is one word** from a fixed set: `live`, `paused`, `armed`, `capturing`,
`event held`, `disarmed`. `no data` appears in the rate's place when nothing is arriving.
- **The rate is the *stored* rate**, which is the wire rate only while the whole requested window
fits in the buffer. A window wider than the budget allows is bought by averaging groups of samples,
so the rate falls and the status line says so — that is the anti-alias filter doing its job.
- **`connected, but no data is arriving`** appears after three seconds in which no frame arrived while
connected: the usual cause is the unicast point above. It clears the moment data returns.
- **`N reconnects`** is appended when the client has had to re-establish the session.
## 3. Signals
The signal panel lists every signal the CONFIG described, with a switch each. **Signals start
disabled — enabling one is what starts acquiring it**, and a signal nobody enables costs nothing.
Drag a signal from the panel onto a plot row to add a trace; drag it again to another row to plot the
same signal twice. The colour is per *signal*, so the same signal is the same colour wherever it
appears.
The panel also holds the per-signal calibration: gain and offset, applied at ingest, so the
measurements and the export carry the same numbers the plot shows.
## 4. The window and navigation
The header's window control (`2 s`) sets the time span on screen; the popover takes a number and a
unit, with a decade stepper either side. The trigger's event window is a share of *this* span, so a
window change moves the trigger with it.
- **Wheel** zooms about the pointer; **Shift+wheel** on x only, **Ctrl+wheel** y only.
- **Ctrl `+` / `-`** zooms x, **Alt `+` / `-`** y, about the plot centre.
- **Drag** pans, **right-drag** too (the right button also grabs a cursor at the pointer).
- **Ctrl+drag** frames x to a box, **Alt+drag** y.
- **Fit to data** (menu) auto-scales both axes and frames the whole buffered range.
The view is bounded by the **time window**, not by the buffer: zooming out stops at the span you set,
and panning stops where the data starts. When the buffer holds *less* than the window — right after a
signal is enabled — the window is left empty on its left, which is what a scope that has just started
should look like.
## 5. Trigger
![a capture held](img/trigger-capture.png)
*Single mode with a capture held: the axis is the event's own `[-pre, +post]`, the trigger marker
sits at the edge, and the status line reads `event held`.*
The trigger panel has the edge, the pre/post split (as a share of the event span), Normal or Single,
and Arm/Disarm. Selecting a signal for the trigger enables it.
- **While a capture fills** the plot keeps drawing live data on the event's clock, so the post side
fills in view; the axis does not move.
- **A held event is navigable within its own data** — zooming in works, panning stops at the capture's
edges — and it is never quietly replaced by live data.
- **Disarming mid-capture** publishes what the capture got and holds that, so the picture on screen
when you disarm is the picture you keep.
- The event window is counted in **stream seconds** end to end, so a source whose clock advances in
jumps still gets the window it asked for.
## 6. Pause
Pause freezes the *picture*, not the acquisition: the producer keeps filling the buffer behind it, and
the frozen view is a snapshot of the window that was on screen. Resuming shows the data that arrived
meanwhile — there is no hole. A paused view is held like a held event, so zooming in works and panning
does not walk it off its snapshot.
## 7. Cursors
The cursors toolbar toggle switches on two instruments per row:
- **vertical** — two times, shared by every row;
- **horizontal** — two rulers per row;
- **both** — the two together;
- **mix** — the two times, read off each *trace* where they cross it.
The pane beside it reports each trace's values at the markers, and **Reset** puts them back at the
window's edges. Values read `--` when a marker is past the newest sample and `partial` when only one
marker is over data.
## 8. Measurements
The measurements pane computes statistics of the **visible window** of a signal: average, top,
bottom, min, max, peak to peak, amplitude, pulse duration, period and frequency. `+` adds one, the
row's pencil edits it, and **show on plot** draws its result as a ruler (levels) or markers (times).
`top` and `bottom` take a *split* as a share of the window's own range — the percentage travels
between signals where a threshold in volts would not. `period` and `frequency` deliberately have no
split: they count rising crossings of the mid level, one edge per cycle, and a split there is a
setting you can only get wrong.
The values are refreshed on a cadence — at most every 100 ms while the window is scrolling, once when
it stops, and once per mode change — so a live view cannot cost you frames. The pane's cost is
reported in the exit dump with the app's self-check.
## 9. Math channels
![the math channel editor](img/math-editor.png)
*The compact node editor: each node is a box, its inputs the squares on the left edge and its output
the square on the right. The orange dot marks the node that carries the channel's value.*
A math channel is derived from the others and is then an ordinary channel: enable it and it is
acquired, plotted, measured and exported like a streamed signal, and another math channel can use it
as a source.
Operations: `channel` (a source), `gain + offset`, `abs`, `sqrt`, `clamp`, `low pass`, `high pass`,
`band pass` (biquads, corner in Hz against the stored rate), `sum`, `difference`, `product`, `ratio`.
- **Click** a node to select it; its parameters appear in the form beside the canvas.
- **Click an output square, then an input square**, to wire them. An input is only offered the nodes
above it — the graph is a list walked in order, so a cycle cannot be drawn.
- **Drag** a box to move it. **add node**, **delete node** and **set as output** are below the form.
- The **output is the last node** of a graph you build top to bottom, and the orange dot says which;
*set as output* changes it. A graph whose output is the source is valid and just passes the input
through — that is the usual reason an operation "does nothing".
- **Save** refuses an incomplete graph and says why: an unconnected input, no output, or a source that
is not a channel the scope is acquiring.
The samples are produced at ingest, once per stored slot, so the filters carry real history — a low
pass is the filter you asked for rather than a window recomputed every frame.
## 10. Sessions
Everything above is saved per source in `~/.config/udpscope/<host>_<port>.toml`: the window, the rows
and which signal each slot holds, the calibration, **which signals were enabled**, **the math channels
with their graphs** and **the measurements**. Reopening the same source brings the scope back as you
left it.
The file is plain TOML and safe to edit — a malformed line is skipped rather than refusing the whole
file. Deleting it starts from the defaults.
## 11. Export
**Export visible range…** writes the plotted signals over the visible x range at the *stored* rate to
`.npy` or `.csv`, picked by the file name's extension. The table is collected when you click, not when
the chooser closes, so a live view cannot scroll under the dialog. The `.npy` is a 2-D array: one row
per sample, `time`, `stream_time`, then one column per signal.
## 12. Keyboard
| key | action |
|---|---|
| `space` | pause / resume |
| `a` | arm / disarm the trigger |
| `t` | the trigger panel |
| `l` | the layout editor |
| `g` | go live |
| `f` | fit to data |
| `d` | add the first free signal to the focused row |
| `c` | cursors on/off |
| `m` | measurements pane |
| `e` | export |
| `?` | shortcuts |
`Ctrl+E` exports, `Ctrl+Q` quits, `Ctrl+plus`/`Ctrl+minus` zoom x and `Alt+plus`/`Alt+minus` zoom y.
A keystroke is ignored while a text field has focus, so typing in the window control never arms the
trigger.
## 13. Troubleshooting
| symptom | cause |
|---|---|
| `connected, but no data is arriving` | another client holds a unicast source — use `--multicast` |
| the plot is empty, the status reads `no data` | nothing is enabled: enable a signal in the panel |
| the trace is blank while the trigger is armed | `Waiting` — armed and nothing latched yet |
| the axis has a margin at the right | a capture is filling: the post side arrives as it is acquired |
| a math channel equals its input | its output node is the source — see §9 |
| a very wide window shows a low rate | the ingest's anti-alias filter, not a fault |
| a level reads `--` | the measurement is invalid in this window (e.g. a period with one edge) |
Binary file not shown.

After

Width:  |  Height:  |  Size: 168 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 140 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 148 KiB