fix(ablegacy): gate array read on isArray:true; 1-element arrays + assumption comments (review C-2/I-3)
This commit is contained in:
@@ -267,15 +267,15 @@ public sealed class AbLegacyDriver : IDriver, IReadable, IWritable, ITagDiscover
|
||||
await runtime.ReadAsync(cancellationToken).ConfigureAwait(false);
|
||||
status = runtime.GetStatus();
|
||||
var parsed = AbLegacyAddress.TryParse(def.Address);
|
||||
// Phase 4c #137 — when the tag addresses a multi-element span (ArrayLength > 1)
|
||||
// decode the whole contiguous read into a typed CLR array; otherwise decode a
|
||||
// single scalar value as before. The runtime was created with a matching
|
||||
// ElementCount in EnsureTagRuntimeAsync so its buffer holds all the elements.
|
||||
var arrayLen = EffectiveArrayLength(def);
|
||||
// Phase 4c #137 — an ARRAY tag (non-null ArrayLength, ≥1) decodes the whole
|
||||
// contiguous read into a typed CLR array of that count, INCLUDING a 1-element
|
||||
// array (review I-3); a SCALAR tag (null ArrayLength) decodes a single value.
|
||||
// The runtime was created with a matching ElementCount in EnsureTagRuntimeAsync
|
||||
// so its buffer holds all the elements.
|
||||
if (status != 0)
|
||||
value = null;
|
||||
else if (arrayLen > 1)
|
||||
value = runtime.DecodeArray(def.DataType, arrayLen);
|
||||
else if (IsArrayTag(def))
|
||||
value = runtime.DecodeArray(def.DataType, EffectiveArrayLength(def));
|
||||
else
|
||||
value = runtime.DecodeValue(def.DataType, parsed?.BitIndex);
|
||||
}
|
||||
@@ -439,11 +439,14 @@ public sealed class AbLegacyDriver : IDriver, IReadable, IWritable, ITagDiscover
|
||||
foreach (var tag in tagsForDevice)
|
||||
{
|
||||
// Phase 4c #137 — PCCC data files are inherently arrays of elements (a single N7
|
||||
// file is up to 256 words). A tag whose ArrayLength addresses a multi-element span
|
||||
// now materialises a 1-D array OPC UA node. ArrayDim is clamped to the PCCC file
|
||||
// maximum (AbLegacyArray.MaxElements = 256) so the declared dimension can never
|
||||
// exceed what a single data file holds; an ArrayLength of 1 (or null) stays scalar.
|
||||
var isArray = tag.ArrayLength is int len && len > 1;
|
||||
// file is up to 256 words). The canonical contract: a tag is an ARRAY ⟺ its
|
||||
// ArrayLength is non-null (≥1, set by the parser only when isArray:true). A tag
|
||||
// with a non-null ArrayLength materialises a 1-D array OPC UA node, INCLUDING a
|
||||
// 1-element array (ArrayLength:1 → a [1] node — review I-3). ArrayDim is clamped
|
||||
// to the PCCC file maximum (AbLegacyArray.MaxElements = 256) so the declared
|
||||
// dimension can never exceed what a single data file holds; ArrayLength == null
|
||||
// (scalar) stays scalar.
|
||||
var isArray = tag.ArrayLength is int len && len >= 1;
|
||||
var arrayDim = isArray
|
||||
? (uint)Math.Min(tag.ArrayLength!.Value, AbLegacyArray.MaxElements)
|
||||
: (uint?)null;
|
||||
@@ -685,15 +688,23 @@ public sealed class AbLegacyDriver : IDriver, IReadable, IWritable, ITagDiscover
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Phase 4c #137 — whether a tag definition is an ARRAY. The canonical contract: a tag is
|
||||
/// an array ⟺ its <see cref="AbLegacyTagDefinition.ArrayLength"/> is non-null (the parser
|
||||
/// sets it ≥1 only when isArray:true), so a 1-element array (ArrayLength:1) IS an array
|
||||
/// (review I-3). <c>null</c> ArrayLength ⇒ scalar.
|
||||
/// </summary>
|
||||
private static bool IsArrayTag(AbLegacyTagDefinition def) => def.ArrayLength is int len && len >= 1;
|
||||
|
||||
/// <summary>
|
||||
/// Phase 4c #137 — the effective libplctag element count for a tag definition: the tag's
|
||||
/// <see cref="AbLegacyTagDefinition.ArrayLength"/> clamped to the PCCC file maximum
|
||||
/// (<see cref="AbLegacyArray.MaxElements"/> = 256), or <c>1</c> when the tag is scalar
|
||||
/// (null or non-positive ArrayLength). Used both to size the runtime at create time and to
|
||||
/// decide whether the read path decodes a scalar or an array.
|
||||
/// (<see cref="AbLegacyArray.MaxElements"/> = 256) when it is an array (≥1, INCLUDING 1),
|
||||
/// or <c>1</c> when the tag is scalar (null ArrayLength). Used both to size the runtime at
|
||||
/// create time and as the element count the read path decodes into an array.
|
||||
/// </summary>
|
||||
private static int EffectiveArrayLength(AbLegacyTagDefinition def) =>
|
||||
def.ArrayLength is int len && len > 1 ? Math.Min(len, AbLegacyArray.MaxElements) : 1;
|
||||
def.ArrayLength is int len && len >= 1 ? Math.Min(len, AbLegacyArray.MaxElements) : 1;
|
||||
|
||||
private async Task<IAbLegacyTagRuntime> EnsureTagRuntimeAsync(
|
||||
DeviceState device, AbLegacyTagDefinition def, CancellationToken ct)
|
||||
|
||||
Reference in New Issue
Block a user