STALE-STATUS (Snap7ServerFixture.cs:40):
- TL;DR + "What the fixture is": localhost:1102 → 10.100.0.35:1102 (shared
Docker host migrated 2026-04-28; fixture already defaults to 10.100.0.35)
CODE-REALITY (S7_1500SmokeTests.cs exists and sends real S7comm):
- "What it does NOT cover" §1 ("No ISO-on-TCP frame is ever sent") was
simply wrong — the integration suite DOES send real S7comm. Rewritten
to clarify that the unit suite uses IS7Client fakes while the integration
suite exercises the full wire path.
- "What it does NOT cover" §2 ("successful read not tested end-to-end")
was also wrong — Driver_reads_seeded_u16_through_real_S7comm does exactly
that. Rewritten to scope the error-branch-only claim to unit tests.
- "When to trust" table: added Integration (python-snap7) column reflecting
what the existing S7_1500SmokeTests actually answer.
- "Follow-up candidates" §1: removed the suggestion to build a Snap7 server
fixture — python-snap7 fixture (task #216) already ships. Follow-ups now
correctly list Plcsim Advanced and real lab rig only.
INLINE COMPLETENESS:
- "Key fixture / config files": was missing all integration test artefacts.
Added Snap7ServerFixture.cs, S7_1500SmokeTests.cs, Docker/docker-compose.yml,
and Docker/profiles/s7_1500.json with descriptions matching file contents.
STRUCTURAL: no broken links in links-report.md for this doc.
VERIFY: check_links.py — 0 rows for S7-Test-Fixture.md.
Drivers
OtOpcUa is a multi-driver OPC UA server. The Core (ZB.MOM.WW.OtOpcUa.Core + Core.Abstractions + Server) owns the OPC UA stack, address space, session/security/subscription machinery, resilience pipeline, and namespace kinds (Equipment + SystemPlatform). Drivers plug in through capability interfaces defined in src/Core/ZB.MOM.WW.OtOpcUa.Core.Abstractions/:
IDriver— lifecycle (InitializeAsync,ReinitializeAsync,ShutdownAsync,GetHealth)IReadable/IWritable— one-shot reads and writesITagDiscovery— address-space enumerationISubscribable— driver-pushed data-change streamsIHostConnectivityProbe— per-host reachability eventsIPerCallHostResolver— multi-host drivers that route each call to a target endpoint at dispatch timeIAlarmSource— driver-emitted OPC UA A&C eventsIHistoryProvider— driver-side raw / processed / at-time / events HistoryRead (see HistoricalDataAccess.md)IRediscoverable— driver-initiated address-space rebuild notificationsIHistorianDataSource— server-side historian sink registration (the Wonderware Historian backend), distinct from the driver-sideIHistoryProviderHistoryRead path
Each driver opts into only the capabilities it supports. Every async capability call at the Server dispatch layer goes through CapabilityInvoker (Core/Resilience/CapabilityInvoker.cs), which wraps it in a Polly pipeline keyed on (DriverInstanceId, HostName, DriverCapability). The OTOPCUA0001 analyzer enforces the wrap at build time. Drivers themselves never depend on Polly; they just implement the capability interface and let the Core wrap it.
Driver type metadata is registered at startup in DriverTypeRegistry (src/Core/ZB.MOM.WW.OtOpcUa.Core.Abstractions/DriverTypeRegistry.cs). The registry records each type's allowed namespace kinds (Equipment / SystemPlatform / Simulated), its JSON Schema for DriverConfig / DeviceConfig / TagConfig columns, and its stability tier per docs/v2/driver-stability.md.
Ground-truth driver list
| Driver | Project path | Tier | Wire / library | Capabilities | Notable quirk |
|---|---|---|---|---|---|
| Galaxy | Driver.Galaxy (+ .Browser, .Contracts) |
C | gRPC to the external mxaccessgw gateway (the gateway owns MXAccess COM + the Galaxy Repository SQL reader) |
IDriver, ITagDiscovery, IReadable, IWritable, ISubscribable, IAlarmSource, IRediscoverable, IHostConnectivityProbe | In-process .NET 10 driver — the COM bitness constraint lives in the gateway's x86 net48 worker, not here. PR 7.2 retired the legacy in-process Galaxy.{Shared, Host, Proxy} + named-pipe Windows service. Native MxAccess alarms work end-to-end |
| Modbus TCP | Driver.Modbus |
A | NModbus-derived in-house client | IDriver, ITagDiscovery, IReadable, IWritable, ISubscribable, IHostConnectivityProbe, IPerCallHostResolver | Polled subscriptions via the shared PollGroupEngine. DL205 PLCs are covered by AddressFormat=DL205 (octal V/X/Y/C/T/CT translation) — no separate driver |
| Siemens S7 | Driver.S7 |
A | S7netplus | IDriver, ITagDiscovery, IReadable, IWritable, ISubscribable, IHostConnectivityProbe | Single S7netplus Plc instance per PLC serialized with SemaphoreSlim — the S7 CPU's comm mailbox is scanned at most once per cycle, so parallel reads don't help |
| AB CIP | Driver.AbCip |
A | libplctag CIP | IDriver, ITagDiscovery, IReadable, IWritable, ISubscribable, IHostConnectivityProbe, IPerCallHostResolver, IAlarmSource | ControlLogix / CompactLogix. Tag discovery uses the @tags walker to enumerate controller-scoped + program-scoped symbols; UDT member resolution via the UDT template reader |
| AB Legacy | Driver.AbLegacy |
A | libplctag PCCC | IDriver, ITagDiscovery, IReadable, IWritable, ISubscribable, IHostConnectivityProbe, IPerCallHostResolver | SLC 500 / MicroLogix. File-based addressing (N7:0, F8:0) — no symbol table, tag list is user-authored in the config DB |
| TwinCAT | Driver.TwinCAT |
B | Beckhoff TwinCAT.Ads (TcAdsClient) |
IDriver, ITagDiscovery, IReadable, IWritable, ISubscribable, IHostConnectivityProbe, IPerCallHostResolver | The only native-notification driver outside Galaxy — ADS delivers ValueChangedCallback events the driver forwards straight to ISubscribable.OnDataChange without polling. Symbol tree uploaded via SymbolLoaderFactory |
| FOCAS | Driver.FOCAS |
A | Pure-managed FocasWireClient — FOCAS/2 Ethernet binary protocol on TCP:8193, inlined into the driver assembly |
IDriver, ITagDiscovery, IReadable, IWritable, ISubscribable, IHostConnectivityProbe, IPerCallHostResolver, IAlarmSource | IWritable is implemented but read-only by design — WriteAsync returns BadNotWritable for every point. CNC-shaped data model (axes, spindle, PMC, macros, alarms) not a flat tag map. Previously Tier-C (Host + P/Invoke + shim DLL); retired in the 2026-04-24 migration when the managed wire client landed |
| OPC UA Client | Driver.OpcUaClient |
B | OPCFoundation Opc.Ua.Client |
IDriver, ITagDiscovery, IReadable, IWritable, ISubscribable, IAlarmSource, IHistoryProvider, IHostConnectivityProbe | Gateway/aggregation driver — the only driver implementing driver-side IHistoryProvider (forwards HistoryRead to the upstream server). Opens a single Session against a remote OPC UA server and re-exposes its address space. Owns its own ApplicationConfiguration (distinct from Client.Shared) because it's always-on with keep-alive + TransferSubscriptions across SDK reconnect, not an interactive CLI |
| Historian.Wonderware | Driver.Historian.Wonderware (+ .Client, .Client.Contracts) |
— | aahClientManaged write SDK + AVEVA Historian SQL, over a pipe IPC backend |
IHistorianDataSource (server-side historian sink) | Not a tag driver — a historian backend that registers IHistorianDataSource (HistorianDataSource : IHistorianDataSource) to satisfy HistoryRead and to sink tag/alarm history. No IDriver/ITagDiscovery surface |
Per-driver documentation
-
Galaxy has its own docs in this folder because the gRPC-to-gateway architecture + MXAccess rules (owned by the gateway) + Galaxy Repository SQL + Historian + runtime probe manager don't fit a single table row:
- Galaxy.md — gateway gRPC bridge, hierarchy source, runtime probes
- Galaxy-Repository.md — ZB SQL reader,
LocalPlatformscope filter, change detection (v1 archive)
-
FOCAS has a short getting-started doc because the backend-selection env var + alarm projection opt-in need explaining up front:
- FOCAS.md — deployment, config, capability surface, alarm projection, troubleshooting
-
All other drivers share a single per-driver specification in docs/v2/driver-specs.md — addressing, data-type maps, connection settings, and quirks live there. That file is the authoritative per-driver reference; this index points at it rather than duplicating.
Test-fixture coverage maps
Each driver has a dedicated fixture doc that lays out what the integration / unit harness actually covers vs. what's trusted from field deployments. Read the relevant one before claiming "green suite = production-ready" for a driver.
- AB CIP — Dockerized
ab_server(multi-stage build from libplctag source); atomic-read smoke across 4 families; UDT / ALMD / family quirks unit-only - Modbus — Dockerized
pymodbus+ per-family JSON profiles (4 compose profiles); best-covered driver, gaps are error-path-shaped - Siemens S7 — Dockerized
python-snap7server; DB/MB read + write round-trip verified end-to-end on:1102 - AB Legacy — Dockerized
ab_serverPCCC mode across SLC500 / MicroLogix / PLC-5 profiles (task #224); N/F/L-file round-trip verified end-to-end./1,0cip-path required for the Docker fixture; real hardware uses empty. Residual gap: bit-file writes (B3:0/5) still surface BadState — real HW / RSEmulate 500 for those - TwinCAT — XAR-VM integration scaffolding (task #221); three smoke tests skip when VM unreachable. Unit via
FakeTwinCATClientwith native-notification harness - FOCAS — no integration fixture, unit-only via
FakeFocasClient; Tier C out-of-process isolation scoped but not shipped - OPC UA Client — no integration fixture, unit-only via mocked
Session; loopback against this repo's own server is the obvious next step - Galaxy — richest harness: gateway E2E + ZB SQL live-smoke + MXAccess opt-in (v1 archive)
Related cross-driver docs
- HistoricalDataAccess.md —
IHistoryProviderdispatch, aggregate mapping, continuation points. The OPC UA Client driver is the only driver that implements driver-sideIHistoryProvider(it forwards HistoryRead to the upstream server); the Aveva Historian path is served server-side by the WonderwareIHistorianDataSourcesink instead. Other drivers do not implement the interface and returnBadHistoryOperationUnsupported. - AlarmTracking.md —
IAlarmSourceevent model and filtering. Implemented by Galaxy (native MxAccess alarms, working end-to-end), OPC UA Client, AB CIP, and FOCAS; AB Legacy, Modbus, S7, and TwinCAT have no alarm source. - Subscriptions.md — how the Server multiplexes subscriptions onto
ISubscribable.OnDataChange. - docs/v2/driver-stability.md — tier system (A / B / C), shared
CapabilityPolicydefaults per tier × capability,MemoryTrackinghybrid formula, and process-level recycle rules. - docs/v2/plan.md — authoritative vision, architecture decisions, migration strategy.