feat(opcua): add ISurgicalAddressSpaceSink + node-manager in-place tag-attribute update (F10b)
This commit is contained in:
@@ -1332,9 +1332,8 @@ public sealed class OtOpcUaNodeManager : CustomNodeManager2
|
||||
// access bit (on top of the writable composite) so clients can browse + HistoryRead it.
|
||||
var historized = historianTagname is not null;
|
||||
// The SDK exposes the flags separately (no CurrentReadWrite composite): ReadWrite is
|
||||
// CurrentRead | CurrentWrite. OR-ing two byte constants promotes to int, so cast back.
|
||||
byte access = writable ? (byte)(AccessLevels.CurrentRead | AccessLevels.CurrentWrite) : AccessLevels.CurrentRead;
|
||||
if (historized) access = (byte)(access | AccessLevels.HistoryRead);
|
||||
// CurrentRead | CurrentWrite, with the HistoryRead bit OR-ed in when historized.
|
||||
var access = ComposeAccessLevel(writable, historized);
|
||||
var variable = new BaseDataVariableState(parent)
|
||||
{
|
||||
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",
|
||||
/// "DateTime") to the OPC UA built-in NodeId. Unknown names fall back to BaseDataType
|
||||
/// (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
|
||||
/// been started (so the node manager exists).
|
||||
/// </summary>
|
||||
public sealed class SdkAddressSpaceSink : IOpcUaAddressSpaceSink
|
||||
public sealed class SdkAddressSpaceSink : IOpcUaAddressSpaceSink, ISurgicalAddressSpaceSink
|
||||
{
|
||||
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)
|
||||
=> _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>
|
||||
public void RebuildAddressSpace() => _nodeManager.RebuildAddressSpace();
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user