14 KiB
FOCAS deployment guide
Per-driver runbook for deploying the FANUC FOCAS driver. See
docs/drivers/FOCAS.md for the per-feature
reference and focas-version-matrix.md for
the per-CNC-series capability surface.
Operator config-knob cheat sheet
| Knob | Where | Default | Notes |
|---|---|---|---|
Devices[].HostAddress |
FocasDriverOptions.Devices |
— | focas://{ip}[:{port}] |
Devices[].Series |
FocasDriverOptions.Devices |
Unknown |
Drives per-series range validation in FocasCapabilityMatrix. |
Devices[].OverrideParameters |
FocasDriverOptions.Devices |
null |
MTB-specific parameter numbers for Feed/Rapid/Spindle/Jog overrides. null suppresses the Override/ subtree. |
Probe.Enabled |
FocasDriverOptions.Probe |
true |
Background reachability probe. |
Probe.Interval |
FocasDriverOptions.Probe |
00:00:05 |
Probe cadence. |
FixedTree.ApplyFigureScaling |
FocasDriverOptions.FixedTree |
true |
Divide position values by 10^decimal-places (issue #262). |
AlarmProjection.Mode |
FocasDriverOptions.AlarmProjection |
ActiveOnly |
ActiveOnly keeps today's behaviour. ActivePlusHistory polls cnc_rdalmhistry on connect + on HistoryPollInterval ticks (issue #267, plan PR F3-a). |
AlarmProjection.HistoryPollInterval |
FocasDriverOptions.AlarmProjection |
00:05:00 |
Cadence of the history poll. Operator dashboards run the default; high-frequency rigs can drop to 30 s. |
AlarmProjection.HistoryDepth |
FocasDriverOptions.AlarmProjection |
100 |
Most-recent-N ring-buffer entries pulled per poll. Hard-capped at 250 so misconfigured values can't blast the wire session. |
Sample appsettings.json snippet for ActivePlusHistory
{
"Drivers": {
"FOCAS": {
"Devices": [
{ "HostAddress": "focas://10.0.0.5:8193", "Series": "Series30i" }
],
"AlarmProjection": {
"Mode": "ActivePlusHistory",
"HistoryPollInterval": "00:05:00",
"HistoryDepth": 100
}
}
}
}
The history projection emits each unseen entry through
IAlarmSource.OnAlarmEvent with SourceTimestampUtc set from the CNC's
reported wall-clock — keep CNC clocks on UTC so the dedup key
(OccurrenceTime, AlarmNumber, AlarmType) stays stable across DST
transitions.
Write safety — issue #269 (PARAM/MACRO, F4-b) + issue #270 (PMC, F4-c)
The FOCAS driver supports cnc_wrparam, cnc_wrmacro, and pmc_wrpmcrng
writes behind multiple independent opt-ins. A misdirected parameter write
can put the CNC in a bad state; a misdirected PMC write can move motion or
latch a feedhold. The runbook below MUST be followed before flipping any
of the granular kill switches on.
Operator pre-checks (every deployment, every change)
- CNC must be in MDI mode. Most parameter writes fail with
EW_PASSWD(surfaces asBadUserAccessDenied) unless the CNC is in MDI. The server-side write returns immediately with the access-denied status; no value reaches the wire. - Parameter-write switch enabled on the CNC pendant. Even in MDI mode
protected parameters require the operator to physically enable the
parameter-write switch. Without it
cnc_wrparamreturnsEW_PASSWD. Plan PR F4-d will land an OPC UA-side unlock workflow; today the only path is the pendant. - Verify each tag's address against the FANUC manual. Ranges vary per
CNC series; the
focas-version-matrixcapability matrix rejects out-of-range numbers at startup, but address-vs-meaning is the operator's job. - Dry run with
Writable = truebutWrites.AllowParameter = false. Staged opt-in catches mis-mapped tags: every PARAM write returnsBadNotWritableuntil you flip the granular flag, so you can confirm the tag list before any wire write fires.
PMC pre-checks (in addition to the above) — F4-c
PMC writes have a higher blast radius than PARAM/MACRO writes because PMC
is the ladder's working memory — bits in R/G/F/D directly drive servo
enables, feedhold latches, and safety interlocks. Before flipping
Writes.AllowPmc on:
- E-stop verified live + reachable. The first PMC write of a session should be issued with the operator's hand on the e-stop. PMC writes bypass the ladder's normal MDI-mode protections; a misdirected bit can move motion the moment it lands on the wire.
- Machine in JOG mode (or equivalent low-energy mode). Auto / MEM modes interpret PMC state immediately; JOG / MDI surface symptoms slowly enough that the e-stop is the recovery path. Never issue the first PMC write of a deployment in Auto.
- Audit the PMC tag list against the ladder print-out.
R100.3on one machine is "homing complete"; on another it's "feedhold released". The driver has no way to distinguish — the ladder source is the only ground truth. - Bit writes are read-modify-write — see
docs/drivers/FOCAS.md"PMC bit-write read-modify-write semantics".pmc_wrpmcrngis byte-addressed; the driver reads the parent byte first, masks the target bit, and writes the byte back. Concurrent ladder writes to the same byte create a small race window. Coordinate through a ladder-side handshake when this matters. - Dry run with
Writable = truebutWrites.AllowPmc = false. Same staged-opt-in pattern as PARAM/MACRO — confirm tag mapping before any PMC byte hits the wire.
LDAP group requirements
Per docs/security.md the server-layer ACL maps
SecurityClassification to LDAP groups. Post-F4-b:
| Tag kind | LDAP group required |
|---|---|
PARAM:N (writable) |
WriteConfigure — heaviest write tier; matches commissioning roles |
MACRO:N (writable) |
WriteOperate — standard HMI recipe / setpoint group |
| PMC R/G/F (writable) | WriteOperate |
| Read-only | ReadOnly |
Per the feedback_acl_at_server_layer design note, the FOCAS driver
declares the classification but does NOT enforce it; DriverNodeManager
applies the gate before the driver's WriteAsync ever runs. A user
without WriteConfigure who attempts a PARAM: write gets
BadUserAccessDenied from the server with no driver-level audit entry —
the OPC UA layer's audit log catches it.
Audit-log expectations
Every successful write produces:
- An OPC UA AuditWriteEvent (server layer — see
docs/security.md"Audit logging"). - A FOCAS driver-level Serilog entry tagged
Driver=FOCAS DriverInstanceId=... TagName=... Address=... ResultStatus=.... - A
Writes/LastWriteAtandWrites/LastWriteStatusdiagnostic counter refresh on the device'sDiagnostics/fixed-tree node (planned; populated as F4-c lands).
Failures to write (BadUserAccessDenied, BadCommunicationError, etc.)
produce the same audit entries with the failure status code so a
post-incident reviewer sees the same shape regardless of whether the write
succeeded.
Audit PMC writes specifically. Because PMC writes have the highest blast
radius of the three write kinds, ops should set up a saved-search /
dashboard query for Driver=FOCAS + Address matching the PMC letter
prefixes (R*, G*, F*, D*, Y*, etc.) and review on the same
cadence as ladder change reviews. A spike in PMC write rate or a write
to an address outside the audited tag list is the leading indicator of a
misconfigured client or compromised credential.
Granular config example
{
"Drivers": {
"FOCAS": {
"Devices": [
{ "HostAddress": "focas://10.0.0.5:8193", "Series": "Series30i" }
],
"Writes": {
"Enabled": true,
"AllowMacro": true, // recipe / setpoint writes — operator role
"AllowParameter": false, // commissioning only — keep locked except during planned work
"AllowPmc": false // PMC writes — keep locked unless the deployment specifically needs them
},
"Tags": [
{ "Name": "Recipe.PartCount", "DeviceHostAddress": "focas://10.0.0.5:8193",
"Address": "MACRO:500", "DataType": "Int32",
"Writable": true, "WriteIdempotent": true },
{ "Name": "MaxFeedrate", "DeviceHostAddress": "focas://10.0.0.5:8193",
"Address": "PARAM:1815", "DataType": "Int32",
"Writable": false /* keep read-only until commissioning window */ },
{ "Name": "OperatorRequest", "DeviceHostAddress": "focas://10.0.0.5:8193",
"Address": "R100.3", "DataType": "Bit",
"Writable": false /* keep PMC read-only until ladder handshake reviewed */ }
]
}
}
}
Flipping AllowParameter / AllowPmc on for the commissioning window
(and back off afterward) is the recommended deployment cadence — the
granular kill switches are lightweight runtime toggles, not config-DB
redeploys. PMC in particular should default OFF in production and only
flip on for windows where the ladder team has signed off on the write
path.
FOCAS password handling — issue #271 (F4-d)
Some controllers (16i + certain 30i firmwares with parameter-protect on)
gate cnc_wrparam and selected reads behind a connection-level password.
The driver supports this via the Password field on FocasDeviceOptions
which is emitted via cnc_wrunlockparam on connect and re-emitted on any
EW_PASSWD read/write retry path. See
docs/drivers/FOCAS.md § "FOCAS password" for the
driver-side behaviour; this section covers the deployment side.
Storage in appsettings.json
{
"Drivers": {
"Focas01": {
"DriverConfigJson": {
"Backend": "fwlib",
"Series": "Sixteen_i",
"Devices": [
{
"HostAddress": "focas://10.0.0.5:8193",
"Password": "1234"
}
]
}
}
}
}
For dev environments, the password is materialised under
.local/focas-passwords.txt (or whichever .local subkey the deployment
team prefers); production deployments use the same secrets-store /
KeyVault pattern the LDAP Authentication.Ldap.Password field follows.
The .local/ directory is .gitignore'd — this is the same posture
as .local/galaxy-host-secret.txt and other dev secrets in this repo.
No-log invariant
The driver guarantees the password is never logged:
FocasDeviceOptionsToString redaction. The record overridesPrintMembersso any Serilog destructure of the device options rendersPassword = ***when the field is non-null. This catches the most common leak path — a structured-log statement that included{@Device}for diagnostic context.- No password in exception messages.
FwlibFocasClient.UnlockAsyncomits the password from itsInvalidOperationExceptiontext — only the FWLIB error code (EW_PASSWD,EW_HANDLE, etc.) makes it through. - Driver log line uses host only. When unlock succeeds the driver
updates
DriverHealth.StatusTextto"FOCAS unlock applied for {host}"— no password. - CLI flag covered by the same choke point. The
Driver.FOCAS.Cli --cnc-passwordflag flows throughFocasDeviceOptions.Password, so its redaction is identical to the server's. The PowerShell e2e harness (scripts/e2e/test-focas.ps1 -CncPassword) follows the same path.
Any new logging surface that touches FocasDeviceOptions MUST continue
to use the record's ToString (or otherwise omit Password). A code
review checklist item: "no log statement contains device.Options.Password
or device.Password directly."
Password-rotation runbook
When the CNC password rotates (operator team flipped a parameter-protect gate, or your security policy requires periodic rotation):
- Update the password on the controller (CNC pendant or vendor's admin tool). The exact path varies by series — Fanuc service manual page reference depends on the MTB.
- Update
appsettings.jsonin place with the new value.- Production: bump the secrets-store entry that backs the
Devices[*].Passwordconfig-DB column. Same workflow as rotating the LDAP service-account password. - Dev: update
.local/focas-passwords.txt(or wherever the dev deployment sources the secret).
- Production: bump the secrets-store entry that backs the
- Restart the OtOpcUa server (or trigger a config-DB bump that
forces driver reinitialise). The driver picks up the new password
on the next
EnsureConnectedAsynccall. No need to manually reconnect each device —cnc_wrunlockparamemits on the next wire-call boundary. - Verify. The first wire call after restart logs
"FOCAS unlock applied for focas://{host}:{port}"at info. A wrong password surfaces asBadUserAccessDeniedon the next gated read or write. - Audit. OPC UA wrote-event entries (per
audit-log-rules.md) cover the parameter/macro write paths. Password rotation itself is NOT logged beyond "unlock applied" — same posture as LDAP service-account rotation, where the password change is logged out-of-band by the IAM system.
Cross-references
docs/Security.md— server-wide secrets handling + the same.local/pattern used for LDAP and the Galaxy.Host pipe secret. The FOCAS password follows the same posture.docs/drivers/FOCAS.md§ "FOCAS password" — driver-side behaviour, EW_PASSWD retry semantics, status-code surface.docs/v2/implementation/focas-wire-protocol.md§ "cnc_wrunlockparam" — wire-frame layout for the password buffer.