Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
14 KiB
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 with a source streaming: the header bar above (the window control — 2 s — first on
the left), the plot, the badge bar under it with one badge per signal, the status line at the foot, and
the dock on the right with the cursor and measurement readouts and the events block.
The window is a plot with three things around it:
- the badge bar under the plot: a badge for each signal you have switched on, a
+to add one (past six they fold into oneN signalsbadge), and, on the right, Trigger and — only for a source with controls of its own — Source; - the side panel, which a badge opens over the plot's right edge, one page at a time: the trigger,
a signal, the list of signals to add, the cursors, the measurements, the events, or a source's own
controls. A second click on the badge,
Esc, Close or a click on the plot closes it, and it never resizes the plot; - the dock on the right, which the dock button in the header bar (
s) shows and hides: read-only Readouts of the cursors and of the measurements, each with an Edit button that opens its page, and an Events block. With the dock hidden, or in a narrow window where the dock slides over the plot, the readouts show in a one-line strip under the plot instead (the cursors' deltas and each measurement's value; Cursors and Measures open the pages); the strip is there only while the cursors are on or there is a measurement.
1. Connecting
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
At the foot of the window, under the badge bar, 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 dataappears 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 arrivingappears 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 reconnectsis appended when the client has had to re-establish the session.
3. Signals
The + badge (or g) opens the Signals page, which lists what can be added: the signals the
CONFIG described that are not acquired yet, each with a tick. Signals start disabled — ticking one
is what starts acquiring it, and a signal nobody enables costs nothing. Its badge then appears in
the badge bar. Drag the badge onto a plot row to add a trace; drag it again to another row to plot
the same signal twice, or onto another badge to reorder the badges. The colour is per signal, so
the same signal is the same colour wherever it appears.
Clicking a signal's badge opens its own page. Directly under the name are its Unit, Gain and Offset — each with a pencil button that edits that one value — and then what the stream declared about it (type, size, range, sample rate, timing). Below come the colour and the Plot on buttons for the rows. Gain and offset are applied at ingest, so the measurements and the export carry the same numbers the plot shows; a renamed unit is used there too and is saved with the session.
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.
Each plot has its own right-click menu, in the same compact row style as the app's menu:
Y axis: auto scale, Y axis: fixed range..., Fit X axis, Fit all (f), and
Remove signal — which swaps the menu to one entry per trace on that plot plus All traces on
this plot, with a Back row to return. That is how a trace comes off a row without going through
the layout editor.
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
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 button in the header bar (t) turns trigger mode on. The Trigger badge opens the
trigger page, which has the signal, the condition, 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.
- Events. With record events on (trigger page), each latch is stored. The dock's Events block lists them newest first and one click replays one; Manage opens the full list as a page, where a click selects, a double click replays, and Compare, Accumulate and Live act on the selection or go back to the stream. The block is dimmed until something is recorded.
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 toggle in the header bar (c) 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 dock reports each trace's values at the markers; Edit beside Cursors opens the page with
the mode and Reset, which 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
A measurement is a statistic of the visible window of a signal: average, top, bottom, min, max,
peak to peak, amplitude, pulse duration, period and frequency. m, or Edit beside
Measurements in the dock, opens the page; its + adds one, a click on a row (in the page or in
the dock) edits it, and show on plot draws its result as a ruler (levels) or markers (times).
The dock shows each measurement's value.
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 cost is reported in the exit dump with the app's self-check.
9. Math channels
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. The Signals page lists them under a MATH heading whose + creates one; a math
channel's own page has Edit graph and Delete.
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.
- Drag from an output square to 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 |
enable / disable trigger mode |
c |
enable / disable the cursors |
m |
open or close the Measurements page |
g |
open or close the Signals page (the + badge opens it too) |
l |
show / hide the layout editor |
s |
show / hide the dock |
Esc |
close the slide-over page (the guided tour takes it first) |
f |
fit to data (the plot's own key) |
Ctrl+E |
export visible range |
F1 |
open the manual |
Ctrl+/ |
open the shortcut overview |
Ctrl+T |
start the guided tour |
Ctrl+H |
turn the contextual help on or off |
Ctrl+Q |
quit |
The plot also answers +/- (zoom both axes), Ctrl+plus/Ctrl+minus (zoom x only),
Alt+plus/Alt+minus (zoom y only) and the arrow keys (pan). 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) |


