docs: the TCPStreamer source kind, the client's TCP mode, and its probes
This commit is contained in:
1 parent
3dad65c487
commit
a65f953f17
5 files changed
+110
-7
No files matched your search
@@ -32,8 +32,16 @@ What feeds `Acquisition` is a `DataSource`, one of the registered *kinds*.
|
||||
source-neutral, and its pointers are valid **only during `on_data`**: copy what is kept.
|
||||
- The GTK thread reaches a source **only** through `DataSource::post(Command)`; the queue is drained
|
||||
at the top of `poll()`. A panel never calls a source directly.
|
||||
- `UdpsSource` wraps `deps/udpsclient` unchanged; `udps_*` names appear only in
|
||||
- `UdpsSource` wraps `deps/udpsclient` and takes a `Transport` (UDP or TCP); `udps_*` names appear only in
|
||||
`src/source/udps_*.cpp` (`grep -rn "udps_" src` outside those files is a leak of the old coupling).
|
||||
- **The TCPStreamer kind (`tcps`) is `UdpsSource` with `Transport::Tcp`**, not a new `DataSource`: the
|
||||
transport argument sets `use_tcp` / `stream_name` in the client config, defaults the port to 44500 and
|
||||
ignores multicast and interface. `src/source/tcps_kind.cpp` is only the form and the identity (`host:port` or
|
||||
`host:port/stream`). One stream per connection (no multiplexed `SUBSCRIBE` list: a source is
|
||||
single-timebase; merge on the server with `TCPStreamJoin`), and the form refuses a name with a comma.
|
||||
`has_datagrams()` is false, so the capture button stays disabled. `Disconnected` follows the client's 2 s
|
||||
silence timeout or a closed connection, and it reconnects every 0.5 s; a stream whose frames are further
|
||||
apart than 2 s therefore reconnects continually, as UDPS does.
|
||||
- The frame is **single-timebase**. A multi-timebase source (ABScope) will change the interface and
|
||||
the data path together; do not bend one frame to carry two clocks.
|
||||
- A kind's capability panels are pages of the slide-over (`slideover_add_page`), named `source-<id>`. A disconnected source
|
||||
|
||||
@@ -164,6 +164,16 @@ python3 tools/mock_flatudp.py --port 44588 --rate 1000 --signals 'Trigger:uint8,
|
||||
(Quote the `signals` value: zsh globs the `[4]`. The mock takes the same text with or without the trailing `w`;
|
||||
the datagram is the same, `w` only says how the app shows it.)
|
||||
|
||||
`--source tcps` receives a MARTe2 `TCPStreamer` (standalone) or `TCPStreamService` (named stream) over
|
||||
TCP: `server` (default 127.0.0.1), `port` 44500, and `stream` (optional; a `SUBSCRIBE` frame is sent only when it is
|
||||
set). `tools/mock_tcpstreamer.py` serves one client with the `mock_udpstreamer.py` signals (`--port`, `--stream`
|
||||
to require a `SUBSCRIBE`, `--rate`, `--batch`, `--freq`, `--close-after S`, `--drop-every N`).
|
||||
|
||||
```sh
|
||||
python3 tools/mock_tcpstreamer.py --port 44500 --stream fast &
|
||||
./build/udpscope --source tcps --source-opt port=44500 --source-opt stream=fast
|
||||
```
|
||||
|
||||
`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
|
||||
@@ -201,7 +211,8 @@ form for a new host. Every probe below passes `--server`, so none of them sees i
|
||||
|
||||
`deps/plotter` is built from source with only the GTK4 adapter enabled
|
||||
(`PLOT_BUILD_GTK4`); its tests and examples are forced off. `deps/udpsclient` is a
|
||||
vendored C99 source pair compiled into a static lib. `deps/udpsclient` gained one
|
||||
vendored C99 source pair compiled into a static lib. `deps/udpsclient` gained a TCP
|
||||
transport mode (item 6 below) and one
|
||||
additive entry point for the packet capture — `udps_client_set_datagram_cb()`, firing
|
||||
inside `udps_client_poll()` immediately after the UDP `recv()` returns, before the
|
||||
magic check or reassembly — plus the public `UDPS_MAX_DATAGRAM_BYTES` the capture
|
||||
@@ -227,7 +238,7 @@ handler clears either pointer without knowing which dialog it is. Presenting eit
|
||||
app's plain `GtkApplicationWindow` is supported and warning-free; it was checked.
|
||||
|
||||
`deps/udpsclient` is a vendored C99 source pair and its diff from the upstream source
|
||||
is intentional. Five things were changed:
|
||||
is intentional. Six things were changed:
|
||||
|
||||
1. **A reorder ring of reassembled payloads.** When a packet arrives with a higher counter
|
||||
than expected, the raw datagram is held in a fixed-size ring rather than decoded immediately.
|
||||
@@ -258,6 +269,15 @@ is intentional. Five things were changed:
|
||||
would be the same silent loss by another route. `stats.reorder_overflows` counts the event,
|
||||
so an undersized ring is a reading rather than a mystery; it is surfaced in the exit dump and
|
||||
the `[reseq-probe]` line as `overflows=`.
|
||||
6. **A TCP transport mode.** `udps_client_config_t::use_tcp` / `stream_name` select a TCP
|
||||
connection to a MARTe2 `TCPStreamer` or `TCPStreamService`: `connect_tcp()` (a 3 s connect
|
||||
bound through `SO_SNDTIMEO`, a 1 s receive bound, an optional `SUBSCRIBE` frame,
|
||||
`UDPS_PKT_SUBSCRIBE = 5`, sent only when `stream_name` is set), the existing
|
||||
`read_tcp_frame()` for the frames, the reorder window forced to 0 (TCP is ordered, so a
|
||||
counter gap is a server-side drop and is reported at once), multicast forced off, no
|
||||
DISCONNECT frame on close (a write to a closed peer would raise SIGPIPE), no keepalive ACK
|
||||
and no datagram tap. A frame must fit `UDPS_MAX_DATAGRAM_BYTES` (the server's
|
||||
`MaxPayloadSize` default of 60000 does). A stream name over 255 bytes is refused at create.
|
||||
|
||||
`deps/plotter` is upstream's own repository and this app's figure support was added
|
||||
there rather than worked around here: `Figure::set_min_y_gutter()` (a floor under the
|
||||
|
||||
@@ -2117,8 +2117,9 @@ and `regained` reads 400 ms later.
|
||||
## `[source-kinds]`
|
||||
|
||||
```
|
||||
[source-kinds] kinds=udps,e1608,flatudp,probe
|
||||
[source-kinds] kinds=udps,tcps,e1608,flatudp,probe
|
||||
[source-kinds] form udps: round_trip=1 valid=1
|
||||
[source-kinds] form tcps: 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
|
||||
@@ -2132,10 +2133,10 @@ and `regained` reads 400 ms later.
|
||||
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,flatudp,probe`: a missing kind means `register_builtin_sources()` was not called before `parse_args()`,
|
||||
- `kinds=udps,tcps,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, plot0 and gen_rate; a mismatch names one of those. The flatudp sample sets port, bind, multicast, iface, rate, endian and signals.
|
||||
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, plot0 and gen_rate; a mismatch names one of those. The flatudp sample sets port, bind, multicast, iface, rate, endian and signals. The tcps sample sets server, port and stream.
|
||||
- `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
|
||||
@@ -2302,6 +2303,39 @@ same reason.
|
||||
- `resumed: ok=1`: `Connected` was raised again and frames resumed. `connected=0` here means the source never
|
||||
recovered after the sender came back.
|
||||
|
||||
## `[tcps]`
|
||||
|
||||
```
|
||||
[tcps] stream: connected=1 signals=5 names=time,sine,square,ramp,noise frames=373 ok=1
|
||||
[tcps] capture button: present=1 sensitive=0
|
||||
[tcps] reconnect: connected=1 frames_before=211 after=301 reconnects_before=0 after=1 ok=1
|
||||
```
|
||||
|
||||
`UDPSCOPE_PROBE_TCPS=1500` (the first two lines; counts vary) and `UDPSCOPE_PROBE_TCPS_RECONNECT=2000` (the last)
|
||||
run as `--source tcps --source-opt port=44500` against `tools/mock_tcpstreamer.py` with the options in
|
||||
`verification.md`. A service run (`--stream fast` on both sides) reads the same two lines; the mock logs
|
||||
`SUBSCRIBE 'fast'`. With no mock the stream line reads `connected=0 signals=0 names= frames=0 ok=0`: a reading,
|
||||
not a failure (the retries are quiet on stderr).
|
||||
|
||||
- `stream: ok=1`: connected, the five names the mock sends (its `time` master channel is a signal of its own, so
|
||||
the list is `time,sine,square,ramp,noise`, not four), and frames counted. `connected=0` with a mock running is
|
||||
the connect or the `SUBSCRIBE` failing -- a mock started with `--stream` and an app without `stream=` (or a
|
||||
different name) is closed by the mock (at once on a wrong name, after 5 s on none) and the app reconnects
|
||||
forever; the mock's log says `no valid SUBSCRIBE`. `names` wrong or `signals=0` with `connected=1` is a CONFIG that was not decoded.
|
||||
- `capture button: present=1 sensitive=0`: the capture is of the UDP socket and a TCP source reports no
|
||||
datagrams (`has_datagrams()` false), so it is disabled, not hidden. `sensitive=1` is the button enabled for a
|
||||
kind that has nothing to capture.
|
||||
- `reconnect: ok=1`: after the mock closed the connection (`--close-after 3`) the client came back
|
||||
(`connected=1`), frames advanced past `frames_before` and the `reconnects` stat advanced past
|
||||
`reconnects_before`. The probe polls every 100 ms for up to 6 s, so it reads the first moment all three hold.
|
||||
`ok=0` is a client that did not come back after the close, or `Acquisition` not mapping `reconnects`;
|
||||
`reconnects_before=1` is the probe's baseline falling **after** the close (`UDPSCOPE_PROBE_TCPS_RECONNECT` must
|
||||
be earlier than the mock's `--close-after`).
|
||||
- **Not a defect:** after the reconnect the mock restarts its time base at 0 and the app prints repeating
|
||||
`warning: the clock stalled at ... samples stamped [...] are all behind it`. That is `Acquisition`'s behaviour
|
||||
for any stream whose clock goes backwards, identical to restarting the UDPS mock; the line is a reading, not a
|
||||
failure of this probe.
|
||||
|
||||
## `[prefs-probe]`
|
||||
|
||||
```
|
||||
|
||||
@@ -576,7 +576,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.
|
||||
Nine `[source-kinds]` lines (one `form` line per registered kind: `udps`, `e1608`, `flatudp`, `probe`); the
|
||||
Ten `[source-kinds]` lines (one `form` line per registered kind: `udps`, `tcps`, `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
|
||||
@@ -637,6 +637,22 @@ precedent, not an exception.
|
||||
- **no sender:** `connected=0` throughout, a reading and not a failure.
|
||||
- `UDPSCOPE_PROBE_FLATUDP_GAP=1500` -- starts the silence poll described above (2 s after the source's last
|
||||
datagram it must be disconnected; then it must reconnect and the frame count move on).
|
||||
- `UDPSCOPE_PROBE_TCPS=1500` with `UDPSCOPE_EXIT_AFTER_MS`, run as `--source tcps --source-opt port=44500`
|
||||
against `tools/mock_tcpstreamer.py --port 44500` -- the TCPStreamer source end to end: the connection, the
|
||||
five channel names the mock sends (`time,sine,square,ramp,noise`), frames flowing and the capture button
|
||||
present but disabled. It enables the signals itself, so do **not** add `UDPSCOPE_ENABLE_ALL_MS`. Runs, each
|
||||
short and serial, with the mock killed afterwards:
|
||||
- **standalone:** the mock with no `--stream`, `UDPSCOPE_PROBE_TCPS=1500 UDPSCOPE_EXIT_AFTER_MS=4000`;
|
||||
- **service:** mock `--stream fast`, app `--source-opt stream=fast`, the same environment; the mock logs
|
||||
`SUBSCRIBE 'fast'`;
|
||||
- **reconnect:** mock `--close-after 3`, `UDPSCOPE_PROBE_TCPS=1500 UDPSCOPE_PROBE_TCPS_RECONNECT=2000
|
||||
UDPSCOPE_EXIT_AFTER_MS=8000`; stderr also carries `the clock stalled at ...` warnings after the
|
||||
reconnect (see `probe-readings.md`);
|
||||
- **no mock:** `connected=0` throughout, a reading and not a failure, with no retry flood on stderr.
|
||||
- `UDPSCOPE_PROBE_TCPS_RECONNECT=2000` -- records the frame and reconnect counts, then polls every 100 ms
|
||||
(up to 6 s) until connected, frames and reconnects have all advanced, and prints. **It must fall before the
|
||||
mock's `--close-after`** (the baseline is read before the close); the poll, not a fixed delay, absorbs
|
||||
when the close lands.
|
||||
- `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=`.
|
||||
|
||||
Reference in new issue
Block a user