Catch-all commit for pending work on the task-galaxy-e2e branch that
wasn't part of the FOCAS migration. Grouping by topic so future per-topic
commits can be cherry-picked if needed.
TwinCAT
- src/.../Driver.TwinCAT/AdsTwinCATClient.cs + TwinCATDriverFactoryExtensions.cs:
factory-registration extensions + ADS client refinements.
- src/.../Driver.TwinCAT.Cli/Commands/BrowseCommand.cs: new browse command
for the TwinCAT test-client CLI.
- tests/.../Driver.TwinCAT.IntegrationTests/TwinCAT3SmokeTests.cs + TwinCatProject/:
fixture scaffold with a minimal POU + README pointing at the TCBSD/ESXi
VM for e2e.
- docs/Driver.TwinCAT.Cli.md + docs/drivers/TwinCAT-Test-Fixture.md:
documentation for the above.
- docs/v3/twincat-backlog.md: forward-looking backlog seed.
Admin UI + fleet status
- src/.../Admin/Components/Pages/Clusters/DriversTab.razor + Hosts.razor:
UI refresh for fleet-status rendering.
- src/.../Admin/Hubs/FleetStatusHub.cs + FleetStatusPoller.cs +
Admin/Program.cs: SignalR hub + poller plumbing for live fleet data.
- tests/.../Admin.Tests/FleetStatusPollerTests.cs: poller coverage.
Server + redundancy runtime (Phase 6.3 follow-ups)
- src/.../Server/Hosting/RedundancyPublisherHostedService.cs: HostedService
that owns the RedundancyStatePublisher lifecycle + wires peer reachability.
- src/.../Server/Redundancy/ServerRedundancyNodeWriter.cs: OPC UA
variable-node writer binding ServiceLevel + ServerUriArray to the
publisher's events.
- src/.../Server/Program.cs + Server.csproj: hosted-service registration.
- tests/.../Server.Tests/ServerRedundancyNodeWriterTests.cs +
Server.Tests.csproj: coverage for the above.
Configuration
- src/.../Configuration/Validation/DraftValidator.cs +
tests/.../Configuration.Tests/DraftValidatorTests.cs: draft-validation
refinements.
E2E scripts (shared infrastructure)
- scripts/e2e/README.md + _common.ps1 + test-all.ps1: shared helpers + the
all-drivers test-all runner.
- scripts/e2e/test-opcuaclient.ps1: OPC UA Client e2e runner.
Docs
- docs/v2/implementation/phase-6-{1,2,3,4}*.md + exit-gate-phase-{3,7}.md:
phase-gate + implementation doc updates.
- docs/v2/plan.md: top-level plan refresh.
- docs/v2/redundancy-interop-playbook.md: client interop playbook for the
Phase 6.3 redundancy-runtime work.
Two orphan FOCAS docs remain on disk but deliberately unstaged —
docs/v2/focas-deployment.md and docs/v2/implementation/focas-simulator-plan.md
describe the now-retired Tier-C topology and should either be rewritten
or deleted in a follow-up.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
9.7 KiB
TwinCAT XAR fixture project
This folder holds the TwinCAT 3 XAE project that the XAR VM (or TCBSD target) runs for the integration-tests suite and the broader browse / UDT / array / enum coverage exercised by the OPC UA driver.
Status today: the .sln / .tsproj / .plcproj wrappers still get
generated per-workstation in XAE (GUIDs are install-specific), but every
PLC object is committed as a standalone .TcGVL / .TcDUT / .TcPOU
under PLC/{GVLs,DUTs,POUs}/. Reconstruction is "Add Existing Item" of
each file into a fresh XAE project — see Importing the committed PLC
objects below.
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
.tsprojon "Activate Configuration") - Doesn't carry per-install state (target AmsNetId, source licensing)
Reconstruction workflow on the VM:
- Open TC3 XAE (Visual Studio shell)
- File → Open →
OtOpcUaTwinCatFixture.tsproj - Target system → the VM's AmsNetId (set in System Manager → Routes)
- Build → Build Solution (produces the bootproject)
- Activate Configuration → Run Mode (deploys to XAR + starts the runtime)
Required project state
TwinCAT3SmokeTests.cs ships 14 [TwinCATFact] methods plus a 16-case
[TwinCATTheory] — the tests depend on the exact shapes below. Missing
or renamed symbols surface as ADS DeviceSymbolNotFound or wrong-type
read failures, not silent skips. Changed seed values will flip specific
assertions — the load-bearing ones are called out inline.
Integration-test contract
The dependency surface spans GVL_Fixture, GVL_Primitives, GVL_Arrays,
GVL_Enums, GVL_Plant, and MAIN.
GVL_Fixture (source: PLC/GVLs/GVL_Fixture.TcGVL)
holds exactly three variables — nCounter : DINT := 1234,
rSetpoint : REAL := 0.0, bFlag : BOOL := TRUE. MAIN
(source: PLC/POUs/MAIN.TcPOU) increments
nCounter every cycle so the native-notification test sees monotonic
change without writing. Seeded values aren't reliable — PlcTask
watchdog restarts reset them — so the read test only asserts a
non-negative DINT.
GVL_Primitives.vWord := 16#BEEF — the bit-indexed BOOL test pins
to bit 3 (set in 0xBEEF) and bit 4 (clear). Changing the seed flips
that test.
GVL_Primitives numeric seeds — the primitive-type theory reads
each of vSInt, vUSInt, vInt, vUInt, vDInt, vUDInt, vLInt, vULInt, vReal, vLReal, vString and asserts the exact seed value. Matches
the declarations in PLC/GVLs/GVL_Primitives.TcGVL.
GVL_Arrays.aReal1D[5] — the array round-trip test writes + reads
element index 5. The array must be writable; don't apply read-only
attributes.
GVL_Plant.Line1.Stations[1].Axes[1].Motor.{Temperature, Running} —
the nested-UDT test reads both (LREAL + BOOL). FB_LineSim must be
driving the hierarchy (see MAIN) so values are alive.
GVL_Enums.currentAxisState — the browse test asserts this symbol
surfaces by name.
Complex hierarchy (for browse / UDT / array / enum coverage)
All sources in PLC/. Commits are split
one-object-per-file so XAE can "Add Existing Item" each into a fresh
project (the .plcproj wrapper is environment-specific and not
committed).
Type coverage — GVL_Primitives has one of every ADS primitive:
BOOL, BYTE, WORD, DWORD, LWORD, SINT, USINT, INT, UINT, DINT, UDINT, LINT, ULINT, REAL, LREAL, STRING(80), WSTRING(80), TIME, TOD, DATE, DT.
Array coverage — GVL_Arrays has 1-D primitives, ARRAY[1..4,1..4] OF REAL, ARRAY[0..2,0..2,0..2] OF DINT, plus ARRAY[1..4] OF ST_Axis
for per-element nested-struct browse.
Enum + alias coverage — E_AxisState : DINT, E_Severity : INT,
T_Temperature : LREAL, T_MeterPerSec : LREAL. GVL_Enums exposes
each at the root so tests can assert EnumStrings /
DataTypeDefinition rendering without walking the plant hierarchy.
5-level plant hierarchy — rooted at GVL_Plant.Line1 : ST_Line:
GVL_Plant.Line1 (ST_Line)
.Stations[1..3] (ARRAY OF ST_Station)
.Axes[1..4] (ARRAY OF ST_Axis)
.Motor (ST_Motor)
.Encoder (ST_Encoder)
.Commands (ST_AxisCommands)
.TravelLog[1..8] (ARRAY OF LREAL)
.IO (ST_StationIO — 32x DI/DO, 8x AI/AO)
.Alarms[1..16] (ARRAY OF ST_Alarm)
.Recipe (ST_Recipe)
.Steps[1..10] (ARRAY OF ST_RecipeStep)
.SupportedSkus[1..4] (ARRAY OF STRING)
.Stats (ST_Stats)
Live value churn — FB_LineSim + FB_AxisSim are called from
MAIN every cycle: axes ramp position + derive velocity/accel, motor
current/temperature track a sine, IO masks rotate, one alarm per station
toggles each 32 cycles, stats counters increment. Subscription tests
see real data-change notifications without an external writer.
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 / port852only if the single runtime is already taken by another project on the same VM.
Importing the committed PLC objects
On a machine with a working TcXaeShell (or Visual Studio with the TC3 XAE integration):
- File → New → Project → TwinCAT XAE Project → name
OtOpcUaTwinCatFixture. This lays down the.sln+.tsprojscaffolding. - In the Solution Explorer, right-click the
PLCnode → Add New Item → Standard PLC Project → namePLC. Delete the auto-generated stubMAIN— the committed one will replace it. - Right-click the PLC project's
DUTsfolder → Add → Existing Item … → multi-select every file underPLC/DUTs/and Add As Link (so the repo stays the source of truth). Repeat forGVLs/→PLC/GVLs/andPOUs/→PLC/POUs/. - Assign
MAINtoPlcTask(cyclic, 10 ms, priority 20). - Set the target system to the TCBSD / XAR VM via the AMS route, then Build → Build Solution + Activate Configuration → Run Mode.
If XAE complains about missing references while importing, add the DUTs before the GVLs (the structs are referenced by the plant GVL) and the enums/aliases first within DUTs.
XAR VM setup (one-time)
Full bootstrap lives in docs/v2/dev-environment.md. The TwinCAT-specific
steps:
- 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. - 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. - Note the VM's AmsNetId — shown in the TwinCAT system tray icon →
Properties → AMS NetId (format like
5.23.91.23.1.1). - 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
- Import this project per the reconstruction workflow above.
- Hit Activate Configuration + Run Mode. The runtime starts; the
system tray icon goes green; port
48898is live.
License rotation
The XAR trial expires every 7 days. When it lapses:
- The runtime goes silent (red tray icon, ADS port
48898stops responding to new connections). - Integration tests skip with the reason message pointing at this folder's README.
- Operator runs
C:\TwinCAT\3.1\Target\StartUp\TcActivate.exe /reactivateon 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_NETID = '5.23.91.23.1.1' # replace with the VM AmsNetId — REQUIRED
$env:TWINCAT_TARGET_HOST = '10.0.0.42' # replace with the VM IP (defaults to localhost)
# $env:TWINCAT_TARGET_PORT = '852' # only if not using PLC runtime 1 (default 851)
dotnet test tests\ZB.MOM.WW.OtOpcUa.Driver.TwinCAT.IntegrationTests
Only TWINCAT_TARGET_NETID is required — fixture gates on it specifically.
TWINCAT_TARGET_HOST defaults to localhost when unset; TWINCAT_TARGET_PORT
defaults to 851. With the NetID unset, the whole integration suite
(~28 cases, both [TwinCATFact] and [TwinCATTheory]) skips cleanly;
unit suite (TwinCAT.Tests) runs unchanged.
See also
docs/drivers/TwinCAT-Test-Fixture.md— coverage mapdocs/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)