# 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 `Historian:Wonderware:Enabled=true`. 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). ## Architecture ``` +-------------------------------------------+ | OtOpcUa Host (.NET 10 AnyCPU) | | Server.History.IHistoryRouter --read--+--+ | Core.AlarmHistorian.SqliteStore | | | AndForwardSink --write----+--+ | WonderwareHistorianClient (.NET 10) | | +-------------------------------------------+ | | named pipe MessagePack frames | (shared secret + allowed-SID) v +-------------------------------------------+ | OtOpcUaWonderwareHistorian (sidecar) | | net48 / x64 | | PipeServer + 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 pipe contract only. ## Project split | Project | Target | Role | |---------|--------|------| | `src/Drivers/ZB.MOM.WW.OtOpcUa.Driver.Historian.Wonderware/` | net48 / x64 | The **sidecar** (`OutputType=Exe`). Hosts the named-pipe 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 pipe client consumed by the history router and the alarm sink | | `src/Drivers/ZB.MOM.WW.OtOpcUa.Driver.Historian.Wonderware.Client.Contracts/` | net10.0 | `WonderwareHistorianClientOptions` (pipe name, 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 named pipe: ### 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**: the supervisor passes the pipe name, the allowed server principal SID, and a per-process shared secret via environment (`OTOPCUA_HISTORIAN_PIPE`, `OTOPCUA_ALLOWED_SID`, `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`). - **Pipe-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; the named-pipe ACL restricts the pipe to the allowed SID and the client proves the shared secret in a Hello frame. 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 pipe-frame handler with a faked SDK seam. - **Client unit tests** — `tests/Drivers/ZB.MOM.WW.OtOpcUa.Driver.Historian.Wonderware.Client.Tests/` cover the pipe client + framing against an in-process duplex pipe pair. ## 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