feat(ablegacy): PCCC multi-element file array read + IsArray discovery

This commit is contained in:
Joseph Doherty
2026-06-16 21:55:41 -04:00
parent f4d5a5ee9c
commit 950069392c
7 changed files with 448 additions and 15 deletions
@@ -41,13 +41,31 @@ public sealed record AbLegacyDeviceOptions(
/// One PCCC-backed OPC UA variable. <paramref name="Address"/> is the canonical PCCC
/// file-address string that parses via <c>AbLegacyAddress.TryParse</c>.
/// </summary>
/// <param name="ArrayLength">
/// Element count when the tag addresses a multi-element span of a PCCC data file (e.g. an
/// <c>N7</c> integer file is inherently up to 256 words); <see langword="null"/> for a scalar.
/// A PCCC data file holds at most <see cref="AbLegacyArray.MaxElements"/> (256) elements, so a
/// value above that is clamped where it is materialised/read. <c>1</c> reads as a scalar.
/// </param>
public sealed record AbLegacyTagDefinition(
string Name,
string DeviceHostAddress,
string Address,
AbLegacyDataType DataType,
bool Writable = true,
bool WriteIdempotent = false);
bool WriteIdempotent = false,
int? ArrayLength = null);
/// <summary>PCCC array-tag constants shared by the parser, discovery, and read paths.</summary>
public static class AbLegacyArray
{
/// <summary>
/// Maximum element count for a single PCCC data file. The PCCC/DF1 protocol addresses a
/// data file element with a single byte sub-element offset, so a file holds at most 256
/// elements (words for N/B/I/O/S/A, 32-bit elements for L/F). Array tags clamp to this.
/// </summary>
public const int MaxElements = 256;
}
public sealed class AbLegacyProbeOptions
{
@@ -29,9 +29,18 @@ public static class AbLegacyEquipmentTagParser
if (string.IsNullOrWhiteSpace(address)) return false;
var dataType = ReadEnum(root, "dataType", AbLegacyDataType.Int);
var deviceHostAddress = ReadString(root, "deviceHostAddress");
// Phase 4c #137 — thread the equipment tag's array element count. arrayLength is the
// authoritative count; isArray is the AdminUI's boolean toggle. A positive arrayLength
// (regardless of isArray) makes this an array tag. Clamp to the PCCC file maximum
// (AbLegacyArray.MaxElements = 256) so a fat-fingered count can never request a span
// larger than a single data file holds. Absent / non-positive → null (scalar).
int? arrayLength = null;
var rawLength = ReadInt(root, "arrayLength");
if (rawLength > 0)
arrayLength = Math.Min(rawLength, AbLegacyArray.MaxElements);
def = new AbLegacyTagDefinition(
Name: reference, DeviceHostAddress: deviceHostAddress, Address: address,
DataType: dataType, Writable: true);
DataType: dataType, Writable: true, ArrayLength: arrayLength);
return true;
}
catch (JsonException) { return false; }
@@ -46,4 +55,8 @@ public static class AbLegacyEquipmentTagParser
private static string ReadString(JsonElement o, string name)
=> o.TryGetProperty(name, out var e) && e.ValueKind == JsonValueKind.String
? e.GetString() ?? "" : "";
private static int ReadInt(JsonElement o, string name)
=> o.TryGetProperty(name, out var e) && e.ValueKind == JsonValueKind.Number
&& e.TryGetInt32(out var v) ? v : 0;
}