fix(address-space): numeric array writes convert (checked) to the node element type; array-to-scalar refusal only for Modbus/S7 (#610 review R6)
MAJOR-1: R4's exact element-type match refused ScadaBridge's CLR-list writes (its element types are only Boolean/Int32/Float/Double/String/ DateTime) to every Int16/UInt16/Byte/UInt32/Int64/UInt64 array node and a Double list to a Float array. EvaluateEquipmentWriteStructure now converts a numeric array of another numeric element type element-wise, CHECKED, into the node's element type and hands the converted array back through OnEquipmentTagWrite's ref value, so the node stores and the driver receives the node's own type. Any out-of-range, fractional-to-integer, NaN/Inf-to- integer or Float-overflowing element refuses the whole write BadTypeMismatch (nothing dispatched) - so the UInt32 -> Int32 bit reinterpretation stays closed. Non-numeric mismatches stay refused. The wrong 'no client relies on a looser rule' comment is corrected. MINOR-1: the array-value-to-scalar-node refusal broke writes back to browse-committed array tags (committed scalar, isArray never set) on pass-through drivers. It now applies only when the owning driver's scalar encoder cannot take an array (DriverArrayWriteSupport. RefusesArrayValueOnScalarTag: Modbus, S7), carried RawTagPlan. ScalarRefusesArrayValue / UnsReferenceVariable.BackingScalarRefusesArrayValue -> EnsureVariable(refuseArrayOnScalar) -> a per-node set in the node manager. OpcUaClient/Galaxy/others keep pre-#610 dispatch. Not on the classifier whitelist (driver-type edit rebuilds); a mirror whose flag changes is recreated, not updated in place. Claude-Session: https://claude.ai/code/session_01Ef2ozxS7QPanGT8uoATbTe
This commit is contained in:
@@ -81,7 +81,7 @@ public class AddressSpaceSinkBatchTests
|
||||
public void MaterialiseAlarmCondition(string alarmNodeId, string equipmentNodeId, string displayName, string alarmType, int severity, AddressSpaceRealm realm, bool isNative = false) { }
|
||||
public void WireAlarmNotifiers(string alarmNodeId, AddressSpaceRealm alarmRealm, IReadOnlyList<string> notifierFolderNodeIds, AddressSpaceRealm notifierFolderRealm) { }
|
||||
public void EnsureFolder(string folderNodeId, string? parentNodeId, string displayName, AddressSpaceRealm realm) { }
|
||||
public void EnsureVariable(string variableNodeId, string? parentFolderNodeId, string displayName, string dataType, bool writable, AddressSpaceRealm realm, string? historianTagname = null, bool isArray = false, uint? arrayLength = null) { }
|
||||
public void EnsureVariable(string variableNodeId, string? parentFolderNodeId, string displayName, string dataType, bool writable, AddressSpaceRealm realm, string? historianTagname = null, bool isArray = false, uint? arrayLength = null, bool refuseArrayOnScalar = false) { }
|
||||
public void RebuildAddressSpace() { }
|
||||
public void RaiseNodesAddedModelChange(string affectedNodeId, AddressSpaceRealm realm) { }
|
||||
public void AddReference(string sourceNodeId, AddressSpaceRealm sourceRealm, string targetNodeId, AddressSpaceRealm targetRealm, string referenceType = "Organizes") { }
|
||||
@@ -104,7 +104,7 @@ public class AddressSpaceSinkBatchTests
|
||||
public void MaterialiseAlarmCondition(string alarmNodeId, string equipmentNodeId, string displayName, string alarmType, int severity, AddressSpaceRealm realm, bool isNative = false) { }
|
||||
public void WireAlarmNotifiers(string alarmNodeId, AddressSpaceRealm alarmRealm, IReadOnlyList<string> notifierFolderNodeIds, AddressSpaceRealm notifierFolderRealm) { }
|
||||
public void EnsureFolder(string folderNodeId, string? parentNodeId, string displayName, AddressSpaceRealm realm) { }
|
||||
public void EnsureVariable(string variableNodeId, string? parentFolderNodeId, string displayName, string dataType, bool writable, AddressSpaceRealm realm, string? historianTagname = null, bool isArray = false, uint? arrayLength = null) { }
|
||||
public void EnsureVariable(string variableNodeId, string? parentFolderNodeId, string displayName, string dataType, bool writable, AddressSpaceRealm realm, string? historianTagname = null, bool isArray = false, uint? arrayLength = null, bool refuseArrayOnScalar = false) { }
|
||||
public void RebuildAddressSpace() { }
|
||||
public void RaiseNodesAddedModelChange(string affectedNodeId, AddressSpaceRealm realm) { }
|
||||
public void AddReference(string sourceNodeId, AddressSpaceRealm sourceRealm, string targetNodeId, AddressSpaceRealm targetRealm, string referenceType = "Organizes") { }
|
||||
|
||||
@@ -214,7 +214,7 @@ public class DeferredAddressSpaceSinkTests
|
||||
public void WriteAlarmCondition(string alarmNodeId, AlarmConditionSnapshot state, DateTime sourceTimestampUtc, AddressSpaceRealm realm = AddressSpaceRealm.Uns) { }
|
||||
public void MaterialiseAlarmCondition(string alarmNodeId, string equipmentNodeId, string displayName, string alarmType, int severity, AddressSpaceRealm realm, bool isNative = false) { }
|
||||
public void EnsureFolder(string folderNodeId, string? parentNodeId, string displayName, AddressSpaceRealm realm = AddressSpaceRealm.Uns) { }
|
||||
public void EnsureVariable(string variableNodeId, string? parentFolderNodeId, string displayName, string dataType, bool writable, AddressSpaceRealm realm, string? historianTagname = null, bool isArray = false, uint? arrayLength = null) { }
|
||||
public void EnsureVariable(string variableNodeId, string? parentFolderNodeId, string displayName, string dataType, bool writable, AddressSpaceRealm realm, string? historianTagname = null, bool isArray = false, uint? arrayLength = null, bool refuseArrayOnScalar = false) { }
|
||||
public void RebuildAddressSpace() => RebuildCalled = true;
|
||||
public void RaiseNodesAddedModelChange(string affectedNodeId, AddressSpaceRealm realm = AddressSpaceRealm.Uns) { }
|
||||
public void WireAlarmNotifiers(string alarmNodeId, AddressSpaceRealm alarmRealm, IReadOnlyList<string> notifierFolderNodeIds, AddressSpaceRealm notifierFolderRealm)
|
||||
@@ -232,7 +232,7 @@ public class DeferredAddressSpaceSinkTests
|
||||
public void WriteAlarmCondition(string alarmNodeId, AlarmConditionSnapshot state, DateTime sourceTimestampUtc, AddressSpaceRealm realm = AddressSpaceRealm.Uns) { }
|
||||
public void MaterialiseAlarmCondition(string alarmNodeId, string equipmentNodeId, string displayName, string alarmType, int severity, AddressSpaceRealm realm, bool isNative = false) { }
|
||||
public void EnsureFolder(string folderNodeId, string? parentNodeId, string displayName, AddressSpaceRealm realm = AddressSpaceRealm.Uns) { }
|
||||
public void EnsureVariable(string variableNodeId, string? parentFolderNodeId, string displayName, string dataType, bool writable, AddressSpaceRealm realm, string? historianTagname = null, bool isArray = false, uint? arrayLength = null) { }
|
||||
public void EnsureVariable(string variableNodeId, string? parentFolderNodeId, string displayName, string dataType, bool writable, AddressSpaceRealm realm, string? historianTagname = null, bool isArray = false, uint? arrayLength = null, bool refuseArrayOnScalar = false) { }
|
||||
public void RebuildAddressSpace() { }
|
||||
public void RaiseNodesAddedModelChange(string affectedNodeId, AddressSpaceRealm realm = AddressSpaceRealm.Uns) { }
|
||||
public void WireAlarmNotifiers(string alarmNodeId, AddressSpaceRealm alarmRealm, IReadOnlyList<string> notifierFolderNodeIds, AddressSpaceRealm notifierFolderRealm) { }
|
||||
|
||||
@@ -156,4 +156,19 @@ public sealed class DriverArrayWriteSupportTests
|
||||
DriverArrayWriteSupport.Supports(driverType, "Int32", blob).ShouldBeFalse(driverType ?? "<null>");
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>#610 review R6 MINOR-1 — only Modbus and S7 have a scalar encoder that cannot take an array value;
|
||||
/// every other driver (the pass-through OpcUaClient and Galaxy above all) keeps dispatching an array written to
|
||||
/// a scalar-shaped tag, as before #610.</summary>
|
||||
[Fact]
|
||||
public void Only_modbus_and_s7_refuse_an_array_value_on_a_scalar_tag()
|
||||
{
|
||||
DriverArrayWriteSupport.RefusesArrayValueOnScalarTag(DriverTypeNames.Modbus).ShouldBeTrue();
|
||||
DriverArrayWriteSupport.RefusesArrayValueOnScalarTag(DriverTypeNames.S7).ShouldBeTrue();
|
||||
foreach (var driverType in DriverTypeNames.All
|
||||
.Except([DriverTypeNames.Modbus, DriverTypeNames.S7]).Append("NoSuchDriver").Append(null))
|
||||
{
|
||||
DriverArrayWriteSupport.RefusesArrayValueOnScalarTag(driverType).ShouldBeFalse(driverType ?? "<null>");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user