Three asked-for changes, one commit because they are one coordinate system. The live axis is a fixed [-window, 0] with the newest sample at 0, so x reads as an age and the labels stand still while the trace rolls under them. That makes the display clock distinct from the stream clock for the first time, so `ScopeModel::stream_at_x0()` becomes the single conversion between them (`display_x = stream_t - stream_at_x0()` in all four modes) and a snapshot carries its own origin in `EventSnapshot::x_offset`. The pause view is now rebased like an event's, reversing an earlier deliberate choice: a frozen absolute-stamped picture would sit a whole uptime right of a newest-relative axis. That also fixed a latent bug, the export adding `trigger_time()` to a *paused* view's stamps. The status bar says how long you have been paused -- wall time, since the producer never stops, through `format_elapsed()` so the field cannot widen. The export writes two time columns: `time`, the axis as displayed, and `abs_seconds`, the stream's own clock for the same sample. f64 seconds and not the wire's integer, because `udps_frame_t::values` is a `const double*` and the integer never exists in this process. Cursors and measurement marks move in opposite directions under the rolling axis, deliberately: a cursor is a position on screen and stays put, a mark names a sample and travels with it. `App::measure_window` is stream-clocked for a sharper reason -- a display-coordinate live window never moves, so the "the window moved" recompute would never fire again. Moves the deps/plotter pin to 58473db, which stops a time label's mantissa reaching its own radix: a one-second tick differenced off a live axis read `1e+3ms`. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
79 lines
4.9 KiB
Plaintext
79 lines
4.9 KiB
Plaintext
<b>What udpscope is</b>
|
|
|
|
udpscope plots a live UDPStreamer source. One window holds up to 8 rows of plots, each with up
|
|
to 4 traces, and every row shares one time axis, so the whole figure scrolls together.
|
|
Acquisition runs on its own thread and writes a ring buffer sized from <tt>--buffer-mb</tt>;
|
|
the drawing thread reads whatever that ring holds when a frame starts, which is why a trace
|
|
never makes the window stutter.
|
|
|
|
The stream is the clock. Every interval you read on screen is measured between timestamps the
|
|
source sent, never your machine's, so a stalled source freezes the view instead of scrolling it
|
|
-- and a source whose clock steps still gets the window you asked for.
|
|
|
|
The time axis is <i>relative to now</i>. While streaming it is a fixed <tt>[-window .. 0]</tt>
|
|
with the newest sample at 0, so x reads as an age: <tt>-1.5s</tt> is a sample a second and a half
|
|
old. In trigger mode 0 is the event's edge instead, and a paused view keeps the ages it was
|
|
frozen with. The source's own clock is still there when you need it -- an export writes it as a
|
|
second column.
|
|
|
|
<b>How to reach it</b>
|
|
|
|
Start the app with a source (<tt>udpscope --server 127.0.0.1 --port 8000</tt>), or start it with
|
|
no <tt>--server</tt> and pick one from the chooser, which lists the sessions you have opened
|
|
before. A unicast source serves one client at a time: if a second viewer connects, they take
|
|
turns and the status bar says so. To watch one source from several instances, use
|
|
<tt>--multicast</tt> with the group and <tt>--interface</tt> with the local address.
|
|
|
|
The signals arrive <i>disabled</i>. Nothing is acquired until you turn one on in the signal
|
|
list, and enabling is what starts acquisition -- so a freshly opened scope with an idle side
|
|
panel is working correctly, not failing to connect.
|
|
|
|
<b>The window</b>
|
|
|
|
• <b>The header bar</b> -- left to right: the time window (<tt>2 s</tt>), the trigger and the
|
|
pause, then the pane switches -- signal list, cursors, measurements, math, side panel -- and
|
|
the menu, which holds Fit to data, the layout editor, Export, Keyboard shortcuts, Help and
|
|
About.
|
|
|
|
• <b>The side panel</b> -- the trigger, cursors, measurements and math panes at the top, then
|
|
the signal list: one row per signal with its colour, an enable switch, its name and its unit.
|
|
Signals are dragged from here onto a plot row.
|
|
|
|
• <b>The plot</b> -- up to 8 rows, each with its own y axis and up to 4 traces, all on one
|
|
shared time axis. Right-click a row for its own menu (fit, add a signal, remove a trace).
|
|
|
|
• <b>The status bar</b>, directly above the plot's bottom edge -- the state word, the stored
|
|
sample rate and the window, and the ⓘ beside it for packets, gaps and the buffer's own
|
|
figures. The second screenshot above is what a held capture looks like: <tt>event held</tt>,
|
|
the axis pinned to the event, and the trigger marker at the edge.
|
|
|
|
<b>The first five minutes</b>
|
|
|
|
1. <b>Connect.</b> With no <tt>--server</tt>, pick a stored session or type a host and port.
|
|
2. <b>Enable a signal</b> in the signal list. The plot stays empty for the first fraction of a
|
|
second while the first window fills -- the buffer holds only what has arrived.
|
|
3. <b>Drag it onto a row.</b> Drop a second signal on another row, or the same signal again for
|
|
a second view of it. The colour is per signal, so it is the same one wherever it appears.
|
|
4. <b>Set the window</b> with the control in the header bar: type a number, pick a unit, or step
|
|
a decade. The trigger's event window is a share of this span, so a window change moves it.
|
|
5. <b>Pause</b> (<tt>space</tt>) to look at a still picture; the producer keeps filling the
|
|
buffer behind it, and resuming shows what arrived meanwhile rather than a hole.
|
|
6. <b>Arm the trigger</b> (<tt>a</tt>) and wait for a latch: the view freezes on one event, and
|
|
you can zoom in on it. Disarming mid-capture keeps what the capture got.
|
|
7. <b>Measure it</b> -- the measurements pane computes a statistic of the visible window, and
|
|
<b>show on plot</b> marks it as a ruler or a pair of markers.
|
|
8. <b>Save the data</b> with Export visible range, which writes the plotted signals over the
|
|
window at the stored rate to <tt>.npy</tt> or <tt>.csv</tt>.
|
|
|
|
<b>Worth knowing</b>
|
|
|
|
• A signal nobody enables costs nothing -- no buffer, no socket work, no measurements.
|
|
• The window remembers itself. Rows, traces, the time window, calibration, which signals were
|
|
enabled, the math channels with their graphs, and the measurements are written per host and
|
|
port, and reloaded the next time you open that source.
|
|
• A very wide window on a fast stream is drawn from averaged groups of samples, so the rate in
|
|
the status bar falls: that is the anti-alias filter, not a fault -- striding would fold a fast
|
|
signal back as a slow one, which looks entirely plausible on screen.
|
|
• Every instrument the app computed -- the trigger marker, the measurement marks -- is locked to
|
|
the pointer's reach. The cursors are yours to drag; those are not.
|