feat(twincat): 1-D array symbol read via ADS + IsArray discovery

This commit is contained in:
Joseph Doherty
2026-06-16 21:59:17 -04:00
parent 950069392c
commit 3e74239532
7 changed files with 341 additions and 14 deletions
@@ -70,13 +70,19 @@ public sealed record TwinCATDeviceOptions(
/// One TwinCAT-backed OPC UA variable. <paramref name="SymbolPath"/> is the full TwinCAT
/// symbolic name (e.g. <c>MAIN.bStart</c>, <c>GVL.Counter</c>, <c>Motor1.Status.Running</c>).
/// </summary>
/// <param name="ArrayLength">
/// When non-null, this tag is a 1-D array of <paramref name="ArrayLength"/> elements of
/// <paramref name="DataType"/>. Drives <c>IsArray</c>/<c>ArrayDim</c> at discovery and a
/// native ADS array read at runtime (Phase 4c). <c>null</c> = scalar (the default).
/// </param>
public sealed record TwinCATTagDefinition(
string Name,
string DeviceHostAddress,
string SymbolPath,
TwinCATDataType DataType,
bool Writable = true,
bool WriteIdempotent = false);
bool WriteIdempotent = false,
int? ArrayLength = null);
/// <summary>Probe options for TwinCAT connection monitoring.</summary>
public sealed class TwinCATProbeOptions
@@ -29,9 +29,14 @@ public static class TwinCATEquipmentTagParser
if (string.IsNullOrWhiteSpace(symbolPath)) return false;
var deviceHostAddress = ReadString(root, "deviceHostAddress");
var dataType = ReadEnum(root, "dataType", TwinCATDataType.DInt);
// Array intent — same shape the runtime/OPC-UA foundation parses (camelCase
// `isArray` bool + `arrayLength` uint). arrayLength is honoured ONLY when isArray
// is true AND it is a positive JSON number, so a stale length behind a cleared
// isArray never produces an orphan array tag (Phase 4c).
var arrayLength = ReadArrayLength(root);
def = new TwinCATTagDefinition(
Name: reference, DeviceHostAddress: deviceHostAddress, SymbolPath: symbolPath,
DataType: dataType, Writable: true);
DataType: dataType, Writable: true, ArrayLength: arrayLength);
return true;
}
catch (JsonException) { return false; }
@@ -46,4 +51,18 @@ public static class TwinCATEquipmentTagParser
private static string ReadString(JsonElement o, string name)
=> o.TryGetProperty(name, out var e) && e.ValueKind == JsonValueKind.String
? e.GetString() ?? "" : "";
/// <summary>
/// Reads the optional 1-D array length: <c>arrayLength</c> (a positive uint) honoured ONLY
/// when <c>isArray</c> is the JSON literal <c>true</c>. Returns <c>null</c> (scalar) when
/// isArray is absent/false, when arrayLength is absent / non-numeric / zero / negative.
/// </summary>
private static int? ReadArrayLength(JsonElement o)
{
if (!o.TryGetProperty("isArray", out var aEl) || aEl.ValueKind != JsonValueKind.True)
return null;
if (!o.TryGetProperty("arrayLength", out var lEl) || lEl.ValueKind != JsonValueKind.Number)
return null;
return lEl.TryGetInt32(out var len) && len > 0 ? len : null;
}
}