Files
lmxopcua/tests/ZB.MOM.WW.OtOpcUa.Driver.TwinCAT.IntegrationTests/TwinCatProject/README.md
Joseph Doherty 7b49ea13c7 TwinCAT XAR integration fixture — scaffold the code + docs so the Hyper-V VM + .tsproj drop in without fixture-code changes. Mirrors the AB CIP Logix Emulate scaffold shipped in PR #165: tier-gated smoke tests that skip cleanly when the VM isn't reachable, a project README documenting exactly what the XAR needs to run, fixture-coverage doc promoting TwinCAT from "no integration fixture" to "scaffolded + needs operational setup". The actual Beckhoff-side work (provision VM, install XAR, author tsproj, rotate 7-day trial) lives in #221 + the new TwinCatProject/README.md walkthrough.
New project tests/ZB.MOM.WW.OtOpcUa.Driver.TwinCAT.IntegrationTests/ with four pieces. TwinCATXarFixture — TCP probe against the ADS-over-TCP port 48898 on the host from TWINCAT_TARGET_HOST env var, requires TWINCAT_TARGET_NETID for the target AmsNetId, optional TWINCAT_TARGET_PORT for runtime 2+ (default 851 = PLC runtime 1). Doesn't own a lifecycle — XAR can't run in Docker because it bypasses the Windows kernel scheduler to hit real-time cycles, so the VM stays operator-managed. Explicit skip reasons surface the setup steps (start VM, set env vars, reactivate trial license) instead of a confusing hang. TwinCATFactAttribute + TwinCATTheoryAttribute — xunit skip gate matching AbServerFactAttribute / OpcPlcCollection patterns.

TwinCAT3SmokeTests — three smoke tests through the real AdsTwinCATClient + real ADS over TCP. Driver_reads_seeded_DINT_through_real_ADS reads GVL_Fixture.nCounter, asserts >= 1234 (MAIN increments every cycle so an exact match would race). Driver_write_then_read_round_trip_on_scratch_REAL writes 42.5 to GVL_Fixture.rSetpoint + reads back, catches the ADS write path regression that unit tests can't see. Driver_subscribe_receives_native_ADS_notifications_on_counter_changes validates the #189 native-notification path end-to-end — AddDeviceNotification fires OnDataChange at the PLC cycle boundary, the test observes one firing within 3 s. All three gated on TWINCAT_TARGET_HOST + NETID; skip via TwinCATFactAttribute when unset, verified in this commit with 3 clean [SKIP] results.

TwinCatProject/README.md — the tsproj state the smoke tests depend on. GVL_Fixture with nCounter:DINT:=1234 + rSetpoint:REAL:=0.0 + bFlag:BOOL:=TRUE; MAIN program with the single-line ladder `GVL_Fixture.nCounter := GVL_Fixture.nCounter + 1;`; PlcTask cyclic @ 10 ms priority 20; PLC runtime 1 (AMS port 851). Explains why tsproj over the compiled bootproject (text-diffable, rebuildable, no per-install state). Full XAR VM setup walkthrough — Hyper-V Gen 2 VM, TC3 XAE+XAR install, noting the AmsNetId from the tray icon, bilateral route configuration (VM System Manager → Routes + dev box StaticRoutes.xml), project import, Activate Configuration + Run Mode. License-rotation section walks through two options — scheduled TcActivate.exe /reactivate via Task Scheduler (not officially Beckhoff-supported, reportedly works on current builds) or paid runtime license (~$1k one-time per runtime per CPU). Final section shows the exact env-var recipe + dotnet test command on the dev box.

docs/drivers/TwinCAT-Test-Fixture.md — flipped TL;DR from "there is no integration fixture" to "scaffolding lives at tests/..., remaining operational work is VM + tsproj + license rotation". "What the fixture is" gains an Integration section describing the XAR VM target. "What it actually covers" gains an Integration subsection listing the three named smoke tests. Follow-up candidates rewritten — the #1 item used to be "TwinCAT 3 runtime on CI" as a speculative option; now it's concrete "XAR VM live-population" with a link to #221 + the project README for the operational walkthrough. License rotation becomes #2 with both automation paths. Key fixture / config files list adds the three new files + the project README. docs/drivers/README.md coverage-map row updated from "no integration fixture" to "XAR-VM integration scaffolding".

Solution file picks up the new tests/ZB.MOM.WW.OtOpcUa.Driver.TwinCAT.IntegrationTests entry alongside the existing TwinCAT.Tests. xunit CollectionDefinition added to TwinCATXarFixture after the first build revealed the [Collection("TwinCATXar")] reference on TwinCAT3SmokeTests had no matching registration. Build 0 errors; 3 skip-clean test outcomes verified. #221 stays open as in_progress until the VM + tsproj land.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-20 13:11:17 -04:00

5.3 KiB

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

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

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

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.

How to run the TwinCAT-tier tests

On the dev box:

$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