# FOCAS version / capability matrix Authoritative source for the per-CNC-series ranges that [`FocasCapabilityMatrix`](../../src/ZB.MOM.WW.OtOpcUa.Driver.FOCAS/FocasCapabilityMatrix.cs) enforces at driver init time. Every row cites the Fanuc FOCAS Developer Kit function whose documented input range determines the ceiling. **Why this exists** — we have no FOCAS hardware on the bench and no working simulator. Fwlib32 returns `EW_NUMBER` / `EW_PARAM` when you hand it an address outside the controller's supported range; the driver would map that to a per-read `BadOutOfRange` at steady state. Catching at `InitializeAsync` with this matrix surfaces operator typos + mismatched series declarations as config errors before any session is opened, which is the only feedback loop available without a live CNC to read against. **Who declares the series** — `FocasDeviceOptions.Series` in `appsettings.json`. Defaults to `Unknown`, which is permissive — every address passes validation. Pre-matrix configs don't break on upgrade. --- ## Series covered | Enum value | Controller family | Typical era | | --- | --- | --- | | `Unknown` | (legacy / not declared) | permissive fallback | | `Sixteen_i` | 16i / 18i / 21i | 1997-2008 | | `Zero_i_D` | 0i-D | 2008-2013 | | `Zero_i_F` | 0i-F | 2013-present, general-purpose | | `Zero_i_MF` | 0i-MF | 0i-F lathe variant | | `Zero_i_TF` | 0i-TF | 0i-F turning variant | | `Thirty_i` | 30i-A / 30i-B | 2007-present, high-end | | `ThirtyOne_i` | 31i-A / 31i-B | 30i simpler variant | | `ThirtyTwo_i` | 32i-A / 32i-B | 30i compact | | `PowerMotion_i` | Power Motion i-A / i-MODEL A | motion-only controller | ## Macro variable range (`cnc_rdmacro` / `cnc_wrmacro`) Common macros `1-33` + `100-199` + `500-999` are universal across all series. Extended macros (`#10000+`) exist only on higher-end series. The numbers below reflect the extended ceiling per series per the DevKit range tables. | Series | Min | Max | Notes | | --- | ---: | ---: | --- | | `Sixteen_i` | 0 | 999 | legacy ceiling — no extended | | `Zero_i_D` | 0 | 999 | 0i-D still at legacy ceiling | | `Zero_i_F` / `Zero_i_MF` / `Zero_i_TF` | 0 | 9999 | extended added on 0i-F | | `Thirty_i` / `ThirtyOne_i` / `ThirtyTwo_i` | 0 | 99999 | full extended set | | `PowerMotion_i` | 0 | 999 | atypical — limited macro coverage | ## Parameter range (`cnc_rdparam` / `cnc_wrparam`) | Series | Min | Max | | --- | ---: | ---: | | `Sixteen_i` | 0 | 9999 | | `Zero_i_D` / `Zero_i_F` / `Zero_i_MF` / `Zero_i_TF` | 0 | 14999 | | `Thirty_i` / `ThirtyOne_i` / `ThirtyTwo_i` | 0 | 29999 | | `PowerMotion_i` | 0 | 29999 | ## PMC letters (`pmc_rdpmcrng` / `pmc_wrpmcrng`) Addresses are letter + number (e.g. `R100`, `F50.3`). Legacy controllers omit the `F`/`G` signal groups that 30i-family ladder programs use, and only the 30i-family exposes `K` (keep-relay) + `T` (timer). | Letter | 16i | 0i-D | 0i-F family | 30i family | Power Motion-i | | --- | :-: | :-: | :-: | :-: | :-: | | `X` | yes | yes | yes | yes | yes | | `Y` | yes | yes | yes | yes | yes | | `R` | yes | yes | yes | yes | yes | | `D` | yes | yes | yes | yes | yes | | `E` | — | yes | yes | yes | — | | `A` | — | yes | yes | yes | — | | `F` | — | — | yes | yes | — | | `G` | — | — | yes | yes | — | | `M` | — | — | yes | yes | — | | `C` | — | — | yes | yes | — | | `K` | — | — | — | yes | — | | `T` | — | — | — | yes | — | Letter match is case-insensitive. `FocasAddress.PmcLetter` is carried as a string (not char) so the matrix can do ordinal-ignore-case comparison. ## PMC address-number ceiling PMC addresses are byte-addressed on read + bit-addressed on write; `FocasAddress` carries the bit index separately, so these are byte ceilings. | Series | Max byte | Notes | | --- | ---: | --- | | `Sixteen_i` | 999 | legacy | | `Zero_i_D` | 1999 | doubled since 16i | | `Zero_i_F` family | 9999 | | | `Thirty_i` family | 59999 | highest density | | `PowerMotion_i` | 1999 | | ## Error surface When a tag fails validation, `FocasDriver.InitializeAsync` throws `InvalidOperationException` with a message of the form: ``` FOCAS tag '' (
) rejected by capability matrix: ``` `` is the verbatim string from `FocasCapabilityMatrix.Validate` and always names the series + the documented limit so the operator can either raise the limit (if wrong) or correct the CNC series they declared (if mismatched). Sample: ``` FOCAS tag 'X_axis_macro_ext' (MACRO:50000) rejected by capability matrix: Macro variable #50000 is outside the documented range [0, 9999] for Zero_i_F. ``` ## How this matrix stays honest - Every row is covered by a parameterized test in [`FocasCapabilityMatrixTests.cs`](../../tests/ZB.MOM.WW.OtOpcUa.Driver.FOCAS.Tests/FocasCapabilityMatrixTests.cs) — 46 cases across macro / parameter / PMC-letter / PMC-number boundaries + unknown-series permissiveness + rejection-message content + case-insensitivity. - Widening or narrowing a range in the matrix without updating this doc will fail a test, because the theories cite the specific row they reflect in their `InlineData`. - The matrix is not comprehensive — it encodes only the subset of FOCAS surface the driver currently exposes (Macro / Parameter / PMC). When the driver gains a new capability (e.g. tool management, alarm history), add its series-specific range tables here + matching tests at the same time. ## Follow-up This validation closes the cheap half of the FOCAS hardware-free stability gap — config errors now fail at load instead of per-read. The expensive half is Tier-C process isolation so that a crashing `Fwlib32.dll` doesn't take the main OPC UA server down with it. See [`docs/v2/implementation/focas-isolation-plan.md`](implementation/focas-isolation-plan.md) for that plan (task #220).