7.5 KiB
ZB.MOM.WW.SPHistorianClient
Pure-managed .NET 10 client for AVEVA System Platform Historian (Wonderware), for the ZB.MOM.WW SCADA family. This is a library, not a service — it is linked directly into the consuming application and runs in-process alongside it.
The wire protocol is reverse-engineered and re-implemented in C#. There is no native AVEVA
runtime dependency — aahClientManaged.dll / aahClient.dll are not referenced or loaded.
The library runs on any OS for offline/unit testing; live WCF transports require Windows.
Status: ported and rebranded into this repo; builds and 191 tests pass on macOS. Version 0.1.0.
The RemoteGrpc (2023 R2) read path is live-verified (2026-06-19). NOT yet packed/published to any
NuGet feed beyond the local artifacts/ nupkg. NOT yet adopted by any consumer.
Supported operation surface
All operations are exposed via the public façade HistorianClient.
| Operation | Status |
|---|---|
ProbeAsync |
live-verified |
ReadRawAsync |
live-verified |
ReadAggregateAsync |
live-verified across the RetrievalMode enum (15 modes) |
ReadAtTimeAsync |
live-verified |
ReadBlocksAsync |
block history read |
ReadEventsAsync |
live-verified (typed event + property bag) |
BrowseTagNamesAsync |
live-verified |
GetTagMetadataAsync |
live-verified across many native data-type codes |
GetConnectionStatusAsync |
synthesized from authenticated probe |
GetStoreForwardStatusAsync |
synthesized defaults |
GetSystemParameterAsync |
live-verified |
EnsureTagAsync |
live-verified for analog Float; Double/Int2/Int4/UInt4 supported (optional ApplyScaling persists distinct MinRaw/MaxRaw) |
DeleteTagAsync |
live-verified (known issue: server-side cascade may not always complete; use SMC as fallback to clean up sandbox tags) |
Out of scope
- Writing sample values (
AddS2) is architecturally blocked — the server runtime cache only ingests from configured IOServer / Application Server pipelines, not from a standalone AddTag client flow. - Store-forward write, historian configuration changes, discrete/string tag creation (native AddTag rejects them).
Transport matrix
Configured via HistorianClientOptions.Transport (HistorianTransport enum).
| Transport | Protocol | Platform | Verification |
|---|---|---|---|
LocalPipe |
WCF/MDAS over Net.NamedPipe (local) | Windows-only | live-verified (read / browse / metadata / event / status) |
RemoteTcpIntegrated |
WCF/MDAS over Net.TCP + Windows transport auth | Windows-only | live-verified (full read / browse / metadata / event / status surface) |
RemoteTcpCertificate |
WCF/MDAS over Net.TCP + server-cert TLS | Windows-only | ProbeAsync live-verified; deeper coverage pending |
RemoteGrpc |
gRPC (2023 R2), Grpc.Net.Client/.Web | cross-platform | live-verified 2026-06-19 against a 2023 R2 server — TLS + StorageService.ValidateClientCredential NTLM handshake + raw read returning correct values |
DI registration
services.AddZbSpHistorianClient(new HistorianClientOptions
{
Host = "localhost",
IntegratedSecurity = true,
Transport = HistorianTransport.RemoteTcpIntegrated,
});
AddZbSpHistorianClient registers the options instance (singleton) + HistorianClient
(transient). Because HistorianClientOptions uses required/init-only properties, the
consumer passes a fully-built instance. In a real app, bind it from configuration:
services.AddZbSpHistorianClient(
config.GetSection("Historian").Get<HistorianClientOptions>()!);
The package depends only on Microsoft.Extensions.DependencyInjection.Abstractions — no
ASP.NET Core or framework reference required.
Architecture
Three decoupled subsystems under src/ZB.MOM.WW.SPHistorianClient/:
| Subsystem | Path | Responsibility |
|---|---|---|
| Public façade | HistorianClient.cs, HistorianClientOptions.cs |
Entry point; delegates to the transport layer |
| WCF/MDAS layer | Wcf/ |
Managed WCF transport; custom MdasMessageEncoder, binding factory, versioned service contracts |
| Binary frame layer | Protocol/ |
Historian2020ProtocolDialect; methods without protocol evidence throw ProtocolEvidenceMissingException |
| Public models | Models/ |
Public DTOs and enums (HistorianSample, HistorianTagMetadata, RetrievalMode, …) |
| gRPC transport | Grpc/ |
2023 R2 gRPC transport; recovered .proto compiled by Grpc.Tools at build; wire contracts keep AVEVA's ArchestrA.Grpc.Contract.* namespaces |
Build, test, and pack commands
# From ZB.MOM.WW.SPHistorianClient/
# Build
dotnet build ZB.MOM.WW.SPHistorianClient.slnx
dotnet build ZB.MOM.WW.SPHistorianClient.slnx -c Release
# Test (offline unit/golden-byte tests run on any OS;
# Windows-only WCF tests no-op off Windows;
# live integration tests skip when env vars are unset — see below)
dotnet test ZB.MOM.WW.SPHistorianClient.slnx
# Run a single test class
dotnet test ZB.MOM.WW.SPHistorianClient.slnx --filter "FullyQualifiedName~WcfDataQueryProtocolTests"
# Pack (one .nupkg lands in artifacts/)
dotnet pack ZB.MOM.WW.SPHistorianClient.slnx -c Release -o ./artifacts
Test posture
All tests run offline by default; live integration tests are gated by environment variables and skip cleanly when unset.
| Test type | Count |
|---|---|
| Offline unit / golden-byte tests | the bulk of the 191 total |
| WCF live integration (gated) | skipped off Windows or when HISTORIAN_HOST is unset |
| gRPC live integration (gated) | skipped when HISTORIAN_GRPC_HOST is unset |
| Total | 191 |
GeneratePackageOnBuild is off — pack explicitly with the command above.
Live integration test environment variables
WCF transports:
| Variable | Required | Notes |
|---|---|---|
HISTORIAN_HOST |
yes (gates WCF tests) | Historian server hostname or IP |
HISTORIAN_PORT |
optional | Override the WCF TCP port (default 32568) |
HISTORIAN_TEST_TAG |
yes | A historized tag that exists on the server (use a system tag such as SysTimeSec for safe testing) |
HISTORIAN_USER |
optional | Omit to use Windows integrated security |
HISTORIAN_PASSWORD |
optional | Only used when HISTORIAN_USER is set |
HISTORIAN_TAG_FILTER |
optional | Browse filter pattern passed to BrowseTagNamesAsync |
gRPC transport:
| Variable | Required | Notes |
|---|---|---|
HISTORIAN_GRPC_HOST |
yes (gates gRPC tests) | 2023 R2 gRPC endpoint host |
HISTORIAN_GRPC_PORT |
optional | Default 32565 |
HISTORIAN_GRPC_TLS |
optional | Set true to enable TLS |
HISTORIAN_GRPC_DNSID |
optional | Override DNS identity for certificate validation |
Status and provenance
Version 0.1.0. Ported from a reverse-engineering migration bundle and rebranded into this repo. Builds and all 191 tests pass on macOS. NOT yet packed/published to the Gitea NuGet feed. NOT yet adopted by any consumer (OtOpcUa, MxAccessGateway, ScadaBridge).
Production code is pure-managed .NET 10 with no native AVEVA reference. Reverse-engineering tooling and proprietary decompilations from the source bundle were intentionally excluded from this repo.
Safety rules for this library (hard — never violate):
- Never commit real server hostnames, IP addresses, or credentials.
- Never commit customer tag names or live capture data (
.gitignoreblocks*.ndjsonand similar raw-capture extensions). - Use only generic placeholders (
localhost,<your-historized-tag>) and built-in AVEVA system tags (e.g.,SysTimeSec) in all documentation and test defaults.