Files
lmxopcua/docs/v2/focas-deployment.md
Joseph Doherty 2d07d716dc Recover stashed driver-gaps work from pre-v2-mxgw-merge working tree
Captures uncommitted work that lived in the working tree on
v2-mxgw-integration but was orthogonal to the migration. Stashed
during the v2-mxgw merge to master (2026-04-30) and replanted here on
a feature branch off master so it's git-visible rather than living in
the stash list.

Two distinct buckets:

1. Tracked fixture/config refinements (10 files, ~36 lines):
   - scripts/e2e/test-opcuaclient.ps1
   - src/ZB.MOM.WW.OtOpcUa.Admin/appsettings.json
   - 5 docker-compose.yml under tests/.../IntegrationTests/Docker/
     (AbCip, Modbus, OpcUaClient, S7)
   - 4 fixture .cs files (AbServerFixture, ModbusSimulatorFixture,
     OpcPlcFixture, Snap7ServerFixture)

2. Untracked driver-gaps queue artifacts (~8000 lines):
   - docs/plans/{abcip,ablegacy,focas,opcuaclient,s7,twincat}-plan.md
     — per-driver gap plans
   - docs/featuregaps.md — cross-cutting analysis
   - docs/v2/focas-deployment.md, docs/v2/implementation/focas-simulator-plan.md
   - followup.md — auto/driver-gaps queue follow-ups
   - scripts/queue/ — PR-queue automation tooling (12 files including
     pr-manifest.yaml at 1473 lines)

This commit is a snapshot for recoverability — review and split into
focused PRs (or discard) before merging anywhere downstream.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-30 08:28:01 -04:00

9.2 KiB

FOCAS deployment guide

Operational reference for deploying the Fanuc FOCAS driver in production.

Licence + DLL provisioning

Fanuc's FOCAS2 library is proprietary + closed-source. Two DLL variants exist:

Variant Bitness OtOpcUa usage
Fwlib64.dll x64 Default production binary. Loaded by Driver.FOCAS.Host (net10.0 x64 Windows service) and by the Driver.FOCAS.Cli when running on an x64 server.
Fwlib32.dll x86 Historical — what the project was originally scaffolded against. Not used by any current binary post the 2026-04-23 Host retarget. Kept in the licence set for legacy deployments that insist on x86-only Hosts.

Both are licensed for this project — this project has a valid Fanuc FOCAS developer-kit licence that grants redistribution for either variant internally.

The DLLs now ship with the Host (2026-04-23)

As of the vendoring change, the Host csproj copies the licensed FOCAS binaries from vendor/fanuc/ to its build output automatically. So after a dotnet build / dotnet publish, the layout is:

<publish-root>\Driver.FOCAS.Host\
├── OtOpcUa.Driver.FOCAS.Host.exe
├── OtOpcUa.Driver.FOCAS.Host.dll
├── ... runtime deps ...
├── Fwlib64.dll               ← master FOCAS runtime (generic x64)
├── fwlib0iD64.dll            ← 0i-D series dispatch target
├── fwlib30i64.dll            ← 30i / 31i / 32i series dispatch target
├── fwlibe64.dll              ← Ethernet transport variant
├── fwlibNCG64.dll            ← NC Guide (Fanuc PC simulator) target
└── fwlib0DN64.dll            ← 0i-D Numeric-control thin variant

No operator step required to "drop Fwlib64.dll on PATH" anymore — the Host loads Fwlib64.dll via bare-name and Windows finds it in the exe's own directory first. Shipping the full set of series-specific siblings lets the Host work against any Fanuc CNC the deployment points it at; the master Fwlib64.dll dispatches to the right variant based on what the CNC reports during cnc_allclibhndl3.

The DLL loads lazily on the first OpenSessionAsync call. When somehow missing (deployment artefact surgery), Fwlib64FocasBackend returns a structured Fwlib64DllMissing error-code rather than crashing; the Proxy maps it to BadCommunicationError with a clear operator message.

Repo confidentiality note

The FOCAS runtime DLLs in vendor/fanuc/ are licensed binaries — treat this repo accordingly. Do not mirror / push / fork to any public forge without first confirming the redistribution is covered by whoever manages the Fanuc relationship. Internal / customer-licensed mirrors are fine. See vendor/fanuc/README.md for the full provenance + licence context.

Tier-C architecture recap

The FOCAS driver is Tier-C — out-of-process — for blast-radius isolation, not bitness. Fanuc's DLL has documented crash modes (network errors, malformed responses, handle-recycle bugs) that could take the main OPC UA server down if loaded in-process. Splitting the P/Invoke into a separate Host process means a Fwlib crash only loses FOCAS tags; every other driver keeps running, and the supervisor restarts the Host.

Galaxy has the same pattern but is forced by MXAccess's 32-bit-only COM — there's no x64 path. FOCAS would work in-process on x64 (Fwlib64 is licensed), but the blast-radius argument keeps it Tier-C anyway.

See implementation/focas-isolation-plan.md for the full topology.

Installing the Host service

Use the NSSM wrapper script:

.\scripts\install\Install-FocasHost.ps1 `
  -InstallRoot 'C:\Program Files\OtOpcUa\Driver.FOCAS.Host' `
  -ServiceAccount 'OTOPCUA\svc-otopcua' `
  -FocasBackend fwlib64

Parameters:

Parameter Default Purpose
-InstallRoot required Where the Host binaries + Fwlib64.dll live
-ServiceAccount required Must match the main OtOpcUa server account so the named-pipe ACL allows the Proxy to connect
-FocasBackend fwlib64 fwlib64 (production), fake (in-memory for Tier-C pipeline smoke without a CNC), unconfigured (returns BadDeviceFailure for every call)
-FocasSharedSecret auto-gen Per-process secret passed at service start so it never touches disk
-FocasPipeName OtOpcUaFocas Named pipe the Proxy connects to
-ServiceName OtOpcUaFocasHost Windows service display name

fwlib32 is accepted as a legacy alias but maps to Fwlib64FocasBackend internally — the Host is x64 post-2026-04-23, so 32-bit-only deployments would need to rebuild + retarget.

Configuring a FOCAS driver instance

In the Admin UI's Drivers tab, create a DriverInstance with DriverType = "FOCAS" and a JSON config of the shape:

{
    "Backend": "ipc",
    "PipeName": "OtOpcUaFocas",
    "SharedSecret": "<matches OTOPCUA_FOCAS_SECRET env var on the Host>",
    "Devices": [
        { "Name": "Mill-01", "HostAddress": "focas://192.168.1.50:8193", "Series": "ThirtyOne_i" }
    ],
    "Tags": [
        { "Name": "SpindleLoad",  "DeviceName": "Mill-01", "Address": "R100",        "DataType": "Int16" },
        { "Name": "CycleRunning", "DeviceName": "Mill-01", "Address": "X0.0",        "DataType": "Bit"   },
        { "Name": "PartCount",    "DeviceName": "Mill-01", "Address": "MACRO:500",   "DataType": "Float64" }
    ],
    "Probe": { "Enabled": true, "IntervalMs": 5000, "TimeoutMs": 2000 }
}

Backend selector (on the Proxy side — not to be confused with OTOPCUA_FOCAS_BACKEND on the Host):

Value Meaning
ipc (default) Route through Driver.FOCAS.Host over the named pipe. Production shape.
fwlib Direct in-process P/Invoke via FwlibFocasClient. Only valid on x64 servers that are willing to accept the blast-radius trade-off.
unimplemented Throws at construction — used for scaffolding DriverInstance rows before the Host is deployed.

Smoke testing

Without a CNC — pipeline only:

$env:OTOPCUA_FOCAS_BACKEND = "fake"
Start-Service OtOpcUaFocasHost

The FakeFocasBackend stores per-address values in-memory and survives read/write/subscribe exercising. Use otopcua-focas-cli (in-process, bypasses the Host) or the OtOpcUa server's own driver registration to exercise the pipeline.

Version-aware fake (Stream A of the simulator plan, shipped 2026-04-23) — set OTOPCUA_FOCAS_SERIES to simulate a specific Fanuc controller's capability matrix. Addresses outside the series' documented ranges get rejected with BadOutOfRange (matching what the real DLL returns as EW_NUMBER / EW_PARAM):

$env:OTOPCUA_FOCAS_BACKEND = "fake"
$env:OTOPCUA_FOCAS_SERIES = "ThirtyOne_i"   # or Zero_i_D / Zero_i_F / Sixteen_i / PowerMotion_i / ...
Start-Service OtOpcUaFocasHost

Optional behavioural quirksOTOPCUA_FOCAS_QUIRKS is a comma-separated list:

Token Behaviour
EditMode OpenSessionAsync refuses sessions with ErrorCode=EditModeActive, mimicking a CNC in Edit mode
Emergency ProbeAsync reports the session as unhealthy with emergency-stop active error even after a clean open — exercises the driver's probe-surfaces-non-connectivity path
SlowFirstConnect[=ms] First OpenSessionAsync blocks for ms (default 3000) milliseconds, mimicking the 16i-series slow-first-connect — subsequent opens are fast
CrashAfterCycles=N After N session opens, the N+1-th returns ErrorCode=Fwlib64Crashed — mimics the documented Fanuc handle-leak

Example combining several:

$env:OTOPCUA_FOCAS_QUIRKS = "EditMode,CrashAfterCycles=5,SlowFirstConnect=500"

Unknown tokens log a warning but don't abort startup.

With a real CNC:

$env:OTOPCUA_FOCAS_BACKEND = "fwlib64"
$env:FOCAS_TRUST_WIRE = "1"
Start-Service OtOpcUaFocasHost
.\scripts\e2e\test-focas.ps1 -CncHost 192.168.1.50 -BridgeNodeId 'ns=2;s=Focas/R100'

Requires Fwlib64.dll on PATH alongside the Host exe.

Observability

  • Host logs: %ProgramData%\OtOpcUa\focas-host-*.log (Serilog daily rolling)
  • Post-mortem: %ProgramData%\OtOpcUa\focas-post-mortem.mmf — ring buffer of the last ~1000 IPC operations, survives a Host crash so the Proxy-side supervisor can read it during respawn diagnostic
  • DriverHostStatus rows in the central Config DB under HostName = <configured device host>State transitions + Polly resilience counters surface on the Admin /hosts page

Known issues

  • No public simulator — Fanuc FOCAS has no published emulator. Lab-rig validation (a real FANUC 0i-F / 30i controller or an FDK-licenced dev rig) is the only way to confirm wire-level correctness. Tracked under task #222.
  • 32-bit-only deployments unsupported — post the 2026-04-23 retarget, running the Host as net48 x86 is not a supported mode. If you genuinely need Fwlib32-only, revert the Host csproj + Program.cs changes from that commit.
  • Handle-recycling cadence — documented Fanuc issue where long-lived FWLIB session handles can leak inside the DLL; the Host periodically cycles them. Currently on a fixed 60-minute cadence; future config knob tracked as a post-release follow-up.