Fifth PR of the alarms-over-gateway epic (docs/plans/alarms-over-gateway.md). Depends on PR C.1 (AahClientManagedAlarmEventWriter), already merged. Today HistorianFrameHandler is constructed at Program.cs line 57 without an alarmWriter, so every WriteAlarmEvents frame replies "Sidecar not configured with an alarm-event writer" and the lmxopcua side keeps the row queued. C.2 wires a real writer behind a new OTOPCUA_HISTORIAN_ALARM_WRITE_ENABLED toggle. - Program.BuildAlarmWriter — gated on the env var (default true, fail-open under accidental misconfiguration). Constructs an AahClientManagedAlarmEventWriter wrapping a SdkAlarmHistorianWriteBackend with the same connection config the read path uses. - Install-Services.ps1 — appends OTOPCUA_HISTORIAN_ALARM_WRITE_ENABLED=true to the OtOpcUaWonderwareHistorian service env block when the sidecar is installed. Read-only deployments flip it to false at service-config edit time without re-installing. - HistorianFrameHandler signature already accepts IAlarmEventWriter? — supplying non-null at line 57 lights up the WriteAlarmEvents reply path that's been dormant since PR 3.3. Until PR D.1 pins the live aahClientManaged entry point, the SdkAlarmHistorianWriteBackend reports RetryPlease for every event with a structured diagnostic. The lmxopcua-side SqliteStoreAndForwardSink retains queued events; same effective behaviour as today's NullAlarmHistorianSink fallback but with visible diagnostics rather than silent discard. Tests: - 6 BuildAlarmWriter env-var cases — unset / true / false / unrecognized → default-on / capitalization variants. - Full sidecar test suite: 56 passed (was 48; 8 new). Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
OtOpcUa
OPC UA server (.NET 10 AnyCPU) that exposes a fleet of industrial drivers as a single OPC UA address space. Drivers ship in-process for AVEVA System Platform Galaxy (via the sibling mxaccessgw repo), Modbus TCP, Siemens S7, Allen-Bradley CIP (ControlLogix / CompactLogix), Allen-Bradley Legacy (SLC 500 / MicroLogix), Beckhoff TwinCAT (ADS), FANUC FOCAS, and OPC UA Client (gateway).
A cross-platform client stack (.NET 10) — shared library, CLI, and Avalonia desktop app — connects to any OPC UA server.
Architecture
OPC UA Clients (CLI, Desktop UI, 3rd-party)
|
v
+-------------------------------------+
| OtOpcUa.Server (.NET 10 AnyCPU) |
| address space + capability fan-out|
+-------------------------------------+
| | | | | | | |
Galaxy Modbus S7 AbCip AbLeg TwinCAT FOCAS OpcUaClient
|
v
mxaccessgw (sibling repo, gRPC)
|
v
MXAccess COM (x86 worker, on AVEVA box)
Galaxy is the only driver with an external runtime: it speaks gRPC to a separately installed mxaccessgw server (sibling repo at c:\Users\dohertj2\Desktop\mxaccessgw\) which owns the MXAccess COM apartment and the x86/STA bitness constraint server-side. Everything in this repo is platform-agnostic .NET 10.
Prerequisites
- .NET 10 SDK (server, drivers, clients all target .NET 10)
- SQL Server reachable for the central config DB
- For Galaxy specifically: a running
mxaccessgwdeployment — see docs/v2/Galaxy.ParityRig.md - For Wonderware Historian read-back: optional
OtOpcUaWonderwareHistoriansidecar — see docs/ServiceHosting.md
Quick Start
dotnet restore ZB.MOM.WW.OtOpcUa.slnx
dotnet build ZB.MOM.WW.OtOpcUa.slnx
dotnet test ZB.MOM.WW.OtOpcUa.slnx
# Run the server in dev (foreground)
dotnet run --project src/ZB.MOM.WW.OtOpcUa.Server
The server starts on opc.tcp://localhost:4840 with the None security profile. Configure Security.Profiles in src/ZB.MOM.WW.OtOpcUa.Server/appsettings.json to enable Basic256Sha256-Sign or Basic256Sha256-SignAndEncrypt. See docs/security.md.
Install as Windows Services
Production deployment is driven by scripts/install/Install-Services.ps1, which registers the OtOpcUa server service (and optionally the OtOpcUaWonderwareHistorian sidecar) under a chosen service account. Galaxy support requires a separately installed mxaccessgw — neither this repo nor the install script provisions it.
.\scripts\install\Install-Services.ps1 `
-InstallRoot 'C:\Program Files\OtOpcUa' `
-ServiceAccount 'DOMAIN\svc-otopcua'
Add -InstallWonderwareHistorian for the historian sidecar. See the script header and docs/ServiceHosting.md for full options.
Client CLI
dotnet run --project src/ZB.MOM.WW.OtOpcUa.Client.CLI -- connect -u opc.tcp://localhost:4840
dotnet run --project src/ZB.MOM.WW.OtOpcUa.Client.CLI -- browse -u opc.tcp://localhost:4840 -r -d 3
dotnet run --project src/ZB.MOM.WW.OtOpcUa.Client.CLI -- read -u opc.tcp://localhost:4840 -n "ns=2;s=SomeNode"
dotnet run --project src/ZB.MOM.WW.OtOpcUa.Client.CLI -- write -u opc.tcp://localhost:4840 -n "ns=2;s=SomeNode" -v 42
dotnet run --project src/ZB.MOM.WW.OtOpcUa.Client.CLI -- subscribe -u opc.tcp://localhost:4840 -n "ns=2;s=SomeNode" -i 500
See docs/Client.CLI.md and docs/Client.UI.md.
Documentation
Architecture deep-dives
| Topic | Doc |
|---|---|
| OPC UA server composition, namespace fan-out, Polly invoker | docs/OpcUaServer.md |
| Address space layout | docs/AddressSpace.md |
| Read / Write dispatch (driver vs virtual vs scripted-alarm) | docs/ReadWriteOperations.md |
| Incremental sync (driver-backend rediscovery + config publishes) | docs/IncrementalSync.md |
| Service hosting (Server + Admin + optional historian sidecar) | docs/ServiceHosting.md |
| Security (transport, LDAP, certificates) | docs/security.md |
| Redundancy | docs/Redundancy.md |
| Status dashboard | docs/StatusDashboard.md |
Drivers
| Topic | Doc |
|---|---|
| Driver specs (per-driver capability surface, config, addressing) | docs/v2/driver-specs.md |
| Galaxy driver | docs/drivers/Galaxy.md |
| Modbus / S7 / AbCip / AbLegacy / TwinCAT / FOCAS / OpcUaClient | docs/drivers/ |
| Galaxy parity rig (mxaccessgw setup) | docs/v2/Galaxy.ParityRig.md |
| Galaxy performance + tracing | docs/v2/Galaxy.Performance.md |
Clients
| Topic | Doc |
|---|---|
| Client CLI | docs/Client.CLI.md |
| Client UI (Avalonia desktop) | docs/Client.UI.md |
v1 archive
The original v1 in-process MXAccess docs (Galaxy.Host topology, Configuration env vars, AlarmTracking, DataTypeMapping, HistoricalDataAccess, Subscriptions, etc.) are preserved under docs/v1/ — historical reference only. PR 7.2 retired the v1 architecture on 2026-04-30; current state is documented in the sections above.
License
Internal use only.