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

@@ -0,0 +1,15 @@
<?xml version="1.0" encoding="utf-8"?>
<TcPlcObject Version="1.1.0.1" ProductVersion="3.1.4024.0">
<DUT Name="ST_AlarmRecord" Id="{00000000-0000-0000-0000-000000000403}">
<Declaration><![CDATA[// PR 4.1 / #315 — element type for the GVL_Plant.aAlarmRecords ARRAY[1..2000] cutoff
// fixture. Two atomic members per element so an over-cap browse short-circuits to a
// single IsArrayRoot leaf rather than 2000 × 2 = 4000 individual leaves.
TYPE ST_AlarmRecord :
STRUCT
nCode : DINT;
bActive : BOOL;
END_STRUCT
END_TYPE
]]></Declaration>
</DUT>
</TcPlcObject>

View File

@@ -0,0 +1,20 @@
<?xml version="1.0" encoding="utf-8"?>
<TcPlcObject Version="1.1.0.1" ProductVersion="3.1.4024.0">
<DUT Name="ST_NestedFlags" Id="{00000000-0000-0000-0000-000000000401}">
<Declaration><![CDATA[// PR 4.1 / #315 — exercises TwinCATTypeWalker.Walk against a mixed-atomic struct.
// Members chosen to span integer / boolean / real so the per-leaf flatten emits one
// row per type the OPC UA layer renders. Bit-packed BOOL members reuse the existing
// PR 1.5 bit-extract path on read.
TYPE ST_NestedFlags :
STRUCT
bRunning : BOOL;
bFault : BOOL;
bWarning : BOOL;
nState : INT;
rTemperature : REAL;
sTagName : STRING(40);
END_STRUCT
END_TYPE
]]></Declaration>
</DUT>
</TcPlcObject>

View File

@@ -0,0 +1,18 @@
<?xml version="1.0" encoding="utf-8"?>
<TcPlcObject Version="1.1.0.1" ProductVersion="3.1.4024.0">
<DUT Name="ST_RecursiveCap" Id="{00000000-0000-0000-0000-000000000402}">
<Declaration><![CDATA[// PR 4.1 / #315 — exercises the depth-cap / cycle-guard path in TwinCATTypeWalker.
// A POINTER TO ST_RecursiveCap surfaces in the IDataType graph as IsPointer=true
// (DataTypeCategory.Pointer); the walker should skip the pointer member rather than
// recurse, leaving nValue as the only atomic leaf. If the cycle guard ever regresses
// the walker would either stack-overflow or emit a flood of self-referential paths,
// both of which the integration test asserts against.
TYPE ST_RecursiveCap :
STRUCT
nValue : DINT;
pNext : POINTER TO ST_RecursiveCap;
END_STRUCT
END_TYPE
]]></Declaration>
</DUT>
</TcPlcObject>

View File

@@ -0,0 +1,17 @@
<?xml version="1.0" encoding="utf-8"?>
<TcPlcObject Version="1.1.0.1" ProductVersion="3.1.4024.0">
<GVL Name="GVL_Plant" Id="{00000000-0000-0000-0000-000000000404}">
<Declaration><![CDATA[// PR 4.1 / #315 — UDT decomposition + array-cutoff fixture for TwinCATUdtBrowseTests.
// stFlags : nested flags struct exercises the per-member flatten path (one OPC UA
// variable per atomic field). aAlarmRecords : 2000-element array of struct-typed
// elements exercises the MaxArrayExpansion cutoff; default cap is 1024, so the
// browse short-circuits to a single IsArrayRoot leaf instead of 4000 individual
// rows. Keep the GVL small overall so the symbol table stays under the AMS request
// budget.
VAR_GLOBAL
stFlags : ST_NestedFlags;
aAlarmRecords : ARRAY[1..2000] OF ST_AlarmRecord;
END_VAR
]]></Declaration>
</GVL>
</TcPlcObject>

View File

@@ -198,6 +198,45 @@ Options to eliminate the manual step:
the rotation permanently, worth it if the integration host is
long-lived.
## Complex hierarchy
PR 4.1 / #315 (nested-UDT browse via online type walker) exercises
`TwinCATTypeWalker.Walk` against a real PLC symbol graph. The fixture
state required:
### DUTs
- `PLC/DUTs/ST_NestedFlags.TcDUT` — mixed-atomic struct (BOOL / INT / REAL /
STRING) used for the per-member flatten coverage.
- `PLC/DUTs/ST_AlarmRecord.TcDUT` — small two-field struct used as the
element type of the cutoff array.
- `PLC/DUTs/ST_RecursiveCap.TcDUT` — struct with a `POINTER TO`
self-reference; verifies the walker's pointer-skip + cycle-guard
paths terminate without exploding the symbol stream.
### GVL: `GVL_Plant`
```st
VAR_GLOBAL
stFlags : ST_NestedFlags;
aAlarmRecords : ARRAY[1..2000] OF ST_AlarmRecord;
END_VAR
```
`stFlags` produces N atomic leaves where N = the number of `ST_NestedFlags`
fields. `aAlarmRecords` has 2000 elements which exceeds the default
`TwinCATDriverOptions.MaxArrayExpansion` (1024) — discovery surfaces it as
a single `IsArrayRoot` leaf rather than 4000 per-element rows. Lower the
cap on the driver instance to force per-element expansion (or raise it if
the operator wants the per-element view).
The XAE-form artefacts ship at `PLC/DUTs/*.TcDUT` + `PLC/GVLs/GVL_Plant.TcGVL`;
import them into the PLC project alongside `GVL_Fixture` + `GVL_Perf`.
The integration test that exercises this fixture lives at
`tests/.../TwinCATUdtBrowseTests.cs` and skips via `[TwinCATFact]` when
the XAR runtime isn't reachable.
## Online-change test scenario
PR 2.3 (proactive Symbol-Version invalidation listener) ships an