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:
+22
-13
@@ -189,8 +189,14 @@ to exercise Windows-1252 live — that rule is unit-tested only.
|
||||
|
||||
Two items ported from the stranded `auto/driver-gaps` branch (#310, #314), re-implemented against the
|
||||
current raw-byte read path rather than cherry-picked (the branch predates the type guard, the string codec
|
||||
and the array codec). Unit-tested against the fake client only; the live gate is read-only and pending a
|
||||
route (see `docs/v3/twincat-backlog.md`).
|
||||
and the array codec). Live-gated read-only on 2026-10-08 against Z35474 (TC3) and Z34834 (TC2 2.11) — see
|
||||
`docs/v3/twincat-backlog.md` items 6–8 for the evidence.
|
||||
|
||||
⚠️ **Beckhoff's `SumRead` must be handed an ARRAY of entities.** Its `IList<IgIoSumReadEntity>` overload casts to
|
||||
`IList<SumDataEntity>` internally, which only an array survives (covariance); a `List<>` throws
|
||||
`InvalidCastException` before anything is sent. The shipped #645 code passed a `List<>`, so on real hardware every
|
||||
sum chunk came back `BadCommunicationError` while all fake-client tests passed. `AdsTwinCATClient.CreateSumRead`
|
||||
owns the construction; an unexpected chunk failure now logs one Warning per device.
|
||||
|
||||
### Sum-read (opt-in)
|
||||
|
||||
@@ -229,8 +235,12 @@ ReadState. One device: flat keys `TwinCAT.AdsState`, `.AppName`, `.OnlineChangeC
|
||||
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).
|
||||
best-effort: a runtime that does not export one keeps the others, and a symbol answering "not
|
||||
found" is not re-asked until a new client is installed (reconnect). **TwinCAT 2** has no
|
||||
`TwinCAT_SystemInfoVarList`; once a TC3 name answers "not found" the driver reads the TC2 globals instead —
|
||||
`.SYSTEMINFO.PROJECTNAME` (AppName), `.SYSTEMINFO.ONLINECHANGECOUNT` (UINT), `.SYSTEMTASKINFOARR[1].CYCLETIME` /
|
||||
`.LASTEXECTIME` (same 100 ns units). Live on TC2 2.11: 8 reads on the first tick, 4 after; a TC3 target never
|
||||
reads the TC2 names.
|
||||
|
||||
## Capability surface
|
||||
|
||||
@@ -448,16 +458,15 @@ Discovered struct/UDT/FB member **writes** are **deferred** in this release. Sca
|
||||
members can be written when pre-declared with `Writable: true`; the controller-browse expansion
|
||||
path does not yet wire the write channel.
|
||||
|
||||
### Live caveat — Flat-mode vs VirtualTree-mode browse
|
||||
### Flat-mode vs VirtualTree-mode browse — resolved, Flat stays (#612)
|
||||
|
||||
The ADS client's `BrowseSymbolsAsync` may return symbols in **Flat mode** (a flat list of all
|
||||
instance paths, with no `SubSymbols` populated) on some TC3 runtime configurations, in which
|
||||
case the recursive struct expansion produces no sub-symbols and the walk yields only top-level
|
||||
symbols. If a live TC3 browse does not populate `SubSymbols` on struct-typed symbols, the
|
||||
alternative is **VirtualTree mode** (which returns a hierarchical symbol tree with `SubSymbols`
|
||||
populated). VirtualTree-mode browse is an unverified follow-up; if Flat-mode struct expansion
|
||||
produces empty `Discovered/` sub-folders for struct symbols, switch to VirtualTree mode via the
|
||||
driver configuration.
|
||||
The driver loads symbols with `SymbolsLoadMode.Flat`. The worry was that Flat might leave `SubSymbols`
|
||||
empty on some TC3 runtimes and drop every struct member. Live on the only TC3 press, Z35474 (TwinCAT
|
||||
3.1.4024, 2026-10-08, read-only): Flat returned 3,384 roots, every one of the 573 struct roots carried its
|
||||
`SubSymbols`, and the expander produced 10,770 leaves (9,921 of them struct members; `DiscoverAsync` 10,628
|
||||
variables). VirtualTree returned 1,027 roots and 1,459 leaves — a strict subset (0 tree-only leaves) that
|
||||
misses array-of-struct element members such as `.L_Axis[10].IN._AT.DriveStatusWord`. There is no load-mode
|
||||
option and none is needed. TC2 Flat was already proven (45,083 / 42,666 variables, 2026-10-07).
|
||||
|
||||
## Further reading
|
||||
|
||||
|
||||
+23
-16
@@ -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.
|
||||
|
||||
Reference in New Issue
Block a user