From f2176364676c2478200327a21b4816eb7216ddc1 Mon Sep 17 00:00:00 2001 From: Joseph Doherty Date: Mon, 20 Apr 2026 01:25:18 -0400 Subject: [PATCH 1/5] =?UTF-8?q?Doc=20refresh=20(task=20#206)=20=E2=80=94?= =?UTF-8?q?=20Client.CLI=20+=20Client.UI=20brand=20flip=20+=20new=20top-le?= =?UTF-8?q?vel=20docs/README.md=20index.=20Client.CLI.md:=20replaced=20sta?= =?UTF-8?q?le=20LmxOpcUa-OPC=20UA-server=20references=20with=20OtOpcUa=20t?= =?UTF-8?q?hroughout=20the=20overview=20+=20sample=20output=20+=20applicat?= =?UTF-8?q?ionUri=20examples=20(opc.tcp://localhost:4840/OtOpcUa,=20urn:lo?= =?UTF-8?q?calhost:OtOpcUa:instanceN);=20confirmed=20against=20src/ZB.MOM.?= =?UTF-8?q?WW.OtOpcUa.Server/Program.cs:69-71=20which=20sets=20the=20live?= =?UTF-8?q?=20endpoint=20url=20+=20application=20uri=20to=20those=20exact?= =?UTF-8?q?=20values.=20Added=20a=20driver-agnostic=20note=20in=20the=20ov?= =?UTF-8?q?erview=20=E2=80=94=20the=20CLI=20is=20reachable=20against=20eve?= =?UTF-8?q?ry=20shipped=20driver=20surface=20because=20the=20OPC=20UA=20en?= =?UTF-8?q?dpoint=20abstracts=20them=20all.=20Kept=20the=20`lmxopcua-cli`?= =?UTF-8?q?=20executable=20name=20+=20the=20`{LocalAppData}/LmxOpcUaClient?= =?UTF-8?q?/pki/`=20PKI=20folder=20name=20AS-IS=20because=20those=20are=20?= =?UTF-8?q?real=20filesystem-level=20residuals=20the=20code=20still=20uses?= =?UTF-8?q?=20(Program.cs=20SetExecutableName=20+=20OpcUaClientService.cs:?= =?UTF-8?q?428)=20=E2=80=94=20flipping=20them=20requires=20migration=20shi?= =?UTF-8?q?ms=20so=20existing=20dev=20boxes=20don't=20lose=20their=20trust?= =?UTF-8?q?ed-cert=20store;=20added=20explicit=20doc=20text=20explaining?= =?UTF-8?q?=20the=20residual=20+=20why=20it=20persists=20so=20future=20rea?= =?UTF-8?q?ders=20aren't=20confused.=20Fixed=20the=20sample=20connect-outp?= =?UTF-8?q?ut=20"Server:=20LmxOpcUa"=20to=20"Server:=20OtOpcUa=20Server"?= =?UTF-8?q?=20matching=20the=20live=20ApplicationName=20in=20OpcUaServerOp?= =?UTF-8?q?tions.cs:39.=20Client.UI.md:=20replaced=20the=204=20LmxOpcUa=20?= =?UTF-8?q?references=20=E2=80=94=20overview=20one-liner,=20status-bar=20m?= =?UTF-8?q?ock=20(now=20reads=20"OtOpcUa=20Server"=20matching=20the=20serv?= =?UTF-8?q?er's=20reported=20ApplicationName),=20endpoint-url=20example,?= =?UTF-8?q?=20settings=20persistence=20path.=20Same=20residual-explanation?= =?UTF-8?q?=20note=20added=20under=20the=20LmxOpcUaClient/settings.json=20?= =?UTF-8?q?path=20pointing=20at=20the=20Client.Shared=20session-factory=20?= =?UTF-8?q?literal=20at=20OpcUaClientService.cs:428.=20docs/README.md=20is?= =?UTF-8?q?=20new=20=E2=80=94=20a=20top-level=20index=20distinguishing=20t?= =?UTF-8?q?he=20two=20documentation=20tiers=20(current=20reference=20at=20?= =?UTF-8?q?docs/*.md=20vs=20implementation=20history=20+=20design=20notes?= =?UTF-8?q?=20at=20docs/v2/*.md).=20Every=20current-reference=20doc=20gets?= =?UTF-8?q?=20a=20one-line=20role=20description=20in=20a=20section=20table?= =?UTF-8?q?=20(Architecture=20+=20data-path=20/=20Drivers=20/=20Operationa?= =?UTF-8?q?l=20/=20Client=20tooling=20/=20Requirements)=20so=20a=20new=20r?= =?UTF-8?q?eader=20picking=20up=20the=20repo=20finds=20their=20way=20in=20?= =?UTF-8?q?without=20having=20to=20grep=20file=20names.=20Cross-link=20cal?= =?UTF-8?q?ls=20out=20that=20load-bearing=20references=20from=20top-level?= =?UTF-8?q?=20docs=20(plan.md=20decisions,=20admin-ui.md,=20acl-design.md,?= =?UTF-8?q?=20config-db-schema.md,=20driver-specs.md,=20dev-environment.md?= =?UTF-8?q?,=20test-data-sources.md)=20live=20under=20v2/.=20Notes=20up=20?= =?UTF-8?q?front=20that=20the=20project=20was=20renamed=20LmxOpcUa=20?= =?UTF-8?q?=E2=86=92=20OtOpcUa=20and=20that=20any=20remaining=20LmxOpcUa-s?= =?UTF-8?q?tring=20in=20paths=20is=20a=20deliberate=20residual=20with=20a?= =?UTF-8?q?=20migration=20follow-up,=20so=20readers=20don't=20chase=20phan?= =?UTF-8?q?tom=20bugs.=20Four=20parallel=20doc-refresh=20agents=20currentl?= =?UTF-8?q?y=20working=20on=20the=20rest=20of=20docs/*.md=20(task=20#202?= =?UTF-8?q?=20core=20architecture,=20#203=20driver=20docs=20split,=20#204?= =?UTF-8?q?=20operational,=20#205=20requirements)=20=E2=80=94=20those=20co?= =?UTF-8?q?mmits=20will=20land=20on=20separate=20worktree=20branches=20+?= =?UTF-8?q?=20get=20folded=20in=20together=20once=20complete;=20this=20ind?= =?UTF-8?q?ex=20already=20lists=20the=20docs=20they'll=20produce=20(driver?= =?UTF-8?q?s/README.md,=20drivers/Galaxy.md,=20drivers/Galaxy-Repository.m?= =?UTF-8?q?d)=20so=20the=20final=20merge=20just=20has=20the=20content=20sh?= =?UTF-8?q?owing=20up=20where=20the=20index=20already=20points.?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-Authored-By: Claude Opus 4.7 (1M context) --- docs/Client.CLI.md | 38 ++++++++--------- docs/Client.UI.md | 8 ++-- docs/README.md | 83 ++++++++++++++++++++++++++++++++++++ docs/reqs/HighLevelReqs.md | 87 +++++++++++++++++++++++++++++--------- 4 files changed, 173 insertions(+), 43 deletions(-) create mode 100644 docs/README.md diff --git a/docs/Client.CLI.md b/docs/Client.CLI.md index 32ed038..cdab19d 100644 --- a/docs/Client.CLI.md +++ b/docs/Client.CLI.md @@ -2,9 +2,9 @@ ## Overview -`ZB.MOM.WW.OtOpcUa.Client.CLI` is a cross-platform command-line client for the LmxOpcUa OPC UA server. It targets .NET 10 and uses the shared `IOpcUaClientService` from `Client.Shared` for all OPC UA operations. Commands are routed and parsed by [CliFx](https://github.com/Tyrrrz/CliFx). +`ZB.MOM.WW.OtOpcUa.Client.CLI` is a cross-platform command-line client for the OtOpcUa OPC UA server. It targets .NET 10 and uses the shared `IOpcUaClientService` from `Client.Shared` for all OPC UA operations. Commands are routed and parsed by [CliFx](https://github.com/Tyrrrz/CliFx). -The CLI is the primary tool for operators and developers to test and interact with the server from a terminal. It supports all core operations: connectivity testing, browsing, reading, writing, subscriptions, alarm monitoring, history reads, and redundancy queries. +The CLI is the primary tool for operators and developers to test and interact with the server from a terminal. It supports all core operations: connectivity testing, browsing, reading, writing, subscriptions, alarm monitoring, history reads, and redundancy queries. Any driver surface exposed by the server (Galaxy, Modbus, S7, AB CIP, AB Legacy, TwinCAT, FOCAS, OPC UA Client) is reachable through these commands — the CLI is driver-agnostic because everything below the OPC UA endpoint is. ## Build and Run @@ -14,7 +14,7 @@ dotnet build dotnet run -- [options] ``` -The executable name is `lmxopcua-cli`. +The executable name is still `lmxopcua-cli` — a residual from the pre-v2 rename (`Program.cs:SetExecutableName`). Scripts + operator muscle memory depend on the name; flipping it to `otopcua-cli` is a follow-up that also needs to move the client-side PKI store folder ({LocalAppData}/LmxOpcUaClient/pki/ — used by the shared client for its application certificate) so trust relationships survive the rename. ## Architecture @@ -54,7 +54,7 @@ lmxopcua-cli write -u opc.tcp://localhost:4840 -n "ns=2;s=MyNode" -v 42 -U opera When `-F` is provided, the shared service tries the primary URL first, then each failover URL in order. For long-running commands (`subscribe`, `alarms`), the service monitors the session via keep-alive and automatically reconnects to the next available server on failure. ```bash -lmxopcua-cli connect -u opc.tcp://localhost:4840/LmxOpcUa -F opc.tcp://localhost:4841/LmxOpcUa +lmxopcua-cli connect -u opc.tcp://localhost:4840/OtOpcUa -F opc.tcp://localhost:4841/OtOpcUa ``` ### Transport Security @@ -67,7 +67,7 @@ When `sign` or `encrypt` is specified, the shared service: 4. Fails with a clear error if no matching endpoint is found ```bash -lmxopcua-cli browse -u opc.tcp://localhost:4840/LmxOpcUa -S encrypt -U admin -P secret -r -d 2 +lmxopcua-cli browse -u opc.tcp://localhost:4840/OtOpcUa -S encrypt -U admin -P secret -r -d 2 ``` ### Verbose Logging @@ -81,14 +81,14 @@ The `--verbose` flag switches Serilog output from `Warning` to `Debug` level, sh Tests connectivity to an OPC UA server. Creates a session, prints connection metadata, and disconnects. ```bash -lmxopcua-cli connect -u opc.tcp://localhost:4840/LmxOpcUa -U admin -P admin123 +lmxopcua-cli connect -u opc.tcp://localhost:4840/OtOpcUa -U admin -P admin123 ``` Output: ```text -Connected to: opc.tcp://localhost:4840/LmxOpcUa -Server: LmxOpcUa +Connected to: opc.tcp://localhost:4840/OtOpcUa +Server: OtOpcUa Server Security Mode: None Security Policy: http://opcfoundation.org/UA/SecurityPolicy#None Connection successful. @@ -99,7 +99,7 @@ Connection successful. Reads the current value of a single node and prints the value, status code, and timestamps. ```bash -lmxopcua-cli read -u opc.tcp://localhost:4840/LmxOpcUa -n "ns=3;s=DEV.ScanState" -U admin -P admin123 +lmxopcua-cli read -u opc.tcp://localhost:4840/OtOpcUa -n "ns=3;s=DEV.ScanState" -U admin -P admin123 ``` | Flag | Description | @@ -135,10 +135,10 @@ Browses the OPC UA address space starting from the Objects folder or a specified ```bash # Browse top-level Objects folder -lmxopcua-cli browse -u opc.tcp://localhost:4840/LmxOpcUa -U admin -P admin123 +lmxopcua-cli browse -u opc.tcp://localhost:4840/OtOpcUa -U admin -P admin123 # Browse a specific node recursively to depth 3 -lmxopcua-cli browse -u opc.tcp://localhost:4840/LmxOpcUa -U admin -P admin123 -r -d 3 -n "ns=3;s=ZB" +lmxopcua-cli browse -u opc.tcp://localhost:4840/OtOpcUa -U admin -P admin123 -r -d 3 -n "ns=3;s=ZB" ``` | Flag | Description | @@ -166,12 +166,12 @@ Reads historical data from a node. Supports raw history reads and aggregate (pro ```bash # Raw history -lmxopcua-cli historyread -u opc.tcp://localhost:4840/LmxOpcUa \ +lmxopcua-cli historyread -u opc.tcp://localhost:4840/OtOpcUa \ -n "ns=1;s=TestMachine_001.TestHistoryValue" \ --start "2026-03-25" --end "2026-03-30" # Aggregate: 1-hour average -lmxopcua-cli historyread -u opc.tcp://localhost:4840/LmxOpcUa \ +lmxopcua-cli historyread -u opc.tcp://localhost:4840/OtOpcUa \ -n "ns=1;s=TestMachine_001.TestHistoryValue" \ --start "2026-03-25" --end "2026-03-30" \ --aggregate Average --interval 3600000 @@ -203,10 +203,10 @@ Subscribes to alarm events on a node. Prints structured alarm output including s ```bash # Subscribe to alarm events on the Server node -lmxopcua-cli alarms -u opc.tcp://localhost:4840/LmxOpcUa +lmxopcua-cli alarms -u opc.tcp://localhost:4840/OtOpcUa # Subscribe to a specific source node with condition refresh -lmxopcua-cli alarms -u opc.tcp://localhost:4840/LmxOpcUa \ +lmxopcua-cli alarms -u opc.tcp://localhost:4840/OtOpcUa \ -n "ns=1;s=TestMachine_001" --refresh ``` @@ -221,7 +221,7 @@ lmxopcua-cli alarms -u opc.tcp://localhost:4840/LmxOpcUa \ Reads the OPC UA redundancy state from a server: redundancy mode, service level, server URIs, and application URI. ```bash -lmxopcua-cli redundancy -u opc.tcp://localhost:4840/LmxOpcUa -U admin -P admin123 +lmxopcua-cli redundancy -u opc.tcp://localhost:4840/OtOpcUa -U admin -P admin123 ``` Example output: @@ -230,9 +230,9 @@ Example output: Redundancy Mode: Warm Service Level: 200 Server URIs: - - urn:localhost:LmxOpcUa:instance1 - - urn:localhost:LmxOpcUa:instance2 -Application URI: urn:localhost:LmxOpcUa:instance1 + - urn:localhost:OtOpcUa:instance1 + - urn:localhost:OtOpcUa:instance2 +Application URI: urn:localhost:OtOpcUa:instance1 ``` ## Testing diff --git a/docs/Client.UI.md b/docs/Client.UI.md index 3c9c861..6839055 100644 --- a/docs/Client.UI.md +++ b/docs/Client.UI.md @@ -2,7 +2,7 @@ ## Overview -`ZB.MOM.WW.OtOpcUa.Client.UI` is a cross-platform Avalonia desktop application for connecting to and interacting with the LmxOpcUa OPC UA server. It targets .NET 10 and uses the shared `IOpcUaClientService` from `Client.Shared` for all OPC UA operations. +`ZB.MOM.WW.OtOpcUa.Client.UI` is a cross-platform Avalonia desktop application for connecting to and interacting with the OtOpcUa OPC UA server. It targets .NET 10 and uses the shared `IOpcUaClientService` from `Client.Shared` for all OPC UA operations. The UI provides a single-window interface for browsing the address space, reading and writing values, monitoring live subscriptions, managing alarms, and querying historical data. @@ -43,7 +43,7 @@ The application uses a single-window layout with five main areas: │ │ │ ││ │ (lazy-load) │ └──────────────────────────────────────────────┘│ ├──────────────┴──────────────────────────────────────────────┤ -│ Connected to opc.tcp://... | LmxOpcUa | Session: ... | 3 subs│ +│ Connected to opc.tcp://... | OtOpcUa Server | Session: ... | 3 subs│ └─────────────────────────────────────────────────────────────┘ ``` @@ -55,7 +55,7 @@ The top bar provides the endpoint URL, Connect, and Disconnect buttons. The **Co | Setting | Description | |---------|-------------| -| Endpoint URL | OPC UA server endpoint (e.g., `opc.tcp://localhost:4840/LmxOpcUa`) | +| Endpoint URL | OPC UA server endpoint (e.g., `opc.tcp://localhost:4840/OtOpcUa`) | | Username / Password | Credentials for `UserName` token authentication | | Security Mode | Transport security: None, Sign, SignAndEncrypt | | Failover URLs | Comma-separated backup endpoints for redundancy failover | @@ -65,7 +65,7 @@ The top bar provides the endpoint URL, Connect, and Disconnect buttons. The **Co ### Settings Persistence -Connection settings are saved to `{LocalAppData}/LmxOpcUaClient/settings.json` after each successful connection and on window close. The settings are reloaded on next launch, including: +Connection settings are saved to `{LocalAppData}/LmxOpcUaClient/settings.json` after each successful connection and on window close. The folder name is a residual from the pre-v2 rename (the `Client.Shared` session factory still calls itself `LmxOpcUaClient` at `OpcUaClientService.cs:428`); renaming to `OtOpcUaClient` is a follow-up that needs a migration shim so existing users don't lose their settings on upgrade. The settings are reloaded on next launch, including: - All connection parameters - Active subscription node IDs (restored after reconnection) diff --git a/docs/README.md b/docs/README.md new file mode 100644 index 0000000..e06888c --- /dev/null +++ b/docs/README.md @@ -0,0 +1,83 @@ +# 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 + `/metrics` scrape 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](OpcUaServer.md) | Top-level server architecture — Core, driver dispatch, Config DB, generations | +| [AddressSpace.md](AddressSpace.md) | `GenericDriverNodeManager` + `ITagDiscovery` + `IAddressSpaceBuilder` | +| [ReadWriteOperations.md](ReadWriteOperations.md) | OPC UA Read/Write → `CapabilityInvoker` → `IReadable`/`IWritable` | +| [Subscriptions.md](Subscriptions.md) | Monitored items → `ISubscribable` + per-driver subscription refcount | +| [AlarmTracking.md](AlarmTracking.md) | `IAlarmSource` + `AlarmSurfaceInvoker` + OPC UA alarm conditions | +| [DataTypeMapping.md](DataTypeMapping.md) | Per-driver `DriverAttributeInfo` → OPC UA variable types | +| [IncrementalSync.md](IncrementalSync.md) | Address-space rebuild on redeploy + `sp_ComputeGenerationDiff` | +| [HistoricalDataAccess.md](HistoricalDataAccess.md) | `IHistoryProvider` as a per-driver optional capability | + +### Drivers + +| Doc | Covers | +|-----|--------| +| [drivers/README.md](drivers/README.md) | Index of the seven shipped drivers + capability matrix | +| [drivers/Galaxy.md](drivers/Galaxy.md) | Galaxy driver — MXAccess bridge, Host/Proxy split, named-pipe IPC | +| [drivers/Galaxy-Repository.md](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](v2/driver-specs.md). + +### Operational + +| Doc | Covers | +|-----|--------| +| [Configuration.md](Configuration.md) | appsettings bootstrap + Config DB + Admin UI draft/publish | +| [security.md](security.md) | Transport security profiles, LDAP auth, ACL trie, role grants, OTOPCUA0001 analyzer | +| [Redundancy.md](Redundancy.md) | `RedundancyCoordinator`, `ServiceLevelCalculator`, apply-lease, Prometheus metrics | +| [ServiceHosting.md](ServiceHosting.md) | Three-process deploy (Server + Admin + Galaxy.Host) install/uninstall | +| [StatusDashboard.md](StatusDashboard.md) | Pointer — superseded by [v2/admin-ui.md](v2/admin-ui.md) | + +### Client tooling + +| Doc | Covers | +|-----|--------| +| [Client.CLI.md](Client.CLI.md) | `lmxopcua-cli` — command-line client | +| [Client.UI.md](Client.UI.md) | Avalonia desktop client | + +### Requirements + +| Doc | Covers | +|-----|--------| +| [reqs/HighLevelReqs.md](reqs/HighLevelReqs.md) | HLRs — numbered system-level requirements | +| [reqs/OpcUaServerReqs.md](reqs/OpcUaServerReqs.md) | OPC UA server-layer reqs | +| [reqs/ServiceHostReqs.md](reqs/ServiceHostReqs.md) | Per-process hosting reqs | +| [reqs/ClientRequirements.md](reqs/ClientRequirements.md) | Client CLI + UI reqs | +| [reqs/GalaxyRepositoryReqs.md](reqs/GalaxyRepositoryReqs.md) | Galaxy-scoped repository reqs | +| [reqs/MxAccessClientReqs.md](reqs/MxAccessClientReqs.md) | Galaxy-scoped MXAccess reqs | +| [reqs/StatusDashboardReqs.md](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](v2/plan.md) — authoritative v2 vision doc + numbered decision log (referenced as "decision #N" elsewhere) +- [v2/admin-ui.md](v2/admin-ui.md) — Admin UI spec +- [v2/acl-design.md](v2/acl-design.md) — data-plane ACL + permission-trie design (Phase 6.2) +- [v2/config-db-schema.md](v2/config-db-schema.md) — Config DB schema reference +- [v2/driver-specs.md](v2/driver-specs.md) — per-driver addressing + quirks for every shipped protocol +- [v2/dev-environment.md](v2/dev-environment.md) — dev-box bootstrap +- [v2/test-data-sources.md](v2/test-data-sources.md) — integration-test simulator matrix (includes the pinned libplctag `ab_server` version for AB CIP tests) +- [v2/implementation/phase-*-*.md](v2/implementation/) — per-phase execution plans with exit-gate evidence diff --git a/docs/reqs/HighLevelReqs.md b/docs/reqs/HighLevelReqs.md index a977567..6771ed8 100644 --- a/docs/reqs/HighLevelReqs.md +++ b/docs/reqs/HighLevelReqs.md @@ -1,47 +1,94 @@ # High-Level Requirements +> **Revision** — Refreshed 2026-04-19 for the OtOpcUa v2 multi-driver platform (task #205). The original 2025 text described a single-process Galaxy/MXAccess server called LmxOpcUa. Today the project is the **OtOpcUa** multi-driver OPC UA platform deployed as three cooperating processes (Server, Admin, Galaxy.Host). The Galaxy integration is one of seven shipped drivers. HLR-001 through HLR-008 have been rewritten driver-agnostically; HLR-009 has been retired (the embedded Status Dashboard is superseded by the Admin UI). HLR-010 through HLR-017 are new and cover plug-in drivers, resilience, Config DB / draft-publish, cluster redundancy, fleet-wide identifier uniqueness, Admin UI, audit logging, metrics, and the Roslyn capability-wrapping analyzer. + ## HLR-001: OPC UA Server -The system shall expose an OPC UA server endpoint that OPC UA clients can connect to for browsing, reading, and writing Galaxy tag data. +The system shall expose an OPC UA server endpoint that OPC UA clients can connect to for browsing, reading, writing, subscribing, acknowledging alarms, and reading historical values. Data is sourced from one or more **driver instances** that plug into the common core; OPC UA clients see a single unified address space per endpoint regardless of how many drivers are active behind it. -## HLR-002: Galaxy Hierarchy as OPC UA Address Space +## HLR-002: Multi-Driver Plug-In Model -The system shall build an OPC UA address space that mirrors the System Platform Galaxy object hierarchy, using contained names for browse structure and tag names for runtime data access. +The system shall support pluggable driver modules that bind to specific data sources. v2.0 ships seven drivers: Galaxy (AVEVA System Platform via MXAccess), Modbus TCP (including DL205 via `AddressFormat=DL205`), Allen-Bradley CIP (ControlLogix/CompactLogix), Allen-Bradley Legacy (SLC/MicroLogix via PCCC), Siemens S7, Beckhoff TwinCAT (ADS), FANUC FOCAS, and OPC UA Client (aggregation/gateway). Drivers implement only the capability interfaces (`IDriver`, `ITagDiscovery`, `IReadable`, `IWritable`, `ISubscribable`, `IAlarmSource`, `IHistoryProvider`, `IHostConnectivityProbe`, `IPerCallHostResolver`, `IRediscoverable`) defined in `ZB.MOM.WW.OtOpcUa.Core.Abstractions` that apply to their protocol. Multiple instances of the same driver type are supported; each instance binds to its own OPC UA namespace index. -## HLR-003: MXAccess Runtime Data Access +## HLR-003: Address Space Composition per Namespace -The system shall use the MXAccess toolkit to subscribe to, read, and write Galaxy tag attribute values at runtime on behalf of connected OPC UA clients. +The system shall build the OPC UA address space by composing per-driver subtrees into a single endpoint. Each driver instance owns one namespace and registers its nodes via the core-provided `IAddressSpaceBuilder` streaming API. The Galaxy driver continues to mirror the deployed ArchestrA object hierarchy (contained-name browse paths) in a namespace of kind `SystemPlatform`. Native-protocol drivers populate a namespace of kind `Equipment` whose browse structure conforms to the canonical 5-level Unified Namespace (`Enterprise / Site / Area / Line / Equipment / Signal`). ## HLR-004: Data Type Mapping -The system shall map Galaxy attribute data types (mx_data_type) to appropriate OPC UA built-in types, including support for array attributes. +Each driver shall map its native data types to OPC UA built-in types via `DriverDataType` conversions, including support for arrays (ValueRank=1 with ArrayDimensions). Type mapping is driver-specific — `docs/DataTypeMapping.md` covers Galaxy/MXAccess; each other driver's spec in `docs/v2/driver-specs.md` covers its own mapping. Unknown/unmapped driver types shall default to String per the driver's spec. -## HLR-005: Dynamic Address Space Rebuild +## HLR-005: Live Data Access -The system shall detect Galaxy deployment changes (via `galaxy.time_of_last_deploy`) and rebuild the OPC UA address space to reflect the current deployed state. +For every data-path operation (read, write, subscribe notification, alarm event, history read, tag rediscovery, host connectivity probe), the system shall route the call through the capability interface owned by the target driver instance. Reads and subscriptions shall deliver a `DataValueSnapshot` carrying value, OPC UA `StatusCode`, and source timestamp regardless of the underlying protocol. Every async capability invocation at dispatch shall pass through `Core.Resilience.CapabilityInvoker`. -## HLR-006: Windows Service Hosting +## HLR-006: Change Detection and Rediscovery -The system shall run as a Windows service (via TopShelf) with support for install, uninstall, and interactive console modes. +Drivers whose backend has a native change signal (e.g. Galaxy's `time_of_last_deploy`, OPC UA Client receiving `ServerStatusChange`) shall implement the optional `IRediscoverable` interface so the core can rebuild only the affected subtree. Drivers whose tag set is static relative to a published config generation are not required to implement `IRediscoverable`; their address-space structure changes only via a new published Config DB generation (see HLR-012). -## HLR-007: Logging +## HLR-007: Service Hosting -The system shall log operational events to rolling daily log files using Serilog. +The system shall be deployed as three cooperating Windows services: -## HLR-008: Connection Resilience +- **OtOpcUa.Server** — .NET 10 x64, `Microsoft.Extensions.Hosting` + `AddWindowsService`, hosts all non-Galaxy drivers in-process and the OPC UA endpoint. +- **OtOpcUa.Admin** — .NET 10 x64 Blazor Server web app, hosts the admin UI, SignalR hubs for live updates, `/metrics` Prometheus endpoint, and audit log writers. +- **OtOpcUa.Galaxy.Host** — .NET Framework 4.8 x86 (TopShelf), hosts MXAccess COM + Galaxy Repository SQL + Historian plugin. Talks to `Driver.Galaxy.Proxy` inside `OtOpcUa.Server` via a named pipe (MessagePack over length-prefixed frames, per-process shared secret, SID-restricted ACL). -The system shall automatically reconnect to MXAccess after connection loss, replaying active subscriptions upon reconnect. +## HLR-008: Logging -## HLR-009: Status Dashboard +The system shall log operational events to rolling daily file sinks using Serilog on every process. Plain-text is on by default; structured JSON (CompactJsonFormatter) is opt-in via `Serilog:WriteJson = true` so SIEMs (Splunk, Datadog) can ingest without a regex parser. -The system shall host an embedded HTTP status dashboard (similar to the LmxProxy dashboard) providing at-a-glance operational visibility including connection state, health, subscription statistics, and operation metrics. +## HLR-009: Transport Security and Authentication + +The system shall support configurable OPC UA transport-security profiles (`None`, `Basic256Sha256-Sign`, `Basic256Sha256-SignAndEncrypt`, `Aes128_Sha256_RsaOaep-Sign`, `Aes128_Sha256_RsaOaep-SignAndEncrypt`, `Aes256_Sha256_RsaPss-Sign`, `Aes256_Sha256_RsaPss-SignAndEncrypt`) resolved at startup by `SecurityProfileResolver`. UserName-token authentication shall be validated against LDAP (production: Active Directory; dev: GLAuth). The server certificate is always created even for `None`-only deployments because UserName token encryption depends on it. + +## HLR-010: Per-Driver-Instance Resilience + +Every async capability call at dispatch shall pass through `Core.Resilience.CapabilityInvoker`, which runs a Polly v8 pipeline keyed on `(DriverInstanceId, HostName, DriverCapability)`. Retry and circuit-breaker strategies are per capability per decision #143: Read / Discover / Probe / Subscribe / AlarmSubscribe / HistoryRead retry automatically; Write and AlarmAcknowledge do **not** retry unless the tag or capability is explicitly marked with `WriteIdempotentAttribute`. A driver-instance circuit-breaker trip sets Bad quality on that instance's nodes only; other drivers are unaffected (decision #144 — per-host Polly isolation). + +## HLR-011: Config DB and Draft/Publish + +Cluster topology, driver instances, namespaces, UNS hierarchy, equipment, tags, node ACLs, poll groups, and role grants shall live in a central MSSQL Config DB, not in `appsettings.json`. Changes accumulate in a draft generation that is validated and then atomically published. Each published generation gets a monotonically increasing `GenerationNumber` scoped per cluster. Nodes poll the DB for new published generations and diff-apply surgically against an atomic snapshot. `appsettings.json` is reduced to bootstrap-only fields (Config DB connection, NodeId, ClusterId, LDAP, security profile, redundancy role, logging, local cache path). + +## HLR-012: Local Cache Fallback + +Each node shall maintain a sealed LiteDB local cache of the most recent successfully applied generation. If the central Config DB is unreachable at startup, the node shall boot from its cached generation and log a warning. Cache reads are the Polly `Fallback` leg of the Config DB pipeline. + +## HLR-013: Cluster Redundancy + +The system shall support non-transparent OPC UA redundancy via 2-node clusters sharing a Config DB generation. `RedundancyCoordinator` + `ServiceLevelCalculator` compute a dynamic OPC UA `ServiceLevel` reflecting role (Primary/Secondary), publish state (current generation applied vs mid-apply), health (driver circuit-breaker state), and apply-lease state. Clients select an endpoint by `ServerUriArray` + `ServiceLevel` per the OPC UA spec; there is no VIP or load balancer. Single-node deployments use the same model with `NodeCount = 1`. + +## HLR-014: Fleet-Wide Identifier Uniqueness + +Equipment identifiers that integrate with external systems (`ZTag` for ERP, `SAPID` for SAP PM) shall be unique fleet-wide (across all clusters), not just within a cluster. The Admin UI enforces this at draft-publish time via the `ExternalIdReservation` table, which reserves external IDs across clusters so two clusters cannot publish the same ZTag or SAPID. `EquipmentUuid` is immutable and globally unique (UUIDv4). `EquipmentId` and `MachineCode` are unique within a cluster. + +## HLR-015: Admin UI Operator Surface + +The system shall provide a Blazor Server Admin UI (`OtOpcUa.Admin`) as the sole write path into the Config DB. Capabilities include: cluster + node management, driver-instance CRUD with schemaless JSON editors, UNS drag-and-drop hierarchy editor, CSV-driven equipment import with fleet-wide external-id reservation, draft/publish with a 6-section diff viewer (Drivers / Namespaces / UNS / Equipment / Tags / ACLs), node-ACL editor producing a permission trie, LDAP role grants, redundancy tab, live cluster-generation state via SignalR, audit log viewer. Users authenticate via cookie-auth over LDAP bind; three admin roles (`ConfigViewer`, `ConfigEditor`, `FleetAdmin`) gate UI operations. + +## HLR-016: Audit Logging + +Every publish event and every ACL / role-grant change shall produce an immutable audit log row in the Config DB via `AuditLogService` with the acting principal, timestamp, action, before/after generation numbers, and affected entity ids. Audit rows are never mutated or deleted. + +## HLR-017: Prometheus Metrics + +The Admin service shall expose a `/metrics` endpoint using OpenTelemetry → Prometheus. Core / Server shall emit driver health (per `DriverInstanceId`), Polly circuit-breaker states (per `DriverInstanceId` + `HostName` + `DriverCapability`), capability-call duration histograms, subscription counts, session counts, memory-tracking gauges (Phase 6.1), publish durations, and Config-DB apply-status gauges. + +## HLR-018: Roslyn Analyzer OTOPCUA0001 + +All direct call sites to capability-interface methods (`IReadable.ReadAsync`, `IWritable.WriteAsync`, `ITagDiscovery.DiscoverAsync`, `ISubscribable.SubscribeAsync`, `IAlarmSource.SubscribeAlarmsAsync` / `AcknowledgeAsync`, `IHistoryProvider.*`, `IHostConnectivityProbe.*`) made outside `Core.Resilience.CapabilityInvoker` shall produce Roslyn diagnostic **OTOPCUA0001** at build time. The analyzer is shipped in `ZB.MOM.WW.OtOpcUa.Analyzers` and referenced by every project that could host a capability call, guaranteeing that resilience cannot be accidentally bypassed. + +## Retired HLRs + +- **HLR-009 (Status Dashboard)** — retired. Superseded by the Admin UI (HLR-015). See `docs/v2/admin-ui.md`. ## Component-Level Requirements Detailed requirements are broken out into the following documents: - [OPC UA Server Requirements](OpcUaServerReqs.md) -- [MXAccess Client Requirements](MxAccessClientReqs.md) -- [Galaxy Repository Requirements](GalaxyRepositoryReqs.md) -- [Service Host Requirements](ServiceHostReqs.md) -- [Status Dashboard Requirements](StatusDashboardReqs.md) +- [Galaxy Driver — Repository Requirements](GalaxyRepositoryReqs.md) (Galaxy driver only) +- [Galaxy Driver — MXAccess Client Requirements](MxAccessClientReqs.md) (Galaxy driver only) +- [Service Host Requirements](ServiceHostReqs.md) (all three processes) +- [Client Requirements](ClientRequirements.md) (Client CLI + Client UI) +- [Status Dashboard Requirements](StatusDashboardReqs.md) (retired — pointer only) -- 2.49.1 From 48970af41638ea0841a40597bcbbb3b925adbdf9 Mon Sep 17 00:00:00 2001 From: Joseph Doherty Date: Mon, 20 Apr 2026 01:31:58 -0400 Subject: [PATCH 2/5] =?UTF-8?q?Doc=20refresh=20(task=20#205)=20=E2=80=94?= =?UTF-8?q?=20requirements=20updated=20for=20multi-driver=20OtOpcUa=20thre?= =?UTF-8?q?e-process=20deploy?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Per-file summary: - docs/reqs/OpcUaServerReqs.md — rewritten driver-agnostic. OPC-001..OPC-013 re-scoped to multi-driver address-space composition + capability dispatch; OPC-014 AuthorizationGate + permission trie; OPC-015 dynamic ServiceLevel via RedundancyCoordinator; OPC-017 surgical generation-apply rebuild; OPC-012 capability dispatch via CapabilityInvoker (decision #143 idempotence-aware retry); OPC-013 per-host Polly isolation (decision #144); OPC-019 OpenTelemetry metrics. Transport-security profile matrix (OPC-010) + UserName/LDAP (OPC-011) preserved. - docs/reqs/GalaxyRepositoryReqs.md — scope clarified as Galaxy-driver-only (not platform). GR-001..GR-004 tied to ITagDiscovery.DiscoverAsync + IRediscoverable; all SQL runs inside OtOpcUa.Galaxy.Host and streams to Proxy via named pipe. GR-008 capability wrapping via CapabilityInvoker added. Cross-links to docs/v2/driver-specs.md + docs/GalaxyRepository.md. - docs/reqs/MxAccessClientReqs.md — scope clarified as Galaxy-Host-only. MXA-001..MXA-009 preserved (STA pump, register/unregister, subscription refcount, auto-reconnect, probe, COM cleanup, operation metrics, error translation). MXA-010 Proxy-side capability wrapping + MXA-011 pipe ACL + per-process shared secret (OTOPCUA_ALLOWED_SID / OTOPCUA_GALAXY_SECRET) added. - docs/reqs/ServiceHostReqs.md — rewritten for three-process deployment. Shared section (SVC-SHARED-001/002) for Serilog + bootstrap-only appsettings. SRV-* for OtOpcUa.Server (net10 x64, Microsoft.Extensions.Hosting + AddWindowsService, in-process driver hosting, redundancy-node bootstrap). ADM-* for OtOpcUa.Admin (Blazor Server, cookie+LDAP auth, CanEdit/CanPublish policies, sole DB writer, Prometheus /metrics, audit logging). GHX-* for OtOpcUa.Galaxy.Host (TopShelf, net48 x86, named-pipe IPC bootstrap, STA backend lifecycle, crash handling tied to supervisor). - docs/reqs/ClientRequirements.md — restructured as numbered, verifiable requirements. SHR-* for Client.Shared (single IOpcUaClientService, ConnectionSettings, failover, cross-platform certs, type-coercing write, UI-thread neutrality). CLI-001..CLI-011 cover connect/read/write/browse/subscribe/historyread/alarms/redundancy. UI-001..UI-008 cover connection panel, tree browser, each tab, connection-state reflection, cross-platform build. Reference design content (IOpcUaClientService shape, models, view-model map, mock layout) preserved. - docs/reqs/StatusDashboardReqs.md — retired cleanly. Replaced with a pointer to docs/v2/admin-ui.md + HLR-015 / HLR-016 / HLR-017 / ADM-*. Mapping table shows each retired DASH-001..DASH-009 requirement's replacement (live cluster-node view via SignalR, Prometheus metrics, driver-instance detail views, etc.). Note that a formal AdminUiReqs.md can be written later if needed for cert compliance. HighLevelReqs.md was already at the target shape (HLR-001..HLR-018 with Revision header noting retired HLR-009) as of commit f217636; verified identical and no additional edit required. Co-Authored-By: Claude Opus 4.7 (1M context) --- docs/reqs/ClientRequirements.md | 326 +++++++++++++++--------------- docs/reqs/GalaxyRepositoryReqs.md | 104 ++++++---- docs/reqs/MxAccessClientReqs.md | 159 +++++++++------ docs/reqs/OpcUaServerReqs.md | 308 +++++++++++++++------------- docs/reqs/ServiceHostReqs.md | 314 ++++++++++++++++++++-------- docs/reqs/StatusDashboardReqs.md | 172 ++-------------- 6 files changed, 739 insertions(+), 644 deletions(-) diff --git a/docs/reqs/ClientRequirements.md b/docs/reqs/ClientRequirements.md index c9bd0ab..277469a 100644 --- a/docs/reqs/ClientRequirements.md +++ b/docs/reqs/ClientRequirements.md @@ -1,8 +1,10 @@ # OPC UA Client Requirements -## Overview +> **Revision** — Refreshed 2026-04-19 for the OtOpcUa v2 multi-driver platform (task #205). The Client surface (shared library + CLI + UI) shipped for v2 is preserved; this refresh restructures the document into numbered, directly-verifiable requirements (CLI-* and UI-* prefixes) layered on top of the existing detailed design content. Requirement coverage added for the `redundancy` command, alarm subscribe/ack round-trip, history-read, and UI tree-browser drag-to-subscribe behaviors. Original design-spec material for `ConnectionSettings`, `IOpcUaClientService`, models, and view-models is retained as reference-level details below the numbered requirements. -Three new .NET 10 cross-platform projects providing a shared OPC UA client library, a CLI tool, and an Avalonia desktop UI. All projects target Windows and macOS. +Parent: [HLR-001](HighLevelReqs.md#hlr-001-opc-ua-server), [HLR-009](HighLevelReqs.md#hlr-009-transport-security-and-authentication), [HLR-013](HighLevelReqs.md#hlr-013-cluster-redundancy) + +See also: `docs/Client.CLI.md`, `docs/Client.UI.md`. ## Projects @@ -10,134 +12,161 @@ Three new .NET 10 cross-platform projects providing a shared OPC UA client libra |---------|------|---------| | `ZB.MOM.WW.OtOpcUa.Client.Shared` | Class library | Core OPC UA client, models, interfaces | | `ZB.MOM.WW.OtOpcUa.Client.CLI` | Console app | Command-line interface using CliFx | -| `ZB.MOM.WW.OtOpcUa.Client.UI` | Avalonia app | Desktop UI with tree browser, subscriptions, alarms | -| `ZB.MOM.WW.OtOpcUa.Client.Shared.Tests` | Test project | Unit tests for shared library | -| `ZB.MOM.WW.OtOpcUa.Client.CLI.Tests` | Test project | Unit tests for CLI commands | -| `ZB.MOM.WW.OtOpcUa.Client.UI.Tests` | Test project | Unit tests for UI view models | +| `ZB.MOM.WW.OtOpcUa.Client.UI` | Avalonia app | Desktop UI | +| `ZB.MOM.WW.OtOpcUa.Client.Shared.Tests` | Test project | Shared-library unit tests | +| `ZB.MOM.WW.OtOpcUa.Client.CLI.Tests` | Test project | CLI command tests | +| `ZB.MOM.WW.OtOpcUa.Client.UI.Tests` | Test project | ViewModel unit tests | + +## Shared Requirements (Client.Shared) + +### SHR-001: Single Service Interface + +The Client.Shared library shall expose a single service interface `IOpcUaClientService` covering connect, disconnect, read, write, browse, subscribe, alarm-subscribe, alarm-ack, history-read-raw, history-read-aggregate, and get-redundancy-info operations. + +### SHR-002: ConnectionSettings Model + +The library shall expose a `ConnectionSettings` record with the fields: `EndpointUrl` (required), `FailoverUrls[]`, `Username`, `Password`, `SecurityMode` (None/Sign/SignAndEncrypt; default None), `SessionTimeoutSeconds` (default 60), `AutoAcceptCertificates` (default true), `CertificateStorePath`. + +### SHR-003: Automatic Failover + +The library shall monitor session keep-alive and automatically fail over across `FailoverUrls` when the primary endpoint is unreachable, emitting a `ConnectionStateChanged` event on each transition (Disconnected / Connecting / Connected / Reconnecting). + +### SHR-004: Cross-Platform Certificate Store + +The library shall auto-generate a client certificate on first use and store it in a cross-platform path (default `{AppData}/OtOpcUaClient/pki/`). Server certificates are auto-accepted when `AutoAcceptCertificates = true`. + +### SHR-005: Type-Coercing Write + +The library's `WriteValueAsync(NodeId, object)` shall read the node's current value to determine target type and coerce the input value before sending. + +### SHR-006: UI-Thread Dispatch Neutrality + +The library shall not assume any specific synchronization context. Events (`DataChanged`, `AlarmEvent`, `ConnectionStateChanged`) are raised on the OPC UA stack thread; the consuming CLI / UI is responsible for dispatching to its UI thread. + +--- + +## CLI Requirements (Client.CLI) + +### CLI-001: Command Surface + +The CLI shall expose the following commands: `connect`, `read`, `write`, `browse`, `subscribe`, `historyread`, `alarms`, `redundancy`. + +### CLI-002: Common Options + +All CLI commands shall accept the options `-u, --url` (required), `-U, --username`, `-P, --password`, `-S, --security none|sign|encrypt`, `-F, --failover-urls` (comma-separated), `--verbose`. + +### CLI-003: Connect Command + +The `connect` command shall attempt to establish a session using the supplied options and print `Connected` plus the resolved endpoint's `ServerUriArray` and `ApplicationUri` on success, or a diagnostic error message on failure. + +### CLI-004: Read Command + +The `read -n ` command shall print `NodeId`, `Value`, `StatusCode`, `SourceTimestamp`, `ServerTimestamp` one per line. + +### CLI-005: Write Command + +The `write -n -v ` command shall coerce the value to the node's current type (per SHR-005) and print the resulting `StatusCode`. A `Bad_UserAccessDenied` result is printed verbatim so operators see the authorization outcome. + +### CLI-006: Browse Command + +The `browse [-n ] [-r] [-d ]` command shall list child nodes under `parent` (or the `Objects` folder if omitted). `-r` enables recursion up to `-d` depth (default 1). + +### CLI-007: Subscribe Command + +The `subscribe -n -i ` command shall create a monitored item at `intervalMs` publishing interval, print each `DataChanged` event as ` ` until Ctrl-C, then cleanly unsubscribe. + +### CLI-008: Historyread Command + +The `historyread -n --start --end [--max ] [--aggregate --interval ]` command shall print raw values or aggregate buckets. Supported aggregate types: Average, Minimum, Maximum, Count, Start, End. + +### CLI-009: Alarms Command + +The `alarms [-n ] [-i ]` command shall subscribe to alarm events, print each event as `