flatudp: the signal-row editor form with a per-row Vector as drop-down, its probe line, README and architecture notes

Multicast was verified on a loopback group (239.255.42.99 on 127.0.0.1); a real network was not exercised.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
Martino FerrariandClaude Opus 4.6 committed 2026-11-02 01:23:51 +01:00
1 parent e3bc1f72f3
commit d955baf612
6 files changed
+385 -20

No files matched your search

+27
View File
@@ -61,6 +61,33 @@ What feeds `Acquisition` is a `DataSource`, one of the registered *kinds*.
only through `post()`. `Acquisition::on_event` ignores the detail string and does nothing for `Error`,
so the source also writes each new error to stderr as `[e1608] ...` (once per distinct message, not per
retry); that line is where a user sees why a connection failed.
- **The flat UDP kind is three files and no thread.** `flatudp_signals` is the `signals` string codec,
the validity rules and the decoders, with no GTK and no sockets (the probe round-trips it);
`flatudp_source` is `FlatUdpSettings` plus the `DataSource`; `flatudp_kind` is the form, a signal-row
editor (a name, a type, a count and, while the count is above 1, a "Vector as" drop-down per row). The
datagram has no description, so a list that is wrong is invisible except as dropped datagrams: the size
check is the only defence, and it is why a wrong-size datagram is counted (`stale`) and reported once per
size.
- A vector row is shown one of two ways, chosen per row (`Row::wave`, the `w` suffix in the text). As
**channels** it is `count` scalar channels of one sample per cycle, so the list batches like an
all-scalar one. As a **waveform** it is one channel with `rows = count` and
`sampling_rate = rate * count`, one datagram per `Frame` (`num_samples = 1`). The two cannot share a
list because the model spans a frame by its largest element block and `Frame::value` is 0 past a scalar's
one value: a scalar beside a vector block would be zero-padded, so `problem()` demands that a list with a
waveform row be waveform rows of one count, and the form shows that message in the subtitle. The channel
total is capped at 256.
- Its frames are **fixed-size blocks** like the E-1608's for the same reason (`build_stamps` warns when the
sample count varies): `round(rate / 100)` datagrams for a list with no waveform row, a partial block
flushed after 100 ms. `rate` is required because `sampling_rate = 0` makes the model size the ring and
the ingest decimation for 1 kHz; there is no time-base switch, since `acquisition.cpp` has no stamping
path that ignores the declared cadence.
- `Disconnected` after 2 s without a *valid* datagram (wrong-size ones do not refresh the clock); it resets
`Acquisition`'s per-session state, so the next datagram re-anchors rather than reading as a gap, and the
history survives (`CONFIG` is sent once, from the first `poll()`).
- The form's row editor removes a row from inside its own button's `clicked` emission (GTK keeps the emitter
alive to the end of the emission, and the handler copies the form and the row out of its context first);
every handler tests `Form::alive`, which the groups' `destroy` clears, because the chooser may have
destroyed the sibling widgets the handlers reach by raw pointer.
### Data path
+17
View File
@@ -1912,6 +1912,7 @@ the one `[e1608] handshake with ...: Connection refused` line, `stream: ... sign
```
[flatudp] codec: ok=1
[flatudp] form: initial_valid=0 good_valid=1 size_ok=1 dup_valid=0 dup_msg=1 oversize_valid=0 add=1 remove=1 wave_valid=1 wave_sub=1 wave_roundtrip=1 mixed_valid=0 mixed_msg=1 length_valid=0 length_msg=1 cap_valid=0 cap_msg=1 cap_wave_valid=1 vec_shown=1 vec_drive=1 vec_reset=1 ok=1
[flatudp] datagram of 22 bytes, expected 21, check the signal list
[flatudp] stream: connected=1 signals=7 names=Trigger,Ia,Cnt,Wave[0],Wave[1],Wave[2],Wave[3] expect=Trigger,Ia,Cnt,Wave[0],Wave[1],Wave[2],Wave[3] frames=368 rate=1000.000 ok=1
[flatudp] channels: count=7 expect=7 ok=1
@@ -1945,6 +1946,22 @@ same reason.
rules refuse what they should (a waveform of count 1, a waveform mixed with another row or of another length,
257 channels). It needs no source, so `--source probe` shows it too. `ok=0` is a codec defect: a swapped byte
order, a wrong width or a rule that does not fire.
- `form: ... ok=1`: driven through the widgets, not the settings. `initial_valid=0` -- an empty list is not
connectable (`1` is a form that lets a source with no signals through). `good_valid=1 size_ok=1` -- a
13-byte list is valid and its subtitle says `13 bytes` and `6 channels` (`W:int16[4]` as channels).
`dup_valid=0 dup_msg=1` -- a duplicate name is refused *and the subtitle says why* ("used twice").
`oversize_valid=0` -- two `float64[4096]` rows (65536 bytes) exceed a datagram. `add=1` -- two clicks on
"Add signal" append `S1` and `S2`; `remove=1` -- the first row's "Remove" button takes that row out. A `0`
on `add`/`remove` with a `Gtk-CRITICAL` is a row handled after it was removed. The vector fields:
`wave_valid=1 wave_sub=1 wave_roundtrip=1` -- two waveform rows of one length are valid, the subtitle says
`2 channels`, and `signals` reads back with its `w`; `mixed_valid=0 mixed_msg=1` -- a waveform beside a
scalar is refused and the subtitle names the waveform rule; `length_valid=0 length_msg=1` -- waveforms of
two lengths are refused ("same length"); `cap_valid=0 cap_msg=1 cap_wave_valid=1` -- 300 channels are over
the 256 cap and the subtitle says `256`, while two `[200]w` waveforms (2 channels) are fine; `vec_shown=1`
-- the "Vector as" drop-down (`flatudp-vector`) is shown on a count above 1 and hidden on a scalar row;
`vec_drive=1` -- setting it to Waveform writes `Row::wave` (one waveform beside a channels row, then beside
a scalar, are both still invalid, and the text gains its `w`); `vec_reset=1` -- a row whose count returns
to 1 hides its drop-down and returns to Channels. Like `codec`, it needs no flatudp source.
- `stream: ... ok=1`: the model's signals are the list's channels in order, each at its declared rate: `rate`
for a scalar or a `channels` element (named `Name[k]`), `rate * count` for a waveform. `names` out of order is a
decode that mixed rows; a rate that is not the expected one is a `sampling_rate` that did not reach the model
+2 -2
View File
@@ -442,8 +442,8 @@ precedent, not an exception.
- `UDPSCOPE_PROBE_E1608_RECONNECT=8000` -- reads the source after the mock dropped the link and 500 ms
later checks the frame count moved on. The reading's own entry is in `probe-readings.md`.
- `UDPSCOPE_PROBE_FLATUDP=2500` with `UDPSCOPE_EXIT_AFTER_MS`, run as `--source flatudp` against
`tools/mock_flatudp.py` -- the flat UDP source end to end: a hand-computed codec and decode check (it
needs no source; it covers the `w` suffix, the waveform and 256-channel rules, the channel decode and the
`tools/mock_flatudp.py` -- the flat UDP source end to end: a hand-computed codec and decode check and the connect form's validity rules and signal-row editor (both
need no source; it covers the `w` suffix, the waveform and 256-channel rules, the channel decode and the
float64/uint16/int32/uint32/int64 types in both byte orders), the stream's channel names (`Wave[0]`..
for a `channels` vector) and declared rates (`rate * count` for a waveform), the channel count, value
continuity per row (and the offset between a vector's elements, or a waveform's block phase), the