Auto: focas-f4d — password / unlock parameter

Closes #271
This commit is contained in:
Joseph Doherty
2026-04-26 05:45:13 -04:00
parent d676b4056d
commit 86f3fc2733
16 changed files with 1016 additions and 40 deletions

View File

@@ -147,6 +147,64 @@ non-idempotent action (alarm acks, M-code pulses, recipe steps). Flip
`WriteIdempotent` on per tag for genuinely-idempotent writes (a parameter
value that the operator simply wants forced to a target).
### FOCAS password — issue #271 (F4-d)
Some controllers — notably 16i and certain 30i firmwares with the
parameter-protect switch on — gate `cnc_wrparam` and a handful of reads
behind a connection-level password. Without unlocking the session, every
gated wire call returns `EW_PASSWD`, which the F4-b mapping surfaces as
`BadUserAccessDenied`.
`FocasDeviceOptions.Password` plumbs the password through the device config:
```jsonc
{
"Devices": [
{
"HostAddress": "focas://10.0.0.5:8193",
"Password": "1234" // F4-d — optional CNC password
}
]
}
```
When set, the driver:
1. **On connect**, calls `IFocasClient.UnlockAsync(password, ct)` after
the FWLIB handle opens but before any read/write fires. The FWLIB-backed
client emits `cnc_wrunlockparam` with the password ASCII-encoded into
the 4-byte FOCAS password slot (right-padded with `0x00`, truncated at
4 bytes — that's the shape the public Fanuc samples document).
2. **On `BadUserAccessDenied` from any gated read or write**, re-issues
`UnlockAsync` and retries the call **exactly once**. A second
`EW_PASSWD` propagates unchanged so a wrong password doesn't loop
forever on the wire.
3. **Reset on reconnect** — FWLIB unlock state lives on the handle, so
any reconnect path (planned or unplanned) re-runs unlock automatically
via `EnsureConnectedAsync`.
**No-log invariant.** The password is a secret. The driver MUST NOT log
it. Specifically:
- `FocasDeviceOptions` overrides the record's auto-generated `ToString`
to print `Password = ***` when the field is non-null. Any Serilog
destructure that flows the device options through `{Device}` gets the
redaction for free.
- `FwlibFocasClient.UnlockAsync` does not include the password in any
exception message — only the FWLIB return code (`EW_PASSWD`,
`EW_HANDLE`, etc.) makes it into the surface.
- `FocasDriver` logs only `"FOCAS unlock applied for {host}"` when the
unlock succeeds — no password.
- The Driver.FOCAS.Cli `--cnc-password` flag is also redacted at the
same `FocasDeviceOptions` choke point.
- See [`docs/v2/focas-deployment.md`](../v2/focas-deployment.md)
§ "FOCAS password handling" for the storage/rotation runbook + the
cross-link to [`docs/Security.md`](../Security.md).
When the controller does **not** need a password, leave `Password`
unset (`null`) and the driver short-circuits the unlock call entirely —
no wire-level cost.
### Status-code semantics post-F4-b
- `BadNotWritable` — one of: driver-level `Writes.Enabled = false`; per-tag
@@ -155,9 +213,13 @@ value that the operator simply wants forced to a target).
**`Writes.AllowPmc = false` for a PMC tag (F4-c)**. Same status code,
five distinct paths — operators distinguish by checking the knobs.
- `BadUserAccessDenied`**F4-b** — the CNC reported `EW_PASSWD`
(parameter-write switch off / unlock required). F4-d will land the
unlock workflow on top of this surface; today the deployment instructs
the operator to flip the parameter-write switch on the CNC pendant.
(parameter-write switch off / unlock required). **F4-d** wires the
`cnc_wrunlockparam` retry path on top: when `Password` is configured
the driver re-issues unlock + retries the gated call once before
surfacing this status. A persistent `BadUserAccessDenied` after F4-d
means either (a) the password doesn't match the controller, or (b)
the parameter-write switch on the pendant is still off and the
controller wants both the switch + the password.
- `BadNotSupported` — both opt-ins flipped on, but the wire client doesn't
implement the kind being written (e.g. older transport variant). F4-a
wired the generic dispatch; F4-b adds typed `WriteParameterAsync` /