diff --git a/tests/Drivers/ZB.MOM.WW.OtOpcUa.Driver.S7.IntegrationTests/Docker/README.md b/tests/Drivers/ZB.MOM.WW.OtOpcUa.Driver.S7.IntegrationTests/Docker/README.md index 9b278870..358f90c0 100644 --- a/tests/Drivers/ZB.MOM.WW.OtOpcUa.Driver.S7.IntegrationTests/Docker/README.md +++ b/tests/Drivers/ZB.MOM.WW.OtOpcUa.Driver.S7.IntegrationTests/Docker/README.md @@ -142,17 +142,22 @@ counter to an `int` count (`Counter.FromByteArray`, raw word — **not** BCD). Live-probed 2026-08-17: snap7's server resolves the TM/CT request address as a **bit address** — it serves byte `addr >> 3` of the area — whereas S7.Net, and a -real S7 CPU, put the device **number** in that field. Measured directly against -`10.100.0.35:1102`: +real S7 CPU, put the device **number** in that field. Measured with S7netplus +0.20.0 against `10.100.0.35:1102` running **the profile above**: ``` -ReadBytes(Timer,db=0,start=0,count=8) => [00 00 00 00 24 56 00 00] # bytes 0..7 (T2 seeded at byte 4) -ReadBytes(Timer,db=0,start=2,count=2) => [00 00] # 2>>3 = byte 0, NOT timer 2 at byte 4 -ReadBytes(Timer,db=0,start=4,count=2) => [00 00] # 4>>3 = byte 0 -ReadBytes(Timer,db=0,start=32,count=2) => [24 56] # 32>>3 = byte 4 ← the timer-2 cell -ReadBytes(Counter,db=0,start=48,count=2) => [12 67] # 48>>3 = byte 6 ← the counter-3 cell +ReadBytes(Timer,db=0,start=0,count=8) => [24 56 00 00 00 00 00 00] # TM bytes 0..7: T0 = 0x2456 +ReadBytes(Timer,db=0,start=0,count=2) => [24 56] # T0 -> byte 0. 0>>3 == 2*0, so BOTH conventions agree here +ReadBytes(Timer,db=0,start=5,count=2) => [24 56] # T5 -> 5>>3 = byte 0. WRONG cell: T5 lives at bytes 10..11 (0x1123) +ReadBytes(Timer,db=0,start=80,count=2) => [11 23] # byte 10 needs start 80 (= 10*8): the T5 cell, reached the bit-address way +ReadBytes(Counter,db=0,start=0,count=2) => [12 67] # C0 -> byte 0 = 4711. Both conventions agree +ReadBytes(Counter,db=0,start=7,count=2) => [12 67] # C7 -> 7>>3 = byte 0. WRONG cell: C7 lives at bytes 14..15 (0x7A69 = 31337) +ReadBytes(Counter,db=0,start=112,count=2) => [7A 69] # byte 14 needs start 112 (= 14*8): the C7 cell ``` +Note lines 2 and 3: asking for `T0` and asking for `T5` return the **same bytes** +— every device 0..7 collapses onto TM byte 0. Same for `C0` / `C7`. + No error is raised — the reads succeed, they just land on the wrong cell. Device **0** is the single fixed point of the two conventions (`0 >> 3 == 2 × 0 == 0`), so `T0` / `C0` are the only cells whose *value* the fixture can pin; that is what diff --git a/tests/Drivers/ZB.MOM.WW.OtOpcUa.Driver.S7.IntegrationTests/S7_1500/S7_1500SpanReadTests.cs b/tests/Drivers/ZB.MOM.WW.OtOpcUa.Driver.S7.IntegrationTests/S7_1500/S7_1500SpanReadTests.cs index 5ffed5d4..e81da7c7 100644 --- a/tests/Drivers/ZB.MOM.WW.OtOpcUa.Driver.S7.IntegrationTests/S7_1500/S7_1500SpanReadTests.cs +++ b/tests/Drivers/ZB.MOM.WW.OtOpcUa.Driver.S7.IntegrationTests/S7_1500/S7_1500SpanReadTests.cs @@ -488,6 +488,13 @@ public sealed class S7_1500SpanReadTests(Snap7ServerFixture sim) /// test can assert the wire shape rather than infer it. Delegates to the real factory, so /// every counted call is the one production would have issued. /// + /// + /// Not thread-safe, by design. Every ReadBatchAsync builds its own factory and + /// the driver serialises all wire calls on its _gate, so nothing here is ever touched + /// concurrently today. A future caller that shared one factory across parallel reads would + /// silently corrupt the counts and shape lists rather than fail loudly — give it a lock, or + /// a factory per batch, before doing that. + /// /// The factory producing the real connection. private sealed class CountingPlcFactory(IS7PlcFactory inner) : IS7PlcFactory {