Files
lmxopcua/docs/Driver.FOCAS.Cli.md
2026-04-26 05:45:13 -04:00

9.0 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
--cnc-password (none) F4-d (issue #271) — optional CNC connection-level password emitted via cnc_wrunlockparam on connect. Required only by controllers that gate parameter writes / selected reads behind a password switch (16i + some 30i firmwares with parameter-protect on). PASSWORD INVARIANT: never logged. The CLI's Serilog config does not destructure this flag and FocasDeviceOptions.ToString redacts the value. See v2/focas-deployment.md § "FOCAS password handling".
--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

WARNING — write -a G50.3 -t Bit -v on is a read-modify-write. The wire call pmc_wrpmcrng is byte-addressed; the driver reads the parent byte at G50 first, sets bit 3, and writes the byte back. Other bits in G50 that the ladder is concurrently updating may be clobbered by the byte we read a millisecond ago. Coordinate via a ladder-side handshake when this matters. PMC writes also bypass the ladder's normal MDI-mode protection — a misdirected bit can move motion or latch a feedhold the moment it lands. Verify e-stop is live and the machine is in JOG mode before issuing the first PMC write of a session. See docs/drivers/FOCAS.md "PMC bit-write read-modify-write semantics" for the full RMW flow.

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 require WriteConfigure group membership — heavier ACL because a misdirected parameter write can put the CNC in a bad state.
  • MACRO: writes require WriteOperate — 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 + #270 F4-c)

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), Writes.AllowPmc (PMC kill switch — F4-c), 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 / read emit a multi-line block: Tag / Value / Status / Source Time / Server Time. probe prefixes it with CNC, Series, Health, and Last error lines.
  • write emits one line: Write <address>: 0x... (Good | BadCommunicationError | …).
  • subscribe emits 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.