fix(twincat): size strings by the resolved type and refuse VAR_IN_OUT references (live TC2 gate)

Found by the read-only live gate against the TwinCAT 2.11 Elma cleaners
Z33856 / Z33733:

- The Beckhoff library reports a TC2 dotted MEMBER symbol's ByteSize as if the
  PLC's byte count were a bit count (STRING(80) -> 11, INT -> 1, UDINT -> 1)
  while DataType.ByteSize is right (81/2/4, raw SYM_INFOBYNAMEEX says 81). The
  previous commit's typeof(byte[]) read sliced by the symbol size, so
  .OPC_MACHINEDATA.PROCESSSTATUS read "ready for o" at Good; the notification
  width check would also have refused every numeric member. The shape now takes
  the resolved type's size, and string reads go by index group/offset (or a
  fresh handle for 0xF005) at that width.
- P_INITINPUT_ACTION.SLOTID is "STRING(20) (VAR_IN_OUT)" - a 4-byte reference -
  but resolves to a plain STRING(20); a String tag read 21 bytes at the pointer
  as text at Good. The symbol's own VAR_IN_OUT marker now classifies it
  NonScalar -> BadTypeMismatch.
This commit is contained in:
Joseph Doherty
2026-10-07 14:27:22 -04:00
parent 75754478ac
commit 8060cfd2a5
5 changed files with 136 additions and 15 deletions
+26
View File
@@ -135,6 +135,32 @@ found against a real TwinCAT 2 PLC:
registers, and the probe-tick retry re-checks it after a PLC program change.
Reads go through the Beckhoff symbol cache, which converts numeric values by
the PLC's own type, so only the string-kind rule applies there.
- **Sizes come from the resolved data type, not the symbol.** On a TwinCAT 2
target the Beckhoff library reports a dotted *member* symbol's `ByteSize` as if
the PLC's byte count were a bit count (`.OPC_MACHINEDATA.PROCESSSTATUS`
`STRING(80)` → 11 B, an `INT` member → 1, a `UDINT` → 1) while its resolved
`DataType.ByteSize` is correct (81 / 2 / 4, matching raw `SYM_INFOBYNAMEEX`).
String reads therefore address the symbol's index group/offset explicitly at
the type's width — sizing them by the symbol truncated `"ready for operation"`
to `"ready for o"` at Good.
- **A `VAR_IN_OUT` member is a reference, not a value.** TC2 names it
`STRING(20) (VAR_IN_OUT)` (4 bytes) but the library resolves its type to the
plain `STRING(20)`; the symbol's own name wins and such a tag is refused
(`BadTypeMismatch`) instead of publishing the memory at the pointer as text.
### Live validation (2026-10-07, TwinCAT 2.11 Elma cleaners)
Read-only, against Z33856 (`10.201.45.236.1.1:801`) and Z33733
(`5.25.53.46.1.1:801`) over the VPN, through an in-process
`Beckhoff.TwinCAT.Ads.TcpRouter` in a scratch harness: ReadState RUN; browse
discovered 45,083 / 42,666 variables incl. the `INT16`/`UINT16` globals
(`.ACTUALOPERATIONMODE`, `.ADSSTATE_RUN`) and 3,663 / 3,564 leading-dot names, no
empty names; `.ADSSTATE_RUN` = 5 with bits `.0`/`.1`/`.2` = true/false/true;
work-order and part-number strings whose PLC buffers carry a stale tail after
the NUL (`68261005\0` + `01`) read as `68261005` through both poll and native
notifications; `DInt` over a `STRING(80)` → `BadTypeMismatch` in both paths with
the rest of the subscription Good. No PLC value held a genuine non-ASCII string
to exercise Windows-1252 live — that rule is unit-tested only.
## Capability surface