flatudp: the source, kind, mock sender and stream probes

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:05:38 +01:00
1 parent 6e8e71b32f
commit 78a29fbc39
13 files changed
+962 -7

No files matched your search

+19
View File
@@ -135,6 +135,25 @@ python3 tools/mock_e1608.py --port 54711 &
--source-opt ai0=BIP10V --source-opt ai1=BIP5V --source-opt plot0=1
```
`--source flatudp` receives the MARTe2 `UDPSender` datasource (one datagram per cycle: the signals
concatenated in declaration order, no header, no padding). The datagram carries **no description**, so the
settings are the whole contract: `port` 44488, `bind`, `multicast` and `iface` (a group to join and the local
interface for it), `rate` (the sender's cycle rate in Hz; **required**, it sizes the ring and the ingest
decimation and spaces the samples within a frame), `endian` `little|big`, and `signals`
`name:type[count],...` with types `uint8/16/32/64`, `int8/16/32/64`, `float32`, `float64` (a count above 1
makes a vector signal: one channel whose `count` values per datagram are consecutive samples, as a UDPStreamer
block signal), e.g. `Trigger:uint8,Ia:float32,Wave:float32[16]`. A datagram of any other size than
the list implies is dropped and counted as stale; each distinct wrong size is reported once on stderr as
`[flatudp] ...`. Loss is never reported: the stream has no counter. `tools/mock_flatudp.py` is a sender for
a signal spec (`--rate`, `--signals`, `--big-endian`, `--gap S,D`, `--bad-every S`, `--multicast G --mcast-if A`,
`--log FILE`).
```sh
python3 tools/mock_flatudp.py --port 44588 --rate 1000 --signals Trigger:uint8,Ia:float32,Wave:int16[4] &
./build/udpscope --source flatudp --source-opt port=44588 --source-opt rate=1000 \
--source-opt signals=Trigger:uint8,Ia:float32,Wave:int16[4]
```
`tools/mock_udpstreamer.py` has waveform-injection options used by the probes:
- `--square-harmonics N` shapes `square`: `N` odd harmonics of the ideal step's Fourier
+52 -3
View File
@@ -1827,9 +1827,10 @@ are unrealized and measure 0x0.
## `[source-kinds]`
```
[source-kinds] kinds=udps,e1608,probe
[source-kinds] kinds=udps,e1608,flatudp,probe
[source-kinds] form udps: round_trip=1 valid=1
[source-kinds] form e1608: round_trip=1 valid=1
[source-kinds] form flatudp: round_trip=1 valid=1
[source-kinds] form probe: round_trip=1 valid=1
[source-kinds] panel disconnected: panels=1 present=1 sensitive=0
[source-kinds] panel connected: panels=1 present=1 sensitive=1
@@ -1841,10 +1842,10 @@ are unrealized and measure 0x0.
source-plugin path from the registry to the producer thread. The variable also registers the test-only `probe`
kind, so a run without it never shows that kind. The `form` line appears once per registered kind.
- `kinds=udps,e1608,probe`: a missing kind means `register_builtin_sources()` was not called before `parse_args()`,
- `kinds=udps,e1608,flatudp,probe`: a missing kind means `register_builtin_sources()` was not called before `parse_args()`,
or the env var gate is wrong.
- `round_trip=0`: a form's `write` and `read` disagree about a key -- a renamed `SourceConfig` key, or an
absent key that was not defaulted. `valid=0` is a default configuration the form refuses. The e1608 form's sample sets address, port, scan_rate, mode, ai1, trigger, dir1 and plot0; a mismatch names one of those.
absent key that was not defaulted. `valid=0` is a default configuration the form refuses. The e1608 form's sample sets address, port, scan_rate, mode, ai1, trigger, dir1 and plot0; a mismatch names one of those. The flatudp sample sets port, bind, multicast, iface, rate, endian and signals.
- `panel disconnected: sensitive=0 present=1`: **disable, don't hide.** `present=0` is a panel that was hidden
or never added; `sensitive=1` is a panel that stays live with no source behind it.
- `panel connected: sensitive=1`: anything else means `apply_source_panels` acted on the wrong widget (it must
@@ -1907,6 +1908,54 @@ the one `[e1608] handshake with ...: Connection refused` line, `stream: ... sign
the scan (it also reads 0 when `connects` is still 1, since a frame count that grows on a link that never dropped
proves nothing).
## `[flatudp]`
```
[flatudp] codec: ok=1
[flatudp] datagram of 22 bytes, expected 21, check the signal list
[flatudp] stream: connected=1 signals=4 names=Trigger,Ia,Cnt,Wave expect=Trigger,Ia,Cnt,Wave frames=3735 rate=1000.000 ok=1
[flatudp] channels: count=4 expect=4 ok=1
[flatudp] continuity: ch0_breaks=0 ch1_breaks=0 ok=1
[flatudp] wrong size: count=3 message='datagram of 22 bytes, expected 21, check the signal list' ok=1
[flatudp] capture button: present=1 sensitive=1
[flatudp] state: valid=3735 wrong=3
[flatudp] silence: connected=0 model_connected=0 ok=1
[flatudp] resumed: connected=1 frames_before=4990 frames_after=7147 ok=1
```
`UDPSCOPE_PROBE_FLATUDP=2500` (the first lines; counts vary) and `UDPSCOPE_PROBE_FLATUDP_GAP=8000` (the last two)
run as `--source flatudp` against `tools/mock_flatudp.py` with the options in `verification.md`. The second line is
the source's own stderr message, printed once per distinct wrong size, not a probe line. The `continuity` line
above is the scalar-list run (`Trigger,Ia,Cnt`, big endian); the mixed-list run with `Wave:int16[4]` reads
`continuity: skipped (...)`. With no sender the stream lines read `connected=0 ... frames=0 ... ok=0`, `continuity`
is skipped and the capture button reads `sensitive=0`: a reading, not a failure. A run without `--bad-every` reads
`wrong size: count=0 ... ok=0` for the same reason.
- `codec: ok=1`: the `signals` text round-trips, a hand-built 24-byte datagram decodes to the hand-computed
values in both byte orders (`uint8` 254, `int16` -2, `float32` 1.5, `uint64[2]` 1 and 4294967298, `int8` -1)
and the validity rules refuse what they should. 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.
- `stream: ... ok=1`: the model's signals are the list's rows in order, at the declared rate. `names` out of
order is a decode that mixed rows; `rate` of 1000 against a different declared rate is a `sampling_rate`
that did not reach the model (the ring and the decimation are then sized for the wrong cadence).
- `channels: count=4`: one channel per signal. A vector signal is **not** one channel per element: the model
reads its `count` values as consecutive samples of one channel, as for a UDPStreamer block signal.
- `continuity: ch0_breaks=0 ch1_breaks=0`: consecutive samples of the mock's first two signals step by 1 and
by 2 modulo 100. A break is a lost or reordered datagram, a batch flushed with the wrong number of samples,
a wrong byte order or ingest decimation that is not 1:1. It is skipped for a list with a vector row: the
model spans each frame by its largest element block, so the scalar channels of a mixed list are padded
with zeros (`Frame::value` reads 0 out of range) and a continuity check on them would read nearly every
step as a break.
- `wrong size: ok=1`: wrong-size datagrams were counted and the message names the received and expected size.
The message must also be the only stderr line of its kind: one per distinct size, however many arrive.
- `capture button: present=1 sensitive=1`: the kind reports datagrams, so the capture UI is live (contrast
`[source-kinds]`, where the probe kind leaves it disabled). With no stream it reads `sensitive=0`.
- `silence: connected=0 model_connected=0`: 2 s without a valid datagram raised `Disconnected`. `connected=1`
is the timeout not firing, wrong-size datagrams refreshing the silence clock (they must not), or the mock's
gap not covering the probe (see the timing note in `verification.md`).
- `resumed: ok=1`: `Connected` was raised again and frames resumed. `connected=0` here means the source never
recovered after the sender came back.
## `[prefs-probe]`
```
+19 -1
View File
@@ -423,7 +423,7 @@ precedent, not an exception.
- `UDPSCOPE_PROBE_SOURCE_KINDS=2000` with `UDPSCOPE_EXIT_AFTER_MS=5000`, run as `--source probe --source-opt rate=500`
-- the source-plugin path end to end. The variable's presence also registers the test-only `probe` kind (a
synthetic sine with one panel), so it is the only way a run sees that kind; its value is the delay in ms.
Eight `[source-kinds]` lines (one `form` line per registered kind: `udps`, `e1608`, `probe`); the
Nine `[source-kinds]` lines (one `form` line per registered kind: `udps`, `e1608`, `flatudp`, `probe`); the
reading's own entry is in `probe-readings.md`.
- `UDPSCOPE_PROBE_E1608=1500` with `UDPSCOPE_EXIT_AFTER_MS`, run as `--source e1608` against
`tools/mock_e1608.py` -- the MCC E-1608 end to end: panels present and sensitive, a DAC write, a
@@ -441,6 +441,24 @@ precedent, not an exception.
- **a rejected code:** mock `--code 1234`, app `code=7`: one error line, the retries stop.
- `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), the stream's signal names and declared rate, the channel count (one per signal), value
continuity across the first two signals (all-scalar lists only), the wrong-size accounting and the capture
button. It enables the signals itself, so do **not** add `UDPSCOPE_ENABLE_ALL_MS`. Runs, each short and
serial, with the mock killed afterwards (quote the `signals` value: zsh globs the `[4]`):
- **mixed list, gap and bad datagrams:** mock `--rate 1000 --signals Trigger:uint8,Ia:float32,Cnt:uint64,Wave:int16[4]
--gap 9,5 --bad-every 1 --log /tmp/flatudp-mock.log` started 1 s before the app, the same `signals` as a
`--source-opt`, with `UDPSCOPE_PROBE_FLATUDP=2500 UDPSCOPE_PROBE_FLATUDP_GAP=8000 UDPSCOPE_EXIT_AFTER_MS=14500`;
stderr carries exactly one `datagram of 22 bytes, expected 21` line. The mock's clock starts about 4 s
before the app's probe clock (`xvfb-run` and the app's own startup), so a gap of 9 s to 14 s on the mock's
clock covers the app's 8 s probe (silence) and ends before its 12 s one (resumed); a mock gap measured in
the app's own seconds would miss both;
- **scalar list, big endian (batching, continuity):** mock `--signals Trigger:uint8,Ia:float32,Cnt:uint64 --big-endian`,
app `--source-opt endian=big`, `UDPSCOPE_PROBE_FLATUDP=2500 UDPSCOPE_EXIT_AFTER_MS=5000`;
- **no sender:** `connected=0` throughout, a reading and not a failure.
- `UDPSCOPE_PROBE_FLATUDP_GAP=8000` -- reads the source inside the mock's silence (2 s after its last datagram
the source is disconnected) and 4 s later checks it reconnected and the stream moved on.
- `UDPSCOPE_PROBE_SEARCH_MS` — runs a buffer search for the armed condition, cross-checks the live
trigger's own `t0` against the nearest hit (`agree=1` is the load-bearing reading), and jumps to
hit 0. Reports the resulting `mode=`, `axis=` and `marks placed=`.