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
7.9 KiB
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. Seedocs/v2/focas-version-matrix.mdfor 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 mappingFocasScaffoldingTests—IDriverlifecycle + multi-device routingFocasPmcBitRmwTests— PMC bit read-modify-write synchronization (per-byteSemaphoreSlim, mirrors the AB / Modbus pattern from #181)FwlibNativeHelperTests—Focas32.dll→Fwlib32.dllbridge 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 A–E, task #220):
Driver.FOCAS.Sharedcarries MessagePack IPC contractsDriver.FOCAS.Host(.NET 4.8 x86 Windows service via NSSM) accepts a connection on a strictly-ACL'd named pipe + dispatches frames to anIFocasBackendDriver.FOCAS.Ipc.IpcFocasClientimplements theIFocasClientDI seam by forwarding over IPC — swap the DI registration and the driver runs Tier-C with zero other changesDriver.FOCAS.Supervisor.FocasHostSupervisorowns the spawn + heartbeat + respawn + 3-in-5min crash-loop breaker + sticky alertDriver.FOCAS.Host.Stability.PostMortemMmf↔Driver.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 pollscnc_rdalmhistryon connect- on the configured cadence (
HistoryPollInterval, default 5 min). Each unseen entry fires anOnAlarmEventwithSourceTimestampUtcset from the CNC's reported timestamp, not Now.
- on the configured cadence (
Unit-test coverage in FocasAlarmProjectionTests:
- mode
ActiveOnly— noReadAlarmHistoryAsynccall 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)
OccurrenceTimeis the wire timestamp (round-trips a year-old stamp without bleeding into Now)HistoryDepthclamp — user-supplied 500 collapses to 250 on the wire; zero / negative falls back to the 100 defaultFocasAlarmHistoryDecoder— round-trips throughEncode/Decodeand pins the simulator command id at0x0F1A
Future integration coverage (not yet shipped — no FOCAS integration test project exists):
- a focas-mock with a per-profile ring buffer and
mock_patch_alarmhistoryadmin endpoint will letcnc_rdalmhistryround-trip end-to-end through the wire protocol FocasSimFixture.SeedAlarmHistoryAsyncwill let series tests prime canned history without per-test JSON
Follow-up candidates
- 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.
- Lab rig — used FANUC 0i-F simulator controller (or a retired machine tool) on a dedicated network; only path that covers real CNC behavior.
- 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 implementingIFocasClienttests/ZB.MOM.WW.OtOpcUa.Driver.FOCAS.Tests/FocasCapabilityMatrixTests.cs— parameterized theories locking the per-series matrixsrc/ZB.MOM.WW.OtOpcUa.Driver.FOCAS/FocasDriver.cs— ctor takesIFocasClientFactorysrc/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 referencedocs/v2/implementation/focas-isolation-plan.md— Tier-C isolation plan (task #220)docs/v2/driver-stability.md— Tier C scope + process-isolation rationale