Auto: focas-f4a — write infrastructure + per-tag opt-in

Closes #268
This commit is contained in:
Joseph Doherty
2026-04-26 04:32:43 -04:00
parent 6f1657b1c0
commit 1bfe8fba0e
13 changed files with 521 additions and 11 deletions

View File

@@ -53,3 +53,56 @@ giant request. Typical FANUC ring buffers cap at ~100 entries; the default
- Tier-C Fwlib32 backend short-circuits the packed-buffer decoder by
surfacing the FWLIB struct fields directly into
`FocasAlarmHistoryEntry`.
## Writes (opt-in, off by default) — issue #268, plan PR F4-a
Writes ship behind two independent opt-ins. Both default off so a freshly
deployed FOCAS driver is read-only until the deployment makes a deliberate
choice. Decision record: [`docs/v2/decisions.md`](../v2/decisions.md) →
"FOCAS write-path opt-in".
| Knob | Default | Effect when off |
| --- | --- | --- |
| `FocasDriverOptions.Writes.Enabled` *(driver-level master switch)* | `false` | Every entry in a `WriteAsync` batch short-circuits to `BadNotWritable` with status text `writes disabled at driver level`. Wire client never gets touched. |
| `FocasTagDefinition.Writable` *(per-tag opt-in)* | `false` | The per-tag check returns `BadNotWritable` for that tag even when the driver-level flag is on. |
### Config shape
```jsonc
{
"Writes": { "Enabled": true },
"Tags": [
{ "Name": "RPM", "Address": "PARAM:1815", "DataType": "Int32",
"Writable": true, "WriteIdempotent": false }
]
}
```
`WriteIdempotent` is plumbed through Polly retry by the server-layer
`CapabilityInvoker.ExecuteWriteAsync`. When `false` (default), failed writes
are NOT auto-retried per plan decisions #44/#45 — a timeout that fires after
the CNC already accepted the write would otherwise risk a duplicate
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).
### Status-code semantics post-F4-a
- `BadNotWritable` — driver-level `Writes.Enabled = false`, OR per-tag
`Writable = false`. Two distinct paths, same status code.
- `BadNotSupported` — both opt-ins flipped on, but the wire client doesn't
yet implement the kind being written. F4-a wires the dispatch surface;
F4-b/c land the actual macro / parameter / PMC writes for unimplemented
kinds, replacing those `BadNotSupported` responses with real wire calls.
- `BadNodeIdUnknown` — full-reference doesn't match any configured
`FocasTagDefinition.Name`.
- `BadCommunicationError` — wire failure (DLL not loaded, IPC peer dead,
etc.).
### CLI bypass
`otopcua-focas-cli write` ([`docs/Driver.FOCAS.Cli.md`](../Driver.FOCAS.Cli.md))
sets `Writes.Enabled=true` locally for the lifetime of one invocation
because the CLI is a per-operator tool — not a long-lived process bound to
the central config DB. The server-side flag is untouched; configure-the-
server code paths remain safer-by-default.