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
@@ -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)");