fix(twincat): review fixes for sum-read and diagnostics (#645)
Strict width+kind eligibility for sum-batched scalars (others read singly); latch only on ADS 1793/1794, split 1792/1797/1798 without latching; 32 KiB per-command budget and 500-item ceiling; short sum response handled per item. Diagnostics: opt-in diagnosticsEnabled (default off), reuse the probe's ReadState, sample after the host-state transition, JitterMs renamed HeadroomMs, compact per-device line so many devices fit the 16-entry cap, unavailable-symbol latch cleared on reconnect. AdminUI: min/max on sum-read max, diagnostics checkbox. Pure planner extracted and unit-tested. Claude-Session: https://claude.ai/code/session_01Ef2ozxS7QPanGT8uoATbTe
This commit is contained in:
+31
-23
@@ -195,33 +195,41 @@ route (see `docs/v3/twincat-backlog.md`).
|
||||
### Sum-read (opt-in)
|
||||
|
||||
`useSumRead: true` (default **off**) batches a poll's reads for one device into ADS sum-read commands
|
||||
(`IndexGroup 0xF080`) of at most `sumReadMaxItems` symbols (default 200) instead of one round-trip per tag.
|
||||
TC2 CE 2.11 support for the sum command is unverified, hence opt-in.
|
||||
(`IndexGroup 0xF080`) of at most `sumReadMaxItems` symbols (default 200, ceiling 500) and at most 32 KiB of
|
||||
payload (read lengths + 4 bytes each) instead of one round-trip per tag. TC2 CE 2.11 support for the sum
|
||||
command is unverified, hence opt-in.
|
||||
|
||||
- The client plans each item exactly as a single read does — symbol from the Beckhoff symbol cache, declared
|
||||
type checked by `TwinCATTypeGuard` — and then reads the raw bytes at the symbol's index group / offset in
|
||||
one command, decoding with the same `TwinCATArrayCodec` (a scalar is a one-element array). A type-mismatched
|
||||
item gets `BadTypeMismatch` without joining the batch; a bit-indexed BOOL and a handle-addressed symbol
|
||||
(`0xF005`) are read individually inside the call.
|
||||
- Per-item ADS error codes map to that tag only (`MapAdsError`); the overall code is the command's.
|
||||
- **Fallback.** If the target refuses the command as a whole (overall ADS 1792–1798: service / group /
|
||||
offset / size not supported) the client throws `TwinCATSumReadRefusedException`; the driver logs once,
|
||||
marks the device `SumReadUnsupported`, and re-reads that batch with individual reads. The flag is sticky
|
||||
until the driver is re-initialised. A comms-level failure is **not** a refusal: that chunk goes
|
||||
`BadCommunicationError` and the next poll tries the sum command again.
|
||||
- **Eligibility is strict.** The sum path reads raw bytes at the symbol's index group / offset and decodes them
|
||||
as the DECLARED type (the notification rules), so a scalar rides the batch only when it passes
|
||||
`TwinCATTypeGuard.CheckNotification` — same width, same float/integer kind. A tag the lenient single read
|
||||
accepts by value conversion (`DInt` over a PLC `INT`, `Real` over `LREAL` or `DINT`) is read **individually**
|
||||
inside the call, keeping the Beckhoff conversion; the batch never publishes reinterpreted bytes at Good.
|
||||
Arrays use the same raw-bytes rule as on the single path. A type-mismatched item gets `BadTypeMismatch`
|
||||
without joining the batch; a bit-indexed BOOL and a handle-addressed symbol (`0xF005`) are read singly.
|
||||
- Per-item ADS error codes map to that tag only (`MapAdsError`); a response shorter than asked is a size error
|
||||
for that item, not a failed chunk. The overall code is the command's.
|
||||
- **Latch vs split.** Only overall ADS **1793 / 1794** (service / index group not supported) throws
|
||||
`TwinCATSumReadRefusedException`; the driver logs once, marks the device `SumReadUnsupported` and reads
|
||||
singly until re-initialise. **1792 / 1797 / 1798** (general error, bad size, bad data) mean *this request*
|
||||
was rejected — typically too big — so that chunk is read singly for this poll and nothing is latched. A
|
||||
comms-level failure is neither: that chunk goes `BadCommunicationError` and the next poll tries again.
|
||||
- A one-item chunk is just a single read. Sum-**write** is not implemented (writes stay per-tag).
|
||||
|
||||
### `/hosts` diagnostics (`IDriverDiagnostics`)
|
||||
### `/hosts` diagnostics (`IDriverDiagnostics`, opt-in)
|
||||
|
||||
The driver implements the optional `IDriverDiagnostics` bag (the S7 `NegotiatedPduSize` precedent, #640),
|
||||
so these ride the health snapshot to `/hosts` — deliberately not synthetic raw tags. Sampled once per
|
||||
healthy probe tick: ADS device state (`ReadState`), and `TwinCAT_SystemInfoVarList` `_AppInfo.AppName`,
|
||||
`_AppInfo.OnlineChangeCnt`, `_TaskInfo[1].CycleTime`, `_TaskInfo[1].LastExecTime` (100 ns ticks).
|
||||
Keys: `TwinCAT.AdsState`, `.AppName`, `.OnlineChangeCnt`, `.CycleTimeMs`, `.LastExecTimeMs`, `.JitterMs`
|
||||
(last exec − cycle; positive = the task overran). Several devices prefix `TwinCAT.<DeviceName or host>.`;
|
||||
the host caps the bag at 16 entries. Times are rounded to 0.1 ms because the bag's digest is in the
|
||||
publish fingerprint. Each symbol is best-effort: a runtime that does not export one (older TC2) keeps the
|
||||
others, and a symbol answering "not found" is not re-asked until the client is rebuilt.
|
||||
Behind `diagnosticsEnabled` (default **off** — not yet live-gated). The driver implements the optional
|
||||
`IDriverDiagnostics` bag (the S7 `NegotiatedPduSize` precedent, #640), deliberately not synthetic raw tags.
|
||||
When enabled, after each healthy probe tick **and after the host-state transition** it reads up to four
|
||||
`TwinCAT_SystemInfoVarList` symbols per device — `_AppInfo.AppName`, `_AppInfo.OnlineChangeCnt`,
|
||||
`_TaskInfo[1].CycleTime`, `_TaskInfo[1].LastExecTime` (100 ns ticks) — so up to **4 extra read requests per
|
||||
device per probe interval** (default 5 s). The ADS state is the one `ProbeAsync` already read; no second
|
||||
ReadState. One device: flat keys `TwinCAT.AdsState`, `.AppName`, `.OnlineChangeCnt`, `.CycleTimeMs`,
|
||||
`.LastExecTimeMs`, `.HeadroomMs` (cycle − last exec; negative = the task overran; this is spare time, not
|
||||
jitter). Several devices: one line per device, `TwinCAT.<DeviceName or host>` =
|
||||
`Run; Plc30 App; online-changes 7; cycle 10.0 ms; exec 2.5 ms; headroom 7.5 ms`, so the host's 16-entry cap
|
||||
covers 16 devices. Times round to 0.1 ms (the bag's digest is in the publish fingerprint). Each symbol is
|
||||
best-effort: a runtime that does not export one (older TC2) keeps the others, and a symbol answering "not
|
||||
found" is not re-asked until a new client is installed (reconnect).
|
||||
|
||||
## Capability surface
|
||||
|
||||
|
||||
@@ -48,13 +48,13 @@ reusing the old source NetId). Nothing below that says "live" has been run again
|
||||
## Added by #612 / #645
|
||||
|
||||
6. **ADS sum-read (#310) — code DONE, live gate pending.** Opt-in `useSumRead` / `sumReadMaxItems`
|
||||
(see `docs/drivers/TwinCAT.md` § "Sum-read"). Unit-tested against the fake client only. Live gate
|
||||
(strict width+kind eligibility; latch only on ADS 1793/1794; see `docs/drivers/TwinCAT.md` § "Sum-read"). Unit-tested against the fake client only. Live gate
|
||||
(read-only, valid on a press): enable on Z33856 / Z33733 (TC2) and Z35474 (TC3), compare a batched poll
|
||||
to individual reads value-for-value, and record whether TC2 CE 2.11 answers or refuses the sum command
|
||||
(either outcome is a result — a refusal must fall back cleanly). Needs the route above.
|
||||
|
||||
7. **Driver diagnostics (#314) — code DONE, live gate pending.** ADS state, app name, online-change
|
||||
count, task cycle time, last exec time and jitter on the `/hosts` diagnostics bag. Live gate: confirm
|
||||
7. **Driver diagnostics (#314) — code DONE (opt-in `diagnosticsEnabled`, default off), live gate pending.** ADS state, app name, online-change
|
||||
count, task cycle time, last exec time and headroom on the `/hosts` diagnostics bag. Live gate: confirm
|
||||
`TwinCAT_SystemInfoVarList._TaskInfo[1]` resolves on TC2 2.11 (the driver tolerates its absence).
|
||||
|
||||
8. **Flat vs VirtualTree browse on TC3 (#612)** — open. Only Z35474 is TC3; read-only symbol-table
|
||||
|
||||
Reference in New Issue
Block a user