Replaced the "ab_server PCCC upstream-broken" skip gate with the actual
root cause: libplctag's ab_server rejects empty CIP routing paths at the
unconnected-send layer before the PCCC dispatcher runs. Real SLC/
MicroLogix/PLC-5 hardware accepts empty paths (no backplane); ab_server
does not. With `/1,0` in place, N (Int16), F (Float32), and L (Int32)
file reads + writes round-trip cleanly across all three compose profiles.
## Fixture changes
- `AbLegacyServerFixture.cs`:
- Drop `AB_LEGACY_TRUST_WIRE` env var + the reachable-but-untrusted
skip branch. Fixture now only skips on TCP unreachability.
- Add `AB_LEGACY_CIP_PATH` env var (default `1,0`) + expose `CipPath`
property. Set `AB_LEGACY_CIP_PATH=` (empty) against real hardware.
- Shorter skip messages on the `[AbLegacyFact]` / `[AbLegacyTheory]`
attributes — one reason: endpoint not reachable.
- `AbLegacyReadSmokeTests.cs`:
- Device URI built from `sim.CipPath` instead of hardcoded empty path.
- New `AB_LEGACY_COMPOSE_PROFILE` env var filters the parametric
theory to the running container's family. Only one container binds
`:44818` at a time, so cross-family params would otherwise fail.
- `Slc500_write_then_read_round_trip` skips cleanly when the running
profile isn't `slc500`.
## E2E + seed + docs
- `scripts/e2e/test-ablegacy.ps1` — drop the `AB_LEGACY_TRUST_WIRE`
skip gate; synopsis calls out the `/1,0` vs empty cip-path split
between the Docker fixture and real hardware.
- `scripts/e2e/e2e-config.sample.json` — sample gateway flipped from
the hardware placeholder (`192.168.1.10`) to the Docker fixture
(`127.0.0.1/1,0`); comment rewritten.
- `scripts/e2e/README.md` — AB Legacy expected-matrix row goes from
SKIP to PASS.
- `scripts/smoke/seed-ablegacy-smoke.sql` — default HostAddress points
at the Docker fixture + header / footer text reflect the new state.
- `tests/.../Docker/README.md` — "Known limitations" section rewritten
to describe the cip-path gate (not a dispatcher gap); env-var table
picks up `AB_LEGACY_CIP_PATH` + `AB_LEGACY_COMPOSE_PROFILE`.
- `docs/drivers/AbLegacy-Test-Fixture.md` + `docs/drivers/README.md`
+ `docs/DriverClis.md` — flip status from blocked to functional;
residual bit-file-write gap (B3:0/5 → 0x803D0000) documented.
## Residual gap
Bit-file writes (`B3:0/5` style) surface `0x803D0000` against
`ab_server --plc=SLC500`; bit reads work. Non-blocking for smoke
coverage — N/F/L round-trip is enough. Real hardware / RSEmulate 500
for bit-write fidelity. Documented in `Docker/README.md` §"Known
limitations" + the `AbLegacy-Test-Fixture.md` follow-ups list.
## Verified
- Full-solution build: 0 errors, 334 pre-existing warnings.
- Integration suite passes per-profile with
`AB_LEGACY_COMPOSE_PROFILE=<slc500|micrologix|plc5>` + matching
compose container up.
- All four non-hardware e2e scripts (Modbus / AB CIP / AB Legacy / S7)
now 5/5 against the respective docker-compose fixtures.
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 | otopcua-cli — OPC UA command-line client |
| Client.UI.md | Avalonia desktop client |
| DriverClis.md | Driver test-client CLIs — index + shared commands |
| Driver.Modbus.Cli.md | otopcua-modbus-cli — Modbus-TCP |
| Driver.AbCip.Cli.md | otopcua-abcip-cli — ControlLogix / CompactLogix / Micro800 / GuardLogix |
| Driver.AbLegacy.Cli.md | otopcua-ablegacy-cli — SLC / MicroLogix / PLC-5 (PCCC) |
| Driver.S7.Cli.md | otopcua-s7-cli — Siemens S7-300 / S7-400 / S7-1200 / S7-1500 |
| Driver.TwinCAT.Cli.md | otopcua-twincat-cli — Beckhoff TwinCAT 2/3 ADS |
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