feat(modbus): String + BitInRegister array decode + equipment-tag arrayLength

- DecodeRegisterArray: add String and BitInRegister cases replacing the
  default:throw; each element decoded by reusing DecodeRegister on its
  contiguous register slice → string[] / bool[]
- ModbusEquipmentTagParser.TryParse: read optional arrayLength key from
  TagConfig JSON and thread it into ModbusTagDefinition.ArrayCount
  (null when absent or zero, preserving scalar behaviour)
- ModbusArrayTests: 8 new tests covering the two decode cases and the
  equipment-tag parser/resolver path; 285/285 green
This commit is contained in:
Joseph Doherty
2026-06-16 21:51:55 -04:00
parent c2006dfb57
commit 8d3dc32148
3 changed files with 197 additions and 1 deletions
@@ -34,9 +34,14 @@ public static class ModbusEquipmentTagParser
var byteOrder = ReadEnum(root, "byteOrder", ModbusByteOrder.BigEndian);
var bitIndex = (byte)ReadInt(root, "bitIndex");
var stringLength = (ushort)ReadInt(root, "stringLength");
// isArray / arrayLength — optional keys authored by the typed Modbus tag editor.
// When arrayLength > 0 we expose an array tag of that count; otherwise scalar.
var arrayLength = ReadInt(root, "arrayLength");
int? arrayCount = arrayLength > 0 ? arrayLength : null;
def = new ModbusTagDefinition(
Name: reference, Region: region, Address: (ushort)address, DataType: dataType,
Writable: true, ByteOrder: byteOrder, BitIndex: bitIndex, StringLength: stringLength);
Writable: true, ByteOrder: byteOrder, BitIndex: bitIndex, StringLength: stringLength,
ArrayCount: arrayCount);
return true;
}
catch (JsonException) { return false; }
@@ -479,6 +479,25 @@ public sealed class ModbusDriver
arr[i] = (double)DecodeRegister(data.AsSpan(i * elementBytes, elementBytes), tag);
return arr;
}
case ModbusDataType.String:
{
// Each element occupies elementRegs registers = elementBytes bytes, encoding
// StringLength chars packed high-byte-first or low-byte-first per tag.StringByteOrder.
// DecodeRegister handles the per-element slice identically to the scalar path.
var arr = new string[count];
for (var i = 0; i < count; i++)
arr[i] = (string)DecodeRegister(data.AsSpan(i * elementBytes, elementBytes), tag);
return arr;
}
case ModbusDataType.BitInRegister:
{
// Each element is 1 holding register (2 bytes). The same BitIndex is applied to
// every element. DecodeRegister returns bool for BitInRegister scalar slices.
var arr = new bool[count];
for (var i = 0; i < count; i++)
arr[i] = (bool)DecodeRegister(data.AsSpan(i * elementBytes, elementBytes), tag);
return arr;
}
default:
throw new InvalidOperationException(
$"Array decode not supported for {tag.DataType} (use scalar tags or split by element)");