docs(focas): Status/ flags, PMC coalescing, parts count via PARAM:6711, read-only policy (#257 #266 #258)
Claude-Session: https://claude.ai/code/session_01Ef2ozxS7QPanGT8uoATbTe
This commit is contained in:
+74
-3
@@ -146,7 +146,7 @@ reference them will throw at startup with a message pointing here.
|
||||
| Capability | Wire path | Notes |
|
||||
|------------|-----------|-------|
|
||||
| `IReadable` | `ReadAsync` → `cnc_rdpmcrng` / `cnc_rdparam` / `cnc_rdmacro` | One TCP request/response per read; `Focas.Wire` serializes requests on socket 2 internally |
|
||||
| `IWritable` | returns `BadNotWritable` | OtOpcUa is read-only against FOCAS by design — no `cnc_wrparam` / `pmc_wrpmcrng` / `cnc_wrmacro` path is implemented |
|
||||
| `IWritable` | returns `BadNotWritable` | OtOpcUa is read-only against FOCAS by design — no `cnc_wrparam` / `pmc_wrpmcrng` / `cnc_wrmacro` path is implemented, and none will be (policy: the stranded write/unlock backlog items #268–#271 are won't-do; a write path on a production CNC is a safety decision, not a feature gap) |
|
||||
| `ITagDiscovery` | `DiscoverAsync` | Emits `FOCAS/{device}/{tag}` folders per configured device |
|
||||
| `ISubscribable` | polled via shared `PollGroupEngine` | FOCAS has no push model — subscriptions turn into per-tag polling groups |
|
||||
| `IHostConnectivityProbe` | periodic `cnc_rdcncstat` | Probe cadence is `Probe.Interval`; transitions fire `OnHostStatusChanged` |
|
||||
@@ -224,15 +224,36 @@ What gets populated (all under `FOCAS/{deviceHostAddress}/`, addressed as
|
||||
| `Spindle/{name}/` | `Load` (Float64, % of rated load), `MotorSpeedPct` (Float64, % of maximum speed) — one folder per discovered spindle | `cnc_rdspdlname` once + `cnc_rdspmeter` type 0 + type 1 per tick |
|
||||
| `Program/` | `Name` (filename), `ONumber`, `Number`, `MainNumber`, `Sequence`, `BlockCount` — a field the CNC did not report (refused sub-read, payload without an O-number) reads `BadNoData`, never `0` | `cnc_exeprgname2` + `cnc_rdblkcount` + cached `cnc_rddynamic2` |
|
||||
| `OperationMode/` | `Mode` (int), `ModeText` ("AUTO", "MDI", "EDIT", …) | `cnc_rdopmode` |
|
||||
| `Status/` | `Auto`, `Run`, `Motion`, `Mstb`, `Emergency`, `Alarm`, `Edit`, `TmMode` (raw FANUC codes, Int32) + `RunText` ("RESET", "STOP", "HOLD", "START", "MSTR") — see [CNC status flags](#cnc-status-flags-status) | `cnc_statinfo` (polled on `ProgramPollInterval`) |
|
||||
| `Timers/` | `PowerOnSeconds`, `OperatingSeconds`, `CuttingSeconds`, `CycleSeconds` | `cnc_rdtimer` × 4 |
|
||||
|
||||
### CNC status flags (`Status/`)
|
||||
|
||||
`cnc_statinfo` (the `ODBST` block) is exposed as read-only `FIXED:Status/<field>` nodes (#257). Values are the
|
||||
control's raw codes, published verbatim as Int32 so a client sees exactly what the CNC reported:
|
||||
|
||||
| Field | Codes |
|
||||
|---|---|
|
||||
| `Run` | 0 RESET, 1 STOP, 2 HOLD, 3 START, 4 MSTR (`RunText` is the label) |
|
||||
| `Auto` | operation mode (0 MDI, 1 MEM, 3 EDIT, 4 HND, 5 JOG, … — control-dependent) |
|
||||
| `Motion` | 0 none, 1 axis motion, 2 dwell |
|
||||
| `Mstb` | 0 idle, 1 executing an M/S/T/B function |
|
||||
| `Emergency` | 0 not, 1 emergency stop, 2 reset |
|
||||
| `Alarm` | 0 none, 1 alarm, … (battery-low etc. are control-dependent) |
|
||||
| `Edit`, `TmMode` | program-edit state; T/M mode |
|
||||
|
||||
Author one the same way as any fixed-tree tag — `FIXED:Status/Run` (Int32), `FIXED:Status/RunText` (String).
|
||||
Like `Program/`, the subtree is gated on a bootstrap probe: a control that refuses `cnc_statinfo` gets no
|
||||
`Status/` nodes and the rest of the tree is unaffected. Reads are served from the poll snapshot, so they
|
||||
issue no wire traffic and go `BadCommunicationError` once the device has stopped answering.
|
||||
|
||||
### Per-series node suppression
|
||||
|
||||
The driver probes each optional call once at bootstrap. If the target CNC
|
||||
returns `EW_FUNC` / `EW_NOOPT` / `EW_VERSION` on the wire, the
|
||||
corresponding subtree is **not emitted** — the operator doesn't see nodes
|
||||
that will only ever return `BadDeviceFailure`. Capability suppression
|
||||
covers `Spindle/`, `Program/` + `OperationMode/`, `Timers/`, and
|
||||
covers `Spindle/`, `Program/` + `OperationMode/`, `Status/`, `Timers/`, and
|
||||
per-axis `ServoLoad` independently. Identity + `Axes/*` position reads
|
||||
(which every Fanuc CNC supports) are always emitted.
|
||||
|
||||
@@ -265,6 +286,54 @@ fallen back to `PositionDecimalPlaces` and published raw counts.
|
||||
numbers whose meaning is MTB-specific, and which no browse can discover. Those
|
||||
go under the device folder alongside the fixed subtree.
|
||||
|
||||
### Parts count (`PARAM:6711`)
|
||||
|
||||
There is deliberately **no** dedicated parts-count node (#258): the count is an ordinary CNC parameter, so it
|
||||
is already readable as a hand-authored `PARAM:` tag (Int32), and a fixed `Parts/` subtree would hard-code
|
||||
parameter numbers that are control- and MTB-specific. On the standard FANUC 0i / 16i / 30i parameter map:
|
||||
|
||||
| Address | Parameter | Meaning |
|
||||
|---|---|---|
|
||||
| `PARAM:6711` | No. 6711 | Number of parts machined (resets with the operator's clear) |
|
||||
| `PARAM:6712` | No. 6712 | Total number of parts machined (cumulative) |
|
||||
| `PARAM:6713` | No. 6713 | Number of parts required (the target) |
|
||||
|
||||
The count-up trigger (which M code or cycle end increments it) is set by No. 6710 and bit 0 of No. 6700,
|
||||
so confirm in the machine's parameter manual that the counter is actually enabled and that 6711–6713 mean
|
||||
what the MTB says they mean before relying on them. Cycle time per part is not provided; the `Timers/`
|
||||
subtree exposes `CycleSeconds` and a client can difference it across a `PARAM:6711` change.
|
||||
|
||||
## PMC range coalescing
|
||||
|
||||
A poll batch that reads several nearby PMC tags used to issue one `pmc_rdpmcrng` per tag. The driver now
|
||||
merges tags of the same PMC letter and width that sit close together into **one** range read and slices each
|
||||
tag's value out of the result (#266). Single, isolated PMC tags — the common "one tag per area" case — are
|
||||
read exactly as before.
|
||||
|
||||
```jsonc
|
||||
"PmcCoalesce": {
|
||||
"Enabled": true, // default
|
||||
"MaxSpanBytes": 128, // hard cap on one merged range
|
||||
"MaxGapBytes": 8 // bytes nobody asked for tolerated between two merged tags; 0 = only strictly adjacent
|
||||
}
|
||||
```
|
||||
|
||||
Rules worth knowing:
|
||||
|
||||
- Tags merge only within the same letter and width class (Bit and Byte are one class; Word, Long, Float, Double
|
||||
are separate). A multi-byte tag joins a range only when its offset from the range start is a multiple of its
|
||||
width, because the CNC splits a range into width-sized slots from the start.
|
||||
- **Spare gap bytes must be valid PMC addresses.** A gap that crosses a hole in the CNC's PMC map makes the CNC
|
||||
refuse the whole range. That is handled, not fatal: a refused range is remembered per device and its tags are
|
||||
read **individually**, each with its own true status (a refusal is never stamped onto healthy members). If
|
||||
that happens routinely, lower `MaxGapBytes` (0 disables gap-spanning entirely) or set `Enabled` to `false`.
|
||||
- A transport failure or timeout on the merged read fails every member with `BadCommunicationError` and does
|
||||
**not** retry per tag — the link is down, so per-tag retries would only multiply timeouts.
|
||||
- Read-only: this changes `pmc_rdpmcrng` reads only; nothing here can write.
|
||||
|
||||
The config key lives in the driver `Config` JSON. The AdminUI typed form does not yet surface it (an unknown
|
||||
key edited through that form is dropped), so set it in the raw config.
|
||||
|
||||
## Alarm projection
|
||||
|
||||
Alarm surfacing is **disabled by default** because the polling cost is wasted
|
||||
@@ -324,7 +393,9 @@ latency spike once per cadence.
|
||||
driver surface via `FakeFocasClient`. Includes the alarm-projection raise /
|
||||
clear diffing tests.
|
||||
- **Integration tests** — `tests/Drivers/ZB.MOM.WW.OtOpcUa.Driver.FOCAS.IntegrationTests/`
|
||||
hold the Docker simulator scaffold; see
|
||||
hold the Docker simulator scaffold (`OTOPCUA_FOCAS_SIM_ENDPOINT=host:port`; they skip when it is absent;
|
||||
`Series/WireBackendStatusAndPmcTests` covers `Status/` and PMC coalescing against the mock's `statinfo` and
|
||||
`pmc` state); see
|
||||
[`docs/v2/implementation/focas-wire-protocol.md`](../v2/implementation/focas-wire-protocol.md)
|
||||
for what the simulator emits vs. real CNC behaviour.
|
||||
- **E2E script** — `scripts/e2e/test-focas.ps1` stages Host + Proxy + a real
|
||||
|
||||
Reference in New Issue
Block a user