[twincat] TwinCAT — Bit-indexed BOOL writes (RMW) #343

Merged
dohertj2 merged 1 commits from auto/twincat/1.3 into auto/driver-gaps 2026-04-25 17:25:23 -04:00
Owner

Summary

Replaces the NotSupportedException for bit-indexed BOOL writes (MyVar.3) with a read-modify-write path.

  • Strips the trailing .N bit selector, reads the parent as UDINT, applies the bit mask (word | (1u<<N) to set / word & ~(1u<<N) to clear), writes the parent back.
  • Concurrent writers against the same parent serialise via a per-parent SemaphoreSlim cached in a never-evicted ConcurrentDictionary<string, SemaphoreSlim>. Same pattern AbCip uses.
  • Extracted TryGetParentSymbolPath + ApplyBit as internal static helpers (project already has InternalsVisibleTo on the test project) so the pure logic is testable without a real ADS target.

The existing bit read path (ExtractBit) is unchanged.

Test plan

  • dotnet build src/ZB.MOM.WW.OtOpcUa.Driver.TwinCAT — clean (0 / 0)
  • dotnet test tests/ZB.MOM.WW.OtOpcUa.Driver.TwinCAT.Tests134 / 134 passed (11 new in TwinCATBitWriteTests: helper unit tests, driver round-trip set/clear, regression that bit writes no longer return BadNotSupported)
  • Extended FakeTwinCATClient.WriteValueAsync to model parent-word RMW so driver-level tests can verify resulting parent state.
  • Integration tests — hardware-gated (TWINCAT_TARGET_NETID)

🤖 Auto-generated by the Mode-B execution loop. Closes #307.

Closes #307

## Summary Replaces the `NotSupportedException` for bit-indexed BOOL writes (`MyVar.3`) with a read-modify-write path. - Strips the trailing `.N` bit selector, reads the parent as UDINT, applies the bit mask (`word | (1u<<N)` to set / `word & ~(1u<<N)` to clear), writes the parent back. - Concurrent writers against the same parent serialise via a **per-parent `SemaphoreSlim`** cached in a never-evicted `ConcurrentDictionary<string, SemaphoreSlim>`. Same pattern AbCip uses. - Extracted `TryGetParentSymbolPath` + `ApplyBit` as `internal static` helpers (project already has `InternalsVisibleTo` on the test project) so the pure logic is testable without a real ADS target. The existing bit *read* path (`ExtractBit`) is unchanged. ## Test plan - [x] `dotnet build src/ZB.MOM.WW.OtOpcUa.Driver.TwinCAT` — clean (0 / 0) - [x] `dotnet test tests/ZB.MOM.WW.OtOpcUa.Driver.TwinCAT.Tests` — **134 / 134 passed** (11 new in `TwinCATBitWriteTests`: helper unit tests, driver round-trip set/clear, regression that bit writes no longer return BadNotSupported) - [x] Extended `FakeTwinCATClient.WriteValueAsync` to model parent-word RMW so driver-level tests can verify resulting parent state. - [ ] Integration tests — hardware-gated (TWINCAT_TARGET_NETID) 🤖 Auto-generated by the Mode-B execution loop. Closes #307. Closes #307
dohertj2 added 1 commit 2026-04-25 17:25:18 -04:00
Replace the NotSupportedException at AdsTwinCATClient.WriteValueAsync
for bit-indexed BOOL writes with a read-modify-write path:

  1. Strip the trailing .N selector from the symbol path.
  2. Read the parent as UDINT.
  3. Set or clear bit N via the standard mask.
  4. Write the parent back.

Concurrent bit writers against the same parent serialise through a
per-parent SemaphoreSlim cached in a ConcurrentDictionary (never
removed — bounded by writable-bit-tag cardinality). Mirrors the AbCip /
Modbus / FOCAS bit-RMW pattern shipped in #181 pass 1.

The path-stripping (TryGetParentSymbolPath) and mask helper (ApplyBit)
are exposed as internal statics so tests can pin the pure logic without
needing a real ADS target. The FakeTwinCATClient mirrors the same RMW
semantics so driver-level round-trip tests assert the parent-word state.

Closes #307
dohertj2 merged commit e6a55add20 into auto/driver-gaps 2026-04-25 17:25:23 -04:00
dohertj2 deleted branch auto/twincat/1.3 2026-04-25 17:25:23 -04:00
Sign in to join this conversation.