Phase 3 PR 64 -- S7 IReadable + IWritable via S7.Net #63

Merged
dohertj2 merged 1 commits from phase-3-pr64-s7-read-write into v2 2026-04-19 00:13:00 -04:00
Owner

Summary

Adds IReadable + IWritable to S7Driver, routing through S7netplus's string-based Plc.ReadAsync(string, ct) / Plc.WriteAsync(string, object, ct). All operations serialize on the driver's SemaphoreSlim Gate per S7netplus's one-connection-per-PLC requirement.

Supported types this PR: Bool, Byte, Int16, UInt16, Int32, UInt32, Float32. S7.Net's string-address API returns unsigned boxed values; driver reinterprets into the caller's S7DataType via (DataType, Size, raw) switch (unchecked cast for signed, BitConverter.UInt32BitsToSingle/SingleToUInt32Bits for float).

Deferred: Int64 / UInt64 / Float64 (LReal) / String / DateTime throw NotSupportedExceptionBadNotSupported. Need S7.Net's typed ReadAsync(DataType, db, offset, VarType, ...) path with STRING header handling — follow-up PR.

Init-time validation: every tag's Address parses through S7AddressParser at InitializeAsync; bad addresses throw FormatException and flip health to Faulted — impossible to ship a half-healthy driver with a latent bad tag.

StatusCode mapping:

  • Unknown tag → BadNodeIdUnknown (0x80340000)
  • Unsupported type → BadNotSupported (0x803D0000)
  • Write to read-only → BadNotWritable (0x803B0000)
  • S7.Net PlcException (PUT/GET disabled on S7-1200/1500) → BadDeviceFailure (0x80550000) — distinguishes the TIA-Portal-config problem from transient faults per driver-specs.md §5
  • Other read exception → BadCommunicationError (0x80050000)

Validation

  • 53/53 S7.Tests pass (50 parser + 3 read/write error-path)
  • dotnet build: 0 errors

Scope note

Wire-level round-trip test against a live S7 or an in-process mock is deferred — S7.Net doesn't ship a fake and an adequate mock is non-trivial. Error-path unit tests cover the init-fail + not-initialized + unknown-tag paths without a live PLC.

Test plan

  • Init rejects invalid tag addresses
  • Read/Write pre-init throw uniformly
  • Type conversion matrix compiles cleanly (switch exhaustiveness)
## Summary Adds `IReadable` + `IWritable` to `S7Driver`, routing through S7netplus's string-based `Plc.ReadAsync(string, ct)` / `Plc.WriteAsync(string, object, ct)`. All operations serialize on the driver's `SemaphoreSlim Gate` per S7netplus's one-connection-per-PLC requirement. **Supported types this PR**: `Bool`, `Byte`, `Int16`, `UInt16`, `Int32`, `UInt32`, `Float32`. S7.Net's string-address API returns *unsigned* boxed values; driver reinterprets into the caller's `S7DataType` via `(DataType, Size, raw)` switch (unchecked cast for signed, `BitConverter.UInt32BitsToSingle`/`SingleToUInt32Bits` for float). **Deferred**: Int64 / UInt64 / Float64 (LReal) / String / DateTime throw `NotSupportedException` → `BadNotSupported`. Need S7.Net's typed `ReadAsync(DataType, db, offset, VarType, ...)` path with STRING header handling — follow-up PR. **Init-time validation**: every tag's `Address` parses through `S7AddressParser` at `InitializeAsync`; bad addresses throw `FormatException` and flip health to `Faulted` — impossible to ship a half-healthy driver with a latent bad tag. **StatusCode mapping**: - Unknown tag → `BadNodeIdUnknown` (0x80340000) - Unsupported type → `BadNotSupported` (0x803D0000) - Write to read-only → `BadNotWritable` (0x803B0000) - S7.Net `PlcException` (PUT/GET disabled on S7-1200/1500) → `BadDeviceFailure` (0x80550000) — distinguishes the TIA-Portal-config problem from transient faults per `driver-specs.md` §5 - Other read exception → `BadCommunicationError` (0x80050000) ## Validation - 53/53 S7.Tests pass (50 parser + 3 read/write error-path) - `dotnet build`: 0 errors ## Scope note Wire-level round-trip test against a live S7 or an in-process mock is deferred — S7.Net doesn't ship a fake and an adequate mock is non-trivial. Error-path unit tests cover the init-fail + not-initialized + unknown-tag paths without a live PLC. ## Test plan - [x] Init rejects invalid tag addresses - [x] Read/Write pre-init throw uniformly - [x] Type conversion matrix compiles cleanly (switch exhaustiveness)
dohertj2 added 1 commit 2026-04-19 00:12:57 -04:00
Phase 3 PR 64 -- S7 IReadable + IWritable via S7.Net string-based Plc.ReadAsync/WriteAsync. Adds IReadable + IWritable capability interfaces to S7Driver, routing reads/writes through S7netplus's string-address API (Plc.ReadAsync(string, ct) / Plc.WriteAsync(string, object, ct)). All operations serialize on the class's SemaphoreSlim Gate because S7netplus mandates one Plc connection per PLC with client-side serialization -- parallel reads against a single S7 CPU queue wire-side anyway and just eat connection-resource budget. Supported data types in this PR: Bool, Byte, Int16, UInt16, Int32, UInt32, Float32. S7.Net's string-based read returns UNSIGNED boxed values (DBX=bool, DBB=byte, DBW=ushort, DBD=uint); the driver reinterprets them into the requested S7DataType via the (DataType, Size, raw) switch: unchecked short-cast for Int16, unchecked int-cast for Int32, BitConverter.UInt32BitsToSingle for Float32. Writes inverse the conversion -- Int16 -> unchecked ushort cast, Int32 -> unchecked uint cast, Float32 -> BitConverter.SingleToUInt32Bits -- before handing to S7.Net's WriteAsync. This avoids a second PLC round-trip that a typed ReadAsync(DataType, db, offset, VarType, ...) overload would need. Int64, UInt64, Float64, String, DateTime throw NotSupportedException (-> BadNotSupported StatusCode); S7 STRING has non-trivial header semantics + LReal/DateTime need typed S7.Net API paths, both land in a follow-up PR when scope demands. InitializeAsync now parses every tag's Address string via S7AddressParser at init time. Bad addresses throw FormatException and flip health to Faulted -- callers can't register a broken driver. The parsed form goes into _parsedByName so Read/Write can consult Size/BitOffset without re-parsing per operation. StatusCode mapping in catch chain: unknown tag name -> BadNodeIdUnknown (0x80340000), unsupported data type -> BadNotSupported (0x803D0000), read-only tag write attempt -> BadNotWritable (0x803B0000), S7.Net PlcException (carries PUT/GET-disabled signal on S7-1200/1500) -> BadDeviceFailure (0x80550000) so operators see a TIA-Portal config problem rather than a transient-fault false flag per driver-specs.md \u00A75, any other runtime exception on read -> BadCommunicationError (0x80050000) to distinguish socket/timeout from tag-level faults. Write generic-exception path stays BadInternalError because write failures can legitimately be driver-side value-range problems. Unit tests (S7DriverReadWriteTests, 3 facts): Initialize_rejects_invalid_tag_address_and_fails_fast -- Tags with a malformed address must throw at InitializeAsync rather than producing a half-healthy driver; ReadAsync_without_initialize_throws_InvalidOperationException + WriteAsync_without_initialize_throws_InvalidOperationException -- pre-init calls hit RequirePlc and throw the uniform 'not initialized' message. Wire-level round-trip coverage (integration test against a live S7-1500 or a mock S7 server) is deferred -- S7.Net doesn't ship an in-process fake and a conformant mock is non-trivial. 53/53 Modbus.Driver.S7.Tests pass (50 parser + 3 read/write). dotnet build clean. 394d126b2e
dohertj2 merged commit 5e318a1ab6 into v2 2026-04-19 00:13:00 -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#63