diff --git a/ZB.MOM.WW.SPHistorianClient/CLAUDE.md b/ZB.MOM.WW.SPHistorianClient/CLAUDE.md new file mode 100644 index 0000000..634544b --- /dev/null +++ b/ZB.MOM.WW.SPHistorianClient/CLAUDE.md @@ -0,0 +1,171 @@ +# 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. +NOT yet packed/published to any NuGet feed. 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 all 16 retrieval 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 (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 | unit-tested; NOT yet live-verified against a real 2023 R2 server (`ExchangeKey` auth step is unproven) | + +--- + +## DI registration + +```csharp +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: + +```csharp +services.AddZbSpHistorianClient( + config.GetSection("Historian").Get()!); +``` + +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 + +```bash +# 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_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 (`.gitignore` blocks `*.ndjson` and + similar raw-capture extensions). +- Use only generic placeholders (`localhost`, ``) and built-in AVEVA + system tags (e.g., `SysTimeSec`) in all documentation and test defaults. diff --git a/ZB.MOM.WW.SPHistorianClient/README.md b/ZB.MOM.WW.SPHistorianClient/README.md new file mode 100644 index 0000000..765339b --- /dev/null +++ b/ZB.MOM.WW.SPHistorianClient/README.md @@ -0,0 +1,96 @@ +# ZB.MOM.WW.SPHistorianClient + +Pure-managed .NET 10 client library for **AVEVA System Platform Historian** (Wonderware). +Part of the ZB.MOM.WW SCADA family. + +No native AVEVA runtime dependency — `aahClientManaged.dll` / `aahClient.dll` are **not** +required. The wire protocol is re-implemented in managed C#. Live WCF transports require +Windows; offline tests and gRPC run cross-platform. + +--- + +## Quick start + +```csharp +using ZB.MOM.WW.SPHistorianClient; +using ZB.MOM.WW.SPHistorianClient.Models; + +await using HistorianClient client = new(new HistorianClientOptions +{ + Host = "localhost", + IntegratedSecurity = true, + Transport = HistorianTransport.LocalPipe, +}); + +DateTime endUtc = DateTime.UtcNow; +DateTime startUtc = endUtc - TimeSpan.FromMinutes(10); + +await foreach (HistorianSample sample in client.ReadRawAsync( + "SysTimeSec", startUtc, endUtc, maxValues: 100)) +{ + Console.WriteLine($"{sample.TimestampUtc:o} {sample.NumericValue} Q={sample.Quality}"); +} +``` + +### DI registration + +```csharp +// In Program.cs / Startup +services.AddZbSpHistorianClient( + config.GetSection("Historian").Get()!); + +// Resolves HistorianClient (transient) from the container +``` + +--- + +## Supported operations + +| Operation | Status | +|---|---| +| `ProbeAsync` | live-verified | +| `ReadRawAsync` | live-verified | +| `ReadAggregateAsync` | live-verified across all 16 retrieval 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 | +| `DeleteTagAsync` | live-verified (see note below) | + +> **Note:** Writing sample values is architecturally blocked — the Historian server cache only +> ingests from configured IOServer / Application Server pipelines. `DeleteTagAsync` server-side +> cascade may not always complete; use SMC as a fallback to clean up sandbox tags. + +--- + +## Transport matrix + +| Transport | Protocol | Platform | Verification | +|---|---|---|---| +| `LocalPipe` | WCF/MDAS over Net.NamedPipe | Windows-only | live-verified | +| `RemoteTcpIntegrated` | WCF/MDAS over Net.TCP + Windows auth | Windows-only | live-verified | +| `RemoteTcpCertificate` | WCF/MDAS over Net.TCP + TLS | Windows-only | `ProbeAsync` live-verified; deeper coverage pending | +| `RemoteGrpc` | gRPC (2023 R2) | cross-platform | unit-tested; live verification pending | + +--- + +## Build and test + +```bash +# From ZB.MOM.WW.SPHistorianClient/ + +dotnet build ZB.MOM.WW.SPHistorianClient.slnx +dotnet test ZB.MOM.WW.SPHistorianClient.slnx + +# Pack +dotnet pack ZB.MOM.WW.SPHistorianClient.slnx -c Release -o ./artifacts +``` + +Offline unit tests (191 total) run on any OS. Live integration tests are gated by environment +variables (`HISTORIAN_HOST` for WCF, `HISTORIAN_GRPC_HOST` for gRPC) and skip cleanly when unset. +See `CLAUDE.md` for the full environment variable reference.