7.7 KiB
otopcua-focas-cli — Fanuc FOCAS test client
Ad-hoc probe / read / write / subscribe tool for Fanuc CNCs via the FOCAS/2
protocol. Uses the same FocasDriver the OtOpcUa server does — PMC R/G/F
file registers, axis bits, parameters, and macro variables — all through
FocasAddressParser syntax.
Sixth of the driver test-client CLIs, added alongside the Tier-C isolation work tracked in task #220.
Architecture note
FOCAS is a Tier-C driver: Fwlib32.dll is a proprietary 32-bit Fanuc library
with a documented habit of crashing its hosting process on network errors.
The target runtime deployment splits the driver into an in-process
FocasProxyDriver (.NET 10 x64) and an out-of-process Driver.FOCAS.Host
(.NET 4.8 x86 Windows service) that owns the DLL — see
v2/implementation/focas-isolation-plan.md
and
v2/implementation/phase-6-1-resilience-and-observability.md
for topology + supervisor / respawn / back-pressure design.
The CLI skips the proxy and loads FocasDriver directly (via
FwlibFocasClientFactory, which P/Invokes Fwlib32.dll in the CLI's own
process). There is no public simulator for FOCAS; a meaningful probe
requires a real CNC + a licensed Fwlib32.dll on PATH (or next to the
executable). On a dev box without the DLL, every wire call surfaces as
BadCommunicationError — still useful as a "CLI wire-up is correct" signal.
Build + run
dotnet build src/ZB.MOM.WW.OtOpcUa.Driver.FOCAS.Cli
dotnet run --project src/ZB.MOM.WW.OtOpcUa.Driver.FOCAS.Cli -- --help
Or publish a self-contained binary:
dotnet publish src/ZB.MOM.WW.OtOpcUa.Driver.FOCAS.Cli -c Release -o publish/focas-cli
publish/focas-cli/otopcua-focas-cli.exe --help
Common flags
Every command accepts:
| Flag | Default | Purpose |
|---|---|---|
-h / --cnc-host |
required | CNC IP address or hostname |
-p / --cnc-port |
8193 |
FOCAS TCP port (FOCAS-over-EIP default) |
-s / --series |
Unknown |
CNC series — Unknown / Zero_i_D / Zero_i_F / Zero_i_MF / Zero_i_TF / Sixteen_i / Thirty_i / ThirtyOne_i / ThirtyTwo_i / PowerMotion_i |
--timeout-ms |
2000 |
Per-operation timeout |
--verbose |
off | Serilog debug output |
Addressing
FocasAddressParser syntax — the same format the server + FocasTagDefinition
use. Common shapes:
| Address | Meaning |
|---|---|
R100 |
PMC R-file word register 100 |
X0.0 |
PMC X-file bit 0 of byte 0 |
G50.3 |
PMC G-file bit 3 of byte 50 |
F1.4 |
PMC F-file bit 4 of byte 1 |
PARAM:1815/0 |
Parameter 1815, axis 0 |
MACRO:500 |
Macro variable 500 |
Data types
Bit, Byte, Int16, Int32, Float32, Float64, String. Default is
Int16 (matches PMC R-file word width).
Commands
probe — is the CNC reachable?
Opens a FOCAS session, reads one sample address, prints driver health.
# Default: read R100 as Int16
otopcua-focas-cli probe -h 192.168.1.50
# Explicit series + address
otopcua-focas-cli probe -h 192.168.1.50 -s ThirtyOne_i --address R200 --type Int16
read — single address
# PMC R-file word
otopcua-focas-cli read -h 192.168.1.50 -a R100 -t Int16
# PMC X-bit
otopcua-focas-cli read -h 192.168.1.50 -a X0.0 -t Bit
# Parameter (axis 0)
otopcua-focas-cli read -h 192.168.1.50 -a PARAM:1815/0 -t Int32
# Macro variable
otopcua-focas-cli read -h 192.168.1.50 -a MACRO:500 -t Float64
write — single value
Values parse per --type with invariant culture. Booleans accept
true / false / 1 / 0 / yes / no / on / off.
otopcua-focas-cli write -h 192.168.1.50 -a R100 -t Int16 -v 42
otopcua-focas-cli write -h 192.168.1.50 -a G50.3 -t Bit -v on
# MACRO: write — recipe / setpoint surface (server-side WriteOperate ACL)
otopcua-focas-cli write -h 192.168.1.50 -a MACRO:500 -t Int32 -v 42
# PARAM: write — commissioning surface (server-side WriteConfigure ACL,
# CNC must be in MDI mode + parameter-write switch enabled, else EW_PASSWD
# surfaces as BadUserAccessDenied)
otopcua-focas-cli write -h 192.168.1.50 -a PARAM:1815 -t Int32 -v 100
PMC G/R writes land on a running machine — be careful which file you hit. Parameter writes may require the CNC to be in MDI mode with the parameter-write switch enabled.
Server-enforced ACL — issue #269, plan PR F4-b
When the same write flows through the OtOpcUa server (rather than the CLI's direct-to-CNC path), the server-layer ACL gates by tag kind:
PARAM:writes requireWriteConfiguregroup membership — heavier ACL because a misdirected parameter write can put the CNC in a bad state.MACRO:writes requireWriteOperate— matches the standard HMI recipe / setpoint surface.- PMC R/G/F writes require
WriteOperate.
The classification is declared by the FOCAS driver per tag and enforced by
DriverNodeManager; the driver itself never inspects user identity. See
docs/security.md for the full LDAP-group → permission
mapping, docs/v2/acl-design.md for the design, and
docs/v2/focas-deployment.md "Write safety" for
the operator pre-check runbook (MDI mode, parameter-write switch).
Writes are non-idempotent by default — a timeout after the CNC already applied the write will NOT auto-retry (plan decisions #44 + #45).
Server-side Writes enforcement (issue #268 F4-a + #269 F4-b)
The OtOpcUa server gates every FOCAS write behind multiple independent
opt-ins: FocasDriverOptions.Writes.Enabled (driver-level master switch),
Writes.AllowParameter (PARAM kill switch — F4-b), Writes.AllowMacro
(MACRO kill switch — F4-b), and FocasTagDefinition.Writable (per-tag).
All default false; any one off short-circuits the server-side
WriteAsync to BadNotWritable before the wire client is touched. See
docs/drivers/FOCAS.md "Writes (opt-in, off by
default)" subsection + docs/v2/decisions.md for the
decision record.
The CLI bypasses the server-side flag. otopcua-focas-cli write is a
per-invocation operator tool — it sets Writes.Enabled = true locally for
the lifetime of one process and creates the synthesised tag with
Writable = true. This is intentional: the CLI is the operator's
direct-to-CNC fallback, not a long-lived process bound to the central
config DB. Configuring the server still requires both opt-ins to be set
explicitly in the DriverInstance JSON.
subscribe — watch an address until Ctrl+C
FOCAS has no push model; the shared PollGroupEngine handles the tick
loop.
otopcua-focas-cli subscribe -h 192.168.1.50 -a R100 -t Int16 -i 500
Output format
Identical to the other driver CLIs via SnapshotFormatter:
probe/reademit a multi-line block:Tag / Value / Status / Source Time / Server Time.probeprefixes it withCNC,Series,Health, andLast errorlines.writeemits one line:Write <address>: 0x... (Good | BadCommunicationError | …).subscribeemits one line per change:[HH:mm:ss.fff] <address> = <value> (<status>).
Typical workflows
"Is the CNC alive?" → probe.
"Does my parameter write land?" → write + read back against the
same address. Check the parameter-write switch + MDI mode if the write
fails.
"Why did this macro flip?" → subscribe to the macro, let the
operator reproduce the cycle, watch the HH:mm:ss.fff timeline.
"Is the Fwlib32 DLL wired up?" → probe against any host. A
DllNotFoundException surfacing as BadCommunicationError with a
matching Last error line means the driver is loading but the DLL is
missing; anything else means a transport-layer problem.