feat(opcua): FB-7 surgical DataType/array-shape in-place tag writes
Widen the F10b surgical address-space path so a changed equipment tag whose only differences are DataType / IsArray / ArrayLength (on top of the existing Writable / Historizing) is applied IN PLACE on the live node instead of forcing a full RebuildAddressSpace that drops every client's subscriptions server-wide. ISurgicalAddressSpaceSink.UpdateTagAttributes gains (dataType, isArray, arrayLength); the DeferredAddressSpaceSink wrapper forwards all six args (the prod-inertness seam). OtOpcUaNodeManager swaps DataType + ValueRank + ArrayDimensions in place, and on a real shape change (a) resets the node to BadWaitingForInitialData so no stale wrong-typed value is exposed (closes the prior brief-value-type-mismatch objection) and (b) raises a Part 3 GeneralModelChangeEvent (verb=DataTypeChanged) so model-aware clients re-read the definition. A Writable/Historizing-only change leaves the shape untouched (no reset, no model event) — original behaviour preserved byte-for-byte. AddressSpaceApplier.TagDeltaIsSurgicalEligible adds the three shape fields to its whitelist; FullName/Name/DriverInstanceId/alarm differences still rebuild. Tests: new NodeManagerSurgicalShapeUpdateTests boots a real server to prove the in-place swap + value reset + the no-reset backward-compat path + the model-event builder; AddressSpaceApplierTests invert the two former DataType/IsArray-rebuild cases to surgical and assert the shape args land; DeferredAddressSpaceSinkTests assert the shape args forward. 273/273 OpcUaServer.Tests green; full solution builds.
This commit is contained in:
@@ -65,13 +65,17 @@ public sealed class SdkAddressSpaceSink : IOpcUaAddressSpaceSink, ISurgicalAddre
|
||||
public void EnsureVariable(string variableNodeId, string? parentFolderNodeId, string displayName, string dataType, bool writable, string? historianTagname = null, bool isArray = false, uint? arrayLength = null)
|
||||
=> _nodeManager.EnsureVariable(variableNodeId, parentFolderNodeId, displayName, dataType, writable, historianTagname, isArray, arrayLength);
|
||||
|
||||
/// <summary>F10b: surgically update an existing variable node's Writable + Historizing in place
|
||||
/// (no rebuild). Returns false when the node does not exist (caller falls back to a full rebuild).</summary>
|
||||
/// <summary>F10b: surgically update an existing variable node's Writable + Historizing + presentation
|
||||
/// shape (DataType / array-ness) in place (no rebuild). Returns false when the node does not exist
|
||||
/// (caller falls back to a full rebuild).</summary>
|
||||
/// <param name="variableNodeId">The variable node identifier.</param>
|
||||
/// <param name="writable">When true the node becomes read/write with the inbound-write handler; otherwise read-only.</param>
|
||||
/// <param name="historianTagname">null ⇒ not historized; non-null ⇒ Historizing with the HistoryRead bit and tagname binding.</param>
|
||||
public bool UpdateTagAttributes(string variableNodeId, bool writable, string? historianTagname)
|
||||
=> _nodeManager.UpdateTagAttributes(variableNodeId, writable, historianTagname);
|
||||
/// <param name="dataType">The OPC UA built-in data type name to apply in place.</param>
|
||||
/// <param name="isArray">When true the node becomes a 1-D array; when false scalar.</param>
|
||||
/// <param name="arrayLength">The declared length of the 1-D array when <paramref name="isArray"/> is true.</param>
|
||||
public bool UpdateTagAttributes(string variableNodeId, bool writable, string? historianTagname, string dataType, bool isArray, uint? arrayLength)
|
||||
=> _nodeManager.UpdateTagAttributes(variableNodeId, writable, historianTagname, dataType, isArray, arrayLength);
|
||||
|
||||
/// <summary>Rebuilds the entire OPC UA address space.</summary>
|
||||
public void RebuildAddressSpace() => _nodeManager.RebuildAddressSpace();
|
||||
|
||||
Reference in New Issue
Block a user