feat(driver-modbus): shared TagConfigJson readers, writable key, Inspect warnings (R2-11)

This commit is contained in:
Joseph Doherty
2026-07-13 10:55:41 -04:00
parent a9b7a5002a
commit 64b9bba9e9
4 changed files with 121 additions and 8 deletions
@@ -1,4 +1,5 @@
using System.Text.Json;
using ZB.MOM.WW.OtOpcUa.Core.Abstractions;
namespace ZB.MOM.WW.OtOpcUa.Driver.Modbus;
@@ -29,9 +30,9 @@ public static class ModbusEquipmentTagParser
|| !addr.TryGetInt32(out var address)
|| address < 0 || address > ushort.MaxValue)
return false;
var region = ReadEnum(root, "region", ModbusRegion.HoldingRegisters);
var dataType = ReadEnum(root, "dataType", ModbusDataType.Int16);
var byteOrder = ReadEnum(root, "byteOrder", ModbusByteOrder.BigEndian);
var region = TagConfigJson.ReadEnumOrDefault(root, "region", ModbusRegion.HoldingRegisters);
var dataType = TagConfigJson.ReadEnumOrDefault(root, "dataType", ModbusDataType.Int16);
var byteOrder = TagConfigJson.ReadEnumOrDefault(root, "byteOrder", ModbusByteOrder.BigEndian);
var bitIndex = (byte)ReadInt(root, "bitIndex");
var stringLength = (ushort)ReadInt(root, "stringLength");
// Guard: String tags require StringLength >= 1. RegisterCount = (StringLength+1)/2,
@@ -51,9 +52,12 @@ public static class ModbusEquipmentTagParser
var isArray = ReadBool(root, "isArray");
var arrayLength = ReadInt(root, "arrayLength");
int? arrayCount = (isArray && arrayLength >= 1) ? arrayLength : null;
// "writable" defaults to true when absent (today's value) — makes read-only equipment tags
// authorable (UNDER-6). Node-level authz remains the effective write gate.
var writable = TagConfigJson.ReadWritable(root);
def = new ModbusTagDefinition(
Name: reference, Region: region, Address: (ushort)address, DataType: dataType,
Writable: true, ByteOrder: byteOrder, BitIndex: bitIndex, StringLength: stringLength,
Writable: writable, ByteOrder: byteOrder, BitIndex: bitIndex, StringLength: stringLength,
ArrayCount: arrayCount);
return true;
}
@@ -62,9 +66,44 @@ public static class ModbusEquipmentTagParser
catch (InvalidOperationException) { return false; }
}
private static TEnum ReadEnum<TEnum>(JsonElement o, string name, TEnum fallback) where TEnum : struct, Enum
=> o.TryGetProperty(name, out var e) && e.ValueKind == JsonValueKind.String
&& Enum.TryParse<TEnum>(e.GetString(), ignoreCase: true, out var v) ? v : fallback;
/// <summary>
/// Deploy-time inspection of an equipment-tag <c>TagConfig</c> blob (05/CONV-2). Returns
/// human-readable warnings for present-but-invalid enum values (<c>region</c> / <c>dataType</c> /
/// <c>byteOrder</c> — which the lenient runtime silently defaults) and for a structurally
/// unparseable TagConfig (which the runtime turns into a silent <c>BadNodeIdUnknown</c>). Empty when
/// the blob is clean or is not an equipment-tag TagConfig object. Never throws.
/// </summary>
/// <param name="reference">The equipment tag's TagConfig JSON.</param>
/// <returns>The warnings; empty when clean.</returns>
public static IReadOnlyList<string> Inspect(string reference)
{
var warnings = new List<string>();
if (string.IsNullOrWhiteSpace(reference) || reference[0] != '{') return warnings;
try
{
using var doc = JsonDocument.Parse(reference);
var root = doc.RootElement;
if (root.ValueKind != JsonValueKind.Object)
{
warnings.Add("Modbus TagConfig root is not a JSON object — the tag will not resolve (BadNodeIdUnknown).");
return warnings;
}
foreach (var w in new[]
{
TagConfigJson.DescribeInvalidEnum<ModbusRegion>(root, "region"),
TagConfigJson.DescribeInvalidEnum<ModbusDataType>(root, "dataType"),
TagConfigJson.DescribeInvalidEnum<ModbusByteOrder>(root, "byteOrder"),
})
{
if (w is not null) warnings.Add(w);
}
}
catch (JsonException)
{
warnings.Add("Modbus TagConfig is not valid JSON — the tag will not resolve (BadNodeIdUnknown).");
}
return warnings;
}
private static int ReadInt(JsonElement o, string name)
=> o.TryGetProperty(name, out var e) && e.ValueKind == JsonValueKind.Number
@@ -12,6 +12,9 @@
and was designed for exactly this use — Admin can reference it without transport-layer deps. -->
<ItemGroup>
<ProjectReference Include="..\ZB.MOM.WW.OtOpcUa.Driver.Modbus.Addressing\ZB.MOM.WW.OtOpcUa.Driver.Modbus.Addressing.csproj"/>
<!-- R2-11 (05/CONV-2): shared TagConfigJson field readers live in the zero-dependency
Core.Abstractions leaf beside EquipmentTagRefResolver. -->
<ProjectReference Include="..\..\Core\ZB.MOM.WW.OtOpcUa.Core.Abstractions\ZB.MOM.WW.OtOpcUa.Core.Abstractions.csproj"/>
</ItemGroup>
</Project>