docs: flat UDP spec -- drop time_mode, record the batching and event rules found in acquisition.cpp

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
Martino FerrariandClaude Opus 4.6 committed 2026-11-02 00:36:14 +01:00
1 parent cf4d105ece
commit 6c01689bfe
1 file changed
+20 -22
@@ -24,7 +24,7 @@ Read from `UDPSender::Synchronise()` and `MemoryDataSourceI`:
| Question | Decision | | Question | Decision |
| --- | --- | | --- | --- |
| Time base | Both. `time_mode=rate` (default): the user declares the cycle rate in Hz; frames are spaced `1/rate` and no loss is ever reported. `time_mode=arrival`: each datagram is stamped with its receive time. | | Time base | The user declares the nominal cycle rate in Hz (`rate`, required). The existing pipeline stamps the frames: anchored at arrival, samples spaced by the declared cadence, corrected to the measured cadence when the arrivals are more than 25% off it; `--relative-time` gives a purely counted clock. There is no `time_mode` switch: `acquisition.cpp` has no stamping path that ignores the declared cadence, and `sampling_rate = 0` would size the ring and the ingest decimation for 1 kHz (`model.cpp:60`). Loss is never reported (`lost = 0`). |
| Signal list entry | A row editor: name entry, type dropdown, element count and a remove button per row, plus an "Add signal" row. | | Signal list entry | A row editor: name entry, type dropdown, element count and a remove button per row, plus an "Add signal" row. |
| Network | Port and optional bind address, plus an optional multicast group and interface. No source-address filter. | | Network | Port and optional bind address, plus an optional multicast group and interface. No source-address filter. |
| Byte order | `little` (default) or `big`. | | Byte order | `little` (default) or `big`. |
@@ -40,8 +40,7 @@ Persisted in the session, settable with `--source-opt`. Kind id: `flatudp`.
| `bind` | empty | Local address to bind; empty is any. | | `bind` | empty | Local address to bind; empty is any. |
| `multicast` | empty | Group to join; empty is plain unicast. | | `multicast` | empty | Group to join; empty is plain unicast. |
| `iface` | empty | Local interface address for the join. | | `iface` | empty | Local interface address for the join. |
| `rate` | none | Cycle rate in Hz. Required when `time_mode=rate`. | | `rate` | none | Nominal cycle rate in Hz. Required; sizes the ring and the decimation and spaces samples within a frame. |
| `time_mode` | `rate` | `rate` or `arrival`. |
| `endian` | `little` | `little` or `big`. | | `endian` | `little` | `little` or `big`. |
| `signals` | none | `name:type[count],...`, e.g. `Trigger:uint8,Ia:float32,Wave:float32[16]`. A persistence format only: the form builds and parses it. | | `signals` | none | `name:type[count],...`, e.g. `Trigger:uint8,Ia:float32,Wave:float32[16]`. A persistence format only: the form builds and parses it. |
@@ -49,7 +48,7 @@ Types: `uint8/16/32/64`, `int8/16/32/64`, `float32`, `float64`. Count 1 is a sca
is a vector signal. Expected datagram size is the sum of `size * count` over the rows. is a vector signal. Expected datagram size is the sum of `size * count` over the rows.
Validity (form and `--source-opt` alike): at least one signal; names non-empty and unique; Validity (form and `--source-opt` alike): at least one signal; names non-empty and unique;
count 1-4096; total size at most 65507 bytes; `rate > 0` when `time_mode=rate`. count 1-4096; total size at most 65507 bytes; `rate` finite and positive.
## Architecture ## Architecture
@@ -68,26 +67,26 @@ Threading is unchanged: the source lives on the producer thread and `poll()` doe
`poll(timeout_ms)` waits on a non-blocking UDP socket and drains it. `poll(timeout_ms)` waits on a non-blocking UDP socket and drains it.
- A datagram of exactly the expected size is decoded row by row to doubles (byte-swapped when - A datagram of exactly the expected size is decoded row by row to doubles (byte-swapped when
`endian=big`) and delivered as one `Frame`: `num_samples = 1`, `counter` a running count, `endian=big`).
`lost = 0`, `late = false`, `recv_time` the arrival clock. - **Batching.** When every row is scalar, decoded datagrams are queued and delivered
- `set_datagram_tap` is honoured and `has_datagrams()` is true, so packet capture works. `block` at a time in one `Frame` (`num_samples = block`), where
- `on_config` is called once on open with one `SignalInfo` per row (`rows = count`, `block = clamp(round(rate / 100), 1, 1024)`, about 100 frames per second as the E-1608 does. A
`cols = 1`). partial block is flushed after 100 ms so a slow or stopped sender does not hold samples back.
When any row is a vector, a vector signal carries one sample per `Frame`, so each datagram is
### Time its own frame (`num_samples = 1`).
- `Frame`: `counter` a running frame count, `lost = 0`, `late = false`, `recv_time` the arrival
- `rate`: every signal has `time_mode = kTimePacket` and `sampling_rate = rate`, as the E-1608 clock at delivery.
does. Spacing comes from the declared rate; jitter does not appear in the trace. - `set_datagram_tap` is honoured for every datagram, valid or not, and `has_datagrams()` is
- `arrival`: `sampling_rate = 0`, so the existing arrival-clock path stamps each frame. true, so packet capture works.
- **To confirm in the plan:** that `acquisition.cpp`'s packet path with `sampling_rate = 0` - `on_config` is called from the first `poll()` with one `SignalInfo` per row (`rows = count`,
really stamps on arrival and does not degenerate. If it does not, the plan adds the small `cols = 1`, `time_mode = kTimePacket`, `sampling_rate = rate`).
change there.
### Errors and state ### Errors and state
- A wrong-size datagram is dropped, never decoded. Each distinct size raises one `Error` - A wrong-size datagram is dropped, never decoded. Each distinct size raises one `Error`
event ("datagram of N bytes, expected M, check the signal list") and increments `stale`. event ("datagram of N bytes, expected M, check the signal list") and increments `stale`.
- `connected` becomes true after the first valid datagram and false after 2 s of silence. - `Connected` is raised on the first valid datagram and `Disconnected` after 2 s of silence; a
later datagram raises `Connected` again.
- A bind or multicast-join failure raises an `Error` and the source stays disconnected. - A bind or multicast-join failure raises an `Error` and the source stays disconnected.
### Connect form ### Connect form
@@ -95,8 +94,7 @@ Threading is unchanged: the source lives on the producer thread and `poll()` doe
Built from `AdwPreferencesRow`s, with the destroy-notify guard used by the E-1608 form. Built from `AdwPreferencesRow`s, with the destroy-notify guard used by the E-1608 form.
- Network group: port, bind, multicast, interface. - Network group: port, bind, multicast, interface.
- Timing group: rate spin and time-mode combo. The rate spin is **disabled, not hidden**, in - Timing: a rate spin.
arrival mode.
- Byte order combo. - Byte order combo.
- Signals group: one row per signal, "Add signal" at the bottom, and a live subtitle with the - Signals group: one row per signal, "Add signal" at the bottom, and a live subtitle with the
implied size ("41 bytes per datagram"). `valid()` is false while the list breaks a validity implied size ("41 bytes per datagram"). `valid()` is false while the list breaks a validity
@@ -110,7 +108,7 @@ No unit suite; the self-check is the contract.
- `tools/mock_flatudp.py`: emits packed structs for a given signal spec and rate; options for - `tools/mock_flatudp.py`: emits packed structs for a given signal spec and rate; options for
big-endian and for one wrong-size datagram. big-endian and for one wrong-size datagram.
- `UDPSCOPE_PROBE_FLATUDP` in `src/ui_probe.cpp`: stream names and count; a hand-computed - `UDPSCOPE_PROBE_FLATUDP` in `src/ui_probe.cpp`: stream names and count; a hand-computed
decode of known values including a vector signal and a `uint64`; rate-mode spacing; the decode of known values including a vector signal and a `uint64`; the declared rate reaching the model; the
wrong-size error reported once and counted in `stale`; disconnect after silence; the form's wrong-size error reported once and counted in `stale`; disconnect after silence; the form's
validity rules; the `signals` codec round trip. A multicast loopback check runs if the validity rules; the `signals` codec round trip. A multicast loopback check runs if the
sandbox allows it. sandbox allows it.