Files
lmxopcua/docs/drivers/Historian.Wonderware.md
Joseph Doherty 1be06502c7
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
fix(historian): correct AlarmHistorian config-key refs in docs + install (review)
2026-06-12 12:25:13 -04:00

8.1 KiB

Wonderware Historian Backend

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.

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.

For the sidecar's place in a deployment, see ServiceHosting.md. For the alarm-history store-and-forward flow that drains into it, see AlarmHistorian.md.

Architecture

        +-------------------------------------------+
        |  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 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.

Transport & security

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.

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.

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.

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 DataValues 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 teststests/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 teststests/Drivers/ZB.MOM.WW.OtOpcUa.Driver.Historian.Wonderware.Client.Tests/ cover the TCP client + framing against loopback TcpListener fixtures.

Further reading

  • ServiceHosting.md — where the sidecar fits in a deployment and how it's installed
  • AlarmHistorian.md — the alarm store-and-forward flow that feeds the write-back path