diff --git a/src/Drivers/ZB.MOM.WW.OtOpcUa.Driver.S7/S7Driver.cs b/src/Drivers/ZB.MOM.WW.OtOpcUa.Driver.S7/S7Driver.cs index dbb9a475..2245da5b 100644 --- a/src/Drivers/ZB.MOM.WW.OtOpcUa.Driver.S7/S7Driver.cs +++ b/src/Drivers/ZB.MOM.WW.OtOpcUa.Driver.S7/S7Driver.cs @@ -442,11 +442,12 @@ public sealed class S7Driver ? parsed : S7AddressParser.Parse(tag.Address); - // Array path: a tag with a declared count > 1 reads a CONTIGUOUS block of + // Array path: a tag with a declared count >= 1 reads a CONTIGUOUS block of // count × element-bytes in a SINGLE round-trip (Plc.ReadBytesAsync), then decodes each // element from its big-endian slice into an element-typed CLR array. The scalar path - // (count null / <= 1) is left byte-for-byte unchanged below. - if (tag.ArrayCount is > 1) + // (count null) is left byte-for-byte unchanged below. A count of 1 IS a valid 1-element + // array (the foundation materialises a [1] OPC UA array node when isArray:true). + if (tag.ArrayCount is >= 1) return await ReadArrayAsync(plc, tag, addr, ct).ConfigureAwait(false); // S7.Net's string-based ReadAsync returns object where the boxed .NET type depends on @@ -793,10 +794,10 @@ public sealed class S7Driver var folder = builder.Folder("S7", "S7"); foreach (var t in _options.Tags) { - // A tag carrying an array count (> 1) surfaces as a 1-D OPC UA array node; a missing - // count or a count of 1 stays scalar (count == 1 array adds no information over a - // scalar and would force every read down the slower block path). - var isArray = t.ArrayCount is > 1; + // A tag carrying a non-null array count (>= 1) surfaces as a 1-D OPC UA array node. + // A null count stays scalar. A count of 1 IS a valid 1-element array: the foundation + // materialises a [1] OPC UA array node when isArray:true, so the driver must agree. + var isArray = t.ArrayCount is >= 1; folder.Variable(t.Name, t.Name, new DriverAttributeInfo( FullName: t.Name, DriverDataType: MapDataType(t.DataType), diff --git a/tests/Drivers/ZB.MOM.WW.OtOpcUa.Driver.S7.Tests/S7ArrayReadTests.cs b/tests/Drivers/ZB.MOM.WW.OtOpcUa.Driver.S7.Tests/S7ArrayReadTests.cs index 92167a0c..90575033 100644 --- a/tests/Drivers/ZB.MOM.WW.OtOpcUa.Driver.S7.Tests/S7ArrayReadTests.cs +++ b/tests/Drivers/ZB.MOM.WW.OtOpcUa.Driver.S7.Tests/S7ArrayReadTests.cs @@ -216,9 +216,11 @@ public sealed class S7ArrayReadTests arr.ArrayDim.ShouldBe(8u); } - /// Verifies a tag with ArrayCount of 1 is treated as a scalar (no array node). + /// Verifies a tag with ArrayCount of 1 is treated as a 1-element array (not scalar). + /// The foundation materialises a [1] OPC UA array node when IsArray=true, so the driver must + /// honour isArray:true + arrayLength:1 — a 1-element array IS a valid array. [Fact] - public async Task DiscoverAsync_count_of_one_is_scalar() + public async Task DiscoverAsync_count_of_one_is_array() { var opts = new S7DriverOptions { @@ -231,8 +233,40 @@ public sealed class S7ArrayReadTests await drv.DiscoverAsync(builder, TestContext.Current.CancellationToken); var one = builder.Variables.Single().Attr; - one.IsArray.ShouldBeFalse("count<=1 is scalar"); - one.ArrayDim.ShouldBeNull(); + one.IsArray.ShouldBeTrue("ArrayCount=1 is a 1-element array, not scalar"); + one.ArrayDim.ShouldBe(1u); + } + + /// Verifies that a tag with a null ArrayCount (scalar) is still discovered as scalar. + [Fact] + public async Task DiscoverAsync_null_ArrayCount_is_scalar() + { + var opts = new S7DriverOptions + { + Host = "192.0.2.1", + Tags = [new("Scalar", "DB1.DBW0", S7DataType.Int16, ArrayCount: null)], + }; + using var drv = new S7Driver(opts, "s7-arr-null"); + + var builder = new RecordingBuilder(); + await drv.DiscoverAsync(builder, TestContext.Current.CancellationToken); + + var scalar = builder.Variables.Single().Attr; + scalar.IsArray.ShouldBeFalse("null ArrayCount is scalar"); + scalar.ArrayDim.ShouldBeNull(); + } + + /// Verifies a read of an isArray:true arrayLength:1 equipment tag returns a 1-element typed array. + [Fact] + public void DecodeArrayBlock_count_of_one_returns_single_element_array() + { + // A 1-element Int16 array: one big-endian word. + var block = BeWords(0x0064); // 100 + var result = S7Driver.DecodeArrayBlock(ArrTag(S7DataType.Int16, 1), Addr(S7Size.Word), block); + + var arr = result.ShouldBeOfType(); + arr.Length.ShouldBe(1); + arr[0].ShouldBe((short)100); } // ── Equipment-tag resolver threads arrayLength → ArrayCount ───────────────────────────