docs: the E-1608 source kind, its probe readings and its unverified commands
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
1 parent
2e9f9664cf
commit
3093568d11
5 files changed
+138
-5
No files matched your search
@@ -42,6 +42,25 @@ What feeds `Acquisition` is a `DataSource`, one of the registered *kinds*.
|
||||
`[A-Za-z0-9._-]` become `_`) on port **1** (0 is rejected by the session-file parser), the kind is
|
||||
stored as `source_kind` and its settings as `source_opt_<key>`. A UDPS file keeps `<host>_<port>` and
|
||||
has neither key, so a legacy session is read as UDPS and nothing existing moves.
|
||||
- **The E-1608 kind has three layers, and only the top one knows the framework.** `e1608_link`
|
||||
(discovery, handshake, framed TCP commands, the scan socket) and `e1608_device` (typed commands,
|
||||
factory calibration, `decode_scans`) are plain POSIX C++ with no GTK or `DataSource` types;
|
||||
`e1608_source` is the `DataSource`; `e1608_kind` and `e1608_panels` are the UI. A change to the wire
|
||||
never reaches the UI files.
|
||||
- The E-1608 source runs **everything inside `poll()`** on the producer thread: posted commands, the
|
||||
connect state machine (Down, Scanning, Halted), the scan read, the 500 ms status check, the digital
|
||||
input/counter poll and the 20 ms waveform tick. There is no second thread. A connect attempt (discovery,
|
||||
handshake, calibration read, scan start) can block `poll()` for as long as the command timeout allows each
|
||||
step; that delays only the command queue. Every exception is caught inside `poll()`: an escape would end
|
||||
the producer thread, and `drain_commands()` runs inside the same `try` for that reason.
|
||||
- Its frames are **fixed-size blocks** of about 10 ms (`round(scan_rate / 100)` scans, 1 to 4096), not
|
||||
whatever one `recv` returned: `build_stamps` warns once when a frame's sample count differs from the
|
||||
first one's. Digital lines and the counter are held across a block (a step trace).
|
||||
- The E-1608 panels read the board's state from `E1608Readouts`, a set of atomics the producer writes
|
||||
and a tick callback throttled to 100 ms reads; a panel never touches the source's internals, and writes
|
||||
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.
|
||||
|
||||
### Data path
|
||||
|
||||
|
||||
@@ -115,6 +115,26 @@ and also exports `LIBGL_ALWAYS_SOFTWARE=1`: the plot is a `GtkGLArea`, so a disp
|
||||
usable GPU fails twice, and this is the pair `tools/make-help-shots.sh` already sets. Nothing
|
||||
is exported when the option is absent, so GTK's own choice stands.
|
||||
|
||||
`--source e1608` selects the MCC E-1608 Ethernet DAQ; its settings are `--source-opt` keys, all optional
|
||||
(`address` or `mac`+`subnet`, `port` 54211, `code` 0, `timeout_ms` 2000, `scan_rate` 1000 per channel,
|
||||
`ext_clock`, `trigger` `none|rising|falling|high|low`, `auto_restart` 1, `calibrate` 1, `poll_rate` 10,
|
||||
`mode` `se|diff`, `ai0`..`ai7` `BIP10V|BIP5V|BIP2V|BIP1V` (an absent key = off, **and any `aiN` key at all
|
||||
drops the default**: only a run with no `aiN` key gets `ai0=BIP10V`, so `ai1=BIP5V` alone is channel 1
|
||||
alone, and a value that is not a range name is a channel that is off), `dir0`..`dir7` `i|o`,
|
||||
`plot0`..`plot7`, `plot_counter`, `do_init`, `ao0_init`, `ao1_init`). Either `address` or `mac` is
|
||||
required; a MAC with no `address` is looked up by broadcast, or by a sweep of `subnet` (`a.b.c`).
|
||||
The enabled channels times `scan_rate` must not exceed 250 kS/s. `tools/mock_e1608.py` is a protocol
|
||||
mock of the board for development (UDP discovery and handshake, TCP commands, a scan stream with a sine
|
||||
per channel, calibration memory, `--port`, `--mac`, `--log FILE`, `--drop-after S` and `--overrun-after S`
|
||||
switches, `--code N`); there is no real board in the loop, and the mock's header lists the items whose
|
||||
layout is unverified. Errors are written to stderr as `[e1608] ...`, once per distinct message.
|
||||
|
||||
```sh
|
||||
python3 tools/mock_e1608.py --port 54711 &
|
||||
./build/udpscope --source e1608 --source-opt address=127.0.0.1 --source-opt port=54711 \
|
||||
--source-opt ai0=BIP10V --source-opt ai1=BIP5V --source-opt plot0=1
|
||||
```
|
||||
|
||||
`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
|
||||
|
||||
@@ -1827,8 +1827,9 @@ are unrealized and measure 0x0.
|
||||
## `[source-kinds]`
|
||||
|
||||
```
|
||||
[source-kinds] kinds=udps,probe
|
||||
[source-kinds] kinds=udps,e1608,probe
|
||||
[source-kinds] form udps: round_trip=1 valid=1
|
||||
[source-kinds] form e1608: 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
|
||||
@@ -1840,10 +1841,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,probe`: a missing kind means `register_builtin_sources()` was not called before `parse_args()`,
|
||||
- `kinds=udps,e1608,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.
|
||||
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.
|
||||
- `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
|
||||
@@ -1856,6 +1857,53 @@ kind, so a run without it never shows that kind. The `form` line appears once pe
|
||||
|
||||
The UDPS run has no `source-` page in `side_stack`, because the UDPS kind has no panels.
|
||||
|
||||
## `[e1608]`
|
||||
|
||||
```
|
||||
[e1608] panel ao: present=1 sensitive=1 connected=1
|
||||
[e1608] panel dio: present=1 sensitive=1 connected=1
|
||||
[e1608] actions: ao0_level=1 do1=1 counter_reset=1
|
||||
[e1608] stream: connected=1 signals=4 expect=4 names=AI0,AI1,DI0,Counter frames=274 rate=1000.000 ok=1
|
||||
[e1608] calibration: ai0_mean=0.5000 expect=0.5000 ok=1
|
||||
[e1608] capture button: present=1 sensitive=0
|
||||
[e1608] state: connects=1 message=''
|
||||
[e1608] decode: ok=1
|
||||
[e1608] scan socket closed by the board
|
||||
[e1608] reconnect: connects=2 connected=1 ok=1
|
||||
[e1608] resumed: frames_before=612 frames_after=662 ok=1
|
||||
```
|
||||
|
||||
`UDPSCOPE_PROBE_E1608=1500` (the first eight lines; `frames=` and the exact mean vary) and
|
||||
`UDPSCOPE_PROBE_E1608_RECONNECT=8000` (the last two) run as `--source e1608` against `tools/mock_e1608.py`
|
||||
with the options in `verification.md`. The ninth line is the source's own stderr message about the drop, not a
|
||||
probe line (its text is the error the source met, once per distinct message). `connected=0` throughout, with
|
||||
`actions: skipped (not connected)`, is the no-board run; it is a reading and not a failure, and it also prints
|
||||
the one `[e1608] handshake with ...: Connection refused` line, `stream: ... signals=0 expect=1 ... ok=0`,
|
||||
`calibration: ai0_mean=nan ... ok=0` and `state: connects=0 message='handshake with ...'`.
|
||||
|
||||
- `panel ...: present=1 sensitive=<connected>`: **disable, don't hide.** `present=0` is a panel that was never
|
||||
added; `sensitive` that differs from `connected` is `apply_source_panels` acting on the wrong widget.
|
||||
- `actions: ao0_level=1 do1=1 counter_reset=1`: each control was found by its widget name (`e1608-ao0-level`,
|
||||
`e1608-do1`, `e1608-counter-reset`). A `0` is a renamed widget, or (`do1`) `dir1=o` missing from the run. The
|
||||
effects are read off the **mock's log**, not here: a DAC write near 2.5 V (within 0.01), `DOUT 0x02`, `COUNTER_RESET`.
|
||||
A control that was found but whose effect is missing is a command that `post()` did not deliver or `poll()`
|
||||
did not run.
|
||||
- `stream: signals=4 expect=4 rate=1000.000 ok=1`: the enabled channels plus the plotted digital lines plus the
|
||||
counter, at the scan rate. `signals` below `expect` is a signal the source did not declare; `rate` off by a
|
||||
factor is the pacer computed from the wrong aggregate.
|
||||
- `calibration: ai0_mean=0.50xx ok=1`: the mock's AI0 is 0.5 V under a 5 Hz sine of 0.1 V, read through
|
||||
non-trivial factory coefficients; the mean over two periods lands on 0.5 V only when `decode()` applies them.
|
||||
About `0.2043` is the **uncalibrated** reading (the calibration read failed, or `calibrate=0`); `nan` is a
|
||||
run that never got AI0 (not connected, or AI0 not enabled).
|
||||
- `capture button: present=1 sensitive=0`: a source with no datagrams leaves the capture UI disabled, not hidden.
|
||||
- `state: connects=1 message=''`: a non-empty message at this point is a refusal that happened (for example
|
||||
`calibration memory refused; using nominal values`), which the stream then ran through.
|
||||
- `decode: ok=1`: a fixed raw buffer against hand-computed volts (intercept 100, BIP10V and BIP5V, the code
|
||||
clamp at both ends). It needs no board; `ok=0` is a change to the calibration arithmetic.
|
||||
- `reconnect: connects=2 connected=1`, `resumed: ok=1`: the mock dropped both links at about 3 s; the source
|
||||
reported it, retried after 2 s and the frame count moved on. `connects=1` is a source that never noticed the
|
||||
drop (neither the scan read nor the status check raised it), `resumed: ok=0` is a reconnect that did not restart the scan.
|
||||
|
||||
## `[prefs-probe]`
|
||||
|
||||
```
|
||||
|
||||
@@ -423,7 +423,24 @@ 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.
|
||||
Seven `[source-kinds]` lines; the reading's own entry is in `probe-readings.md`.
|
||||
Eight `[source-kinds]` lines (one `form` line per registered kind: `udps`, `e1608`, `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
|
||||
digital-output toggle and a counter reset each reaching the mock, the stream's channels and rate, the
|
||||
factory calibration applied, the capture button disabled, and a hand-computed decode. It enables the
|
||||
signals itself, so do **not** add `UDPSCOPE_ENABLE_ALL_MS` (that probe switches the first signal off
|
||||
after two seconds). Three runs, each short and serial, with the mock killed afterwards:
|
||||
- **no board:** panels `sensitive=0`, `connected=0`, no retry flood on stderr (the one
|
||||
`[e1608] handshake with ...: Connection refused` line is printed once, not per retry);
|
||||
- **the mock:** `--drop-after 3 --log /tmp/e1608-mock.log`, options `scan_rate=1000 ai0=BIP10V
|
||||
ai1=BIP5V plot0=1 plot_counter=1 dir1=o ao0_init=1.5`, with `UDPSCOPE_PROBE_E1608=1500
|
||||
UDPSCOPE_PROBE_E1608_RECONNECT=8000 UDPSCOPE_EXIT_AFTER_MS=10500`; then read the mock's log: the AO
|
||||
levels `1.5, 2.5, 1.5` (the log prints the quantised voltage, so `1.4999, 2.4999, 1.4999` is the
|
||||
expected text), the DOUT bytes `0x00, 0x02, 0x00`, one `COUNTER_RESET`, one `DROP`;
|
||||
- **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_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