Files
lmxopcua/docs/drivers/FOCAS-Test-Fixture.md
Joseph Doherty 7f9d6a778e Auto: focas-f3a — cnc_rdalmhistry alarm-history extension
Adds FocasAlarmProjection with two modes (ActiveOnly default, ActivePlusHistory)
that polls cnc_rdalmhistry on connect + on a configurable cadence (5 min default,
HistoryDepth=100 capped at 250). Emits historic events via IAlarmSource with
SourceTimestampUtc set from the CNC's reported timestamp; dedup keyed on
(OccurrenceTime, AlarmNumber, AlarmType). Ships the ODBALMHIS packed-buffer
decoder + encoder in Wire/FocasAlarmHistoryDecoder.cs and threads
ReadAlarmHistoryAsync through IFocasClient (default no-op so existing transport
variants stay back-compat). FocasDriver now implements IAlarmSource.

13 new unit tests cover: mode switch, dedup, distinct-timestamp emission,
type-as-key behaviour, OccurrenceTime passthrough (not Now), HistoryDepth
clamp/fallback, and decoder round-trip. All 341 FOCAS unit tests still pass.

Docs: docs/drivers/FOCAS.md (new), docs/v2/focas-deployment.md (new),
docs/v2/implementation/focas-wire-protocol.md (new),
docs/v2/implementation/focas-simulator-plan.md (new),
docs/drivers/FOCAS-Test-Fixture.md (alarm-history bullet appended).

Closes #267
2026-04-26 00:07:59 -04:00

7.9 KiB
Raw Permalink Blame History

FOCAS test fixture

Coverage map + gap inventory for the FANUC FOCAS2 CNC driver.

TL;DR: there is no integration fixture. Every test uses a FakeFocasClient injected via IFocasClientFactory. Fanuc's FOCAS library (Fwlib32.dll) is closed-source proprietary with no public simulator; CNC-side behavior is trusted from field deployments.

What the fixture is

Nothing at the integration layer. tests/ZB.MOM.WW.OtOpcUa.Driver.FOCAS.Tests/ is unit-only. The driver ships as Tier C (process-isolated) per docs/v2/driver-stability.md because the FANUC DLL has known crash modes; tests can't replicate those in-process.

What it actually covers (unit only)

  • FocasCapabilityTests — data-type mapping (PMC bit / word / float, macro variable types, parameter types)
  • FocasCapabilityMatrixTests — per-CNC-series range validation (macro / parameter / PMC letter + number) across 16i / 0i-D / 0i-F / 30i / PowerMotion. See docs/v2/focas-version-matrix.md for the authoritative matrix. 46 theory cases lock every documented range boundary — widening a range without updating the doc fails a test.
  • FocasReadWriteTests — read + write against the fake, FOCAS native status → OPC UA StatusCode mapping
  • FocasScaffoldingTestsIDriver lifecycle + multi-device routing
  • FocasPmcBitRmwTests — PMC bit read-modify-write synchronization (per-byte SemaphoreSlim, mirrors the AB / Modbus pattern from #181)
  • FwlibNativeHelperTestsFocas32.dllFwlib32.dll bridge validation
    • P/Invoke signature validation

Capability surfaces whose contract is verified: IDriver, IReadable, IWritable, ITagDiscovery, ISubscribable, IHostConnectivityProbe, IPerCallHostResolver.

Pre-flight validation runs in FocasDriver.InitializeAsync — configs referencing out-of-range addresses fail at load time with a diagnostic message naming the CNC series + documented limit. This closes the cheap half of the hardware-free stability gap; Tier-C process isolation (task #220) closes the expensive half — see docs/v2/implementation/focas-isolation-plan.md.

What it does NOT cover

1. FOCAS wire traffic

No FOCAS TCP frame is sent. Fwlib32.dll's TCP-to-FANUC-gateway exchange is closed-source; the driver trusts the P/Invoke layer per #193. Real CNC correctness is trusted from field deployments.

2. Alarm / parameter-change callbacks

FOCAS has no push model — the driver polls via the shared PollGroupEngine. There are no CNC-initiated callbacks to test; the absence is by design.

3. Macro / ladder variable types

FANUC has CNC-specific extensions (macro variables #100-#999, system variables #1000-#5000, PMC timers / counters / keep-relays) whose per-address semantics differ across 0i-F / 30i / 31i / 32i Series. Driver covers the common address shapes; per-model quirks are not stressed.

4. Model-specific behavior

  • Alarm retention across power cycles (model-specific CNC behavior)
  • Parameter range enforcement (CNC rejects out-of-range writes)
  • MTB (machine tool builder) custom screens that expose non-standard data

5. Tier-C process isolation — architecture shipped, Fwlib32 integration hardware-gated

The Tier-C architecture is now in place as of PRs #169#173 (FOCAS PR AE, task #220):

  • Driver.FOCAS.Shared carries MessagePack IPC contracts
  • Driver.FOCAS.Host (.NET 4.8 x86 Windows service via NSSM) accepts a connection on a strictly-ACL'd named pipe + dispatches frames to an IFocasBackend
  • Driver.FOCAS.Ipc.IpcFocasClient implements the IFocasClient DI seam by forwarding over IPC — swap the DI registration and the driver runs Tier-C with zero other changes
  • Driver.FOCAS.Supervisor.FocasHostSupervisor owns the spawn + heartbeat + respawn + 3-in-5min crash-loop breaker + sticky alert
  • Driver.FOCAS.Host.Stability.PostMortemMmfDriver.FOCAS.Supervisor.PostMortemReader — ring-buffer of the last ~1000 IPC operations survives a Host crash

The one remaining gap is the production FwlibHostedBackend: an IFocasBackend implementation that wraps the licensed Fwlib32.dll P/Invoke. That's hardware-gated on task #222 — we need a CNC on the bench (or the licensed FANUC developer kit DLL with a test harness) to validate it. Until then, the Host ships FakeFocasBackend + UnconfiguredFocasBackend. Setting OTOPCUA_FOCAS_BACKEND=fake lets operators smoke-test the whole Tier-C pipeline end-to-end without any CNC.

When to trust FOCAS tests, when to reach for a rig

Question Unit tests Real CNC
"Does PMC address R100.3 route to the right bit?" yes yes
"Does the FANUC status → OPC UA StatusCode map cover every documented code?" yes (contract) yes
"Does a real read against a 30i Series return correct bytes?" no yes (required)
"Does Fwlib32.dll crash on concurrent reads?" no yes (stress)
"Do macro variables round-trip across power cycles?" no yes (required)

Alarm history (cnc_rdalmhistry) — issue #267, plan PR F3-a

FocasAlarmProjection ships two modes:

  • ActiveOnly (default) — surfaces only currently-active alarms. No history poll. Same back-compat shape every prior FOCAS deployment used.
  • ActivePlusHistory — additionally polls cnc_rdalmhistry on connect
    • on the configured cadence (HistoryPollInterval, default 5 min). Each unseen entry fires an OnAlarmEvent with SourceTimestampUtc set from the CNC's reported timestamp, not Now.

Unit-test coverage in FocasAlarmProjectionTests:

  • mode ActiveOnly — no ReadAlarmHistoryAsync call ever issued
  • mode ActivePlusHistory — first poll fires on subscribe (== "on connect")
  • dedup — same (OccurrenceTime, AlarmNumber, AlarmType) triple across two polls only emits once
  • distinct entries with different timestamps each emit separately
  • same alarm number / different type still emits both (type is part of the dedup key)
  • OccurrenceTime is the wire timestamp (round-trips a year-old stamp without bleeding into Now)
  • HistoryDepth clamp — user-supplied 500 collapses to 250 on the wire; zero / negative falls back to the 100 default
  • FocasAlarmHistoryDecoder — round-trips through Encode / Decode and pins the simulator command id at 0x0F1A

Future integration coverage (not yet shipped — no FOCAS integration test project exists):

  • a focas-mock with a per-profile ring buffer and mock_patch_alarmhistory admin endpoint will let cnc_rdalmhistry round-trip end-to-end through the wire protocol
  • FocasSimFixture.SeedAlarmHistoryAsync will let series tests prime canned history without per-test JSON

Follow-up candidates

  1. Nothing public — Fanuc's FOCAS Developer Kit ships an emulator DLL but it's under NDA + tied to licensed dev-kit installations; can't redistribute for CI.
  2. Lab rig — used FANUC 0i-F simulator controller (or a retired machine tool) on a dedicated network; only path that covers real CNC behavior.
  3. Process isolation first — before trusting FOCAS in production at scale, shipping the Tier-C out-of-process Host architecture (similar to Galaxy) is higher value than a CI simulator.

Key fixture / config files

  • tests/ZB.MOM.WW.OtOpcUa.Driver.FOCAS.Tests/FakeFocasClient.cs — in-process fake implementing IFocasClient
  • tests/ZB.MOM.WW.OtOpcUa.Driver.FOCAS.Tests/FocasCapabilityMatrixTests.cs — parameterized theories locking the per-series matrix
  • src/ZB.MOM.WW.OtOpcUa.Driver.FOCAS/FocasDriver.cs — ctor takes IFocasClientFactory
  • src/ZB.MOM.WW.OtOpcUa.Driver.FOCAS/FocasCapabilityMatrix.cs — per-CNC-series range validator (the matrix the doc describes)
  • docs/v2/focas-version-matrix.md — authoritative range reference
  • docs/v2/implementation/focas-isolation-plan.md — Tier-C isolation plan (task #220)
  • docs/v2/driver-stability.md — Tier C scope + process-isolation rationale