257 lines
8.9 KiB
Markdown
257 lines
8.9 KiB
Markdown
# TwinCAT XAR fixture project
|
||
|
||
This folder holds the TwinCAT 3 XAE project that the XAR VM runs for the
|
||
integration-tests suite (`tests/.../TwinCAT.IntegrationTests/*.cs`).
|
||
|
||
**Status today**: stub. The `.tsproj` isn't committed yet; once the XAR
|
||
VM is up + a project with the required state exists, export via
|
||
File → Export + drop it here as `OtOpcUaTwinCatFixture.tsproj` + its
|
||
PLC `.library` / `.plcproj` companions.
|
||
|
||
## Why `.tsproj`, not the binary bootproject
|
||
|
||
TwinCAT ships two project forms: the XAE `.tsproj` (XML, source of
|
||
truth) and the compiled bootproject that the XAR runtime actually
|
||
loads. Ship the `.tsproj` because:
|
||
|
||
- Text format — reviewable in PR diffs, diffable in git
|
||
- Rebuildable across TC3 engineering versions (the XAE tool rebuilds
|
||
the bootproject from `.tsproj` on "Activate Configuration")
|
||
- Doesn't carry per-install state (target AmsNetId, source licensing)
|
||
|
||
Reconstruction workflow on the VM:
|
||
|
||
1. Open TC3 XAE (Visual Studio shell)
|
||
2. File → Open → `OtOpcUaTwinCatFixture.tsproj`
|
||
3. Target system → the VM's AmsNetId (set in System Manager → Routes)
|
||
4. Build → Build Solution (produces the bootproject)
|
||
5. Activate Configuration → Run Mode (deploys to XAR + starts the
|
||
runtime)
|
||
|
||
## Required project state
|
||
|
||
The smoke tests in `TwinCAT3SmokeTests.cs` depend on this exact GVL +
|
||
PLC setup. Missing or renamed symbols surface as ADS `DeviceSymbolNotFound`
|
||
or wrong-type read failures, not silent skips.
|
||
|
||
### Global Variable List: `GVL_Fixture`
|
||
|
||
```st
|
||
VAR_GLOBAL
|
||
// Monotonically-increasing counter; MAIN increments each cycle.
|
||
// Seed value 1234 picked so the smoke test can assert ">= 1234" without
|
||
// synchronising with the initial cycle.
|
||
nCounter : DINT := 1234;
|
||
|
||
// Scratch REAL for write-then-read round-trip test. Smoke test writes
|
||
// 42.5 + reads back.
|
||
rSetpoint : REAL := 0.0;
|
||
|
||
// Readable boolean with seed value TRUE. Reserved for future
|
||
// expansion (e.g. discovery / symbol-browse tests).
|
||
bFlag : BOOL := TRUE;
|
||
END_VAR
|
||
```
|
||
|
||
### PLC program: `MAIN`
|
||
|
||
```st
|
||
PROGRAM MAIN
|
||
VAR
|
||
END_VAR
|
||
|
||
// One-line program: increment the fixture counter every cycle.
|
||
// The native-notification smoke test subscribes to GVL_Fixture.nCounter
|
||
// + observes the monotonic changes without a write path.
|
||
GVL_Fixture.nCounter := GVL_Fixture.nCounter + 1;
|
||
```
|
||
|
||
### Task
|
||
|
||
- `PlcTask` — cyclic, 10 ms interval, priority 20
|
||
- Assigned to `MAIN`
|
||
|
||
> **Note (PR 3.1 / #313)**: `GVL_Fixture.nCounter` doubles as the
|
||
> coalescing-test driver for `TwinCATMaxDelayTests`. The 10 ms cycle +
|
||
> per-cycle increment in `MAIN` means a no-coalescing subscriber sees ~100
|
||
> events / s; with `MaxDelayMs=500` the test asserts ≤ 3 events / s. No new
|
||
> project state required.
|
||
|
||
## Performance scenarios
|
||
|
||
PR 2.1 (ADS Sum-read / Sum-write) ships an opt-in perf-tier integration test
|
||
(`TwinCATSumCommandPerfTests.Driver_sum_read_1000_tags_beats_loop_baseline_by_5x`)
|
||
that reads 1000 DINTs in one shot and asserts the bulk path beats the per-tag
|
||
loop by ≥ 5×. The fixture state required by that test is:
|
||
|
||
### Global Variable List: `GVL_Perf`
|
||
|
||
```st
|
||
VAR_GLOBAL
|
||
// 1000-DINT array — exercised by the bulk Sum-read benchmark.
|
||
aTags : ARRAY[1..1000] OF DINT;
|
||
fbPerfChurn : FB_PerfChurn;
|
||
END_VAR
|
||
```
|
||
|
||
The XAE-form GVL ships at `PLC/GVLs/GVL_Perf.TcGVL`; import it into the PLC
|
||
project alongside `GVL_Fixture`.
|
||
|
||
### POU: `FB_PerfChurn`
|
||
|
||
```st
|
||
FUNCTION_BLOCK FB_PerfChurn
|
||
VAR
|
||
nIndex : INT := 1;
|
||
END_VAR
|
||
|
||
GVL_Perf.aTags[nIndex] := GVL_Perf.aTags[nIndex] + 1;
|
||
nIndex := nIndex + 1;
|
||
IF nIndex > 1000 THEN
|
||
nIndex := 1;
|
||
END_IF
|
||
```
|
||
|
||
The XAE-form POU ships at `PLC/POUs/FB_PerfChurn.TcPOU`. Wire it into `MAIN`
|
||
so a value rotates each cycle:
|
||
|
||
```st
|
||
PROGRAM MAIN
|
||
VAR
|
||
END_VAR
|
||
|
||
// existing GVL_Fixture line:
|
||
GVL_Fixture.nCounter := GVL_Fixture.nCounter + 1;
|
||
|
||
// PR 2.1 — keep aTags moving so caches don't short-circuit the read.
|
||
GVL_Perf.fbPerfChurn();
|
||
```
|
||
|
||
### Running the perf tier
|
||
|
||
```powershell
|
||
$env:TWINCAT_TARGET_HOST = '10.0.0.42'
|
||
$env:TWINCAT_TARGET_NETID = '5.23.91.23.1.1'
|
||
$env:TWINCAT_PERF = '1'
|
||
dotnet test tests\ZB.MOM.WW.OtOpcUa.Driver.TwinCAT.IntegrationTests `
|
||
--filter "Category=Performance"
|
||
```
|
||
|
||
Without `TWINCAT_PERF=1` the perf test skips via `[TwinCATPerfFact]` even when
|
||
the runtime is reachable — perf runs are opt-in to keep the default integration
|
||
pass fast.
|
||
|
||
### Runtime ID
|
||
|
||
- TC3 PLC runtime 1 (AMS port `851`) — the smoke-test fixture defaults
|
||
to this. Use runtime 2 / port `852` only if the single runtime is
|
||
already taken by another project on the same VM.
|
||
|
||
## XAR VM setup (one-time)
|
||
|
||
Full bootstrap lives in `docs/v2/dev-environment.md`. The TwinCAT-specific
|
||
steps:
|
||
|
||
1. **Create the Hyper-V VM** — Gen 2, Windows 10/11 64-bit, 4 GB RAM,
|
||
2 CPUs. External virtual switch so the dev box can reach
|
||
`<vm-ip>:48898`.
|
||
2. **Install TwinCAT 3 XAE + XAR** — free download from Beckhoff
|
||
(`www.beckhoff.com/en-en/products/automation/twincat/`). Activate the
|
||
7-day trial on first boot.
|
||
3. **Note the VM's AmsNetId** — shown in the TwinCAT system tray icon →
|
||
Properties → AMS NetId (format like `5.23.91.23.1.1`).
|
||
4. **Configure bilateral ADS route**:
|
||
- On the VM: System Manager → Routes → Add Route → dev box's
|
||
AmsNetId + IP
|
||
- On the dev box: edit `%TC_INSTALLPATH%\Target\StaticRoutes.xml` (or
|
||
use the dev box's own TwinCAT System Manager if installed) to add
|
||
the VM's AmsNetId + IP
|
||
5. **Import this project** per the reconstruction workflow above.
|
||
6. **Hit Activate Configuration + Run Mode**. The runtime starts; the
|
||
system tray icon goes green; port `48898` is live.
|
||
|
||
## License rotation
|
||
|
||
The XAR trial expires every 7 days. When it lapses:
|
||
|
||
1. The runtime goes silent (red tray icon, ADS port `48898` stops
|
||
responding to new connections).
|
||
2. Integration tests skip with the reason message pointing at this
|
||
folder's README.
|
||
3. Operator runs `C:\TwinCAT\3.1\Target\StartUp\TcActivate.exe /reactivate`
|
||
on the VM console (not RDP — the trial activation wants the
|
||
interactive-login desktop).
|
||
|
||
Options to eliminate the manual step:
|
||
|
||
- **Scheduled task** that runs the reactivate every 6 days at 02:00 —
|
||
documented in the Beckhoff forums as working for some TC3 builds,
|
||
not officially supported.
|
||
- **Paid runtime license** (~$1k one-time per runtime, per CPU) — kills
|
||
the rotation permanently, worth it if the integration host is
|
||
long-lived.
|
||
|
||
## Online-change test scenario
|
||
|
||
PR 2.3 (proactive Symbol-Version invalidation listener) ships an
|
||
operator-gated integration test
|
||
(`TwinCATSymbolVersionTests.Driver_invalidates_handle_cache_on_symbol_version_bump`)
|
||
that verifies `AdsTwinCATClient`'s `AdsSymbolVersionChanged` listener
|
||
wipes the handle cache when the PLC re-initialises after an online
|
||
change. The test polls for up to 60 s waiting for the operator to
|
||
trigger the change from XAE.
|
||
|
||
The fixture state (`GVL_Perf` + `aTags[1..1000]`) is the same one used by
|
||
the Sum-read perf test — no new project state required.
|
||
|
||
### Manual workflow
|
||
|
||
With the XAR runtime live + the test process polling:
|
||
|
||
1. **Open the project in XAE** on the dev box (or wherever XAE runs).
|
||
2. **Add a dummy variable to `GVL_Perf`** — any new declaration triggers
|
||
a symbol-table rebuild. Example: append
|
||
`bSymVerProbe : BOOL := FALSE;` to the GVL.
|
||
3. **Login** (`Ctrl+F8`) — XAE prompts to load the change.
|
||
4. **Activate Configuration** (Yellow-arrow button, or `TwinCAT → Activate Configuration`).
|
||
The runtime re-initialises; the symbol-version counter increments;
|
||
`AdsTwinCATClient.OnAdsSymbolVersionChanged` fires; the handle cache
|
||
wipes; the test polls observe `SymbolVersionBumps > 0` + asserts the
|
||
post-bump read recreates handles.
|
||
|
||
The test skips by default — opt in by setting
|
||
`TWINCAT_MANUAL_ONLINE_CHANGE=1` alongside the standard
|
||
`TWINCAT_TARGET_HOST` / `TWINCAT_TARGET_NETID` env vars before kicking
|
||
off the test run.
|
||
|
||
```powershell
|
||
$env:TWINCAT_TARGET_HOST = '10.0.0.42'
|
||
$env:TWINCAT_TARGET_NETID = '5.23.91.23.1.1'
|
||
$env:TWINCAT_MANUAL_ONLINE_CHANGE = '1'
|
||
dotnet test tests\ZB.MOM.WW.OtOpcUa.Driver.TwinCAT.IntegrationTests `
|
||
--filter "FullyQualifiedName~TwinCATSymbolVersionTests"
|
||
```
|
||
|
||
## How to run the TwinCAT-tier tests
|
||
|
||
On the dev box:
|
||
|
||
```powershell
|
||
$env:TWINCAT_TARGET_HOST = '10.0.0.42' # replace with the VM IP
|
||
$env:TWINCAT_TARGET_NETID = '5.23.91.23.1.1' # replace with the VM AmsNetId
|
||
# $env:TWINCAT_TARGET_PORT = '852' # only if not using PLC runtime 1
|
||
dotnet test tests\ZB.MOM.WW.OtOpcUa.Driver.TwinCAT.IntegrationTests
|
||
```
|
||
|
||
With any of those env vars unset, all three smoke tests skip cleanly via
|
||
`[TwinCATFact]`; unit suite (`TwinCAT.Tests`) runs unchanged.
|
||
|
||
## See also
|
||
|
||
- [`docs/drivers/TwinCAT-Test-Fixture.md`](../../../docs/drivers/TwinCAT-Test-Fixture.md)
|
||
— coverage map
|
||
- [`docs/v2/dev-environment.md`](../../../docs/v2/dev-environment.md)
|
||
§Integration host — VM + route + license-rotation notes
|
||
- Beckhoff Information System → TwinCAT 3 → Product overview + ADS +
|
||
PLC reference (licensed; internal link only)
|