Final two of the five driver test clients. Pattern carried forward from #249 (Modbus) + #250 (AB CIP, AB Legacy) — each CLI inherits Driver.Cli.Common for DriverCommandBase + SnapshotFormatter and adds a protocol-specific CommandBase + 4 commands (probe / read / write / subscribe). New projects: - src/ZB.MOM.WW.OtOpcUa.Driver.S7.Cli/ — otopcua-s7-cli. S7CommandBase carries host/port/cpu/rack/slot/timeout. Handles all S7 atomic types (Bool, Byte, Int16..UInt64, Float32/64, String, DateTime). DateTime parses via RoundtripKind so "2026-04-21T12:34:56Z" works. - src/ZB.MOM.WW.OtOpcUa.Driver.TwinCAT.Cli/ — otopcua-twincat-cli. TwinCATCommandBase carries ams-net-id + ams-port + --poll-only toggle (flips UseNativeNotifications=false). Covers the full IEC 61131-3 atomic set: Bool, SInt/USInt, Int/UInt, DInt/UDInt, LInt/ULInt, Real, LReal, String, WString, Time/Date/DateTime/TimeOfDay. Structure writes refused as out-of-scope (same as AB CIP). IEC time/date variants marshal as UDINT on the wire per IEC spec. Subscribe banner announces "ADS notification" vs "polling" so the mechanism is obvious in bug reports. Tests (49 new, 122 cumulative driver-CLI): - S7: 22 tests. Every S7DataType has a happy-path + bounds case. DateTime round-trips an ISO-8601 string. Tag-name synthesis round-trips every S7 address form (DB / M / I / Q, bit/word/dword, strings). - TwinCAT: 27 tests. Full IEC type matrix including WString UTF-8 pass- through + the four IEC time/date variants landing on UDINT. Structure rejection case. Tag-name synthesis for Program scope, GVL scope, nested UDT members, and array elements. Docs: - docs/Driver.S7.Cli.md — address grammar cheat sheet + the PUT/GET-must- be-enabled gotcha every S7-1200/1500 operator hits. - docs/Driver.TwinCAT.Cli.md — AMS router prerequisite (XAR / standalone Router NuGet / remote AMS route) + per-command examples. Wiring: - ZB.MOM.WW.OtOpcUa.slnx grew 4 entries (2 src + 2 tests). Full-solution build clean. Both --help outputs verified end-to-end. Driver CLI suite complete: 5 CLIs (otopcua-{modbus,abcip,ablegacy,s7,twincat}-cli) sharing a common base + formatter. 122 CLI tests cumulative. Every driver family shipped in v2 now has a shell-level ad-hoc validation tool. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
3.4 KiB
otopcua-twincat-cli — Beckhoff TwinCAT test client
Ad-hoc probe / read / write / subscribe tool for Beckhoff TwinCAT 2 / TwinCAT 3
runtimes via ADS. Uses the same TwinCATDriver the OtOpcUa server does
(Beckhoff.TwinCAT.Ads package). Native ADS notifications by default;
--poll-only falls back to the shared PollGroupEngine.
Fifth (final) of the driver test-client CLIs.
Build + run
dotnet run --project src/ZB.MOM.WW.OtOpcUa.Driver.TwinCAT.Cli -- --help
Prerequisite: AMS router
The Beckhoff.TwinCAT.Ads library needs a reachable AMS router to open ADS
sessions. Pick one:
- Local TwinCAT XAR — install the free TwinCAT 3 XAR Engineering install on the machine running the CLI; it ships the router.
- Beckhoff.TwinCAT.Ads.TcpRouter — standalone NuGet router. Run in a sidecar process when no XAR is installed.
- Remote AMS route — any Windows box with TwinCAT installed, with an AMS route authorised to the CLI host.
The CLI compiles + runs without a router, but every wire call fails with a transport error until one is reachable.
Common flags
| Flag | Default | Purpose |
|---|---|---|
-n / --ams-net-id |
required | AMS Net ID (e.g. 192.168.1.40.1.1) |
-p / --ams-port |
851 |
AMS port (TwinCAT 3 PLC = 851, TwinCAT 2 = 801) |
--timeout-ms |
5000 |
Per-operation timeout |
--poll-only |
off | Disable native ADS notifications, use PollGroupEngine instead |
--verbose |
off | Serilog debug output |
Data types
TwinCAT exposes the IEC 61131-3 atomic set: Bool, SInt, USInt, Int,
UInt, DInt, UDInt, LInt, ULInt, Real, LReal, String, WString,
Time, Date, DateTime, TimeOfDay. The four IEC time/date variants
marshal as UDINT on the wire — CLI takes a numeric raw value and lets the
caller interpret semantics.
Commands
probe
# Local TwinCAT 3, probe a canonical global
otopcua-twincat-cli probe -n 127.0.0.1.1.1 -s "TwinCAT_SystemInfoVarList._AppInfo.OnlineChangeCnt"
# Remote, probe a project variable
otopcua-twincat-cli probe -n 192.168.1.40.1.1 -s MAIN.bRunning --type Bool
read
# Bool symbol
otopcua-twincat-cli read -n 192.168.1.40.1.1 -s MAIN.bStart -t Bool
# Counter
otopcua-twincat-cli read -n 192.168.1.40.1.1 -s GVL.Counter -t DInt
# Nested UDT member
otopcua-twincat-cli read -n 192.168.1.40.1.1 -s Motor1.Status.Running -t Bool
# Array element
otopcua-twincat-cli read -n 192.168.1.40.1.1 -s "Recipe[3]" -t Real
# WString
otopcua-twincat-cli read -n 192.168.1.40.1.1 -s GVL.sMessage -t WString
write
otopcua-twincat-cli write -n 192.168.1.40.1.1 -s MAIN.bStart -t Bool -v true
otopcua-twincat-cli write -n 192.168.1.40.1.1 -s GVL.Counter -t DInt -v 42
otopcua-twincat-cli write -n 192.168.1.40.1.1 -s GVL.sMessage -t WString -v "running"
Structure writes refused — drop to driver config JSON for those.
subscribe
# Native ADS notifications (default) — PLC pushes on its own cycle
otopcua-twincat-cli subscribe -n 192.168.1.40.1.1 -s GVL.Counter -t DInt -i 500
# Fall back to polling for runtimes where native notifications are constrained
otopcua-twincat-cli subscribe -n 192.168.1.40.1.1 -s GVL.Counter -t DInt -i 500 --poll-only
The subscribe banner announces which mechanism is in play — "ADS notification" or "polling" — so it's obvious in screen-recorded bug reports.