fix(twincat): #645 live gate — sum-read never worked on hardware; TC2 diagnostics fallback

Read-only live gate on Elma presses Z35474 (TC3 3.1.4024) and Z34834 (TC2 2.11):

- Sum-read: every chunk came back BadCommunicationError. Beckhoff's
  SumRead(IList<IgIoSumReadEntity>) ctor casts to IList<SumDataEntity>, which
  throws InvalidCastException for a List<> (only an array survives via
  covariance). The fake-client tests never built a real SumRead. New
  AdsTwinCATClient.CreateSumRead passes an array; TwinCATSumReadConstructionTests
  pins it (red on the List form) plus a falsifiability control on the library.
  After the fix: 59 tags, status identical everywhere, values identical on every
  non-moving tag, ~0.35-0.66 s vs ~2.7 s per poll; TC2 CE 2.11 answers sum reads.
- An unexpected sum chunk failure now logs one Warning per device (it was
  invisible: the health message was overwritten by the next good read).
- Diagnostics: TC2 has no TwinCAT_SystemInfoVarList; fall back to .SYSTEMINFO /
  .SYSTEMTASKINFOARR[1] once the TC3 name answers not-found (live: all six keys).
- #612 browse: Flat verified on TC3 (10,770 leaves; VirtualTree 1,459 strict
  subset) — comment and docs updated, no load-mode option needed.

Claude-Session: https://claude.ai/code/session_01Ef2ozxS7QPanGT8uoATbTe
This commit is contained in:
Joseph Doherty
2026-10-08 21:17:44 -04:00
parent e7dd742541
commit 664ff75ee2
7 changed files with 207 additions and 46 deletions
+23 -16
View File
@@ -22,11 +22,13 @@ The Elma cleaner fleet (25 presses, 23 live) is the only real TwinCAT hardware i
Access facts: ADS is TCP 48898. The driver sends no AddRoute (routes are provisioned out of band) and has
no IP field, so a host needs a local AMS router — TwinCAT XAR, or `Beckhoff.TwinCAT.Ads.TcpRouter` hosted
in-process (the driver csproj does not reference TcpRouter; the 2026-10-07 run used a scratch harness).
The Mac reaches the presses directly over the VPN. **The PLC routes left by earlier tooling are STALE:**
they name the Mac's old VPN IP (10.181.198.189), and the Mac is now 10.181.207.140, so a source NetId of
`10.181.207.140.1.1` has no route on any press. We must not AddRoute on production, so the **live gate for
this backlog is deferred pending a user-approved route** (owner refreshes the route, or the user approves
reusing the old source NetId). Nothing below that says "live" has been run against TC3.
The Mac reaches the presses directly over the VPN. **Routes (2026-10-08, hw2 live gate):** with the
user's approval, an `oee-tagdump-mac` route for the Mac's current VPN IP (`10.181.201.108.1.1`) was added
to **Z35474** (TC3) and **Z34834** (TC2, `5.69.178.220.1.1`, 10.201.45.197) by UDP-48899 AddRoute with the
factory login. ⚠️ A same-name AddRoute does **not** replace on either runtime: both presses now carry TWO
`oee-tagdump-mac` entries — the stale `10.181.198.189.1.1` and the new one (route table read back over ADS,
port 10000 / IG 803). Removing the stale one needs an ADS write and was not done. Every other press still
has only the stale route, so it refuses the Mac; the VPN IP changes again ⇒ the routes go stale again.
## Status of the original items
@@ -47,19 +49,24 @@ 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`
(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.
6. **ADS sum-read (#310) — DONE, live-gated 2026-10-08** on Z35474 (TC3) and Z34834 (TC2 2.11): 59 real
symbols (BOOL/INT/UINT/DINT/UDINT/USINT/REAL/LREAL/STRING/TIME + 7 value-converted pairs), sum vs single
bracketed by two single passes, `sumReadMaxItems` 8 (≥ 7 chunks) and 200: identical status on every tag,
identical value on every tag that did not move between passes; poll 59 tags in ~0.35–0.66 s vs ~2.7 s.
**TC2 CE 2.11 answers the sum command** (no refusal, no fallback). The gate found the shipped path broken —
every chunk `BadCommunicationError` because Beckhoff's `SumRead` constructor throws `InvalidCastException` for
a `List<IgIoSumReadEntity>` (see `AdsTwinCATClient.CreateSumRead`); fixed and pinned by
`TwinCATSumReadConstructionTests`.
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).
7. **Driver diagnostics (#314) — DONE, live-gated 2026-10-08** (opt-in `diagnosticsEnabled`). TC3 Z35474: all six
keys from `TwinCAT_SystemInfoVarList` (`AppName=Port_851`, cycle 5.0 ms, exec 0.1 ms). TC2 Z34834 has **no**
`TwinCAT_SystemInfoVarList`; the driver now falls back to `.SYSTEMINFO.PROJECTNAME` / `.ONLINECHANGECOUNT`
and `.SYSTEMTASKINFOARR[1].CYCLETIME` / `.LASTEXECTIME` (all six keys live: cycle 20.0 ms, exec 0.6 ms).
8. **Flat vs VirtualTree browse on TC3 (#612)** — open. Only Z35474 is TC3; read-only symbol-table
upload in both modes, compare leaf sets. Add a `SymbolsLoadMode` option only if Flat drops struct
members. Needs the route. (Flat on TC2 already proved fine: 45,083 / 42,666 variables.)
8. **Flat vs VirtualTree browse on TC3 (#612) — DONE, keep Flat.** Z35474: Flat 3,384 roots → 10,770 leaves,
every struct root carried SubSymbols (0 without); VirtualTree 1,027 roots → 1,459 leaves, a strict subset
(it loses array-of-struct element members such as `.L_Axis[10].IN._AT.*`). The driver's `DiscoverAsync`
returned 10,628 variables. No `SymbolsLoadMode` option is needed.
9. **Discovered struct/FB member writes (#612)** — deferred: writes, never on a press; fixture-only.
Likewise **sum-write** was deliberately NOT ported from the stranded branch.