New exception_injector.py — standalone pure-Python-stdlib Modbus/TCP server shipped alongside the pymodbus image. Speaks the wire protocol directly (MBAP header parse + FC 01/02/03/04/05/06/15/16 dispatch + store-backed happy-path reads/writes + spec-enforced length caps) and looks up each (fc, starting-address) against a rules list loaded from JSON; a matching rule makes the server respond [fc|0x80, exception_code] instead of the normal response. Zero runtime dependencies outside the stdlib — the Dockerfile just COPY's the script into /fixtures/ alongside the pymodbus profile JSONs, no new pip install needed. ~200 lines. New exception_injection.json profile carries rules for every exception code on FC03 (addresses 1000-1007, one per code), FC06 (2000-2001 for CPU-PROGRAM-mode and busy), and FC16 (3000 for server failure). New exception_injection compose profile binds :5020 like every other service + runs python /fixtures/exception_injector.py --config /fixtures/exception_injection.json. New ExceptionInjectionTests.cs in Modbus.IntegrationTests — 11 tests. Eight FC03-read theories exercise every exception code 0x01/0x02/0x03/0x04/0x05/0x06/0x0A/0x0B asserting the driver's expected OPC UA StatusCode mapping (BadNotSupported/BadOutOfRange/BadOutOfRange/BadDeviceFailure/BadDeviceFailure/BadDeviceFailure/BadCommunicationError/BadCommunicationError). Two FC06-write theories cover the write path for 0x04 (Server Failure, CPU in PROGRAM mode) + 0x06 (Server Busy). One sanity-check read at address 5 confirms the injector isn't globally broken + non-injected reads round-trip cleanly with Value=5/StatusCode=Good. All tests follow the MODBUS_SIM_PROFILE=exception_injection skip guard so they no-op on a fresh clone without Docker running. Docker/README.md gains an §Exception injection section explaining what pymodbus can and cannot emit, what the injector does, where the rules live, and how to append new ones. docs/drivers/Modbus-Test-Fixture.md follow-up item #2 (extend pymodbus profiles to inject exceptions) gets a shipped strikethrough with the new coverage inventory; the unit-level section adds ExceptionInjectionTests next to DL205ExceptionCodeTests so the split-of-responsibilities is explicit (DL205 test = natural out-of-range via dl205 profile, ExceptionInjectionTests = every other code via the injector). Test baselines: Modbus unit 182/182 green (unchanged); Modbus integration with exception_injection profile live 11/11 new tests green. Existing DL205/S7/Mitsubishi integration tests unaffected since they skip on MODBUS_SIM_PROFILE mismatch. Found + fixed during validation: a stale native pymodbus simulator from April 18 was still listening on port 5020 on IPv6 localhost (Windows was load-balancing between it + the Docker IPv4 forward, making injected exceptions intermittently come back as pymodbus's default 0x02). Killed the leftover. Documented the debugging path in the commit as a note for anyone who hits the same "my tests see exception 0x02 but the injector log has no request" symptom. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
OtOpcUa documentation
Two tiers of documentation live here:
- Current reference at the top level (
docs/*.md) — describes what's shipped today. Start here for operator + integrator reference. - Implementation history + design notes at
docs/v2/*.md— the authoritative plan + decision log the current reference is built from. Start here when you need the why behind an architectural choice, or when a top-level doc says "see plan.md § X".
The project was originally called LmxOpcUa (a single-driver Galaxy/MXAccess OPC UA server) and has since become OtOpcUa, a multi-driver OPC UA server platform. Any lingering LmxOpcUa-string in a path you see in docs is a deliberate residual (executable name lmxopcua-cli, client PKI folder {LocalAppData}/LmxOpcUaClient/) — fixing those requires migration shims + is tracked as follow-ups.
Platform overview
- Core owns the OPC UA stack, address space, session/security/subscription machinery.
- Drivers plug in via capability interfaces in
ZB.MOM.WW.OtOpcUa.Core.Abstractions:IDriver,IReadable,IWritable,ITagDiscovery,ISubscribable,IHostConnectivityProbe,IAlarmSource,IHistoryProvider,IPerCallHostResolver. Each driver opts into whichever it supports. - Server is the OPC UA endpoint process (net10, x64). Hosts every driver except Galaxy in-process; talks to Galaxy via a named pipe because MXAccess COM is 32-bit-only.
- Admin is the Blazor Server operator UI (net10, x64). Owns the Config DB draft/publish flow, ACL + role-grant authoring, fleet status +
/metricsscrape endpoint. - Galaxy.Host is a .NET Framework 4.8 x86 Windows service that wraps MXAccess COM on an STA thread for the Galaxy driver.
Where to find what
Architecture + data-path reference
| Doc | Covers |
|---|---|
| OpcUaServer.md | Top-level server architecture — Core, driver dispatch, Config DB, generations |
| AddressSpace.md | GenericDriverNodeManager + ITagDiscovery + IAddressSpaceBuilder |
| ReadWriteOperations.md | OPC UA Read/Write → CapabilityInvoker → IReadable/IWritable |
| Subscriptions.md | Monitored items → ISubscribable + per-driver subscription refcount |
| AlarmTracking.md | IAlarmSource + AlarmSurfaceInvoker + OPC UA alarm conditions |
| DataTypeMapping.md | Per-driver DriverAttributeInfo → OPC UA variable types |
| IncrementalSync.md | Address-space rebuild on redeploy + sp_ComputeGenerationDiff |
| HistoricalDataAccess.md | IHistoryProvider as a per-driver optional capability |
Drivers
| Doc | Covers |
|---|---|
| drivers/README.md | Index of the seven shipped drivers + capability matrix |
| drivers/Galaxy.md | Galaxy driver — MXAccess bridge, Host/Proxy split, named-pipe IPC |
| drivers/Galaxy-Repository.md | Galaxy-specific discovery via the ZB SQL database |
For Modbus / S7 / AB CIP / AB Legacy / TwinCAT / FOCAS / OPC UA Client specifics, see v2/driver-specs.md.
Operational
| Doc | Covers |
|---|---|
| Configuration.md | appsettings bootstrap + Config DB + Admin UI draft/publish |
| security.md | Transport security profiles, LDAP auth, ACL trie, role grants, OTOPCUA0001 analyzer |
| Redundancy.md | RedundancyCoordinator, ServiceLevelCalculator, apply-lease, Prometheus metrics |
| ServiceHosting.md | Three-process deploy (Server + Admin + Galaxy.Host) install/uninstall |
| StatusDashboard.md | Pointer — superseded by v2/admin-ui.md |
Client tooling
| Doc | Covers |
|---|---|
| Client.CLI.md | lmxopcua-cli — command-line client |
| Client.UI.md | Avalonia desktop client |
Requirements
| Doc | Covers |
|---|---|
| reqs/HighLevelReqs.md | HLRs — numbered system-level requirements |
| reqs/OpcUaServerReqs.md | OPC UA server-layer reqs |
| reqs/ServiceHostReqs.md | Per-process hosting reqs |
| reqs/ClientRequirements.md | Client CLI + UI reqs |
| reqs/GalaxyRepositoryReqs.md | Galaxy-scoped repository reqs |
| reqs/MxAccessClientReqs.md | Galaxy-scoped MXAccess reqs |
| reqs/StatusDashboardReqs.md | Pointer — superseded by Admin UI |
Implementation history (docs/v2/)
Design decisions + phase plans + execution notes. Load-bearing cross-references from the top-level docs:
- v2/plan.md — authoritative v2 vision doc + numbered decision log (referenced as "decision #N" elsewhere)
- v2/admin-ui.md — Admin UI spec
- v2/acl-design.md — data-plane ACL + permission-trie design (Phase 6.2)
- v2/config-db-schema.md — Config DB schema reference
- v2/driver-specs.md — per-driver addressing + quirks for every shipped protocol
- v2/dev-environment.md — dev-box bootstrap
- v2/test-data-sources.md — integration-test simulator matrix (includes the pinned libplctag
ab_serverversion for AB CIP tests) - v2/implementation/phase--.md — per-phase execution plans with exit-gate evidence