fix(historian): correct AlarmHistorian config-key refs in docs + install (review)
v2-ci / build (push) Failing after 43s
v2-ci / unit-tests (tests/Core/ZB.MOM.WW.OtOpcUa.Cluster.Tests) (push) Has been skipped
v2-ci / unit-tests (tests/Server/ZB.MOM.WW.OtOpcUa.ControlPlane.Tests) (push) Has been skipped
v2-ci / unit-tests (tests/Server/ZB.MOM.WW.OtOpcUa.OpcUaServer.Tests) (push) Has been skipped
v2-ci / unit-tests (tests/Server/ZB.MOM.WW.OtOpcUa.Runtime.Tests) (push) Has been skipped
v2-ci / unit-tests (tests/Server/ZB.MOM.WW.OtOpcUa.Security.Tests) (push) Has been skipped
v2-ci / integration (tests/Server/ZB.MOM.WW.OtOpcUa.Host.IntegrationTests) (push) Has been skipped
v2-ci / integration (tests/Server/ZB.MOM.WW.OtOpcUa.OpcUaServer.IntegrationTests) (push) Has been skipped

This commit is contained in:
Joseph Doherty
2026-06-12 12:25:13 -04:00
parent fcf84adbad
commit 1be06502c7
8 changed files with 25 additions and 20 deletions
+4 -4
View File
@@ -196,10 +196,10 @@ When `Enabled` is `false` (the default), `AddAlarmHistorian` registers
| `DatabasePath` | string | — | Absolute path to the SQLite queue file. Created on first use (WAL mode). Required when `Enabled`. |
| `SharedSecret` | string | — | Shared secret token the sidecar expects on every connection. Required when `Enabled`. |
| `BatchSize` | int | `100` | Max rows per drain cycle handed to `IAlarmHistorianWriter.WriteBatchAsync`. |
| `Historian:Wonderware:Host` | string | `localhost` | DNS name or IP of the machine running the historian sidecar. |
| `Historian:Wonderware:Port` | int | `32569` | TCP port the sidecar listens on (`OTOPCUA_HISTORIAN_TCP_PORT`). |
| `Historian:Wonderware:UseTls` | bool | `false` | Wrap the TCP stream in TLS before the Hello handshake. |
| `Historian:Wonderware:ServerCertThumbprint` | string | — | Optional SHA-1 thumbprint to pin the sidecar's TLS server certificate. Leave empty to use normal CA-chain validation. |
| `AlarmHistorian:Host` | string | `localhost` | DNS name or IP of the machine running the historian sidecar. |
| `AlarmHistorian:Port` | int | `32569` | TCP port the sidecar listens on (`OTOPCUA_HISTORIAN_TCP_PORT`). |
| `AlarmHistorian:UseTls` | bool | `false` | Wrap the TCP stream in TLS before the Hello handshake. |
| `AlarmHistorian:ServerCertThumbprint` | string | — | Optional SHA-1 thumbprint to pin the sidecar's TLS server certificate. Leave empty to use normal CA-chain validation. |
> Dev and docker-dev deployments leave `Enabled` unset (defaults to `false`) so alarm transitions historize to nowhere unless a historian sidecar is present.
+10 -5
View File
@@ -119,9 +119,11 @@ The Galaxy/MxAccess connection settings are **not an `appsettings` section.** Th
> The `OTOPCUA_GALAXY_*` environment variables that v1's in-process `Galaxy.Host` consumed **no longer live in this repo** — they moved into the separately-installed mxaccessgw gateway's own config (see the v1 archive pointer in `docs/README.md` and the Galaxy overview at [`docs/drivers/Galaxy.md`](drivers/Galaxy.md)). The only Galaxy connection secret this repo touches is the gateway API key via `ApiKeySecretRef` above.
### Historian config (env-driven sidecar)
### Historian config (TCP sidecar)
The Wonderware Historian runs as a supervised sidecar process whose configuration arrives **entirely through environment variables**, not an `appsettings` section. The sidecar entry point (`src/Drivers/ZB.MOM.WW.OtOpcUa.Driver.Historian.Wonderware/Program.cs`) reads them at spawn time. See the `OTOPCUA_HISTORIAN_*` rows in the environment-variable table below. The in-process client-side options POCO is `WonderwareHistorianClientOptions` (`src/Drivers/ZB.MOM.WW.OtOpcUa.Driver.Historian.Wonderware.Client.Contracts/WonderwareHistorianClientOptions.cs`): `PipeName`, `SharedSecret`, `PeerName` (`OtOpcUa`), `ConnectTimeout` (default 10s), `CallTimeout` (default 30s), `ProbeTimeoutSeconds` (`15`).
The Wonderware Historian sidecar (`OtOpcUaWonderwareHistorian`) is an independent Windows service that the OtOpcUa host connects to over TCP. It is **not** spawned as a child process by the host — the two services are started independently (e.g. by NSSM / `sc.exe`). The sidecar entry point (`src/Drivers/ZB.MOM.WW.OtOpcUa.Driver.Historian.Wonderware/Program.cs`) reads its configuration from environment variables; the OtOpcUa host side reads the `AlarmHistorian` appsettings section. See the `OTOPCUA_HISTORIAN_*` rows in the environment-variable table below.
The in-process **client-side** options POCO is `WonderwareHistorianClientOptions` (`src/Drivers/ZB.MOM.WW.OtOpcUa.Driver.Historian.Wonderware.Client.Contracts/WonderwareHistorianClientOptions.cs`), bound from the `AlarmHistorian` section: `Host`, `Port`, `UseTls`, `ServerCertThumbprint`, `SharedSecret`, `ConnectTimeout` (default 10s), `CallTimeout` (default 30s), `ProbeTimeoutSeconds` (`15`).
---
@@ -135,7 +137,6 @@ All names are read in this repo's source via `Environment.GetEnvironmentVariable
|---|---|---|
| `OTOPCUA_ROLES` | `src/Server/ZB.MOM.WW.OtOpcUa.Host/Program.cs` (`RoleParser.Parse`) | Comma-separated cluster roles for the node (`admin`, `driver`, `dev`). Drives the conditional wiring and the per-role appsettings overlay. Used when `Cluster:Roles` is empty. |
| `OTOPCUA_CONFIG_CONNECTION` | `src/Core/ZB.MOM.WW.OtOpcUa.Configuration/DesignTimeDbContextFactory.cs` (design-time / `dotnet ef` only) | Read at **design time** by `DesignTimeDbContextFactory.cs` for `dotnet ef` migrations. At **runtime** the server resolves the connection string from `ConnectionStrings:ConfigDb` (env form: `ConnectionStrings__ConfigDb`) via `configuration.GetConnectionString("ConfigDb")` in `ServiceCollectionExtensions.cs``OTOPCUA_CONFIG_CONNECTION` appears there only as a hint in an error message, not via `GetEnvironmentVariable`. No credential is embedded in source. |
| `OTOPCUA_ALLOWED_SID` | `src/Drivers/ZB.MOM.WW.OtOpcUa.Driver.Historian.Wonderware/Program.cs` | SID of the server principal allowed to connect to the historian sidecar's named pipe (passed by the supervisor at spawn). Required — sidecar throws if unset. |
| `ASPNETCORE_ENVIRONMENT` | ASP.NET host builder (framework) | Selects `appsettings.{Environment}.json` (e.g. `Development`). |
### Historian sidecar (`OTOPCUA_HISTORIAN_*`)
@@ -144,8 +145,12 @@ All read in `src/Drivers/ZB.MOM.WW.OtOpcUa.Driver.Historian.Wonderware/Program.c
| Variable | Effect / default |
|---|---|
| `OTOPCUA_HISTORIAN_PIPE` | Named-pipe name the sidecar listens on. Required (throws if unset). |
| `OTOPCUA_HISTORIAN_SECRET` | Per-process shared secret verified in the pipe Hello frame. Required (throws if unset). |
| `OTOPCUA_HISTORIAN_TCP_PORT` | TCP port the sidecar listens on. Default `32569`. Corresponds to `AlarmHistorian:Port` on the host side. |
| `OTOPCUA_HISTORIAN_BIND` | TCP bind address for the sidecar. Default `0.0.0.0`. |
| `OTOPCUA_HISTORIAN_TLS_ENABLED` | `true` enables TLS on the sidecar's TCP listener. Default `false`. Corresponds to `AlarmHistorian:UseTls` on the host side. |
| `OTOPCUA_HISTORIAN_TLS_CERT` | PFX file path **or** `LocalMachine\My\<thumbprint>` to load the sidecar TLS server certificate from the machine store. |
| `OTOPCUA_HISTORIAN_TLS_CERT_PASSWORD` | Password for a PFX-file certificate. Omit when using a machine-store cert. Never commit a value. |
| `OTOPCUA_HISTORIAN_SECRET` | Per-process shared secret verified in the TCP Hello frame. Required (throws if unset). Corresponds to `AlarmHistorian:SharedSecret` on the host side. |
| `OTOPCUA_HISTORIAN_ENABLED` | `true` opens the real Wonderware SDK connection; anything else → pipe-only mode (smoke/IPC tests). Default: not-true → pipe-only. |
| `OTOPCUA_HISTORIAN_ALARM_WRITE_ENABLED` | `false` disables the alarm-event writer (sidecar rejects `WriteAlarmEvents`). Default `true` (when `ENABLED=true`). |
| `OTOPCUA_HISTORIAN_INTEGRATED` | `false` → SQL auth (use `USER`/`PASS`); any other value → integrated security. Default: integrated. |
+2 -2
View File
@@ -234,8 +234,8 @@ Implementations:
- `WonderwareHistorianClient`
([`Driver.Historian.Wonderware.Client/WonderwareHistorianClient.cs`](../src/Drivers/ZB.MOM.WW.OtOpcUa.Driver.Historian.Wonderware.Client/WonderwareHistorianClient.cs))
— the .NET 10 client that talks to the Wonderware historian sidecar over a
named pipe. It implements both `IHistorianDataSource` (read paths) and
— the .NET 10 client that talks to the Wonderware historian sidecar over
TCP (optional TLS). It implements both `IHistorianDataSource` (read paths) and
`IAlarmHistorianWriter` (the alarm-event drain target; see
[AlarmHistorian.md](AlarmHistorian.md)).
- `HistorianDataSource`
+2 -2
View File
@@ -7,7 +7,7 @@ A production OtOpcUa deployment runs **one binary per node**, plus the optional
| Process | Project | Runtime | Platform | Responsibility |
|---|---|---|---|---|
| **OtOpcUa Host** | `src/Server/ZB.MOM.WW.OtOpcUa.Host` | .NET 10 | AnyCPU | Single fused binary. `OTOPCUA_ROLES` env decides what to mount: `admin` (Blazor + auth + control-plane singletons), `driver` (OPC UA endpoint + per-driver actors), or both. |
| **OtOpcUa Wonderware Historian** *(optional)* | `src/Drivers/ZB.MOM.WW.OtOpcUa.Driver.Historian.Wonderware` | .NET Framework 4.8 | x64 (64-bit) | Out-of-process sidecar exposing the Wonderware Historian SDK over TCP (optional TLS). Required only when `Historian:Wonderware:Enabled=true`. May run on the same machine or a remote host. |
| **OtOpcUa Wonderware Historian** *(optional)* | `src/Drivers/ZB.MOM.WW.OtOpcUa.Driver.Historian.Wonderware` | .NET Framework 4.8 | x64 (64-bit) | Out-of-process sidecar exposing the Wonderware Historian SDK over TCP (optional TLS). Required only when `AlarmHistorian:Enabled=true`. May run on the same machine or a remote host. |
Galaxy access still uses the separately-installed **mxaccessgw** sidecar (see `docs/v2/Galaxy.ParityRig.md`); the gateway owns the MXAccess COM bitness constraint (its worker is x86 net48). Nothing in the OtOpcUa repo carries that constraint anymore.
@@ -70,7 +70,7 @@ Used by Traefik for the active-leader-only routing pattern (see [Architecture-v2
## OtOpcUa Wonderware Historian (optional)
IPC contract types live in `src/Drivers/ZB.MOM.WW.OtOpcUa.Driver.Historian.Wonderware.Client.Contracts/`; sidecar TCP server in `src/Drivers/ZB.MOM.WW.OtOpcUa.Driver.Historian.Wonderware/Ipc/`. The sidecar listens on TCP port 32569 by default; `Install-Services.ps1 -InstallWonderwareHistorian` adds the Windows Firewall inbound rule. The host and sidecar may run on different machines — configure `Historian:Wonderware:Host` + `Port` (and optionally `UseTls`) on the OtOpcUa host side. See [Historian.Wonderware.md](drivers/Historian.Wonderware.md) for the full transport and security reference.
IPC contract types live in `src/Drivers/ZB.MOM.WW.OtOpcUa.Driver.Historian.Wonderware.Client.Contracts/`; sidecar TCP server in `src/Drivers/ZB.MOM.WW.OtOpcUa.Driver.Historian.Wonderware/Ipc/`. The sidecar listens on TCP port 32569 by default; `Install-Services.ps1 -InstallWonderwareHistorian` adds the Windows Firewall inbound rule. The host and sidecar may run on different machines — configure `AlarmHistorian:Host` + `AlarmHistorian:Port` (and optionally `AlarmHistorian:UseTls`) on the OtOpcUa host side. See [Historian.Wonderware.md](drivers/Historian.Wonderware.md) for the full transport and security reference.
## Install / Uninstall
+2 -2
View File
@@ -4,7 +4,7 @@ The Wonderware Historian backend is **not a tag driver** — it has no address
space, no `IDriver` lifecycle, and exposes no PLC. It is a **server-side
historian sink**: an optional sidecar that gives OtOpcUa read access to AVEVA
System Platform (Wonderware) Historian history and a write-back path for alarm
events. It runs only when `Historian:Wonderware:Enabled=true`.
events. It runs only when `AlarmHistorian:Enabled=true`.
The host connects to the sidecar over **TCP** (plaintext in dev, optional TLS
in prod), so the OtOpcUa host no longer needs to be on the same machine as the
@@ -55,7 +55,7 @@ Set `OTOPCUA_HISTORIAN_TLS_ENABLED=true` on the sidecar and supply the server
certificate via `OTOPCUA_HISTORIAN_TLS_CERT` (PFX file path, or
`LocalMachine\My\<thumbprint>` for a cert already in the machine store) and
`OTOPCUA_HISTORIAN_TLS_CERT_PASSWORD` if the PFX is password-protected. On the
client/host side set `Historian:Wonderware:UseTls=true`; optionally set
client/host side set `AlarmHistorian:UseTls=true`; optionally set
`ServerCertThumbprint` to pin the server certificate's SHA-1 thumbprint instead
of relying on normal CA-chain validation.
+2 -2
View File
@@ -84,7 +84,7 @@ param(
[int]$HttpPort = 9000,
# Wonderware historian sidecar. Optional; gates the OtOpcUaWonderwareHistorian
# service. Secret defaults match the server's Historian:Wonderware appsettings.
# service. Secret defaults match the server's AlarmHistorian appsettings.
[switch]$InstallWonderwareHistorian,
[string]$HistorianSharedSecret,
[string]$HistorianServer = 'localhost',
@@ -221,7 +221,7 @@ if ($InstallWonderwareHistorian) { Write-Host " sc.exe start OtOpcUaWonderwareH
Write-Host " sc.exe start OtOpcUaHost"
if ($InstallWonderwareHistorian) {
Write-Host ""
Write-Host "Wonderware historian shared secret (configure into appsettings.json Historian:Wonderware:SharedSecret):"
Write-Host "Wonderware historian shared secret (configure into appsettings.json AlarmHistorian:SharedSecret):"
Write-Host " $HistorianSharedSecret"
}
Write-Host ""
+1 -1
View File
@@ -147,7 +147,7 @@ if (-not $WhatIf) {
if ($missing.Count -gt 0) {
$msg = "Wonderware historian sidecar deploy at '$sidecarDir' is INCOMPLETE — missing: " +
"$($missing -join ', '). It would crash-loop with a FileNotFoundException in PipeServer. " +
"$($missing -join ', '). It would crash-loop with a FileNotFoundException in TcpFrameServer. " +
"Re-run the sidecar 'dotnet publish' into that folder (Step 4) — never hand-copy a partial build output."
throw $msg
}
@@ -16,9 +16,9 @@ namespace ZB.MOM.WW.OtOpcUa.Runtime.Historian;
/// Galaxy native alarm bridge, AB CIP ALMD reader) tells <see cref="AlarmHistorianEvent"/>s to this
/// actor; the actor enqueues them on the sink fire-and-forget. Production deployments register
/// <see cref="SqliteStoreAndForwardSink"/> against <c>IAlarmHistorianSink</c>; the sink owns the
/// durable queue + drain-to-Wonderware-pipe loop. The actor here owns nothing operational beyond
/// durable queue + drain-to-Wonderware-TCP-channel loop. The actor here owns nothing operational beyond
/// the message contract — its job is to keep the engine actors on Akka's mailbox without blocking
/// them on disk I/O or pipe handshakes.
/// them on disk I/O or TCP channel handshakes.
///
/// Query queue depth + drain health via <see cref="GetStatus"/>.
/// </summary>