What udpscope plots, the four parts of the window, and one pass through the application from connecting to saving. Martino Ferrari Overview
What udpscope is

udpscope plots a live UDPStreamer source. One window holds up to eight rows of plots, each with up to four 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 --buffer-mb; 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 relative to now. While streaming it is a fixed [-window .. 0] with the newest sample at 0, so x reads as an age: -1.5s 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.

The udpscope window The header bar above the plots, the side panel on the right, and the status bar along the bottom.

One figure of three rows with four signals enabled, the Measure tab open in the side panel, and the status bar reading live.

How to reach it

Start the app with a source (udpscope --server 127.0.0.1 --port 8000), or start it with no --server 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 --multicast with the group and --interface with the local address.

The signals arrive disabled. 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. For the same reason a start that has enabled nothing prints no buffer allocated: line on stderr.

Over SSH with X11 forwarding the window may never appear, and the cure is udpscope --renderer cairo. Two symptoms tell the two halves apart: a vkCreateSwapchainKHR() ... VK_ERROR_INITIALIZATION_FAILED warning is GTK's default Vulkan renderer failing to make a swapchain and is what the option's GSK_RENDERER=cairo answers; a window whose allocation stays zero (window_button_w=0) means the plot's GL area has no context either, and that is what its LIBGL_ALWAYS_SOFTWARE=1 answers. Both variables are exported before GTK initialises, because they are read when the renderer and the GL context are created. --renderer gl is faster when indirect GL works.

See Choosing a source for the chooser and the multicast options in full.

The window The header bar

Left to right: the time window (2 s), the trigger and the pause, the events, cursors and side-panel switches, and the menu, which holds Fit to data, the Layout editor, Export, Keyboard shortcuts, Help and About.

The side panel

Two tabs. Measure holds the cursors readout and the measurements derived from it; Signals holds the trigger and the channel list, where a math channel is a row of that one list under a MATH heading whose + creates one. One row per channel: its colour, an enable switch, its name and its unit. A channel's badge in the badge bar is dragged onto a plot row.

The plot

Up to eight rows, each with its own y axis and up to four traces, all on one shared time axis. Right-click a row for its own menu (fit, add a signal, remove a trace).

The status bar

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 below is what a held capture looks like: event held, the axis pinned to the event, and the trigger marker at the edge.

A held capture The trigger has latched, the state reads event held, and the axis is pinned to the event rather than to the live edge.

The trigger marker sits at x = 0 and the pre-trigger share of the window is drawn to its left.

The first five minutes

Connect. With no --server, pick a stored session or type a host and port.

Enable a signal in the Add a signal page (the + badge). The plot stays empty for the first fraction of a second while the first window fills — the buffer holds only what has arrived.

Drag it onto a row. 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.

Set the window 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.

Pause (space) to look at a still picture; the producer keeps filling the buffer behind it, and resuming shows what arrived meanwhile rather than a hole.

Arm the trigger (a) 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.

Measure it — the measurements pane computes a statistic of the visible window, and show on plot marks it as a ruler or a pair of markers.

Save the data with Export visible range, which writes the plotted signals over the window at the stored rate to .npy, .csv or .mf3 (MDF 3.30).

Worth knowing

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.

Installing this manual

The manual is a help set the desktop viewer reads off disk, so F1 opens it only once a copy sits where help: resolves. Either of two targets installs it on its own, without building the application, and each prints where it wrote. Prefer the system-wide one: it is the copy every viewer can read, and the reason is worth knowing before you pick the other:

For a system-wide or packaged install: cmake --build build --target install-help writes it under the configured CMAKE_INSTALL_PREFIX, so point that at /usr (cmake -DCMAKE_INSTALL_PREFIX=/usr ..) and wrap the target in sudo. A viewer reads its figures as separate files, and its sandbox exposes the system help directories, so this copy always shows them.

For your own account, needing no root: cmake --build build --target install-help-user writes it under $XDG_DATA_HOME (default ~/.local/share). Be aware that the same sandbox does not expose the user's data directory: those pages open, but a viewer such as Yelp shows them without their figures.