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 x64aahClientManagedbuild; 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 byscripts/install/(Install-Services.ps1 -InstallWonderwareHistorian). - Spawn config: TCP port and bind address are set via
OTOPCUA_HISTORIAN_TCP_PORT(default 32569) andOTOPCUA_HISTORIAN_BIND(default0.0.0.0). TLS is controlled byOTOPCUA_HISTORIAN_TLS_ENABLED/OTOPCUA_HISTORIAN_TLS_CERT/OTOPCUA_HISTORIAN_TLS_CERT_PASSWORD. The shared secret is passed viaOTOPCUA_HISTORIAN_SECRET. Historian connection settings come fromOTOPCUA_HISTORIAN_SERVER/_PORT/_INTEGRATED/_USER/_PASSetc. (seesrc/Drivers/ZB.MOM.WW.OtOpcUa.Driver.Historian.Wonderware/Program.cs). - TCP-only mode: with
OTOPCUA_HISTORIAN_ENABLED!=truethe 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
Helloframe 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;TcpRoundTripTestsexercises 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 loopbackTcpListenerfixtures.
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