@@ -54,7 +54,7 @@ giant request. Typical FANUC ring buffers cap at ~100 entries; the default
|
||||
surfacing the FWLIB struct fields directly into
|
||||
`FocasAlarmHistoryEntry`.
|
||||
|
||||
## Writes (opt-in, off by default) — issue #268 (F4-a) + #269 (F4-b)
|
||||
## Writes (opt-in, off by default) — issue #268 (F4-a) + #269 (F4-b) + #270 (F4-c)
|
||||
|
||||
Writes ship behind multiple independent opt-ins. All default off so a freshly
|
||||
deployed FOCAS driver is read-only until the deployment makes a deliberate
|
||||
@@ -66,22 +66,54 @@ choice. Decision record: [`docs/v2/decisions.md`](../v2/decisions.md) →
|
||||
| `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. |
|
||||
| **`FocasDriverOptions.Writes.AllowParameter`** *(F4-b granular kill switch)* | **`false`** | **`PARAM:` writes return `BadNotWritable` with no wire client constructed. Defense in depth — even if `Enabled = true` an operator must explicitly opt into parameter writes per kind because a misdirected `cnc_wrparam` can put the CNC in a bad state.** |
|
||||
| **`FocasDriverOptions.Writes.AllowMacro`** *(F4-b granular kill switch)* | **`false`** | **`MACRO:` writes return `BadNotWritable` with no wire client constructed. Macro writes are the normal HMI-driven recipe / setpoint surface; gating them separately from `AllowParameter` lets a deployment open MACRO without exposing the heavier PARAM write surface.** |
|
||||
| **`FocasDriverOptions.Writes.AllowPmc`** *(F4-c granular kill switch)* | **`false`** | **PMC writes (R/G/F/D/X/Y/K/A/E/T/C letters, both Bit and Byte) return `BadNotWritable` with no wire client constructed. PMC is ladder working memory — a mistargeted bit can move motion, latch a feedhold, or flip a safety interlock, so PMC writes are gated separately from PARAM/MACRO so an operator team can open PARAM (commissioning) without exposing the much higher-blast-radius PMC surface.** |
|
||||
| `FocasTagDefinition.Writable` *(per-tag opt-in)* | `false` | The per-tag check returns `BadNotWritable` for that tag even when the driver-level flags are on. |
|
||||
|
||||
### Config shape — F4-b
|
||||
> **PMC SAFETY CALLOUT** — PMC is the FANUC ladder's working memory. A
|
||||
> mistargeted bit can move motion (a Y-coil writing to a servo enable),
|
||||
> latch a feedhold (an internal R-relay the ladder ANDs with cycle-start),
|
||||
> or flip a safety interlock (an X-input shadow). **Treat PMC writes the
|
||||
> same way you'd treat editing a live ladder:** verify e-stop is live and
|
||||
> the machine is in jog mode before issuing the first write of a session.
|
||||
> The driver gates these writes behind THREE independent opt-ins
|
||||
> (`Writes.Enabled` + `Writes.AllowPmc` + per-tag `Writable`) precisely
|
||||
> because the blast radius is higher than parameter writes.
|
||||
|
||||
### PMC bit-write read-modify-write semantics — F4-c
|
||||
|
||||
The FOCAS wire call `pmc_wrpmcrng` is **byte-addressed** — there is no
|
||||
sub-byte write primitive. When the driver receives a write request on a
|
||||
`Bit` tag (e.g. `R100.3`), it:
|
||||
|
||||
1. Reads the parent byte via `pmc_rdpmcrng` (1 byte at `R100`).
|
||||
2. Masks the target bit (set: `current | (1 << bit)`; clear: `current & ~(1 << bit)`).
|
||||
3. Writes the modified byte back via `pmc_wrpmcrng` (1 byte at `R100`).
|
||||
|
||||
A **per-byte semaphore** serialises concurrent bit writes against the same
|
||||
byte so two updates that race never lose one another's bit. RMW means **a
|
||||
PMC bit write reads first, then writes back the whole byte** — if the ladder
|
||||
is also writing to that byte at the same instant, there is a small window
|
||||
where the driver's value can clobber the ladder's. Operators who care about
|
||||
this race must coordinate the write through a ladder-side handshake (e.g.
|
||||
the operator sets a request bit, the ladder reads + clears it).
|
||||
|
||||
### Config shape — F4-c
|
||||
|
||||
```jsonc
|
||||
{
|
||||
"Writes": {
|
||||
"Enabled": true,
|
||||
"AllowParameter": true, // F4-b — opt into cnc_wrparam
|
||||
"AllowMacro": true // F4-b — opt into cnc_wrmacro
|
||||
"AllowMacro": true, // F4-b — opt into cnc_wrmacro
|
||||
"AllowPmc": true // F4-c — opt into pmc_wrpmcrng (incl. RMW bit writes)
|
||||
},
|
||||
"Tags": [
|
||||
{ "Name": "RPM", "Address": "PARAM:1815", "DataType": "Int32",
|
||||
"Writable": true, "WriteIdempotent": false },
|
||||
{ "Name": "Recipe", "Address": "MACRO:500", "DataType": "Int32",
|
||||
"Writable": true, "WriteIdempotent": false }
|
||||
"Writable": true, "WriteIdempotent": false },
|
||||
{ "Name": "StartFlag", "Address": "R100.3", "DataType": "Bit",
|
||||
"Writable": true, "WriteIdempotent": true }
|
||||
]
|
||||
}
|
||||
```
|
||||
@@ -119,9 +151,9 @@ value that the operator simply wants forced to a target).
|
||||
|
||||
- `BadNotWritable` — one of: driver-level `Writes.Enabled = false`; per-tag
|
||||
`Writable = false`; **`Writes.AllowParameter = false` for a `PARAM:` tag
|
||||
(F4-b)**; **`Writes.AllowMacro = false` for a `MACRO:` tag (F4-b)**. Same
|
||||
status code, four distinct paths — operators distinguish by checking the
|
||||
knobs.
|
||||
(F4-b)**; **`Writes.AllowMacro = false` for a `MACRO:` tag (F4-b)**;
|
||||
**`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
|
||||
|
||||
Reference in New Issue
Block a user