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
@@ -115,11 +115,40 @@ document describes everywhere else, and it is the default: `--server`, `--port`,
|
||||
`--help` lists the registered kinds. A second kind, `probe` (a synthetic sine with one panel), is registered
|
||||
only when `UDPSCOPE_PROBE_SOURCE_KINDS` is set, and exists only for the self-check.
|
||||
|
||||
The second built-in kind is the **MCC E-1608** Ethernet DAQ (8 single-ended or 4 differential 16-bit inputs, two
|
||||
analog outputs, eight digital lines and a 32-bit counter). Its connection form takes an address, or a MAC address
|
||||
to look up (optionally with the subnet to sweep), and groups the acquisition settings (scan rate, external clock,
|
||||
trigger, restart after an overrun, factory calibration, the digital poll rate), a range per analog input, and the
|
||||
direction and initial value of each digital line. The analog inputs are the plotted channels; each digital input
|
||||
and the counter is an extra channel only if you ask for it. Two side-stack tabs belong to it: **Analog out**
|
||||
(per DAC: a static level, or a sine, square, triangle or ramp from 0.1 to 10 Hz) and **Digital I/O** (a live LED per
|
||||
input bit, a switch per output bit, the counter with a reset); they are insensitive while no board is connected.
|
||||
Acquisition is hardware-paced only (the scan rate or an external clock), the channels times the rate must stay at
|
||||
or below 250 kS/s, and the form refuses more. With the command-line options, a run that names no `aiN` key gets
|
||||
`ai0=BIP10V`; naming any `aiN` key drops that default, so `ai1=BIP5V` alone is channel 1 alone.
|
||||
|
||||
**This kind has not been run against a board.** It is verified against `tools/mock_e1608.py`, a protocol mock written
|
||||
from two independent implementations of the wire protocol. The following rest on a single reference or the two
|
||||
disagree, and are the first thing to check when a board is attached (the mock's header lists the same items):
|
||||
the counter reset command (0x31, from one reference only); the pacer value (the source sends the *aggregate*
|
||||
rate, `round(80e6 / (scan_rate * channels) - 1)`, as one reference does; the other sends the per-channel
|
||||
frequency); the scan-start trigger option bits (none 0, rising 4, falling 8, high 12, low 16, one reference only);
|
||||
whether the handshake may answer status 2 ("code ignored"; one reference accepts it, the other refuses it, and the
|
||||
source accepts it); the first frame id; and the maximum frame size. Two further things
|
||||
are worth a look on a board even though the mock's header does not flag them: the calibration memory layout and
|
||||
its coefficient type, and the status word's bits, in particular whether the "scan running" bit stays set while the
|
||||
board waits for a trigger (the source restarts a scan that reads as stopped when auto-restart is on). The DIFF input
|
||||
numbering (channel + 8) and the filler byte after scan start are agreed by both references. The scan overrun count
|
||||
is approximate: the board does not say how many scans it dropped, so the frame after a restart reports one lost.
|
||||
Errors (no device, a rejected connection code, a timeout, the scan socket closing) are written to stderr as
|
||||
`[e1608] ...` once per distinct message; the window shows only that the source is disconnected (the status popover
|
||||
reads `waiting`, and the panels go insensitive), not why.
|
||||
|
||||
A session file written before kinds existed has no `source_kind`, and is read as UDPS; a UDPS file is
|
||||
written exactly as before. A kind's capability panels (for instance a source's own controls) appear as
|
||||
pages of the side stack, and are insensitive rather than hidden while no source is connected.
|
||||
|
||||
Planned, each as its own sub-project: a flat UDP kind, the MCC E-1608 and ABScope. ABScope has more than one
|
||||
Planned, each as its own sub-project: a flat UDP kind and ABScope. ABScope has more than one
|
||||
timebase, so it will change the frame and the data path together rather than fit the present single-timebase
|
||||
frame.
|
||||
|
||||
|
||||
@@ -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