Files
lmxopcua/README.md
T
Joseph Doherty 07a4a5ff5e docs(mqtt): P1 plain-MQTT milestone live-verified; endpoint recorded
Task 14 — the P1 milestone gate. Ran the live /run verification on a docker-dev
rig against the real Mosquitto TLS+auth fixture, and recorded the result.

The gate did its job: it found MQTT was unauthorable in production.

  RawDriverTypeDialog's driver-type list is a hand-maintained array that nobody
  added MQTT to, so the /raw "New driver" picker never offered it — with every
  other layer (contracts, driver, browser, factory, host registration, typed tag
  editor) complete and 266 green unit tests. Nothing tied that array to
  DriverTypeNames and AdminUI has no bUnit, so no offline test could see it.
  Fixed, plus RawDriverTypeDialogParityTests: a reflection parity guard that
  fails for ANY DriverTypeNames entry missing from the picker. Verified
  falsifiable — RED with "…cannot be authored from the /raw New-driver picker,
  so they are unreachable in production: Mqtt" before the one-line fix.

A second gap is left OPEN and documented, not fixed (no task ever covered it):
MqttDriverForm / MqttDeviceForm do not exist, and DriverConfigModal/DeviceModal
have no raw-JSON fallback — so an operator can create an MQTT driver but cannot
author its broker endpoint or credentials. P1 is not operator-complete until
those two razor forms land. The gate authored config via SQL to get past it.

Live gate result (isolated 2-node MAIN stack, image built from this branch,
fixture at 10.100.0.35:8883, AllowUntrustedServerCertificate rather than
pinning the CA into the rig):

  - typed MQTT tag editor renders and round-trips; Json shows the JSON-path
    field, Raw/Scalar hide it; wildcard topic a/+/c blocked at save; data-type
    list offers Float64 and no Double; qos/retainSeed absent on "(driver
    default)" and qos:2 written as a number; isHistorized/historianTagname
    survive a topic-only edit; SparkplugB renders the P2 placeholder and
    switching back keeps Plain values; jsonPath is guidance not a gate in all
    three shapes ($ seeded only for a brand-new blank tag, blank stays absent,
    clearing saves fine)
  - deployed, and both editor-written blobs resolved and served changing live
    values through OPC UA (22.8 / 1629, StatusCode Good) — no BadNodeIdUnknown
  - the bespoke #-observation browser drove a real broker session and rendered
    the fixture's actual topic tree (line1/{counter,state,temperature},
    noise/chatter, retained/seed); a Modbus device's picker still correctly
    reports "Browsing unavailable"

Also found live and documented (not a code change): both nodes of a redundant
pair run the driver, so a FIXED clientId makes them evict each other forever —
the broker logs "already connected, closing old connection" and both nodes
reconnect every ~2s while still reporting Healthy. Unset (the default) is
correct; confirmed 0 reconnects/60s on both nodes after removing it.

Suites: offline 1134 passed / 0 failed (266 Driver.Mqtt + 730 AdminUI incl. the
3 new guards + 138 Core.Abstractions); live 7/7 against the fixture.

Docs: new docs/drivers/Mqtt.md + Mqtt-Test-Fixture.md (the per-driver + fixture
convention every other driver follows), MQTT rows in docs/drivers/README.md,
TestConnectProbes.md, root README.md, and infra/README.md §3.

CLAUDE.md drift corrected while adding the MQTT endpoints:
  - the "every fixture carries project: lmxopcua" claim was false — only the
    MQTT fixture does; the filter returns one stack, not the fleet
  - lmxopcua-fix.ps1 is Windows-VM-only; on macOS drive the host over ssh/rsync
    (and the docker-dev rig itself runs locally under OrbStack)

Claude-Session: https://claude.ai/code/session_01GASWkNEi68FSCtvr6rLoEW
2026-07-24 18:44:43 -04:00

117 lines
6.0 KiB
Markdown

# 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, OPC UA Client (gateway), and MQTT (broker subscribe; Sparkplug B in progress).
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 MQTT
|
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 `mxaccessgw` deployment — see [docs/v2/Galaxy.ParityRig.md](docs/v2/Galaxy.ParityRig.md)
- For historian read-back / alarm history / continuous historization: a running [`ZB.MOM.WW.HistorianGateway`](docs/Historian.md) deployment (the sole historian backend; consumed as the `ZB.MOM.WW.HistorianGateway.Client` gRPC package). It must run `RuntimeDb:Enabled=true` + `RuntimeDb:EventReadsEnabled=true`, and the API key must carry `historian:read` + `historian:write` + `historian:tags:write` scopes.
## Quick Start
```bash
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/Server/ZB.MOM.WW.OtOpcUa.Host
```
The server starts on `opc.tcp://localhost:4840` with the `None` security profile. Configure `Security.Profiles` in `src/Server/ZB.MOM.WW.OtOpcUa.Host/appsettings.json` to enable `Basic256Sha256-Sign` or `Basic256Sha256-SignAndEncrypt`. See [docs/security.md](docs/security.md).
## Install as Windows Services
Production deployment is driven by `scripts/install/Install-Services.ps1`, which registers the `OtOpcUa` server service under a chosen service account. Historian support requires a separately deployed `ZB.MOM.WW.HistorianGateway` and Galaxy support a separately installed `mxaccessgw` — neither this repo nor the install script provisions them.
```powershell
.\scripts\install\Install-Services.ps1 `
-InstallRoot 'C:\Program Files\OtOpcUa' `
-ServiceAccount 'DOMAIN\svc-otopcua'
```
The historian backend is the external `ZB.MOM.WW.HistorianGateway` (not installed by this script). See the script header and [docs/ServiceHosting.md](docs/ServiceHosting.md) for full options.
## Client CLI
```bash
dotnet run --project src/Client/ZB.MOM.WW.OtOpcUa.Client.CLI -- connect -u opc.tcp://localhost:4840
dotnet run --project src/Client/ZB.MOM.WW.OtOpcUa.Client.CLI -- browse -u opc.tcp://localhost:4840 -r -d 3
dotnet run --project src/Client/ZB.MOM.WW.OtOpcUa.Client.CLI -- read -u opc.tcp://localhost:4840 -n "ns=2;s=SomeNode"
dotnet run --project src/Client/ZB.MOM.WW.OtOpcUa.Client.CLI -- write -u opc.tcp://localhost:4840 -n "ns=2;s=SomeNode" -v 42
dotnet run --project src/Client/ZB.MOM.WW.OtOpcUa.Client.CLI -- subscribe -u opc.tcp://localhost:4840 -n "ns=2;s=SomeNode" -i 500
```
See [docs/Client.CLI.md](docs/Client.CLI.md) and [docs/Client.UI.md](docs/Client.UI.md).
## Documentation
### Architecture deep-dives
| Topic | Doc |
|---|---|
| OPC UA server composition, namespace fan-out, Polly invoker | [docs/OpcUaServer.md](docs/OpcUaServer.md) |
| Address space layout | [docs/AddressSpace.md](docs/AddressSpace.md) |
| Read / Write dispatch (driver vs virtual vs scripted-alarm) | [docs/ReadWriteOperations.md](docs/ReadWriteOperations.md) |
| Incremental sync (driver-backend rediscovery + config publishes) | [docs/IncrementalSync.md](docs/IncrementalSync.md) |
| Service hosting (Server + Admin; external HistorianGateway backend) | [docs/ServiceHosting.md](docs/ServiceHosting.md) |
| Security (transport, LDAP, certificates) | [docs/security.md](docs/security.md) |
| Redundancy | [docs/Redundancy.md](docs/Redundancy.md) |
| Status dashboard | [docs/StatusDashboard.md](docs/StatusDashboard.md) |
### Drivers
| Topic | Doc |
|---|---|
| Driver specs (per-driver capability surface, config, addressing) | [docs/v2/driver-specs.md](docs/v2/driver-specs.md) |
| Galaxy driver | [docs/drivers/Galaxy.md](docs/drivers/Galaxy.md) |
| Modbus / S7 / AbCip / AbLegacy / TwinCAT / FOCAS / OpcUaClient / MQTT | [docs/drivers/](docs/drivers/) |
| Galaxy parity rig (mxaccessgw setup) | [docs/v2/Galaxy.ParityRig.md](docs/v2/Galaxy.ParityRig.md) |
| Galaxy performance + tracing | [docs/v2/Galaxy.Performance.md](docs/v2/Galaxy.Performance.md) |
### Clients
| Topic | Doc |
|---|---|
| Client CLI | [docs/Client.CLI.md](docs/Client.CLI.md) |
| Client UI (Avalonia desktop) | [docs/Client.UI.md](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/](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.