fix(s7): treat ArrayCount>=1 as array so 1-element arrays read as arrays (review I-2)
This commit is contained in:
@@ -442,11 +442,12 @@ public sealed class S7Driver
|
|||||||
? parsed
|
? parsed
|
||||||
: S7AddressParser.Parse(tag.Address);
|
: 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
|
// 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
|
// 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.
|
// (count null) is left byte-for-byte unchanged below. A count of 1 IS a valid 1-element
|
||||||
if (tag.ArrayCount is > 1)
|
// 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);
|
return await ReadArrayAsync(plc, tag, addr, ct).ConfigureAwait(false);
|
||||||
|
|
||||||
// S7.Net's string-based ReadAsync returns object where the boxed .NET type depends on
|
// 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");
|
var folder = builder.Folder("S7", "S7");
|
||||||
foreach (var t in _options.Tags)
|
foreach (var t in _options.Tags)
|
||||||
{
|
{
|
||||||
// A tag carrying an array count (> 1) surfaces as a 1-D OPC UA array node; a missing
|
// A tag carrying a non-null array count (>= 1) surfaces as a 1-D OPC UA array node.
|
||||||
// count or a count of 1 stays scalar (count == 1 array adds no information over a
|
// A null count stays scalar. A count of 1 IS a valid 1-element array: the foundation
|
||||||
// scalar and would force every read down the slower block path).
|
// materialises a [1] OPC UA array node when isArray:true, so the driver must agree.
|
||||||
var isArray = t.ArrayCount is > 1;
|
var isArray = t.ArrayCount is >= 1;
|
||||||
folder.Variable(t.Name, t.Name, new DriverAttributeInfo(
|
folder.Variable(t.Name, t.Name, new DriverAttributeInfo(
|
||||||
FullName: t.Name,
|
FullName: t.Name,
|
||||||
DriverDataType: MapDataType(t.DataType),
|
DriverDataType: MapDataType(t.DataType),
|
||||||
|
|||||||
@@ -216,9 +216,11 @@ public sealed class S7ArrayReadTests
|
|||||||
arr.ArrayDim.ShouldBe(8u);
|
arr.ArrayDim.ShouldBe(8u);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>Verifies a tag with ArrayCount of 1 is treated as a scalar (no array node).</summary>
|
/// <summary>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.</summary>
|
||||||
[Fact]
|
[Fact]
|
||||||
public async Task DiscoverAsync_count_of_one_is_scalar()
|
public async Task DiscoverAsync_count_of_one_is_array()
|
||||||
{
|
{
|
||||||
var opts = new S7DriverOptions
|
var opts = new S7DriverOptions
|
||||||
{
|
{
|
||||||
@@ -231,8 +233,40 @@ public sealed class S7ArrayReadTests
|
|||||||
await drv.DiscoverAsync(builder, TestContext.Current.CancellationToken);
|
await drv.DiscoverAsync(builder, TestContext.Current.CancellationToken);
|
||||||
|
|
||||||
var one = builder.Variables.Single().Attr;
|
var one = builder.Variables.Single().Attr;
|
||||||
one.IsArray.ShouldBeFalse("count<=1 is scalar");
|
one.IsArray.ShouldBeTrue("ArrayCount=1 is a 1-element array, not scalar");
|
||||||
one.ArrayDim.ShouldBeNull();
|
one.ArrayDim.ShouldBe(1u);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>Verifies that a tag with a null ArrayCount (scalar) is still discovered as scalar.</summary>
|
||||||
|
[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();
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>Verifies a read of an isArray:true arrayLength:1 equipment tag returns a 1-element typed array.</summary>
|
||||||
|
[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<short[]>();
|
||||||
|
arr.Length.ShouldBe(1);
|
||||||
|
arr[0].ShouldBe((short)100);
|
||||||
}
|
}
|
||||||
|
|
||||||
// ── Equipment-tag resolver threads arrayLength → ArrayCount ───────────────────────────
|
// ── Equipment-tag resolver threads arrayLength → ArrayCount ───────────────────────────
|
||||||
|
|||||||
Reference in New Issue
Block a user