Phase 3 PR 69 -- OPC UA Client ISubscribable + IHostConnectivityProbe #68

Merged
dohertj2 merged 1 commits from phase-3-pr69-opcua-client-subscribe-probe into v2 2026-04-19 01:24:22 -04:00
Owner

Summary

Completes the OpcUaClientDriver capability surface — now on par with Galaxy / Modbus / S7.

  • ISubscribable: SubscribeAsync creates a Subscription + adds a MonitoredItem per tag, wires NotificationOnDataChange. Tag strings round-trip via MonitoredItem.Handle. UnsubscribeAsync is idempotent (unknown handle = no-op). Shutdown explicitly deletes remote subscriptions before Session.CloseAsync to avoid BadSubscriptionIdInvalid noise.
  • IHostConnectivityProbe: driven by the session's native KeepAlive event — no driver-side polling. HostName = EndpointUrl so the Admin /hosts dashboard can render the full opc.tcp:// URL.
  • Cascading quality preserved through the notification path: SourceTimestamp + StatusCode pass through verbatim into DataValueSnapshot.

Validation

  • 13/13 OpcUaClient.Tests pass
  • dotnet build: 0 errors

Scope

Live-session subscribe/unsubscribe round-trip + keep-alive transitions deferred to the in-process-server-fixture PR.

OPC UA Client driver is feature-complete on all six IDriver* capability interfaces.

Test plan

  • Subscribe pre-init throws uniformly
  • Unsubscribe with unknown handle returns cleanly
  • GetHostStatuses returns EndpointUrl row
## Summary Completes the `OpcUaClientDriver` capability surface — now on par with Galaxy / Modbus / S7. - **ISubscribable**: `SubscribeAsync` creates a `Subscription` + adds a `MonitoredItem` per tag, wires `Notification` → `OnDataChange`. Tag strings round-trip via `MonitoredItem.Handle`. `UnsubscribeAsync` is idempotent (unknown handle = no-op). Shutdown explicitly deletes remote subscriptions before `Session.CloseAsync` to avoid `BadSubscriptionIdInvalid` noise. - **IHostConnectivityProbe**: driven by the session's native `KeepAlive` event — no driver-side polling. `HostName = EndpointUrl` so the Admin `/hosts` dashboard can render the full `opc.tcp://` URL. - **Cascading quality** preserved through the notification path: `SourceTimestamp` + `StatusCode` pass through verbatim into `DataValueSnapshot`. ## Validation - 13/13 OpcUaClient.Tests pass - `dotnet build`: 0 errors ## Scope Live-session subscribe/unsubscribe round-trip + keep-alive transitions deferred to the in-process-server-fixture PR. OPC UA Client driver is **feature-complete** on all six `IDriver*` capability interfaces. ## Test plan - [x] Subscribe pre-init throws uniformly - [x] Unsubscribe with unknown handle returns cleanly - [x] `GetHostStatuses` returns EndpointUrl row
dohertj2 added 1 commit 2026-04-19 01:24:18 -04:00
Phase 3 PR 69 -- OPC UA Client ISubscribable + IHostConnectivityProbe. Completes the OpcUaClientDriver capability surface — now matches the Galaxy + Modbus + S7 driver coverage. ISubscribable: SubscribeAsync creates a new upstream Subscription via the non-obsolete Subscription(ITelemetryContext, SubscriptionOptions) ctor + AddItem/CreateItemsAsync flow, wires each MonitoredItem's Notification event into OnDataChange. Tag strings round-trip through MonitoredItem.Handle so the notification handler can identify which tag changed without a second lookup. Publishing interval floored at 50ms (servers negotiate up anyway; sub-50ms wastes round-trip). SubscriptionOptions uses KeepAliveCount=10, LifetimeCount=1000, TimestampsToReturn=Both so SourceTimestamp passthrough for the cascading-quality rule works through subscription paths too. UnsubscribeAsync calls Subscription.DeleteAsync(silent:true) and tolerates unknown handles (returns cleanly) because the caller's race with server-side cleanup after a session drop shouldn't crash either side. Session shutdown explicitly deletes every remote subscription before closing — avoids BadSubscriptionIdInvalid noise in the upstream server's log on Close. IHostConnectivityProbe: HostName surfaced as the EndpointUrl (not host:port like the Modbus/S7 drivers) so the Admin /hosts dashboard can render the full opc.tcp:// URL as a clickable target back at the remote server. HostState tracked via session.KeepAlive event — OPC UA's built-in keep-alive is authoritative for session liveness (the SDK pings on KeepAliveInterval, sets KeepAliveStopped after N missed pings), strictly better than a driver-side polling probe: no extra wire round-trip, no duplicate semantic with the native protocol. Handler transitions Running on healthy keep-alives and Stopped on any Bad service-result. Initial Running raised at end of InitializeAsync once the session is up; Shutdown transitions back to Unknown + unwires the handler. Unit tests (OpcUaClientSubscribeAndProbeTests, 3 facts): SubscribeAsync_without_initialize_throws_InvalidOperationException, UnsubscribeAsync_with_unknown_handle_is_noop (session-drop-race safety), GetHostStatuses_returns_endpoint_url_row_pre_init (asserts EndpointUrl as the host identity -- the full opc.tcp://plc.example:4840 URL). Live-session subscribe/unsubscribe round-trip + keep-alive state transition coverage lands in a follow-up PR once we scaffold the in-process OPC UA server fixture. 13/13 OpcUaClient.Tests pass. dotnet build clean. All six capability interfaces (IDriver / ITagDiscovery / IReadable / IWritable / ISubscribable / IHostConnectivityProbe) implemented — OPC UA Client driver surface complete. 0433d3a35e
dohertj2 merged commit 82f2dfcfa3 into v2 2026-04-19 01:24:22 -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#68