Auto: twincat-4.1 — nested UDT browse via online type walker

Closes #315
This commit is contained in:
Joseph Doherty
2026-04-26 07:28:52 -04:00
parent da6e19d07d
commit 0444cb699d
15 changed files with 1067 additions and 19 deletions

View File

@@ -28,6 +28,56 @@ sessions. Pick one:
The CLI compiles + runs without a router, but every wire call fails with a
transport error until one is reachable.
## UDT decomposition
PR 4.1 (issue #315) replaces the old "skip non-atomic symbols" behaviour
of `BrowseSymbolsAsync` with a recursive type walker
(`TwinCATTypeWalker`). When the OtOpcUa server's TwinCAT driver runs
discovery with `EnableControllerBrowse=true`, struct / UDT / function-block
typed symbols flatten into one OPC UA variable per atomic leaf. Browse
addresses use the same dotted-instance form the PLC exposes:
| PLC declaration | OPC UA browse paths surfaced |
|---|---|
| `MAIN.bStart : BOOL` | `MAIN.bStart` |
| `GVL.stMotor : ST_Motor` | `GVL.stMotor.bRunning`, `GVL.stMotor.nState`, `GVL.stMotor.rTemperature`, … |
| `GVL.aRecipe : ARRAY[1..10] OF DINT` | `GVL.aRecipe[1]``GVL.aRecipe[10]` |
| `GVL.aPairs : ARRAY[0..2] OF ST_Pair` | `GVL.aPairs[0].nCount`, `GVL.aPairs[0].rValue`, `GVL.aPairs[1].…` |
| `GVL.aBig : ARRAY[1..5000] OF DINT` | `GVL.aBig` (single whole-array root — over the cap) |
The CLI's `read` / `write` / `subscribe` commands take dotted paths
directly:
```powershell
# Read a struct member
otopcua-twincat-cli read -n 192.168.1.40.1.1 -s GVL.stMotor.rTemperature -t Real
# Read an array element
otopcua-twincat-cli read -n 192.168.1.40.1.1 -s "GVL.aRecipe[3]" -t DInt
```
### Array expansion bound
`TwinCATDriverOptions.MaxArrayExpansion` (default `1024`) caps how many
elements an array contributes to the discovered address space. Arrays
whose total element count exceeds the cap surface as a single
whole-array root with `IsArrayRoot=true` instead of one variable per
element. Raise the bound when operators routinely care about individual
elements of large recipe / lookup tables; lower it to keep discovery
cheap for symbol tables that ship multi-thousand-element scratch
arrays. Pre-declared whole-array tags from the `Tags` config bypass the
walker entirely — set `ArrayDimensions` on a `TwinCATTagDefinition` to
keep array reads on the existing PR 1.4 read-array path.
### Cycle / depth guard
The walker tracks the visited-type set + a hard depth cap of 8 levels
so a self-pointer (`POINTER TO ST_Self`) or pathological alias chain
terminates rather than spinning. POINTER / REFERENCE members are
skipped at the type-graph level — surfacing them would require
dereferencing through the AMS routing layer which has its own access
patterns.
## Common flags
| Flag | Default | Purpose |

View File

@@ -57,6 +57,14 @@ All three gated on `TWINCAT_TARGET_HOST` + `TWINCAT_TARGET_NETID` env
vars; skip cleanly via `[TwinCATFact]` when the VM isn't reachable or
vars are unset.
PR 4.1 / #315 adds `TwinCATUdtBrowseTests.Driver_browses_UDT_tree_and_flattens_to_atomic_leaves`
which exercises `TwinCATDriver.DiscoverAsync` end-to-end against the
`GVL_Plant` UDT fixture. Asserts the discovery surface emits one OPC UA
variable per atomic leaf and folds `aAlarmRecords[1..2000]` into a
single `IsArrayRoot` placeholder when the element count exceeds the
default 1024-element cap (UDT per-member coverage; see
`TwinCatProject/README.md §Complex hierarchy` for the supporting DUTs).
### Unit
- `TwinCATAmsAddressTests``ads://<netId>:<port>` parsing + routing
@@ -66,6 +74,14 @@ vars are unset.
- `TwinCATSymbolPathTests` — symbol-path routing for nested struct members
- `TwinCATSymbolBrowserTests``ITagDiscovery.DiscoverAsync` via
`ReadSymbolsAsync` (#188) + system-symbol filtering
- `TwinCATTypeWalkerTests` — PR 4.1 / #315 nested-UDT decomposition:
atomic / single-level struct / nested struct / array-of-atomic
(in / over `MaxArrayExpansion`) / array-of-struct / alias chain /
pointer skip / self-referencing struct depth-cap / per-leaf
`MaxArrayExpansion` honored / ReadOnly propagation. Stub `IDataType`
/ `IStructType` / `IArrayType` / `IMember` / `IDimensionCollection`
trees built in-test so the walker is exercised without
`Beckhoff.TwinCAT.Ads`-internal ctors.
- `TwinCATNativeNotificationTests``AddDeviceNotification` (#189)
registration, callback-delivery-to-`OnDataChange` wiring, unregister on
unsubscribe