docs(historian-gateway): document gateway backend, config keys, EnsureTags hook, known gates; retire Wonderware from docs
v2-ci / build (pull_request) Failing after 38s
v2-ci / unit-tests (tests/Core/ZB.MOM.WW.OtOpcUa.Cluster.Tests) (pull_request) Has been skipped
v2-ci / unit-tests (tests/Server/ZB.MOM.WW.OtOpcUa.ControlPlane.Tests) (pull_request) Has been skipped
v2-ci / unit-tests (tests/Server/ZB.MOM.WW.OtOpcUa.OpcUaServer.Tests) (pull_request) Has been skipped
v2-ci / unit-tests (tests/Server/ZB.MOM.WW.OtOpcUa.Runtime.Tests) (pull_request) Has been skipped
v2-ci / unit-tests (tests/Server/ZB.MOM.WW.OtOpcUa.Security.Tests) (pull_request) Has been skipped
v2-ci / integration (tests/Server/ZB.MOM.WW.OtOpcUa.Host.IntegrationTests) (pull_request) Has been skipped
v2-ci / integration (tests/Server/ZB.MOM.WW.OtOpcUa.OpcUaServer.IntegrationTests) (pull_request) Has been skipped
v2-ci / build (pull_request) Failing after 38s
v2-ci / unit-tests (tests/Core/ZB.MOM.WW.OtOpcUa.Cluster.Tests) (pull_request) Has been skipped
v2-ci / unit-tests (tests/Server/ZB.MOM.WW.OtOpcUa.ControlPlane.Tests) (pull_request) Has been skipped
v2-ci / unit-tests (tests/Server/ZB.MOM.WW.OtOpcUa.OpcUaServer.Tests) (pull_request) Has been skipped
v2-ci / unit-tests (tests/Server/ZB.MOM.WW.OtOpcUa.Runtime.Tests) (pull_request) Has been skipped
v2-ci / unit-tests (tests/Server/ZB.MOM.WW.OtOpcUa.Security.Tests) (pull_request) Has been skipped
v2-ci / integration (tests/Server/ZB.MOM.WW.OtOpcUa.Host.IntegrationTests) (pull_request) Has been skipped
v2-ci / integration (tests/Server/ZB.MOM.WW.OtOpcUa.OpcUaServer.IntegrationTests) (pull_request) Has been skipped
HistorianGateway is now the sole historian backend (read + alarm SendEvent + continuous WriteLiveValues). Document the final state and retire the Wonderware sidecar from the docs/config/labels: - CLAUDE.md: rewrite the Historian section — ServerHistorian / ContinuousHistorization / AlarmHistorian config keys, the IHistorianProvisioning EnsureTags hook, the GatewayAlarmHistorianWriter SendEvent path + ReadEvents dependency on gateway RuntimeDb:EventReadsEnabled=true, gateway-side prerequisites (RuntimeDb flags + historian:read/write/tags:write scopes), migration note, and two KNOWN-LIMITATION callouts (live-validation gate + empty historized-ref-set recorder follow-on). - appsettings.json: fix the stale ServerHistorian block (Host/Port/SharedSecret/ ServerCertThumbprint -> Endpoint/ApiKey/UseTls/AllowUntrustedServerCertificate/ CaCertificatePath/CallTimeout, keep MaxTieClusterOverfetch); add a disabled ContinuousHistorization block; prune the orphaned Wonderware keys from AlarmHistorian (keep the SQLite knobs). ApiKey env-supplied via ServerHistorian__ApiKey (commented; valid strict JSON via _comment keys). - README.md + docs (Historian.md, AlarmHistorian.md, Configuration.md, ServiceHosting.md, DriverLifecycle.md, drivers/README.md, Uns.md, VirtualTags.md, AlarmTracking.md, Client.UI.md, README.md, TestConnectProbes.md): retire the Wonderware historian backend from current-backend descriptions; fix the stale ServerHistorian/AlarmHistorian config tables (now gateway shape); convert drivers/Historian.Wonderware.md to a retired stub pointing at the gateway. - Source/UI labels (descriptive text only, no behavior change): OtOpcUaServerHostedService.cs, HistoryPaging.cs, OtOpcUaSdkServer.cs, HistorianAdapterActor.cs, VirtualTagModal.razor, ScriptedAlarmModal.razor, AlarmsHistorian.razor now name the HistorianGateway backend. Build clean (0 errors); AdminUI.Tests green (514 passed). Claude-Session: https://claude.ai/code/session_012SDSQ3AcaXqPcBtDESBRii
This commit is contained in:
@@ -1,156 +1,46 @@
|
||||
# Wonderware Historian Backend
|
||||
# Wonderware Historian Backend — RETIRED
|
||||
|
||||
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 `AlarmHistorian:Enabled=true`.
|
||||
> **This backend has been retired.** The bespoke Wonderware TCP/ArchestrA historian sidecar
|
||||
> (`OtOpcUaWonderwareHistorian`) and its `Driver.Historian.Wonderware*` projects — plus the vestigial
|
||||
> `Historian.Wonderware` driver type — were removed. **HistorianGateway is now the sole historian
|
||||
> backend** for OtOpcUa (read, alarm-write, and continuous historization).
|
||||
|
||||
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
|
||||
sidecar — a remote host on a different VM is fully supported.
|
||||
## What replaced it
|
||||
|
||||
For the sidecar's place in a deployment, see
|
||||
[ServiceHosting.md](../ServiceHosting.md). For the alarm-history store-and-forward
|
||||
flow that drains into it, see [AlarmHistorian.md](../AlarmHistorian.md).
|
||||
OtOpcUa now consumes the **`ZB.MOM.WW.HistorianGateway`** sidecar through the Gitea-feed
|
||||
**`ZB.MOM.WW.HistorianGateway.Client`** gRPC package (`historian_gateway.v1`), behind the
|
||||
`IHistorianGatewayClient` seam in `ZB.MOM.WW.OtOpcUa.Driver.Historian.Gateway`:
|
||||
|
||||
## Architecture
|
||||
- **HistoryRead** → `GatewayHistorianDataSource` over the `ServerHistorian` appsettings section.
|
||||
- **Alarm history** → `GatewayAlarmHistorianWriter` (the gateway `SendEvent` path) behind the durable
|
||||
`SqliteStoreAndForwardSink`; alarm-history `ReadEvents` needs the gateway running
|
||||
`RuntimeDb:EventReadsEnabled=true`.
|
||||
- **Continuous historization** → a crash-safe FasterLog outbox + `ContinuousHistorizationRecorder`
|
||||
draining to the gateway's `WriteLiveValues` (`ContinuousHistorization` section); needs the gateway
|
||||
running `RuntimeDb:Enabled=true`.
|
||||
- **Tag provisioning** → `AddressSpaceApplier` fires a non-blocking `IHistorianProvisioning` `EnsureTags`
|
||||
hook for added historized tags.
|
||||
|
||||
```
|
||||
+-------------------------------------------+
|
||||
| OtOpcUa Host (.NET 10 AnyCPU) |
|
||||
| Server.History.IHistoryRouter --read--+--+
|
||||
| Core.AlarmHistorian.SqliteStore | |
|
||||
| AndForwardSink --write----+--+
|
||||
| WonderwareHistorianClient (.NET 10) | |
|
||||
+-------------------------------------------+ |
|
||||
| TCP (optional TLS)
|
||||
MessagePack frames | shared-secret Hello auth
|
||||
v
|
||||
+-------------------------------------------+
|
||||
| OtOpcUaWonderwareHistorian (sidecar) |
|
||||
| net48 / x64 |
|
||||
| TcpFrameServer + HistorianFrameHandler |
|
||||
| HistorianDataSource (reads) |
|
||||
| SdkAlarmHistorianWriteBackend (writes) |
|
||||
| aahClientManaged / HistorianAccess |
|
||||
+-------------------------------------------+
|
||||
```
|
||||
The gateway API key must carry the scopes `historian:read`, `historian:write`, `historian:tags:write`.
|
||||
|
||||
The split exists because the AVEVA Historian SDK (`aahClientManaged` +
|
||||
native `aahClient.dll`) is .NET Framework 4.8 / x64 — so it lives out-of-process
|
||||
in the sidecar, and everything in the OtOpcUa host stays .NET 10 AnyCPU. The
|
||||
host never references the SDK; it speaks the TCP contract only. Because the
|
||||
transport is TCP, the host and sidecar can run on different machines.
|
||||
## Where to read now
|
||||
|
||||
### Transport & security
|
||||
- **[../Historian.md](../Historian.md)** — the full historian guide (read path, alarm path, continuous
|
||||
historization, config keys, migration note).
|
||||
- **[README.md](README.md)** — driver / back-end overview.
|
||||
- **[../ServiceHosting.md](../ServiceHosting.md)** — deployment (the historian backend is the external
|
||||
HistorianGateway, not an installed sidecar).
|
||||
|
||||
The sidecar listens on a configurable TCP port (`OTOPCUA_HISTORIAN_TCP_PORT`,
|
||||
default **32569**) and bind address (`OTOPCUA_HISTORIAN_BIND`, default `0.0.0.0`).
|
||||
`Install-Services.ps1` adds a Windows Firewall inbound rule for the port
|
||||
automatically.
|
||||
## Migration
|
||||
|
||||
**TLS (optional, recommended for cross-machine deployments):**
|
||||
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 `AlarmHistorian:UseTls=true`; optionally set
|
||||
`ServerCertThumbprint` to pin the server certificate's SHA-1 thumbprint instead
|
||||
of relying on normal CA-chain validation.
|
||||
Deployments that carried the old `ServerHistorian` Wonderware keys must rename them:
|
||||
|
||||
**Shared secret (required in all modes):**
|
||||
Regardless of whether TLS is on, the client always sends a `Hello` frame
|
||||
carrying the `SharedSecret`; the sidecar rejects connections where the secret
|
||||
does not match. The Windows-SID pipe ACL from the previous named-pipe transport
|
||||
is replaced by this combination of TLS + shared secret.
|
||||
| Old (Wonderware) key | New (gateway) key |
|
||||
|---|---|
|
||||
| `ServerHistorian:Host` + `:Port` | `ServerHistorian:Endpoint` (`https://host:5222`) |
|
||||
| `ServerHistorian:SharedSecret` | `ServerHistorian:ApiKey` (supply via env `ServerHistorian__ApiKey`) |
|
||||
| `ServerHistorian:ServerCertThumbprint` | `ServerHistorian:CaCertificatePath` (+ `UseTls` / `AllowUntrustedServerCertificate`) |
|
||||
|
||||
**TLS troubleshooting note:** If TLS fails on every connection attempt, the
|
||||
most likely cause is a missing private key or an ACL on the key file — the
|
||||
sidecar loads the certificate with `MachineKeySet` (required for service
|
||||
accounts with no loaded user profile), and `SslStream` defers private-key
|
||||
access to the first handshake, so a bad key surfaces as repeated connection
|
||||
failures (→ exit 2 → NSSM restart), not a startup error.
|
||||
|
||||
## Project split
|
||||
|
||||
| Project | Target | Role |
|
||||
|---------|--------|------|
|
||||
| `src/Drivers/ZB.MOM.WW.OtOpcUa.Driver.Historian.Wonderware/` | net48 / x64 | The **sidecar** (`OutputType=Exe`). Hosts the TCP server, the historian reader, and the alarm-write backend bound to the AVEVA SDK |
|
||||
| `src/Drivers/ZB.MOM.WW.OtOpcUa.Driver.Historian.Wonderware.Client/` | net10.0 | `WonderwareHistorianClient` — the in-host TCP client consumed by the history router and the alarm sink |
|
||||
| `src/Drivers/ZB.MOM.WW.OtOpcUa.Driver.Historian.Wonderware.Client.Contracts/` | net10.0 | `WonderwareHistorianClientOptions` (host, port, TLS, shared secret, timeouts) |
|
||||
|
||||
> The csproj targets **net48 / x64** (`PlatformTarget=x64`) — the AVEVA Historian
|
||||
> 2020 SDK ships an x64 `aahClientManaged` build; the earlier x86 default was an
|
||||
> inherited v1 artifact, not a constraint of the Historian SDK.
|
||||
|
||||
## What it does
|
||||
|
||||
The sidecar exposes two surfaces, both over the same TCP connection:
|
||||
|
||||
### Read path — `IHistorianDataSource`
|
||||
|
||||
`HistorianDataSource` (in the sidecar) reads history through the
|
||||
`aahClientManaged` SDK; `WonderwareHistorianClient` (in the host) implements
|
||||
`IHistorianDataSource` and maps returned samples back to OPC UA `DataValue`s for
|
||||
`Server.History.IHistoryRouter`. The read surface is:
|
||||
|
||||
| Call | Maps to |
|
||||
|------|---------|
|
||||
| `ReadRawAsync` | Raw historical samples for a tag over a time range |
|
||||
| `ReadProcessedAsync` / `ReadAggregateAsync` | Aggregated samples at an interval |
|
||||
| `ReadAtTimeAsync` | Samples at specific timestamps |
|
||||
| `ReadEventsAsync` | Historical events for a source |
|
||||
| `GetHealthSnapshot` | Connection health for the host-side health surface |
|
||||
|
||||
### Write path — alarm-historian write-back
|
||||
|
||||
`WonderwareHistorianClient` also implements `IAlarmHistorianWriter`. Alarm events
|
||||
are drained into the sidecar from `Core.AlarmHistorian.SqliteStoreAndForwardSink`
|
||||
and persisted by `SdkAlarmHistorianWriteBackend` via
|
||||
`HistorianAccess.AddStreamedValue(HistorianEvent, out HistorianAccessError)`. The
|
||||
production writer is wrapped by `AahClientManagedAlarmEventWriter`, which handles
|
||||
batch orchestration and per-event `HistorianAccessError` outcome classification
|
||||
(connection-class errors are retryable; malformed-argument errors are not).
|
||||
|
||||
The alarm write path can be disabled independently of reads by setting
|
||||
`OTOPCUA_HISTORIAN_ALARM_WRITE_ENABLED=false` — the sidecar then rejects
|
||||
`WriteAlarmEvents` frames while still serving history reads.
|
||||
|
||||
## Hosting and IPC
|
||||
|
||||
- **Process**: `OtOpcUaWonderwareHistorian`, installed/managed by
|
||||
`scripts/install/` (`Install-Services.ps1 -InstallWonderwareHistorian`).
|
||||
- **Spawn config**: TCP port and bind address are set via
|
||||
`OTOPCUA_HISTORIAN_TCP_PORT` (default 32569) and `OTOPCUA_HISTORIAN_BIND`
|
||||
(default `0.0.0.0`). TLS is controlled by `OTOPCUA_HISTORIAN_TLS_ENABLED` /
|
||||
`OTOPCUA_HISTORIAN_TLS_CERT` / `OTOPCUA_HISTORIAN_TLS_CERT_PASSWORD`. The
|
||||
shared secret is passed via `OTOPCUA_HISTORIAN_SECRET`. Historian connection
|
||||
settings come from `OTOPCUA_HISTORIAN_SERVER` / `_PORT` / `_INTEGRATED` /
|
||||
`_USER` / `_PASS` etc. (see
|
||||
`src/Drivers/ZB.MOM.WW.OtOpcUa.Driver.Historian.Wonderware/Program.cs`).
|
||||
- **TCP-only mode**: with `OTOPCUA_HISTORIAN_ENABLED!=true` the sidecar boots
|
||||
without loading the SDK at all — used for smoke and IPC tests.
|
||||
- **Wire**: MessagePack-framed request/reply over TCP (optionally TLS). The
|
||||
client proves the shared secret in a `Hello` frame before any history calls.
|
||||
The client owns a single channel with one in-flight call at a time and retries
|
||||
a transport failure once before propagating — broader backoff is the caller's
|
||||
responsibility.
|
||||
|
||||
## Testing
|
||||
|
||||
- **Sidecar unit tests** —
|
||||
`tests/Drivers/ZB.MOM.WW.OtOpcUa.Driver.Historian.Wonderware.Tests/` cover the
|
||||
reader, the alarm-write backend outcome classification, and the TCP frame
|
||||
handler with a faked SDK seam; `TcpRoundTripTests` exercises the plaintext +
|
||||
TLS paths including the bad-secret rejection case.
|
||||
- **Client unit tests** —
|
||||
`tests/Drivers/ZB.MOM.WW.OtOpcUa.Driver.Historian.Wonderware.Client.Tests/`
|
||||
cover the TCP client + framing against loopback `TcpListener` fixtures.
|
||||
|
||||
## Further reading
|
||||
|
||||
- [ServiceHosting.md](../ServiceHosting.md) — where the sidecar fits in a
|
||||
deployment and how it's installed
|
||||
- [AlarmHistorian.md](../AlarmHistorian.md) — the alarm store-and-forward flow
|
||||
that feeds the write-back path
|
||||
The `AlarmHistorian` section's old Wonderware connection keys (`Host`/`Port`/`UseTls`/`ServerCertThumbprint`/`SharedSecret`)
|
||||
were pruned — remove them; the SQLite store-and-forward knobs are retained and the downstream connection is
|
||||
now sourced from `ServerHistorian`.
|
||||
|
||||
Reference in New Issue
Block a user