Phase 3 PR 65 -- S7 ITagDiscovery + ISubscribable + IHostConnectivityProbe #64

Merged
dohertj2 merged 1 commits from phase-3-pr65-s7-discovery-subscribe-probe into v2 2026-04-19 00:18:19 -04:00
Owner

Summary

Completes the S7 driver's capability surface — now on par with the Modbus driver.

  • ITagDiscoveryDiscoverAsync streams tags into the IAddressSpaceBuilder under an S7 folder. SecurityClass = Operate (writable) or ViewOnly (read-only) matches the Modbus pattern for server-layer ACL gating.
  • ISubscribable polling overlay — 100 ms floor (S7 CPU scan-cycle pattern), force-raise initial push, tolerates transient poll errors without killing the loop. UnsubscribeAsync idempotent.
  • IHostConnectivityProbehost:port naming matches Modbus convention for uniform Admin /hosts dashboard rows. Probes via Plc.ReadStatusAsync (Get-CPU-Status, cheap + doubles as an 'is PLC up' check) every Probe.Interval with Probe.Timeout cap. Transitions raised only on actual state changes.

Probe starts at end of InitializeAsync (when Probe.Enabled=true); shutdown cancels probe + all subscriptions before disposing the Plc.

Validation

  • 57/57 Modbus.Driver.S7.Tests pass (50 parser + 3 read/write + 4 new discovery/subscribe/probe)
  • dotnet build: 0 errors

Scope

All 5 capability interfaces (IDriver / ITagDiscovery / IReadable / IWritable / ISubscribable / IHostConnectivityProbe) now land on the S7 driver. Extended data types (Int64 / UInt64 / Float64 / String / DateTime) remain deferred per PR 64.

Test plan

  • Discovery shape: tags → variables, writable → Operate, read-only → ViewOnly
  • Host status row shape + pre-init Unknown state
  • Subscribe/Unsubscribe lifecycle + unique handle IDs + idempotent double-unsub
  • Interval floor accepted without throw
## Summary Completes the S7 driver's capability surface — now on par with the Modbus driver. - **ITagDiscovery** — `DiscoverAsync` streams tags into the `IAddressSpaceBuilder` under an `S7` folder. `SecurityClass = Operate` (writable) or `ViewOnly` (read-only) matches the Modbus pattern for server-layer ACL gating. - **ISubscribable polling overlay** — 100 ms floor (S7 CPU scan-cycle pattern), force-raise initial push, tolerates transient poll errors without killing the loop. `UnsubscribeAsync` idempotent. - **IHostConnectivityProbe** — `host:port` naming matches Modbus convention for uniform Admin `/hosts` dashboard rows. Probes via `Plc.ReadStatusAsync` (Get-CPU-Status, cheap + doubles as an 'is PLC up' check) every `Probe.Interval` with `Probe.Timeout` cap. Transitions raised only on actual state changes. Probe starts at end of `InitializeAsync` (when `Probe.Enabled=true`); shutdown cancels probe + all subscriptions before disposing the `Plc`. ## Validation - **57/57** Modbus.Driver.S7.Tests pass (50 parser + 3 read/write + 4 new discovery/subscribe/probe) - `dotnet build`: 0 errors ## Scope All 5 capability interfaces (IDriver / ITagDiscovery / IReadable / IWritable / ISubscribable / IHostConnectivityProbe) now land on the S7 driver. Extended data types (Int64 / UInt64 / Float64 / String / DateTime) remain deferred per PR 64. ## Test plan - [x] Discovery shape: tags → variables, writable → Operate, read-only → ViewOnly - [x] Host status row shape + pre-init Unknown state - [x] Subscribe/Unsubscribe lifecycle + unique handle IDs + idempotent double-unsub - [x] Interval floor accepted without throw
dohertj2 added 1 commit 2026-04-19 00:18:15 -04:00
Phase 3 PR 65 -- S7 ITagDiscovery + ISubscribable polling overlay + IHostConnectivityProbe. Three more capability interfaces on S7Driver, matching the Modbus driver's capability coverage. ITagDiscovery: DiscoverAsync streams every configured tag into IAddressSpaceBuilder under a single 'S7' folder; builder.Variable gets a DriverAttributeInfo carrying DriverDataType (MapDataType: Bool->Boolean, Byte/Int/UInt sizes->Int32 (until Core.Abstractions adds widths), Float32/Float64 direct, String + DateTime direct), SecurityClass (Operate if tag.Writable else ViewOnly -- matches the Modbus pattern so DriverNodeManager's ACL layer can gate writes per role without S7-specific logic), IsHistorized=false (S7 has no native historian surface), IsAlarm=false (S7 alarms land through TIA Portal's alarm-in-DB pattern which is per-site and out of scope for PR 65). ISubscribable polling overlay: same pattern Modbus established in PR 22. SubscribeAsync spawns a Task.Run loop that polls every tag, diffs against LastValues, raises OnDataChange on changes plus a force-raise on initial-data push per OPC UA Part 4 convention. Interval floored at 100ms -- S7 CPUs scan 2-10ms but process the comms mailbox at most once per scan, so sub-scan polling just queues wire-side with worse latency per S7netplus documented pattern. Poll errors tolerated: first-read fault doesn't kill the loop (caller can't receive initial values but subsequent polls try again); transient poll errors also swallowed so the loop survives a power-cycle + reconnect through the health surface. UnsubscribeAsync cancels the CTS + removes the subscription -- unknown handle is a no-op, not a throw, because the caller's race with server-side cleanup shouldn't crash either side. Shutdown tears down every subscription before disposing the Plc. IHostConnectivityProbe: HostName surfaced as host:port to match Modbus driver convention (Admin /hosts dashboard renders both families uniformly). GetHostStatuses returns one row (single-endpoint driver). ProbeLoopAsync serializes on the shared Gate + calls Plc.ReadStatusAsync (cheap Get-CPU-Status PDU that doubles as an 'is PLC up' check) every Probe.Interval with a Probe.Timeout cap, transitions HostState Unknown/Stopped -> Running on success and -> Stopped on any failure, raises OnHostStatusChanged only on actual transitions (no noise for steady-state probes). Probe loop starts at end of InitializeAsync when Probe.Enabled=true (default); Shutdown cancels the probe CTS. Initial state stays Unknown until first successful probe -- avoids broadcasting a premature Running before any PDU round-trip has happened. Unit tests (S7DiscoveryAndSubscribeTests, 4 facts): DiscoverAsync_projects_every_tag_into_the_address_space (3 tags + mixed writable/read-only -> Operate vs ViewOnly asserted), GetHostStatuses_returns_one_row_with_host_port_identity_pre_init, SubscribeAsync_returns_unique_handles_and_UnsubscribeAsync_accepts_them (diagnosticId uniqueness + idempotent double-unsubscribe), Subscribe_publishing_interval_is_floored_at_100ms (accepts 50ms request without throwing -- floor is applied internally). Uses a RecordingAddressSpaceBuilder stub that implements IVariableHandle.FullReference + MarkAsAlarmCondition (throws NotImplementedException since the S7 driver never calls it -- alarms out of scope). 57/57 S7 unit tests pass. dotnet build clean. All 5 capability interfaces (IDriver/ITagDiscovery/IReadable/IWritable/ISubscribable/IHostConnectivityProbe) now implemented -- the S7 driver surface is on par with the Modbus driver, minus the extended data types (Int64/UInt64/Float64/String/DateTime deferred per PR 64). d8ef35d5bd
dohertj2 merged commit d33e38e059 into v2 2026-04-19 00:18:19 -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#64