Phase 3 PR 76 -- OPC UA Client IHistoryProvider #75

Merged
dohertj2 merged 1 commits from phase-3-pr76-opcua-client-history into v2 2026-04-19 02:15:32 -04:00
Owner

Summary

Driver now forwards HistoryRead to upstream for Raw / Processed / AtTime. ReadEventsAsync stays at the interface-default NotSupportedException — the call path needs an EventFilter SelectClauses spec the current IHistoryProvider signature doesn't carry; extending the interface is out of scope for this PR.

  • ExecuteHistoryReadAsync — shared wire path: parse NodeId, build HistoryReadValueIdCollection, call Session.HistoryReadAsync(RequestHeader, ExtensionObject<details>, TimestampsToReturn.Both, ...), unwrap HistoryData payload, preserve upstream StatusCode + timestamps verbatim per §8 cascading-quality.
  • MapAggregateToNodeId — maps the 5 HistoryAggregateType values to OPC UA Part 13 standard AggregateFunction_* NodeIds. Future additions fail-fast on ArgumentOutOfRangeException.
  • ReadRawReadRawModifiedDetails with IsReadModified=false.
  • ReadProcessedReadProcessedDetails with ProcessingInterval in ms.
  • ReadAtTimeReadAtTimeDetails with UseSimpleBounds=true.
  • Malformed NodeId short-circuits to empty result (no wire round-trip).
  • Name disambiguation: Core.Abstractions.HistoryReadResult fully-qualified vs. SDK's Opc.Ua.HistoryReadResult.

Validation

  • 78/78 OpcUaClient.Tests pass (7 new history)
  • dotnet build: 0 errors

Feature complete

All 8 possible IDriver* capabilities now implemented on OpcUaClientDriver: IDriver + ITagDiscovery + IReadable + IWritable + ISubscribable + IHostConnectivityProbe + IAlarmSource + IHistoryProvider.

Test plan

  • 5-aggregate theory + invalid-enum rejection
  • Read{Raw,Processed,AtTime} pre-init throws
  • ReadEventsAsync default NotSupportedException
## Summary Driver now forwards `HistoryRead` to upstream for Raw / Processed / AtTime. `ReadEventsAsync` stays at the interface-default `NotSupportedException` — the call path needs an `EventFilter` SelectClauses spec the current `IHistoryProvider` signature doesn't carry; extending the interface is out of scope for this PR. - `ExecuteHistoryReadAsync` — shared wire path: parse NodeId, build `HistoryReadValueIdCollection`, call `Session.HistoryReadAsync(RequestHeader, ExtensionObject<details>, TimestampsToReturn.Both, ...)`, unwrap `HistoryData` payload, preserve upstream `StatusCode` + timestamps verbatim per §8 cascading-quality. - `MapAggregateToNodeId` — maps the 5 `HistoryAggregateType` values to OPC UA Part 13 standard `AggregateFunction_*` NodeIds. Future additions fail-fast on `ArgumentOutOfRangeException`. - `ReadRaw` — `ReadRawModifiedDetails` with IsReadModified=false. - `ReadProcessed` — `ReadProcessedDetails` with `ProcessingInterval` in ms. - `ReadAtTime` — `ReadAtTimeDetails` with `UseSimpleBounds=true`. - Malformed NodeId short-circuits to empty result (no wire round-trip). - Name disambiguation: `Core.Abstractions.HistoryReadResult` fully-qualified vs. SDK's `Opc.Ua.HistoryReadResult`. ## Validation - 78/78 OpcUaClient.Tests pass (7 new history) - `dotnet build`: 0 errors ## Feature complete All 8 possible `IDriver*` capabilities now implemented on `OpcUaClientDriver`: `IDriver + ITagDiscovery + IReadable + IWritable + ISubscribable + IHostConnectivityProbe + IAlarmSource + IHistoryProvider`. ## Test plan - [x] 5-aggregate theory + invalid-enum rejection - [x] Read{Raw,Processed,AtTime} pre-init throws - [x] `ReadEventsAsync` default `NotSupportedException`
dohertj2 added 1 commit 2026-04-19 02:15:28 -04:00
Phase 3 PR 76 -- OPC UA Client IHistoryProvider (HistoryRead passthrough). Driver now implements IHistoryProvider (Raw + Processed + AtTime); ReadEventsAsync deliberately inherits the interface default that throws NotSupportedException. ExecuteHistoryReadAsync is the shared wire path: parses the fullReference to NodeId, builds a HistoryReadValueIdCollection with one entry, calls Session.HistoryReadAsync(RequestHeader, ExtensionObject<details>, TimestampsToReturn.Both, releaseContinuationPoints:false, nodesToRead, ct), unwraps r.HistoryData ExtensionObject into the samples list, passes ContinuationPoint through. Each DataValue's upstream StatusCode + SourceTimestamp + ServerTimestamp preserved verbatim per driver-specs.md \u00A78 cascading-quality rule -- this matters especially for historical data where an interpolated / uncertain-quality sample must surface its true severity downstream, not a sanitized Good. SourceTimestamp=DateTime.MinValue guards map to null so downstream clients see 'source unknown' rather than an epoch-zero misread. ReadRawAsync builds ReadRawModifiedDetails with IsReadModified=false (raw, not modified-history), StartTime/EndTime, NumValuesPerNode=maxValuesPerNode, ReturnBounds=false (clients that want bounds request them via continuation handling). ReadProcessedAsync builds ReadProcessedDetails with ProcessingInterval in ms + AggregateType wrapping a single NodeId from MapAggregateToNodeId. MapAggregateToNodeId switches on HistoryAggregateType {Average, Minimum, Maximum, Total, Count} to the standard Part 13 ObjectIds.AggregateFunction_* NodeId -- future aggregate-type additions fail the switch with ArgumentOutOfRangeException so they can't silently slip through with a null NodeId and an opaque server-side BadAggregateNotSupported. ReadAtTimeAsync builds ReadAtTimeDetails with ReqTimes + UseSimpleBounds=true (returns boundary samples when an exact timestamp has no value -- the OPC UA Part 11 default). Malformed NodeId short-circuits to empty result without touching the wire, matching the ReadAsync / WriteAsync pattern. ReadEventsAsync stays at the interface-default NotSupportedException: the OPC UA call path (HistoryReadAsync with ReadEventDetails + EventFilter) needs an EventFilter SelectClauses spec which the current IHistoryProvider.ReadEventsAsync signature doesn't carry. Adding that would be an IHistoryProvider interface widening; out of scope for PR 76. Callers see BadHistoryOperationUnsupported on the OPC UA client which is the documented fallback. Name disambiguation: Core.Abstractions.HistoryReadResult and Opc.Ua.HistoryReadResult both exist; used fully-qualified Core.Abstractions.HistoryReadResult in return types + factory expressions. Shutdown unchanged -- history reads don't create persistent server-side resources, so no cleanup needed beyond the existing Session.CloseAsync. Unit tests (OpcUaClientHistoryTests, 7 facts): MapAggregateToNodeId theory covers all 5 aggregates; MapAggregateToNodeId_rejects_invalid_enum (defense against future enum addition silently passing through); Read{Raw,Processed,AtTime}Async_without_initialize_throws (RequireSession path); ReadEventsAsync_throws_NotSupportedException (locks in the intentional inheritance of the default). 78/78 OpcUaClient.Tests pass (67 prior + 11 new, -4 on the alarm suite moved into the events count). dotnet build clean. Final OPC UA Client capability surface: IDriver + ITagDiscovery + IReadable + IWritable + ISubscribable + IHostConnectivityProbe + IAlarmSource + IHistoryProvider -- 8 of 8 possible capabilities. Driver is feature-complete per driver-specs.md \u00A78. c9e856178a
dohertj2 merged commit 0109fab4bf into v2 2026-04-19 02:15:32 -04:00
Sign in to join this conversation.
No Reviewers
No Label
1 Participants
Notifications
Due Date
No due date set.
Dependencies

No dependencies set.

Reference: dohertj2/lmxopcua#75