added auto measuraments
This commit is contained in:
1 parent
44fc4cdf36
commit
0eceaf537b
7 files changed
+2182
-55
No files matched your search
@@ -201,10 +201,94 @@ Two lines of the self-check output are assertions in disguise:
|
||||
2 kHz at about 6%. **A `y=[-1, 1]` there means the filter is gone** and the 2 kHz sine has
|
||||
aliased to a full-amplitude 125 Hz one, which looks entirely plausible on screen. This is
|
||||
the only check that distinguishes averaging from striding.
|
||||
- `[layout-probe] <action> rows=N buttons=B rows_y=[..] close=[..] add=[..] traces=[..]`
|
||||
- `[cursors-probe] <action> mode=.. on=D pane=D t0=.. t1=.. dt=.. T=[..] lines=N [..]`
|
||||
(stderr, `UDPSCOPE_PROBE_CURSORS_MS`) — walks `vertical` → `horizontal` → `both` →
|
||||
`mix`, then moves a marker *through the plot* (`Plot::set_cursor_x(..., Free)`, which is
|
||||
what a drag does) and reports again. **The times must survive every mode change**: they
|
||||
are the `RowSet`'s one pair, and a mode that parks the markers must not read them back
|
||||
(a parked cursor reports a real number, so a read-back would adopt it as a gesture — the
|
||||
bug that made `T0` read 0). **`lines`** must be 0 for `vertical`, one per row for
|
||||
`horizontal`/`both` (and the time row exists only in the modes that show the vertical
|
||||
pair — `T=[]` in `horizontal` is the assertion that it does not), and one per bound trace
|
||||
for `mix`; in `mix` the values must be the
|
||||
traces' own (`--` when a marker is past the newest sample, `partial` when only one
|
||||
marker is over samples). After the move, `t0` must have changed and every row's marker
|
||||
must have followed it (the report's `t0` is the shared value). **`marks`** is the `mix`
|
||||
mode's crossing annotations: **0 in every other mode, and one per bound trace for each
|
||||
marker that is over samples in `mix`** (so a move that brings the second marker into the
|
||||
data doubles it). The `first mark at x=.. y=..` line that follows must agree with the
|
||||
pane's *first* value for the same trace (the pane's lines are `chip Y1 Y2 dY`, with
|
||||
the values formatted as measurements -- two decimals, an SI prefix, the channel's unit --
|
||||
so the comparison is against the first number): both are read through the same
|
||||
`Plot::crosshair_value()`, and that line is the check that they stay the same number.
|
||||
A **ruler** drag is the other direction and the other hazard: the pane's `A`/`B` cells
|
||||
for a ruler row must change when the ruler moves (`after ruler drag` in the probe walks
|
||||
the first ruler to 80% of its y range), because nothing shares a ruler and the read-back
|
||||
in `RowSet::sync_cursors()` is the only thing that can notice. The dump's `cursors in plot
|
||||
0:` line is the other half: it reads the markers back out of
|
||||
the plot (`cursor(id).x/.visible`), which is what the renderer draws from — the pane's
|
||||
numbers and the plot's state agreeing is the difference between "the app placed them" and
|
||||
"the app placed them and they are on screen".
|
||||
- `[measures-probe]` + the dump's `measure N: 'sig' type thr=..% above=D show=D -> ok|invalid value text=[..]
|
||||
[duty=..] [marks=..]` and `measures pane: n=N revealed=D marks_shown=M pane_rows=R pane_w=W
|
||||
pane_h=H`
|
||||
(stderr, `UDPSCOPE_PROBE_MEASURES_MS`) — adds one measurement of every type on the first
|
||||
plottable signal and lets the tick compute them, so what it prints is what a user reads.
|
||||
On the test stream's 1 Hz ±1 sine the values must be: average ≈ 0, `top` ≈ 0.64 and
|
||||
`bottom` ≈ -0.64 (the mean of each half of a sine), max/min ≈ ±1, `pulse duration` ≈ 0.5 s
|
||||
with `duty` ≈ 0.5, `period` ≈ 1 s and `frequency` ≈ 1 Hz. `marks=` lists each mark with its kind, and the **mix is the point**:
|
||||
`level:V` alone for average/top/bottom, `level:V,time:T` for min/max (a ruler at the value
|
||||
*and* a marker at the first sample that reached it — two level marks at both extremes was
|
||||
the bug), `time:A,time:B` for pulse/period/frequency. `text=` is the pane's own string with
|
||||
its unit, so `frequency` must read `Hz` (not the signal's unit), `period`/`pulse duration`
|
||||
seconds, and the rest the channel's unit. `marks_shown` counts two per shown measurement
|
||||
(a min/max showing both its marks counts two of its own). `measures window:` is the
|
||||
timeliness assertion: **`drift` must stay at (or near) zero** -- the window the numbers
|
||||
were computed over against the one on screen -- and every *time* mark must lie inside it
|
||||
(`marks_in_window=K/K`). A measurement is recomputed when the window moves (2 % of its
|
||||
span) or the held/live state changes, never on a wall-clock timer: a timer is what let a
|
||||
marker sit outside the viewport and a dragged one survive a frame. **`marks_selectable` must be 0**: the
|
||||
measurement marks are created unselectable (`Plot::set_cursor_selectable()`, which the
|
||||
library gained for this), read back out of the plot here -- re-placing a marker every frame
|
||||
does **not** stop a drag, because the controller holds it for the gesture's life and the
|
||||
marker follows the pointer between frames. The library's own controller test
|
||||
(`an_unselectable_cursor_is_neither_hovered_nor_grabbed`) is the other half. The probe's last
|
||||
step still drags a mark *programmatically* and requires the next frame to put it back
|
||||
(`restored=1`), which now checks the placement path rather than the read-only property. The dialog report's `type= thr=
|
||||
side= show=` is the per-type behaviour: `period` and `frequency` must show **neither** the
|
||||
split nor the side row (they compute from the mid level's rising crossings, and an option
|
||||
there is one the user can only get wrong), `average`/`min`/`max` no split either, and only
|
||||
`pulse duration` a side. A measurement whose signal is not acquired, or
|
||||
a window with too few crossings, must read `invalid`/`--` rather than a made-up number
|
||||
(`frequency` on a window holding one edge is the case: `duty` is reported only when a
|
||||
period exists to divide by). The probe then **walks the dialog's lifecycle**: open the edit
|
||||
dialog on measurement 0, click its `Save`, open it again, click `Delete` — `measures=` must
|
||||
be unchanged after the save and one lower after the delete, with the process still alive.
|
||||
That is the check for the two things a dialog of this shape gets wrong: two buttons owning
|
||||
one context (a double free aborts on edit/delete, and only those paths have two buttons) and
|
||||
a `const char*` array built from a `std::vector<std::string>` as it grew (every pointer
|
||||
dangling, the first entry the most visibly). The report's `signals=[..]` and `drop_h` are the second and third:
|
||||
the list must be the **plotted traces** (real names, deduplicated, in signal-list order —
|
||||
*not* every channel, and never a stream's timestamp signal, which is what an acquired-but-
|
||||
unplotted list put first and auto-selected), and `drop_h` must be a combo box's height
|
||||
(tens of pixels — a scrolled-window wrapper asked it to grow to 160).
|
||||
- `[fit-probe] <action> window=.. x=[..] span=.. y=[..]` (stderr, `UDPSCOPE_PROBE_FIT_MS`) —
|
||||
the two fits, through the same `RowSet` calls the right-click menu wires. It first puts
|
||||
the y scale and the window where the data is *not* (a fixed `[-100, 100]`, a 50 ms
|
||||
window), so the assertions have something to be wrong about: **`fit x` must widen the
|
||||
window to the data's span and leave `y` exactly as it was**, and **`fit all` must then
|
||||
move `y` to the data's own range**. A `y` that changed on the `fit x` line is the two
|
||||
paths aliased.
|
||||
- `[layout-probe] <action> rows=N buttons=B done=D done_box=[x,y,WxH] rows_y=[..] close=[..] add=[..] traces=[..]`
|
||||
(stderr, `UDPSCOPE_PROBE_LAYOUT_MS`) — **`buttons` must be `2N+1`** while the editor is
|
||||
up (one `×` per row, one `+` per boundary) and **0 on the `editor off` line**: the
|
||||
buttons exist only while the editor does. **`traces=` is the real assertion**: an insert
|
||||
buttons exist only while the editor does. `done` follows the same rule for the editor's
|
||||
own exit button (1 up, 0 off), and its `done_box` on the `placed` line must sit in the
|
||||
area's top-right corner: `x + W` within a few pixels of the area width and `y` within a
|
||||
few of 0 (the on-switch line reports a box from before the first frame, so read the
|
||||
`placed` one). The last probe step leaves the editor by **clicking that button** — the
|
||||
toolbar toggle is still exercised by the first step — so `buttons=0 done=0` with the
|
||||
rows and traces untouched is the assertion that the way out works. **`traces=` is the real assertion**: an insert
|
||||
or a remove moves the rows' *contents* with them, so `add above 0` must read
|
||||
`[0:-|1:sine,…]` — row 0's signals now on row 1, not renumbered onto the new empty row —
|
||||
and `remove 0` must pull them back to `0:`. A `traces=` where the signals stayed on row 0
|
||||
@@ -229,6 +313,14 @@ Two lines of the self-check output are assertions in disguise:
|
||||
first run's values, and the signals it names must come back *acquired* (a `row 0 slot 0
|
||||
-> channel ... samples=N` line). Delete the file before a run whose layout you did not
|
||||
mean to inherit (`127.0.0.1_9540.toml` and friends accumulate otherwise).
|
||||
- `[udpscope] starved: connected (config=D), reconnects=N, no new frames in the last
|
||||
second ...` (stderr, once per second while the state holds, plus a status-line notice) —
|
||||
"connected but nothing is arriving after a reconnect", which over unicast is the second
|
||||
instance taking the source's single session. Two ways to see it without a second client:
|
||||
point the app at a UDP port where nothing answers (the client CONNECTs, times out,
|
||||
reconnects; `config=0`), and check it does **not** fire against a live source. The status
|
||||
line carries ` | N reconnects` only when N > 0, which is the same counter without the
|
||||
prose.
|
||||
- **The export's `time`/`stream_time` columns must be strictly increasing and evenly
|
||||
spaced.** Check the written file, not just the probe: `np.diff(t)` with no `<= 0` and
|
||||
`rows / (t[-1] - t[0])` equal to the stored rate (the `ch` line's figure). Duplicate or
|
||||
@@ -363,6 +455,13 @@ far the largest file.
|
||||
again until the next latch. `PlotArea::create()` is handed `App::placeholder_plot`,
|
||||
never a row: the area keeps a reference to the plot it was created with, and a rebuild
|
||||
destroys every row.
|
||||
- **The cursors' on/off is one flag on the `RowSet`, never a copy per row.** It is one
|
||||
piece of state (the toolbar toggle) and the mode is one value; a per-row copy is something
|
||||
`insert_row()`'s `clear_row()`, or any rebuild, can clear behind the user's back, which is
|
||||
how a row added while the cursors were already on came up with its instruments parked
|
||||
while every other row showed them. The same goes for the vertical pair's `x`: it is one
|
||||
stream time for the figure, so it lives on the `RowSet` too, and `Row` keeps only what is
|
||||
genuinely per row (the rulers' values, the traces, the colours).
|
||||
- **A row's contents are state of the `RowSet`, not of the figure.** `insert_row()` and
|
||||
`remove_row()` shift that state with `carry_row()` and let `rebuild_all()` re-deal the
|
||||
figure handles, so inserting above row 1 moves what row 1 plotted down to row 2 rather
|
||||
|
||||
Reference in new issue
Block a user