feat(opcua): add ISurgicalAddressSpaceSink + node-manager in-place tag-attribute update (F10b)
This commit is contained in:
@@ -0,0 +1,13 @@
|
|||||||
|
namespace ZB.MOM.WW.OtOpcUa.Commons.OpcUa;
|
||||||
|
|
||||||
|
/// <summary>Optional capability on an address-space sink: surgical in-place attribute updates on an
|
||||||
|
/// EXISTING variable node, used by Phase7Applier to avoid a full RebuildAddressSpace for pure-property
|
||||||
|
/// tag changes (Writable / Historizing). A sink that does not implement it ⇒ caller falls back to a
|
||||||
|
/// full rebuild (safe default).</summary>
|
||||||
|
public interface ISurgicalAddressSpaceSink
|
||||||
|
{
|
||||||
|
/// <summary>Update an existing variable node's Writable (AccessLevel + inbound-write handler) and
|
||||||
|
/// Historizing (+ historian-tagname binding) IN PLACE, notifying subscribers (ClearChangeMasks)
|
||||||
|
/// without a rebuild. Returns false if the node does not exist (caller should rebuild instead).</summary>
|
||||||
|
bool UpdateTagAttributes(string variableNodeId, bool writable, string? historianTagname);
|
||||||
|
}
|
||||||
@@ -1332,9 +1332,8 @@ public sealed class OtOpcUaNodeManager : CustomNodeManager2
|
|||||||
// access bit (on top of the writable composite) so clients can browse + HistoryRead it.
|
// access bit (on top of the writable composite) so clients can browse + HistoryRead it.
|
||||||
var historized = historianTagname is not null;
|
var historized = historianTagname is not null;
|
||||||
// The SDK exposes the flags separately (no CurrentReadWrite composite): ReadWrite is
|
// The SDK exposes the flags separately (no CurrentReadWrite composite): ReadWrite is
|
||||||
// CurrentRead | CurrentWrite. OR-ing two byte constants promotes to int, so cast back.
|
// CurrentRead | CurrentWrite, with the HistoryRead bit OR-ed in when historized.
|
||||||
byte access = writable ? (byte)(AccessLevels.CurrentRead | AccessLevels.CurrentWrite) : AccessLevels.CurrentRead;
|
var access = ComposeAccessLevel(writable, historized);
|
||||||
if (historized) access = (byte)(access | AccessLevels.HistoryRead);
|
|
||||||
var variable = new BaseDataVariableState(parent)
|
var variable = new BaseDataVariableState(parent)
|
||||||
{
|
{
|
||||||
NodeId = new NodeId(variableNodeId, NamespaceIndex),
|
NodeId = new NodeId(variableNodeId, NamespaceIndex),
|
||||||
@@ -1369,6 +1368,41 @@ public sealed class OtOpcUaNodeManager : CustomNodeManager2
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>Compose the AccessLevel/UserAccessLevel byte for a tag variable from its Writable +
|
||||||
|
/// Historizing flags — the single source of truth shared by <see cref="EnsureVariable"/> (node
|
||||||
|
/// creation) and <see cref="UpdateTagAttributes"/> (F10b surgical in-place update). The SDK exposes
|
||||||
|
/// the flags separately (no CurrentReadWrite composite): ReadWrite is CurrentRead | CurrentWrite,
|
||||||
|
/// with the HistoryRead bit OR-ed in when historized. OR-ing byte constants promotes to int, so cast back.</summary>
|
||||||
|
private static byte ComposeAccessLevel(bool writable, bool historized)
|
||||||
|
{
|
||||||
|
byte access = writable ? (byte)(AccessLevels.CurrentRead | AccessLevels.CurrentWrite) : AccessLevels.CurrentRead;
|
||||||
|
if (historized) access = (byte)(access | AccessLevels.HistoryRead);
|
||||||
|
return access;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>F10b surgical counterpart of <see cref="EnsureVariable"/>: update an EXISTING tag variable's
|
||||||
|
/// Writable (AccessLevel + <see cref="OnEquipmentTagWrite"/> handler) and Historizing (+ historian-tagname
|
||||||
|
/// binding) in place and notify subscribers, WITHOUT a rebuild — so client MonitoredItems on the node
|
||||||
|
/// survive. Returns false when the node id is unknown (caller falls back to a full rebuild).</summary>
|
||||||
|
public bool UpdateTagAttributes(string variableNodeId, bool writable, string? historianTagname)
|
||||||
|
{
|
||||||
|
ArgumentException.ThrowIfNullOrEmpty(variableNodeId);
|
||||||
|
lock (Lock)
|
||||||
|
{
|
||||||
|
if (!_variables.TryGetValue(variableNodeId, out var v)) return false;
|
||||||
|
var historized = historianTagname is not null;
|
||||||
|
var access = ComposeAccessLevel(writable, historized);
|
||||||
|
v.AccessLevel = access;
|
||||||
|
v.UserAccessLevel = access;
|
||||||
|
v.Historizing = historized;
|
||||||
|
v.OnWriteValue = writable ? OnEquipmentTagWrite : null;
|
||||||
|
if (historized) _historizedTagnames[variableNodeId] = historianTagname!;
|
||||||
|
else _historizedTagnames.TryRemove(variableNodeId, out _);
|
||||||
|
v.ClearChangeMasks(SystemContext, includeChildren: false);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/// <summary>Map a Tag.DataType string ("Boolean", "Int32", "Float", "Double", "String",
|
/// <summary>Map a Tag.DataType string ("Boolean", "Int32", "Float", "Double", "String",
|
||||||
/// "DateTime") to the OPC UA built-in NodeId. Unknown names fall back to BaseDataType
|
/// "DateTime") to the OPC UA built-in NodeId. Unknown names fall back to BaseDataType
|
||||||
/// (matches CreateVariable's default for lazy-created nodes).</summary>
|
/// (matches CreateVariable's default for lazy-created nodes).</summary>
|
||||||
|
|||||||
@@ -8,7 +8,7 @@ namespace ZB.MOM.WW.OtOpcUa.OpcUaServer;
|
|||||||
/// <see cref="OtOpcUaNodeManager"/>. The host wires this in once the StandardServer has
|
/// <see cref="OtOpcUaNodeManager"/>. The host wires this in once the StandardServer has
|
||||||
/// been started (so the node manager exists).
|
/// been started (so the node manager exists).
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public sealed class SdkAddressSpaceSink : IOpcUaAddressSpaceSink
|
public sealed class SdkAddressSpaceSink : IOpcUaAddressSpaceSink, ISurgicalAddressSpaceSink
|
||||||
{
|
{
|
||||||
private readonly OtOpcUaNodeManager _nodeManager;
|
private readonly OtOpcUaNodeManager _nodeManager;
|
||||||
|
|
||||||
@@ -65,6 +65,14 @@ public sealed class SdkAddressSpaceSink : IOpcUaAddressSpaceSink
|
|||||||
public void EnsureVariable(string variableNodeId, string? parentFolderNodeId, string displayName, string dataType, bool writable, string? historianTagname = null, bool isArray = false, uint? arrayLength = null)
|
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);
|
=> _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>
|
||||||
|
/// <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);
|
||||||
|
|
||||||
/// <summary>Rebuilds the entire OPC UA address space.</summary>
|
/// <summary>Rebuilds the entire OPC UA address space.</summary>
|
||||||
public void RebuildAddressSpace() => _nodeManager.RebuildAddressSpace();
|
public void RebuildAddressSpace() => _nodeManager.RebuildAddressSpace();
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user