109 lines
4.9 KiB
Markdown
109 lines
4.9 KiB
Markdown
# FOCAS driver
|
|
|
|
Fanuc CNC driver for the FS 0i / 16i / 18i / 21i / 30i / 31i / 32i / 35i /
|
|
Power Mate i families. Talks to the controller via the licensed
|
|
`Fwlib32.dll` (Tier C, process-isolated per
|
|
[`docs/v2/driver-stability.md`](../v2/driver-stability.md)).
|
|
|
|
For range-validation and per-series capability surface see
|
|
[`docs/v2/focas-version-matrix.md`](../v2/focas-version-matrix.md).
|
|
|
|
## Alarm history (`cnc_rdalmhistry`) — issue #267, plan PR F3-a
|
|
|
|
`FocasAlarmProjection` exposes two modes via `FocasDriverOptions.AlarmProjection`:
|
|
|
|
| Mode | Behaviour |
|
|
| --- | --- |
|
|
| `ActiveOnly` *(default)* | Subscribe / unsubscribe / acknowledge wire up so capability negotiation works, but no history poll runs. Back-compat with every pre-F3-a deployment. |
|
|
| `ActivePlusHistory` | On subscribe (== "on connect") and on every `HistoryPollInterval` tick, the projection issues `cnc_rdalmhistry` for the most recent `HistoryDepth` entries. Each previously-unseen entry fires an `OnAlarmEvent` with `SourceTimestampUtc` set from the CNC's reported timestamp — OPC UA dashboards see the real occurrence time, not the moment the projection polled. |
|
|
|
|
### Config knobs
|
|
|
|
```jsonc
|
|
{
|
|
"AlarmProjection": {
|
|
"Mode": "ActivePlusHistory", // "ActiveOnly" (default) | "ActivePlusHistory"
|
|
"HistoryPollInterval": "00:05:00", // default 5 min
|
|
"HistoryDepth": 100 // default 100, capped at 250
|
|
}
|
|
}
|
|
```
|
|
|
|
### Dedup key
|
|
|
|
`(OccurrenceTime, AlarmNumber, AlarmType)`. The same triple across two
|
|
polls only emits once. The dedup set is in-memory and **resets on
|
|
reconnect** — first poll after reconnect re-emits everything in the ring
|
|
buffer. OPC UA clients that need exactly-once semantics dedupe client-side
|
|
on the same triple (the timestamp + type + number tuple is stable across
|
|
the boundary).
|
|
|
|
### `HistoryDepth` cap
|
|
|
|
Capped at `FocasAlarmProjectionOptions.MaxHistoryDepth = 250` so an
|
|
operator who types `10000` by accident can't blast the wire session with a
|
|
giant request. Typical FANUC ring buffers cap at ~100 entries; the default
|
|
`HistoryDepth = 100` matches the most common ring-buffer size.
|
|
|
|
### Wire surface
|
|
|
|
- Wire-protocol command id: `0x0F1A` (see
|
|
[`docs/v2/implementation/focas-wire-protocol.md`](../v2/implementation/focas-wire-protocol.md)).
|
|
- ODBALMHIS struct decoder: `Wire/FocasAlarmHistoryDecoder.cs`.
|
|
- 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.
|