Files
scadaproj/docs/plans/2026-06-19-sphistorianclient-design.md
T

6.7 KiB
Raw Blame History

ZB.MOM.WW.SPHistorianClient — Design

Date: 2026-06-19 Status: Approved — proceeding to implementation plan.

Goal

Repackage the proven, pure-managed .NET 10 AVEVA.Historian.Client SDK (delivered in HistorianSDK_2023R2/histsdk-migration.zip from 10.100.0.48) as the family-branded shared library ZB.MOM.WW.SPHistorianClient (System Platform Historian Client), following the same conventions as the other ZB.MOM.WW.* shared libraries in this repo.

Context — what the source bundle contains

histsdk-migration.ziphistsdk-migration/:

  • histsdk/ — the SDK git repo. src/AVEVA.Historian.Client/ is a pure-managed .NET 10 client for AVEVA Historian (no aahClientManaged.dll / aahClient.dll / native AVEVA runtime — the wire protocol is reverse-engineered and re-implemented in C#). ~165188 unit + gated-live tests pass.
  • analysis-2023r2/ — reverse-engineering analysis (recovered protos, decompiled stock contract, transport writeup). Kept separate from the repo on purpose.

Two transport families exist in the SDK:

Transport Protocol Platform Verification
LocalPipe, RemoteTcpIntegrated, RemoteTcpCertificate WCF/MDAS (2020) Windows-only live-verified: raw/aggregate(16 modes)/at-time/event reads, browse, metadata, status, EnsureTag/DeleteTag
RemoteGrpc gRPC (2023 R2) cross-platform (Grpc.Net.Client/.Web) unit-tested; not yet live-verified against a real 2023 R2 server (ExchangeKey auth step unproven)

Decisions (locked)

  1. Approach: port + rebrand. Copy the SDK source into ZB.MOM.WW.SPHistorianClient, rename the root namespace, adopt ZB conventions, bring the unit tests, drop non-shippable artifacts. One coherent shared library — a published package should not ship a third-party (AVEVA) namespace or non-redistributable reverse-engineering artifacts.
  2. Transports: both WCF + gRPC. Ship everything that works. WCF members keep [SupportedOSPlatform("windows")]; the gRPC path runs anywhere. No working code discarded.
  3. Not a "component normalization." There is no duplicated historian code across the three apps to converge — this is a net-new shared library that simply follows ZB packaging conventions.

Repository layout

Plain files committed into this repo (NOT a nested git repo — see the shared-libs-are-plain-files-not-nested-repos convention):

ZB.MOM.WW.SPHistorianClient/
  Directory.Build.props          # net10.0, Nullable, ImplicitUsings, LangVersion latest, Version 0.1.0, central pkg mgmt
  Directory.Packages.props       # central PackageVersion entries
  ZB.MOM.WW.SPHistorianClient.slnx
  CLAUDE.md  README.md  .gitignore
  src/ZB.MOM.WW.SPHistorianClient/         # the single package
    HistorianClient.cs, HistorianClientOptions.cs, HistorianTransport.cs
    Models/  Protocol/  Transport/  Wcf/  Wcf/Contracts/  Grpc/  Grpc/Protos/*.proto
    DependencyInjection/AddZbSpHistorianClient (ZB-idiomatic DI extension)
  tests/ZB.MOM.WW.SPHistorianClient.Tests/ # offline unit/golden-byte + gated-live integration
  artifacts/                     # dotnet pack output

Port mechanics

  • Copy src/AVEVA.Historian.Client/ and tests/AVEVA.Historian.Client.Tests/ from the bundle.
  • Rename the C# root namespace AVEVA.Historian.ClientZB.MOM.WW.SPHistorianClient across all files: 74 namespace declarations spanning the root + 6 sub-namespaces (.Models, .Wcf, .Wcf.Contracts, .Protocol, .Transport, .Grpc), all using directives, and the InternalsVisibleTo to the test assembly. Drop the InternalsVisibleTo to AVEVA.Historian.ReverseEngineering (tool not shipped).
  • Leave the proto wire contracts untouched: the 6 Grpc/Protos/*.proto keep option csharp_namespace = "ArchestrA.Grpc.Contract.*" — that is AVEVA's wire contract, not ours. Grpc.Tools keeps generating the client stubs at build.
  • Convert inline PackageReference versions to central management in Directory.Packages.props, matching the ZB.MOM.WW.Telemetry template.

Dependencies

  • Library: Google.Protobuf, Grpc.Net.Client, Grpc.Net.Client.Web, Grpc.Tools (build-only, PrivateAssets=all), System.ServiceModel.NetNamedPipe, System.ServiceModel.NetTcp, System.Security.Cryptography.Xml. Add Microsoft.Extensions.DependencyInjection.Abstractions + Microsoft.Extensions.Options for the DI extension.
  • Tests: xunit, xunit.runner.visualstudio, Microsoft.NET.Test.Sdk, coverlet.collector, Microsoft.Data.SqlClient (SQL post-check tests).

Excluded (safety / non-redistributable / Windows-native)

  • tools/ reverse-engineering harnesses (.NET Framework, reference native AVEVA binaries).
  • analysis-2023r2/decompiled/ — proprietary AVEVA decompilations (not redistributable).
  • scripts/ — Frida / PowerShell / Python capture tooling.
  • docs/reverse-engineering/ — identity-bearing .ndjson / capture evidence.

Kept: the recovered .proto files (needed to build), the offline unit tests, and a sanitized architecture/surface summary folded into CLAUDE.md / README.md. .gitignore blocks the identity-bearing patterns (*.ndjson, current/, aveva-install-*/, artifacts/-raw, etc.).

Public surface (preserved 1:1)

HistorianClient + HistorianClientOptions façade; Models/*; HistorianTransport enum (LocalPipe / RemoteTcpIntegrated / RemoteTcpCertificate / RemoteGrpc); operations: ProbeAsync, ReadRawAsync / ReadAggregateAsync / ReadAtTimeAsync, ReadEventsAsync, BrowseTagNamesAsync, GetTagMetadataAsync, status calls, EnsureTagAsync / DeleteTagAsync.

One ZB-idiomatic addition: AddZbSpHistorianClient(...) DI extension mirroring AddZbTelemetry — thin: binds HistorianClientOptions and registers HistorianClient. Optional to consumers.

Cross-platform & testing posture

  • WCF members already carry [SupportedOSPlatform("windows")]; the library builds and unit-tests on macOS/Linux. gRPC path is portable.
  • Offline unit/golden-byte tests run anywhere. Live integration tests stay gated by HISTORIAN_* env vars and skip cleanly when unset.
  • Verify dotnet build + dotnet test pass locally (macOS) before finishing.

Packaging

dotnet pack -c Release -o ./artifactsZB.MOM.WW.SPHistorianClient.0.1.0.nupkg. Gitea URLs in package metadata. Not pushed/published to any feed unless explicitly requested.

Out of scope (this pass)

  • Wiring ZB.MOM.WW.SPHistorianClient into any consumer (e.g. OtOpcUa Phase C HistoryRead) — a separate follow-on.
  • Live-verifying the gRPC RemoteGrpc path against a real 2023 R2 server.
  • Writing samples (AddS2) — architecturally blocked in the source SDK; remains out of scope.