Phase 3 PR 67 -- OPC UA Client IReadable + IWritable #66

Merged
dohertj2 merged 1 commits from phase-3-pr67-opcua-client-read-write into v2 2026-04-19 01:15:43 -04:00
Owner

Summary

Adds IReadable + IWritable to OpcUaClientDriver, routing through the session's non-obsolete ReadAsync(...) / WriteAsync(...) overloads (all sync + BeginXxx/EndXxx variants are [Obsolete] in SDK 1.5.378). Serializes on the shared Gate.

NodeId parsing: full references use the standard OPC UA serialized form — ns=2;s=Demo.Counter, i=2253, ns=4;g=…, ns=3;b=…. TryParseNodeId goes through NodeId.Parse with the session's MessageContext so server-negotiated namespace URIs are honoured. Malformed → BadNodeIdInvalid (0x80330000) without a wire round-trip.

Cascading quality per driver-specs.md §8: upstream StatusCode, SourceTimestamp, ServerTimestamp all pass through verbatim. Bad codes stay Bad (not translated to BadInternalError) so downstream clients can distinguish upstream-source-down from local-driver-failure. Wire-level exceptions fan out BadCommunicationError across the batch.

Name collision: SDK's Opc.Ua.WriteRequest clashes with Core.Abstractions.WriteRequest; method signature uses the fully-qualified local type.

Validation

  • 7/7 OpcUaClient.Tests pass (5 scaffold + 2 Read/Write error-path)
  • dotnet build: 0 errors

Scope

Wire-level round-trip tests (against a live in-process server fixture) are deferred — the existing Server project is a candidate host but scaffolding that takes its own PR. ITagDiscovery / ISubscribable / IHostConnectivityProbe land in PRs 68-69.

Test plan

  • Read/Write pre-init throw uniformly
  • Signature types disambiguated
  • Cascading-quality code path compiles (verbatim StatusCode passthrough)
## Summary Adds `IReadable` + `IWritable` to `OpcUaClientDriver`, routing through the session's non-obsolete `ReadAsync(...) ` / `WriteAsync(...)` overloads (all sync + BeginXxx/EndXxx variants are `[Obsolete]` in SDK 1.5.378). Serializes on the shared `Gate`. **NodeId parsing**: full references use the standard OPC UA serialized form — `ns=2;s=Demo.Counter`, `i=2253`, `ns=4;g=…`, `ns=3;b=…`. `TryParseNodeId` goes through `NodeId.Parse` with the session's `MessageContext` so server-negotiated namespace URIs are honoured. Malformed → `BadNodeIdInvalid` (0x80330000) without a wire round-trip. **Cascading quality** per `driver-specs.md` §8: upstream `StatusCode`, `SourceTimestamp`, `ServerTimestamp` all pass through **verbatim**. Bad codes stay Bad (not translated to `BadInternalError`) so downstream clients can distinguish upstream-source-down from local-driver-failure. Wire-level exceptions fan out `BadCommunicationError` across the batch. **Name collision**: SDK's `Opc.Ua.WriteRequest` clashes with `Core.Abstractions.WriteRequest`; method signature uses the fully-qualified local type. ## Validation - 7/7 OpcUaClient.Tests pass (5 scaffold + 2 Read/Write error-path) - `dotnet build`: 0 errors ## Scope Wire-level round-trip tests (against a live in-process server fixture) are deferred — the existing `Server` project is a candidate host but scaffolding that takes its own PR. ITagDiscovery / ISubscribable / IHostConnectivityProbe land in PRs 68-69. ## Test plan - [x] Read/Write pre-init throw uniformly - [x] Signature types disambiguated - [x] Cascading-quality code path compiles (verbatim StatusCode passthrough)
dohertj2 added 1 commit 2026-04-19 01:15:39 -04:00
Phase 3 PR 67 -- OPC UA Client IReadable + IWritable via Session.ReadAsync/WriteAsync. Adds IReadable + IWritable capabilities to OpcUaClientDriver, routing reads/writes through the session's non-obsolete ReadAsync(RequestHeader, maxAge, TimestampsToReturn, ReadValueIdCollection, ct) and WriteAsync(RequestHeader, WriteValueCollection, ct) overloads (the sync and BeginXxx/EndXxx patterns are all [Obsolete] in SDK 1.5.378). Serializes on the shared Gate from PR 66 so reads + writes + future subscribe + probe don't race on the single session. NodeId parsing: fullReferences use OPC UA's standard serialized NodeId form -- ns=2;s=Demo.Counter, i=2253, ns=4;g=... for GUID, ns=3;b=... for opaque. TryParseNodeId calls NodeId.Parse with the session's MessageContext which honours the server-negotiated namespace URI table. Malformed input surfaces as BadNodeIdInvalid (0x80330000) WITHOUT a wire round-trip -- saves a request for a fault the driver can detect locally. Cascading-quality implementation per driver-specs.md \u00A78: upstream StatusCode, SourceTimestamp, and ServerTimestamp pass through VERBATIM. Bad codes from the remote server stay as the same Bad code (not translated to generic BadInternalError) so downstream clients can distinguish 'upstream value unavailable' from 'local driver bug'. SourceTimestamp is preserved verbatim (null on MinValue guard) so staleness is visible; ServerTimestamp falls back to DateTime.UtcNow if the upstream omitted it, never overwriting a non-zero value. Wire-level exceptions in the Read batch -- transport / timeout / session-dropped -- fan out BadCommunicationError (0x80050000) across every tag in the batch, not BadInternalError, so operators distinguish network reachability from driver faults. Write-side same pattern: successful WriteAsync maps each upstream StatusCode.Code verbatim into the local WriteResult.StatusCode; transport-layer failure fans out BadCommunicationError across the whole batch. WriteValue carries AttributeId=Value + DataValue wrapping Variant(writeValue) -- the SDK handles the type-to-Variant mapping for common CLR types (bool, int, float, string, etc.) so the driver doesn't need a per-type switch. Name disambiguation: the SDK has its own Opc.Ua.WriteRequest type which collides with ZB.MOM.WW.OtOpcUa.Core.Abstractions.WriteRequest; method signature uses the fully-qualified Core.Abstractions.WriteRequest. Unit tests (OpcUaClientReadWriteTests, 2 facts): ReadAsync_without_initialize_throws_InvalidOperationException + WriteAsync_without_initialize_throws_InvalidOperationException -- pre-init calls hit RequireSession and fail uniformly. Wire-level round-trip coverage against a live remote server lands in a follow-up PR once we scaffold an in-process OPC UA server fixture (the existing Server project in the solution is a candidate host). 7/7 OpcUaClient.Tests pass (5 scaffold + 2 read/write). dotnet build clean. Scope: ITagDiscovery (browse) + ISubscribable + IHostConnectivityProbe remain deferred to PRs 68-69 which also need namespace-index remapping and reference-counted MonitoredItem forwarding per driver-specs.md \u00A78. 238748bc98
dohertj2 merged commit 89bd726fa8 into v2 2026-04-19 01:15:43 -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#66