feat(v3-batch4-wp3): raw-only binding + UNS fan-out + write routing
Wave B of Batch 4 — the runtime binding seam for the dual namespace. Applier (both realms, explicit realm at every sink call site): - MaterialiseRawSubtree: Raw containers as folders + Raw tags as variables keyed by RawPath (native-alarm tag → single Part 9 condition at the RawPath), all in AddressSpaceRealm.Raw; historian tagname = override else RawPath. - MaterialiseUnsReferences: each UNS reference Variable under its equipment folder (Uns realm) + an Organizes UNS->Raw edge; inherits writable/array/ historian tagname from the backing raw tag (both NodeIds -> one tagname). - FeedHistorizedRefs / ProvisionHistorizedTags now source RAW tags (mux ref stays single, keyed by RawPath); ApplyPureRemove tears down raw tags + UNS refs in place (raw-container removal falls back to rebuild). DriverHostActor (dual-NodeId, single-source fan-out): - _nodeIdByDriverRef value gains a realm (NodeRealmRef); rebuilt from RawTags UNION UnsReferenceVariables so one (DriverInstanceId, RawPath) fans to the raw NodeId AND every referencing UNS NodeId with identical value/quality/timestamp. Write inverse map keyed by the bare id; the ns-qualified NodeId the write hook passes is normalised (BareNodeId) so a write to either NodeId resolves the same driver ref (-> RawPath write). - Native raw alarm condition routing is realm-tagged (Raw); AttributeValueUpdate + AlarmStateUpdate carry the realm through to the sink. Retire EquipmentNodeIds -> V3NodeIds.Uns (applier/VirtualTagHostActor) and RawPaths.Combine (DiscoveredNodeMapper, discovered nodes are Raw now). DeploymentArtifact.ParseComposition emits the Raw + UNS subtrees byte-parity with the composer (reconstruct entities -> AddressSpaceComposer.Compose). Sink surface: removed the transitional `= AddressSpaceRealm.Uns` defaults from IOpcUaAddressSpaceSink / ISurgicalAddressSpaceSink / SdkAddressSpaceSink / DeferredAddressSpaceSink / NullOpcUaAddressSpaceSink — every call site is now explicit (realm reordered before the trailing optionals on EnsureVariable + MaterialiseAlarmCondition). Node-manager convenience methods keep their defaults (they are not the interface impl; Sdk delegates explicitly). Tests: rewrote DriverHostActorLiveValueTests (fan-out drift, 1:N) + DriverHostActorWriteRoutingTests (dual-NodeId raw/uns write routing) to the v3 raw+uns model; new AddressSpaceApplierRawUnsTests; migrated the EquipmentTags provisioning/feed tests to RawTags; swept EquipmentNodeIds test callers. Claude-Session: https://claude.ai/code/session_01LVneM3eh1UtJxEisFXgmox
This commit is contained in:
@@ -1,5 +1,6 @@
|
||||
using Microsoft.Extensions.Logging;
|
||||
using ZB.MOM.WW.OtOpcUa.Commons.OpcUa;
|
||||
using ZB.MOM.WW.OtOpcUa.Commons.Types;
|
||||
using ZB.MOM.WW.OtOpcUa.Core.Abstractions;
|
||||
|
||||
namespace ZB.MOM.WW.OtOpcUa.OpcUaServer;
|
||||
@@ -96,29 +97,33 @@ public sealed class AddressSpaceApplier
|
||||
var failedNodes = 0;
|
||||
foreach (var eq in plan.RemovedEquipment)
|
||||
{
|
||||
if (!SafeWriteAlarmCondition(eq.EquipmentId, RemovedConditionState, ts)) failedNodes++;
|
||||
if (!SafeWriteAlarmCondition(eq.EquipmentId, RemovedConditionState, ts, AddressSpaceRealm.Uns)) failedNodes++;
|
||||
removedCount++;
|
||||
}
|
||||
foreach (var alarm in plan.RemovedAlarms)
|
||||
{
|
||||
if (!SafeWriteAlarmCondition(alarm.ScriptedAlarmId, RemovedConditionState, ts)) failedNodes++;
|
||||
if (!SafeWriteAlarmCondition(alarm.ScriptedAlarmId, RemovedConditionState, ts, AddressSpaceRealm.Uns)) failedNodes++;
|
||||
removedCount++;
|
||||
}
|
||||
// Removed equipment tags / VirtualTags are plain variable nodes (no Part 9 condition to write
|
||||
// before tear-down), but they ARE real removals — count them so AddressSpaceApplyOutcome.RemovedNodes
|
||||
// is accurate on a removed-tag-only deploy, which now reaches the rebuild path below.
|
||||
removedCount += plan.RemovedEquipmentTags.Count + plan.RemovedEquipmentVirtualTags.Count;
|
||||
// is accurate on a removed-tag-only deploy, which now reaches the rebuild path below. v3 Batch 4:
|
||||
// removed raw containers/tags + UNS reference variables are likewise real removals.
|
||||
removedCount += plan.RemovedEquipmentTags.Count + plan.RemovedEquipmentVirtualTags.Count +
|
||||
plan.RemovedRawContainers.Count + plan.RemovedRawTags.Count + plan.RemovedUnsReferenceVariables.Count;
|
||||
|
||||
var changedCount =
|
||||
plan.ChangedEquipment.Count + plan.ChangedDrivers.Count + plan.ChangedAlarms.Count +
|
||||
plan.ChangedEquipmentTags.Count +
|
||||
plan.ChangedEquipmentVirtualTags.Count +
|
||||
plan.ChangedRawContainers.Count + plan.ChangedRawTags.Count + plan.ChangedUnsReferenceVariables.Count +
|
||||
// A UNS Area/Line rename is an in-place change to an existing folder node.
|
||||
plan.RenamedFolders.Count;
|
||||
var addedCount =
|
||||
plan.AddedEquipment.Count + plan.AddedDrivers.Count + plan.AddedAlarms.Count +
|
||||
plan.AddedEquipmentTags.Count +
|
||||
plan.AddedEquipmentVirtualTags.Count;
|
||||
plan.AddedEquipmentVirtualTags.Count +
|
||||
plan.AddedRawContainers.Count + plan.AddedRawTags.Count + plan.AddedUnsReferenceVariables.Count;
|
||||
|
||||
// R2-07 03/P1 — classify the plan and route each delta class to its MINIMAL mutation instead of
|
||||
// rebuilding the world for any topology change. AddressSpaceChangeClassifier is a pure policy over
|
||||
@@ -172,7 +177,7 @@ public sealed class AddressSpaceApplier
|
||||
foreach (var rename in renamedFolders)
|
||||
{
|
||||
bool ok;
|
||||
try { ok = surgical.UpdateFolderDisplayName(rename.FolderNodeId, rename.NewDisplayName); }
|
||||
try { ok = surgical.UpdateFolderDisplayName(rename.FolderNodeId, rename.NewDisplayName, AddressSpaceRealm.Uns); }
|
||||
catch (Exception ex)
|
||||
{
|
||||
_logger.LogError(ex, "AddressSpaceApplier: surgical UpdateFolderDisplayName threw for {Node}", rename.FolderNodeId);
|
||||
@@ -185,13 +190,13 @@ public sealed class AddressSpaceApplier
|
||||
// Compute the node id + writable + historian + shape EXACTLY as MaterialiseEquipmentTags
|
||||
// would so the in-place update matches what a rebuild would have produced. Array tags are
|
||||
// forced read-only (same as EnsureVariable: the driver write path doesn't handle arrays).
|
||||
var nodeId = EquipmentNodeIds.Variable(d.Current.EquipmentId, d.Current.FolderPath, d.Current.Name);
|
||||
var nodeId = UnsEquipmentVar(d.Current.EquipmentId, d.Current.FolderPath, d.Current.Name);
|
||||
var writable = d.Current.Writable && !d.Current.IsArray;
|
||||
var historian = d.Current.IsHistorized
|
||||
? (string.IsNullOrWhiteSpace(d.Current.HistorianTagname) ? d.Current.FullName : d.Current.HistorianTagname)
|
||||
: null;
|
||||
bool ok;
|
||||
try { ok = surgical.UpdateTagAttributes(nodeId, writable, historian, d.Current.DataType, d.Current.IsArray, d.Current.ArrayLength); }
|
||||
try { ok = surgical.UpdateTagAttributes(nodeId, writable, historian, d.Current.DataType, d.Current.IsArray, d.Current.ArrayLength, AddressSpaceRealm.Uns); }
|
||||
catch (Exception ex)
|
||||
{
|
||||
_logger.LogError(ex, "AddressSpaceApplier: surgical UpdateTagAttributes threw for {Node}", nodeId);
|
||||
@@ -263,77 +268,108 @@ public sealed class AddressSpaceApplier
|
||||
// Removed equipment tags — value variables OR alarm-bearing conditions — for SURVIVING equipment.
|
||||
foreach (var tag in plan.RemovedEquipmentTags.Where(t => NotSubsumed(t.EquipmentId)))
|
||||
{
|
||||
var nodeId = EquipmentNodeIds.Variable(tag.EquipmentId, tag.FolderPath, tag.Name);
|
||||
var nodeId = UnsEquipmentVar(tag.EquipmentId, tag.FolderPath, tag.Name);
|
||||
if (tag.Alarm is not null)
|
||||
{
|
||||
// Alarm-bearing tag → a Part 9 condition node. Terminal RemovedConditionState (today-gap
|
||||
// closed), then remove the condition in place.
|
||||
if (!SafeWriteAlarmCondition(nodeId, RemovedConditionState, ts)) failedNodes++;
|
||||
if (!SafeRemoveAlarmCondition(surgical, nodeId)) return false;
|
||||
if (!SafeWriteAlarmCondition(nodeId, RemovedConditionState, ts, AddressSpaceRealm.Uns)) failedNodes++;
|
||||
if (!SafeRemoveAlarmCondition(surgical, nodeId, AddressSpaceRealm.Uns)) return false;
|
||||
}
|
||||
else
|
||||
{
|
||||
// Plain value variable → terminal Bad so an in-flight MonitoredItem sees the removal.
|
||||
SafeWriteValue(nodeId, null, OpcUaQuality.Bad, ts);
|
||||
if (!SafeRemoveVariable(surgical, nodeId)) return false;
|
||||
SafeWriteValue(nodeId, null, OpcUaQuality.Bad, ts, AddressSpaceRealm.Uns);
|
||||
if (!SafeRemoveVariable(surgical, nodeId, AddressSpaceRealm.Uns)) return false;
|
||||
}
|
||||
}
|
||||
|
||||
// Removed VirtualTags (always plain value variables) for surviving equipment.
|
||||
foreach (var v in plan.RemovedEquipmentVirtualTags.Where(t => NotSubsumed(t.EquipmentId)))
|
||||
{
|
||||
var nodeId = EquipmentNodeIds.Variable(v.EquipmentId, v.FolderPath, v.Name);
|
||||
SafeWriteValue(nodeId, null, OpcUaQuality.Bad, ts);
|
||||
if (!SafeRemoveVariable(surgical, nodeId)) return false;
|
||||
var nodeId = UnsEquipmentVar(v.EquipmentId, v.FolderPath, v.Name);
|
||||
SafeWriteValue(nodeId, null, OpcUaQuality.Bad, ts, AddressSpaceRealm.Uns);
|
||||
if (!SafeRemoveVariable(surgical, nodeId, AddressSpaceRealm.Uns)) return false;
|
||||
}
|
||||
|
||||
// Removed scripted alarms for surviving equipment — the terminal RemovedConditionState was already
|
||||
// written by the top-of-Apply removal block; here we tear the condition node down in place.
|
||||
foreach (var alarm in plan.RemovedAlarms.Where(a => NotSubsumed(a.EquipmentId)))
|
||||
{
|
||||
if (!SafeRemoveAlarmCondition(surgical, alarm.ScriptedAlarmId)) return false;
|
||||
if (!SafeRemoveAlarmCondition(surgical, alarm.ScriptedAlarmId, AddressSpaceRealm.Uns)) return false;
|
||||
}
|
||||
|
||||
// Removed equipment — one subtree teardown each (subsumes their child tags/vtags/alarms). The
|
||||
// top-of-Apply block already wrote the equipment id's terminal condition state.
|
||||
foreach (var eq in plan.RemovedEquipment)
|
||||
{
|
||||
if (!SafeRemoveEquipmentSubtree(surgical, eq.EquipmentId)) return false;
|
||||
if (!SafeRemoveEquipmentSubtree(surgical, eq.EquipmentId, AddressSpaceRealm.Uns)) return false;
|
||||
}
|
||||
|
||||
// v3 Batch 4 — removed UNS reference variables (Uns realm, plain value nodes): terminal Bad then
|
||||
// in-place remove. A backing-raw rename is a re-point (Changed, → rebuild), NOT a removal, so a UNS
|
||||
// ref only reaches here on a genuine dereference (the equipment dropped the reference).
|
||||
foreach (var v in plan.RemovedUnsReferenceVariables)
|
||||
{
|
||||
SafeWriteValue(v.NodeId, null, OpcUaQuality.Bad, ts, AddressSpaceRealm.Uns);
|
||||
if (!SafeRemoveVariable(surgical, v.NodeId, AddressSpaceRealm.Uns)) return false;
|
||||
}
|
||||
|
||||
// v3 Batch 4 — removed RAW tags (Raw realm): an alarm-bearing raw tag is a condition node (terminal
|
||||
// RemovedConditionState + RemoveAlarmConditionNode); a plain raw value tag is a variable (terminal Bad
|
||||
// + RemoveVariableNode).
|
||||
foreach (var t in plan.RemovedRawTags)
|
||||
{
|
||||
if (t.Alarm is not null)
|
||||
{
|
||||
if (!SafeWriteAlarmCondition(t.NodeId, RemovedConditionState, ts, AddressSpaceRealm.Raw)) failedNodes++;
|
||||
if (!SafeRemoveAlarmCondition(surgical, t.NodeId, AddressSpaceRealm.Raw)) return false;
|
||||
}
|
||||
else
|
||||
{
|
||||
SafeWriteValue(t.NodeId, null, OpcUaQuality.Bad, ts, AddressSpaceRealm.Raw);
|
||||
if (!SafeRemoveVariable(surgical, t.NodeId, AddressSpaceRealm.Raw)) return false;
|
||||
}
|
||||
}
|
||||
|
||||
// Removed raw CONTAINER nodes (Folder/Driver/Device/TagGroup) have no surgical folder-remove on the
|
||||
// sink surface, so any container removal falls back to a full rebuild (the ratchet). Evaluated LAST so
|
||||
// the cheap variable/condition removals above still run in place when only tags were removed.
|
||||
if (plan.RemovedRawContainers.Count > 0) return false;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/// <summary>Remove a value-variable node in place, treating a throw as a false (⇒ rebuild fallback).</summary>
|
||||
/// <returns><c>true</c> when the node was removed; <c>false</c> when unknown or the sink threw.</returns>
|
||||
private bool SafeRemoveVariable(ISurgicalAddressSpaceSink surgical, string nodeId)
|
||||
private bool SafeRemoveVariable(ISurgicalAddressSpaceSink surgical, string nodeId, AddressSpaceRealm realm)
|
||||
{
|
||||
try { return surgical.RemoveVariableNode(nodeId); }
|
||||
try { return surgical.RemoveVariableNode(nodeId, realm); }
|
||||
catch (Exception ex) { _logger.LogError(ex, "AddressSpaceApplier: surgical RemoveVariableNode threw for {Node}", nodeId); return false; }
|
||||
}
|
||||
|
||||
/// <summary>Remove an alarm-condition node in place, treating a throw as a false (⇒ rebuild fallback).</summary>
|
||||
/// <returns><c>true</c> when the node was removed; <c>false</c> when unknown or the sink threw.</returns>
|
||||
private bool SafeRemoveAlarmCondition(ISurgicalAddressSpaceSink surgical, string nodeId)
|
||||
private bool SafeRemoveAlarmCondition(ISurgicalAddressSpaceSink surgical, string nodeId, AddressSpaceRealm realm)
|
||||
{
|
||||
try { return surgical.RemoveAlarmConditionNode(nodeId); }
|
||||
try { return surgical.RemoveAlarmConditionNode(nodeId, realm); }
|
||||
catch (Exception ex) { _logger.LogError(ex, "AddressSpaceApplier: surgical RemoveAlarmConditionNode threw for {Node}", nodeId); return false; }
|
||||
}
|
||||
|
||||
/// <summary>Remove an equipment subtree in place, treating a throw as a false (⇒ rebuild fallback).</summary>
|
||||
/// <returns><c>true</c> when the subtree was removed; <c>false</c> when unknown or the sink threw.</returns>
|
||||
private bool SafeRemoveEquipmentSubtree(ISurgicalAddressSpaceSink surgical, string nodeId)
|
||||
private bool SafeRemoveEquipmentSubtree(ISurgicalAddressSpaceSink surgical, string nodeId, AddressSpaceRealm realm)
|
||||
{
|
||||
try { return surgical.RemoveEquipmentSubtree(nodeId); }
|
||||
try { return surgical.RemoveEquipmentSubtree(nodeId, realm); }
|
||||
catch (Exception ex) { _logger.LogError(ex, "AddressSpaceApplier: surgical RemoveEquipmentSubtree threw for {Node}", nodeId); return false; }
|
||||
}
|
||||
|
||||
/// <summary>Write a value, swallowing (and Warning-logging) any sink fault — used for the terminal Bad
|
||||
/// on a removed variable so an in-flight MonitoredItem observes the removal.</summary>
|
||||
/// <returns><c>true</c> when the write landed; <c>false</c> when the sink threw.</returns>
|
||||
private bool SafeWriteValue(string nodeId, object? value, OpcUaQuality quality, DateTime ts)
|
||||
private bool SafeWriteValue(string nodeId, object? value, OpcUaQuality quality, DateTime ts, AddressSpaceRealm realm)
|
||||
{
|
||||
try { _sink.WriteValue(nodeId, value, quality, ts); return true; }
|
||||
try { _sink.WriteValue(nodeId, value, quality, ts, realm); return true; }
|
||||
catch (Exception ex) { _logger.LogWarning(ex, "AddressSpaceApplier: WriteValue threw for {Node}", nodeId); return false; }
|
||||
}
|
||||
|
||||
@@ -373,7 +409,22 @@ public sealed class AddressSpaceApplier
|
||||
/// <param name="folderPath">The tag/vtag FolderPath, or null/empty for "directly under the equipment".</param>
|
||||
/// <returns>The materialise-parent node id.</returns>
|
||||
private static string MaterialiseParent(string equipmentId, string? folderPath) =>
|
||||
string.IsNullOrWhiteSpace(folderPath) ? equipmentId : EquipmentNodeIds.SubFolder(equipmentId, folderPath);
|
||||
string.IsNullOrWhiteSpace(folderPath) ? equipmentId : UnsSubFolder(equipmentId, folderPath);
|
||||
|
||||
// ----- v3 UNS-equipment NodeId helpers (replace the retired EquipmentNodeIds) -----
|
||||
// Equipment-owned UNS nodes (per-equipment VirtualTags, the vestigial equipment-tag path, and the
|
||||
// discovered-node graft) keep their EquipmentId-anchored slash-path NodeId ({equipmentId}/{folderPath}/{name}),
|
||||
// now expressed through the v3 identity authority V3NodeIds.Uns so the retired EquipmentNodeIds type can go.
|
||||
// These live in the UNS realm. FolderPath may be multi-segment ("a/b") — each segment is validated
|
||||
// by V3NodeIds.Uns like a RawPath. (UnsTagReference variables use the composer's Area/Line/Equipment/Name
|
||||
// NodeId directly and are NOT built here.)
|
||||
private static string UnsSubFolder(string equipmentId, string folderPath) =>
|
||||
V3NodeIds.Uns(new[] { equipmentId }.Concat(folderPath.Split(RawPaths.Separator)));
|
||||
|
||||
private static string UnsEquipmentVar(string equipmentId, string? folderPath, string name) =>
|
||||
string.IsNullOrWhiteSpace(folderPath)
|
||||
? V3NodeIds.Uns(equipmentId, name)
|
||||
: V3NodeIds.Uns(new[] { equipmentId }.Concat(folderPath.Split(RawPaths.Separator)).Append(name));
|
||||
|
||||
/// <summary>
|
||||
/// Announce a Part 3 <c>GeneralModelChangeEvent(NodeAdded)</c> per distinct affected parent from
|
||||
@@ -389,7 +440,7 @@ public sealed class AddressSpaceApplier
|
||||
{
|
||||
foreach (var id in ComputeAddAnnouncements(plan))
|
||||
{
|
||||
try { _sink.RaiseNodesAddedModelChange(id); }
|
||||
try { _sink.RaiseNodesAddedModelChange(id, AddressSpaceRealm.Uns); }
|
||||
catch (Exception ex) { _logger.LogWarning(ex, "AddressSpaceApplier: RaiseNodesAddedModelChange threw for {Node}", id); }
|
||||
}
|
||||
}
|
||||
@@ -407,11 +458,11 @@ public sealed class AddressSpaceApplier
|
||||
try
|
||||
{
|
||||
List<HistorianTagProvisionRequest>? requests = null;
|
||||
foreach (var tag in plan.AddedEquipmentTags)
|
||||
// v3 Batch 4: historized value tags are RAW tags (added-raw-tag delta). Native-alarm tags
|
||||
// materialise as Part 9 conditions (never historized value variables), so mirror the materialiser
|
||||
// and skip them.
|
||||
foreach (var tag in plan.AddedRawTags)
|
||||
{
|
||||
// Only historized value variables are provisioned. Native-alarm tags materialise as
|
||||
// Part 9 condition nodes (never historized value variables) — the materialiser resolves
|
||||
// a historian tagname only for the non-alarm branch, so mirror that and skip them.
|
||||
if (!tag.IsHistorized || tag.Alarm is not null) continue;
|
||||
|
||||
// Parse the driver-agnostic data type from the tag's DataType string. An unparseable
|
||||
@@ -424,9 +475,9 @@ public sealed class AddressSpaceApplier
|
||||
continue;
|
||||
}
|
||||
|
||||
// Resolve the historian name EXACTLY as MaterialiseEquipmentTags does: a null/blank
|
||||
// override falls back to the driver-side FullName.
|
||||
var historianName = string.IsNullOrWhiteSpace(tag.HistorianTagname) ? tag.FullName : tag.HistorianTagname;
|
||||
// Resolve the historian name EXACTLY as MaterialiseRawSubtree does: a null/blank override
|
||||
// falls back to the RawPath (== NodeId).
|
||||
var historianName = string.IsNullOrWhiteSpace(tag.HistorianTagname) ? tag.NodeId : tag.HistorianTagname;
|
||||
(requests ??= new List<HistorianTagProvisionRequest>()).Add(
|
||||
new HistorianTagProvisionRequest(historianName, dataType, EngineeringUnit: null, Description: tag.Name));
|
||||
}
|
||||
@@ -493,22 +544,26 @@ public sealed class AddressSpaceApplier
|
||||
List<HistorizedTagRef>? added = null;
|
||||
List<HistorizedTagRef>? removed = null;
|
||||
|
||||
// Added historized value variables → new interest.
|
||||
foreach (var tag in plan.AddedEquipmentTags)
|
||||
// v3 Batch 4: historized value tags are RAW tags (keyed by RawPath). The mux HistorizedTagRef
|
||||
// stays SINGLE, keyed by RawPath — a UNS reference node registers the same historian tagname in the
|
||||
// node manager but does NOT add a second mux ref (no double historization). So this feed emits raw
|
||||
// refs only.
|
||||
// Added historized raw value tags → new interest.
|
||||
foreach (var tag in plan.AddedRawTags)
|
||||
{
|
||||
if (HistorizedRef(tag) is { } r) (added ??= new List<HistorizedTagRef>()).Add(r);
|
||||
}
|
||||
|
||||
// Removed historized value variables → drop interest.
|
||||
foreach (var tag in plan.RemovedEquipmentTags)
|
||||
// Removed historized raw value tags → drop interest.
|
||||
foreach (var tag in plan.RemovedRawTags)
|
||||
{
|
||||
if (HistorizedRef(tag) is { } r) (removed ??= new List<HistorizedTagRef>()).Add(r);
|
||||
}
|
||||
|
||||
// Changed tags: the historized ref may have flipped on/off or been renamed (override/FullName
|
||||
// change). Compare previous-vs-current resolved ref PAIRS (record equality compares both the
|
||||
// mux ref and the historian name) — an unchanged pair is a no-op.
|
||||
foreach (var d in plan.ChangedEquipmentTags)
|
||||
// Changed raw tags: the historized ref may have flipped on/off or the historian override changed
|
||||
// (a rename is remove+add, handled above). Compare previous-vs-current resolved ref PAIRS — an
|
||||
// unchanged pair is a no-op.
|
||||
foreach (var d in plan.ChangedRawTags)
|
||||
{
|
||||
var prev = HistorizedRef(d.Previous);
|
||||
var cur = HistorizedRef(d.Current);
|
||||
@@ -540,13 +595,15 @@ public sealed class AddressSpaceApplier
|
||||
/// two diverge ONLY when an override is set. Returns <c>null</c> when the tag is not a historized
|
||||
/// value variable (not historized, or a native-alarm condition node).
|
||||
/// </summary>
|
||||
/// <param name="tag">The equipment tag to resolve a historized ref for.</param>
|
||||
/// <param name="tag">The raw tag to resolve a historized ref for.</param>
|
||||
/// <returns>The resolved historized ref pair, or <c>null</c> when the tag is not a historized value variable.</returns>
|
||||
private static HistorizedTagRef? HistorizedRef(EquipmentTagPlan tag) =>
|
||||
private static HistorizedTagRef? HistorizedRef(RawTagPlan tag) =>
|
||||
tag.IsHistorized && tag.Alarm is null
|
||||
// v3: the RawPath (== NodeId) is BOTH the mux ref (the driver fans values by RawPath) and the
|
||||
// default historian tagname; an override diverges only the historian name.
|
||||
? new HistorizedTagRef(
|
||||
tag.FullName,
|
||||
string.IsNullOrWhiteSpace(tag.HistorianTagname) ? tag.FullName : tag.HistorianTagname)
|
||||
tag.NodeId,
|
||||
string.IsNullOrWhiteSpace(tag.HistorianTagname) ? tag.NodeId : tag.HistorianTagname)
|
||||
: null;
|
||||
|
||||
/// <summary>Rebuild the sink's address space, swallowing (and Error-logging) any fault.
|
||||
@@ -585,17 +642,17 @@ public sealed class AddressSpaceApplier
|
||||
var failed = 0;
|
||||
foreach (var area in composition.UnsAreas)
|
||||
{
|
||||
if (!SafeEnsureFolder(area.UnsAreaId, parentNodeId: null, displayName: area.DisplayName)) failed++;
|
||||
if (!SafeEnsureFolder(area.UnsAreaId, parentNodeId: null, displayName: area.DisplayName, realm: AddressSpaceRealm.Uns)) failed++;
|
||||
}
|
||||
foreach (var line in composition.UnsLines)
|
||||
{
|
||||
if (!SafeEnsureFolder(line.UnsLineId, parentNodeId: line.UnsAreaId, displayName: line.DisplayName)) failed++;
|
||||
if (!SafeEnsureFolder(line.UnsLineId, parentNodeId: line.UnsAreaId, displayName: line.DisplayName, realm: AddressSpaceRealm.Uns)) failed++;
|
||||
}
|
||||
foreach (var equipment in composition.EquipmentNodes)
|
||||
{
|
||||
// Equipment with no UnsLineId (legacy / dev rows) hang under the root.
|
||||
var parent = string.IsNullOrWhiteSpace(equipment.UnsLineId) ? null : equipment.UnsLineId;
|
||||
if (!SafeEnsureFolder(equipment.EquipmentId, parentNodeId: parent, displayName: equipment.DisplayName)) failed++;
|
||||
if (!SafeEnsureFolder(equipment.EquipmentId, parentNodeId: parent, displayName: equipment.DisplayName, realm: AddressSpaceRealm.Uns)) failed++;
|
||||
}
|
||||
|
||||
_logger.LogInformation(
|
||||
@@ -604,6 +661,112 @@ public sealed class AddressSpaceApplier
|
||||
return failed;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// v3 Batch 4 — materialise the <b>Raw</b> device-oriented subtree (<c>ns=Raw</c>): the container
|
||||
/// nodes (Folder→Driver→Device→TagGroup, each an <c>Object</c>/<c>Folder</c>) parents-before-children,
|
||||
/// then the tag Variable nodes keyed <c>s=<RawPath></c>. A native-alarm tag (<see cref="RawTagPlan.Alarm"/>
|
||||
/// non-null) materialises as a Part 9 condition node at its RawPath (ConditionId = RawPath, parent = its
|
||||
/// device/group folder) instead of a value variable — the single condition instance; WP4 wires the extra
|
||||
/// per-equipment notifiers. A historized value tag resolves its effective historian tagname HERE
|
||||
/// (override else the RawPath). Array tags are forced read-only (the driver write path can't handle
|
||||
/// arrays). Every sink call passes <see cref="AddressSpaceRealm.Raw"/> EXPLICITLY — the driver binds
|
||||
/// values to these raw NodeIds, and every referencing UNS node fans out from them. Idempotent.
|
||||
/// </summary>
|
||||
/// <param name="composition">The composition carrying the Raw subtree.</param>
|
||||
/// <returns>The count of swallowed sink failures in this pass.</returns>
|
||||
public int MaterialiseRawSubtree(AddressSpaceComposition composition)
|
||||
{
|
||||
ArgumentNullException.ThrowIfNull(composition);
|
||||
if (composition.RawContainers.Count == 0 && composition.RawTags.Count == 0) return 0;
|
||||
|
||||
var failed = 0;
|
||||
// Containers first — the composer sorts them by NodeId (ordinal) so a parent's RawPath precedes each
|
||||
// child's; EnsureFolder is idempotent, so a re-apply is cheap.
|
||||
foreach (var c in composition.RawContainers)
|
||||
{
|
||||
if (!SafeEnsureFolder(c.NodeId, c.ParentNodeId, c.DisplayName, AddressSpaceRealm.Raw)) failed++;
|
||||
}
|
||||
|
||||
foreach (var t in composition.RawTags)
|
||||
{
|
||||
if (t.Alarm is not null)
|
||||
{
|
||||
// Native alarm tag → a single Part 9 condition node at the RawPath (ConditionId = RawPath).
|
||||
// Parent is its device/group folder. Multi-notifier fan-out to referencing equipment is WP4.
|
||||
if (!SafeMaterialiseAlarmCondition(t.NodeId, t.ParentNodeId ?? string.Empty, t.Name, t.Alarm.AlarmType, t.Alarm.Severity, isNative: true, AddressSpaceRealm.Raw)) failed++;
|
||||
}
|
||||
else
|
||||
{
|
||||
// A historized tag materialises Historizing + HistoryRead; the effective historian tagname is
|
||||
// the override else the RawPath (== NodeId). Array writes are out of scope → force read-only.
|
||||
string? historianTagname = t.IsHistorized
|
||||
? (string.IsNullOrWhiteSpace(t.HistorianTagname) ? t.NodeId : t.HistorianTagname)
|
||||
: null;
|
||||
var writable = t.Writable && !t.IsArray;
|
||||
if (!SafeEnsureVariable(t.NodeId, t.ParentNodeId, t.Name, t.DataType, writable, AddressSpaceRealm.Raw, historianTagname, t.IsArray, t.ArrayLength)) failed++;
|
||||
}
|
||||
}
|
||||
|
||||
_logger.LogInformation(
|
||||
"AddressSpaceApplier: raw subtree materialised (containers={Containers}, tags={Tags}, failed={Failed})",
|
||||
composition.RawContainers.Count, composition.RawTags.Count, failed);
|
||||
return failed;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// v3 Batch 4 — materialise the <b>UNS</b> reference Variable nodes (<c>ns=UNS</c>): one per
|
||||
/// <see cref="UnsReferenceVariable"/>, keyed <c>s=<Area>/<Line>/<Equipment>/<EffectiveName></c>,
|
||||
/// parented under its equipment folder (the logical <see cref="UnsReferenceVariable.EquipmentId"/> node
|
||||
/// <see cref="MaterialiseHierarchy"/> already created), THEN an <c>Organizes</c> reference
|
||||
/// UNS→Raw to its backing raw node. The UNS node inherits DataType / writable / array shape / historian
|
||||
/// tagname from its backing RAW tag (looked up by <see cref="UnsReferenceVariable.BackingRawPath"/>), so a
|
||||
/// historized raw tag's UNS reference registers the SAME tagname (both NodeIds → one tagname → HistoryRead
|
||||
/// works against either). The UNS node binds NO driver — the driver publish path fans values to it (WP3
|
||||
/// runtime binding). Every sink call passes <see cref="AddressSpaceRealm.Uns"/> / the Organizes edge
|
||||
/// passes both realms EXPLICITLY. Idempotent.
|
||||
/// </summary>
|
||||
/// <param name="composition">The composition carrying the UNS reference variables + Raw tags (for inherited shape).</param>
|
||||
/// <returns>The count of swallowed sink failures in this pass.</returns>
|
||||
public int MaterialiseUnsReferences(AddressSpaceComposition composition)
|
||||
{
|
||||
ArgumentNullException.ThrowIfNull(composition);
|
||||
if (composition.UnsReferenceVariables.Count == 0) return 0;
|
||||
|
||||
// Backing raw tag shape/historian by RawPath — a UNS reference inherits these so its node matches the
|
||||
// raw node it mirrors (a historized raw tag registers the SAME tagname on the UNS node for HistoryRead).
|
||||
var rawByPath = new Dictionary<string, RawTagPlan>(StringComparer.Ordinal);
|
||||
foreach (var t in composition.RawTags) rawByPath[t.NodeId] = t;
|
||||
|
||||
var failed = 0;
|
||||
foreach (var v in composition.UnsReferenceVariables)
|
||||
{
|
||||
var backing = rawByPath.GetValueOrDefault(v.BackingRawPath);
|
||||
var isArray = backing?.IsArray ?? false;
|
||||
uint? arrayLength = backing?.ArrayLength;
|
||||
// A UNS reference to a historized raw tag registers the SAME effective historian tagname (override
|
||||
// else the backing RawPath) so HistoryRead resolves against the UNS NodeId too. The mux ref stays
|
||||
// single (keyed by RawPath) — FeedHistorizedRefs emits raw refs only.
|
||||
string? historianTagname = backing is { IsHistorized: true }
|
||||
? (string.IsNullOrWhiteSpace(backing.HistorianTagname) ? backing.NodeId : backing.HistorianTagname)
|
||||
: null;
|
||||
var writable = v.Writable && !isArray;
|
||||
if (!SafeEnsureVariable(v.NodeId, v.EquipmentId, v.EffectiveName, v.DataType, writable, AddressSpaceRealm.Uns, historianTagname, isArray, arrayLength))
|
||||
{
|
||||
failed++;
|
||||
continue; // node not created — skip the Organizes edge (a missing endpoint would no-op anyway)
|
||||
}
|
||||
// Organizes UNS→Raw so the linkage is browsable. Both endpoints are realm-qualified; a missing
|
||||
// endpoint is a logged no-op in the sink (never throws), so this can't fault a deploy.
|
||||
try { _sink.AddReference(v.NodeId, AddressSpaceRealm.Uns, v.BackingRawPath, AddressSpaceRealm.Raw); }
|
||||
catch (Exception ex) { _logger.LogWarning(ex, "AddressSpaceApplier: AddReference (Organizes UNS->Raw) threw for {Node}", v.NodeId); failed++; }
|
||||
}
|
||||
|
||||
_logger.LogInformation(
|
||||
"AddressSpaceApplier: UNS reference variables materialised (refs={Refs}, failed={Failed})",
|
||||
composition.UnsReferenceVariables.Count, failed);
|
||||
return failed;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Materialise Equipment-namespace tags from a composition snapshot.
|
||||
/// For each <see cref="EquipmentTagPlan"/>,
|
||||
@@ -635,9 +798,9 @@ public sealed class AddressSpaceApplier
|
||||
foreach (var tag in composition.EquipmentTags)
|
||||
{
|
||||
if (string.IsNullOrWhiteSpace(tag.FolderPath)) continue;
|
||||
var folderNodeId = EquipmentNodeIds.SubFolder(tag.EquipmentId, tag.FolderPath);
|
||||
var folderNodeId = UnsSubFolder(tag.EquipmentId, tag.FolderPath);
|
||||
if (!foldersCreated.Add(folderNodeId)) continue;
|
||||
if (!SafeEnsureFolder(folderNodeId, parentNodeId: tag.EquipmentId, displayName: tag.FolderPath)) failed++;
|
||||
if (!SafeEnsureFolder(folderNodeId, parentNodeId: tag.EquipmentId, displayName: tag.FolderPath, realm: AddressSpaceRealm.Uns)) failed++;
|
||||
}
|
||||
|
||||
// Variables: NodeId is FOLDER-SCOPED ("<parent>/<Name>"), NOT the raw FullName — a driver
|
||||
@@ -650,13 +813,13 @@ public sealed class AddressSpaceApplier
|
||||
{
|
||||
var parent = string.IsNullOrWhiteSpace(tag.FolderPath)
|
||||
? tag.EquipmentId
|
||||
: EquipmentNodeIds.SubFolder(tag.EquipmentId, tag.FolderPath);
|
||||
var nodeId = EquipmentNodeIds.Variable(tag.EquipmentId, tag.FolderPath, tag.Name);
|
||||
: UnsSubFolder(tag.EquipmentId, tag.FolderPath);
|
||||
var nodeId = UnsEquipmentVar(tag.EquipmentId, tag.FolderPath, tag.Name);
|
||||
if (tag.Alarm is not null)
|
||||
{
|
||||
// Native alarm tag → a real Part 9 condition node (reuses the scripted-alarm path),
|
||||
// NOT a value variable. Parent is the sub-folder when set, else the equipment folder.
|
||||
if (!SafeMaterialiseAlarmCondition(nodeId, parent, tag.Name, tag.Alarm.AlarmType, tag.Alarm.Severity, isNative: true)) failed++;
|
||||
if (!SafeMaterialiseAlarmCondition(nodeId, parent, tag.Name, tag.Alarm.AlarmType, tag.Alarm.Severity, isNative: true, realm: AddressSpaceRealm.Uns)) failed++;
|
||||
}
|
||||
else
|
||||
{
|
||||
@@ -670,7 +833,7 @@ public sealed class AddressSpaceApplier
|
||||
// even if authored ReadWrite, so a client write cannot reach the driver write path which
|
||||
// does not handle arrays (e.g. S7 BoxValueForWrite would crash).
|
||||
var writable = tag.Writable && !tag.IsArray;
|
||||
if (!SafeEnsureVariable(nodeId, parent, tag.Name, tag.DataType, writable, historianTagname, tag.IsArray, tag.ArrayLength)) failed++;
|
||||
if (!SafeEnsureVariable(nodeId, parent, tag.Name, tag.DataType, writable, AddressSpaceRealm.Uns, historianTagname, tag.IsArray, tag.ArrayLength)) failed++;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -708,17 +871,17 @@ public sealed class AddressSpaceApplier
|
||||
var failed = 0;
|
||||
// Parent-first: a child folder's parent must exist before it. Ordering by '/' count == depth.
|
||||
foreach (var f in folders.OrderBy(f => f.NodeId.Count(c => c == '/')))
|
||||
if (!SafeEnsureFolder(f.NodeId, f.ParentNodeId, f.DisplayName)) failed++;
|
||||
if (!SafeEnsureFolder(f.NodeId, f.ParentNodeId, f.DisplayName, AddressSpaceRealm.Raw)) failed++;
|
||||
|
||||
foreach (var v in variables)
|
||||
{
|
||||
// Mirror MaterialiseEquipmentTags: arrays forced read-only (the driver write path can't handle arrays).
|
||||
var writable = v.Writable && !v.IsArray;
|
||||
if (!SafeEnsureVariable(v.NodeId, v.ParentNodeId, v.DisplayName, v.DataType, writable,
|
||||
historianTagname: null, isArray: v.IsArray, arrayLength: v.ArrayLength)) failed++;
|
||||
AddressSpaceRealm.Raw, historianTagname: null, isArray: v.IsArray, arrayLength: v.ArrayLength)) failed++;
|
||||
}
|
||||
|
||||
_sink.RaiseNodesAddedModelChange(equipmentRootNodeId);
|
||||
_sink.RaiseNodesAddedModelChange(equipmentRootNodeId, AddressSpaceRealm.Raw);
|
||||
|
||||
_logger.LogInformation(
|
||||
"AddressSpaceApplier: discovered nodes materialised under {Equipment} (folders={Folders}, vars={Vars}, failed={Failed})",
|
||||
@@ -754,9 +917,9 @@ public sealed class AddressSpaceApplier
|
||||
foreach (var v in composition.EquipmentVirtualTags)
|
||||
{
|
||||
if (string.IsNullOrWhiteSpace(v.FolderPath)) continue;
|
||||
var folderNodeId = EquipmentNodeIds.SubFolder(v.EquipmentId, v.FolderPath);
|
||||
var folderNodeId = UnsSubFolder(v.EquipmentId, v.FolderPath);
|
||||
if (!foldersCreated.Add(folderNodeId)) continue;
|
||||
if (!SafeEnsureFolder(folderNodeId, parentNodeId: v.EquipmentId, displayName: v.FolderPath)) failed++;
|
||||
if (!SafeEnsureFolder(folderNodeId, parentNodeId: v.EquipmentId, displayName: v.FolderPath, realm: AddressSpaceRealm.Uns)) failed++;
|
||||
}
|
||||
|
||||
// Variables: NodeId is FOLDER-SCOPED ("<parent>/<Name>"), mirroring the equipment-tag pass.
|
||||
@@ -769,10 +932,10 @@ public sealed class AddressSpaceApplier
|
||||
{
|
||||
var parent = string.IsNullOrWhiteSpace(v.FolderPath)
|
||||
? v.EquipmentId
|
||||
: EquipmentNodeIds.SubFolder(v.EquipmentId, v.FolderPath);
|
||||
var nodeId = EquipmentNodeIds.Variable(v.EquipmentId, v.FolderPath, v.Name);
|
||||
: UnsSubFolder(v.EquipmentId, v.FolderPath);
|
||||
var nodeId = UnsEquipmentVar(v.EquipmentId, v.FolderPath, v.Name);
|
||||
// VirtualTags are computed outputs — read-only nodes (no inbound write).
|
||||
if (!SafeEnsureVariable(nodeId, parent, v.Name, v.DataType, writable: false)) failed++;
|
||||
if (!SafeEnsureVariable(nodeId, parent, v.Name, v.DataType, writable: false, realm: AddressSpaceRealm.Uns)) failed++;
|
||||
}
|
||||
|
||||
_logger.LogInformation(
|
||||
@@ -805,7 +968,7 @@ public sealed class AddressSpaceApplier
|
||||
foreach (var alarm in composition.EquipmentScriptedAlarms)
|
||||
{
|
||||
if (!alarm.Enabled) continue;
|
||||
if (!SafeMaterialiseAlarmCondition(alarm.ScriptedAlarmId, alarm.EquipmentId, alarm.Name, alarm.AlarmType, alarm.Severity, isNative: false)) failed++;
|
||||
if (!SafeMaterialiseAlarmCondition(alarm.ScriptedAlarmId, alarm.EquipmentId, alarm.Name, alarm.AlarmType, alarm.Severity, isNative: false, realm: AddressSpaceRealm.Uns)) failed++;
|
||||
materialised++;
|
||||
}
|
||||
|
||||
@@ -822,9 +985,9 @@ public sealed class AddressSpaceApplier
|
||||
/// on success, <c>false</c> when the sink threw — callers tally the false into their pass's failed-node
|
||||
/// count so a swallowed materialise failure is operator-visible (archreview 01/S-1).</summary>
|
||||
/// <returns><c>true</c> when the folder was ensured; <c>false</c> when the sink threw.</returns>
|
||||
private bool SafeEnsureFolder(string nodeId, string? parentNodeId, string displayName)
|
||||
private bool SafeEnsureFolder(string nodeId, string? parentNodeId, string displayName, AddressSpaceRealm realm)
|
||||
{
|
||||
try { _sink.EnsureFolder(nodeId, parentNodeId, displayName); return true; }
|
||||
try { _sink.EnsureFolder(nodeId, parentNodeId, displayName, realm); return true; }
|
||||
catch (Exception ex) { _logger.LogWarning(ex, "AddressSpaceApplier: EnsureFolder threw for {Node}", nodeId); return false; }
|
||||
}
|
||||
|
||||
@@ -832,9 +995,9 @@ public sealed class AddressSpaceApplier
|
||||
/// on success, <c>false</c> when the sink threw — callers tally the false into their pass's failed-node
|
||||
/// count (archreview 01/S-1).</summary>
|
||||
/// <returns><c>true</c> when the variable was ensured; <c>false</c> when the sink threw.</returns>
|
||||
private bool SafeEnsureVariable(string nodeId, string? parentNodeId, string displayName, string dataType, bool writable, string? historianTagname = null, bool isArray = false, uint? arrayLength = null)
|
||||
private bool SafeEnsureVariable(string nodeId, string? parentNodeId, string displayName, string dataType, bool writable, AddressSpaceRealm realm, string? historianTagname = null, bool isArray = false, uint? arrayLength = null)
|
||||
{
|
||||
try { _sink.EnsureVariable(nodeId, parentNodeId, displayName, dataType, writable, historianTagname, isArray, arrayLength); return true; }
|
||||
try { _sink.EnsureVariable(nodeId, parentNodeId, displayName, dataType, writable, realm, historianTagname, isArray, arrayLength); return true; }
|
||||
catch (Exception ex) { _logger.LogWarning(ex, "AddressSpaceApplier: EnsureVariable threw for {Node}", nodeId); return false; }
|
||||
}
|
||||
|
||||
@@ -859,9 +1022,9 @@ public sealed class AddressSpaceApplier
|
||||
/// Returns <c>true</c> on success, <c>false</c> when the sink threw — the removal pass tallies the
|
||||
/// false into <see cref="AddressSpaceApplyOutcome.FailedNodes"/> (archreview 01/S-1).</summary>
|
||||
/// <returns><c>true</c> when the write landed; <c>false</c> when the sink threw.</returns>
|
||||
private bool SafeWriteAlarmCondition(string nodeId, AlarmConditionSnapshot state, DateTime ts)
|
||||
private bool SafeWriteAlarmCondition(string nodeId, AlarmConditionSnapshot state, DateTime ts, AddressSpaceRealm realm)
|
||||
{
|
||||
try { _sink.WriteAlarmCondition(nodeId, state, ts); return true; }
|
||||
try { _sink.WriteAlarmCondition(nodeId, state, ts, realm); return true; }
|
||||
catch (Exception ex) { _logger.LogWarning(ex, "AddressSpaceApplier: WriteAlarmCondition threw for {Node}", nodeId); return false; }
|
||||
}
|
||||
|
||||
@@ -869,9 +1032,9 @@ public sealed class AddressSpaceApplier
|
||||
/// Returns <c>true</c> on success, <c>false</c> when the sink threw — callers tally the false into
|
||||
/// their pass's failed-node count (archreview 01/S-1).</summary>
|
||||
/// <returns><c>true</c> when the condition was materialised; <c>false</c> when the sink threw.</returns>
|
||||
private bool SafeMaterialiseAlarmCondition(string alarmNodeId, string equipmentNodeId, string displayName, string alarmType, int severity, bool isNative)
|
||||
private bool SafeMaterialiseAlarmCondition(string alarmNodeId, string equipmentNodeId, string displayName, string alarmType, int severity, bool isNative, AddressSpaceRealm realm)
|
||||
{
|
||||
try { _sink.MaterialiseAlarmCondition(alarmNodeId, equipmentNodeId, displayName, alarmType, severity, isNative); return true; }
|
||||
try { _sink.MaterialiseAlarmCondition(alarmNodeId, equipmentNodeId, displayName, alarmType, severity, realm, isNative); return true; }
|
||||
catch (Exception ex) { _logger.LogWarning(ex, "AddressSpaceApplier: MaterialiseAlarmCondition threw for {Node}", alarmNodeId); return false; }
|
||||
}
|
||||
}
|
||||
|
||||
@@ -21,50 +21,50 @@ public sealed class SdkAddressSpaceSink : IOpcUaAddressSpaceSink, ISurgicalAddre
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public void WriteValue(string nodeId, object? value, OpcUaQuality quality, DateTime sourceTimestampUtc, AddressSpaceRealm realm = AddressSpaceRealm.Uns)
|
||||
public void WriteValue(string nodeId, object? value, OpcUaQuality quality, DateTime sourceTimestampUtc, AddressSpaceRealm realm)
|
||||
=> _nodeManager.WriteValue(nodeId, value, quality, sourceTimestampUtc, realm);
|
||||
|
||||
/// <inheritdoc />
|
||||
public void WriteAlarmCondition(string alarmNodeId, AlarmConditionSnapshot state, DateTime sourceTimestampUtc, AddressSpaceRealm realm = AddressSpaceRealm.Uns)
|
||||
public void WriteAlarmCondition(string alarmNodeId, AlarmConditionSnapshot state, DateTime sourceTimestampUtc, AddressSpaceRealm realm)
|
||||
=> _nodeManager.WriteAlarmCondition(alarmNodeId, state, sourceTimestampUtc, realm);
|
||||
|
||||
/// <inheritdoc />
|
||||
public void MaterialiseAlarmCondition(string alarmNodeId, string equipmentNodeId, string displayName, string alarmType, int severity, bool isNative = false, AddressSpaceRealm realm = AddressSpaceRealm.Uns)
|
||||
public void MaterialiseAlarmCondition(string alarmNodeId, string equipmentNodeId, string displayName, string alarmType, int severity, AddressSpaceRealm realm, bool isNative = false)
|
||||
=> _nodeManager.MaterialiseAlarmCondition(alarmNodeId, equipmentNodeId, displayName, alarmType, severity, isNative, realm);
|
||||
|
||||
/// <inheritdoc />
|
||||
public void EnsureFolder(string folderNodeId, string? parentNodeId, string displayName, AddressSpaceRealm realm = AddressSpaceRealm.Uns)
|
||||
public void EnsureFolder(string folderNodeId, string? parentNodeId, string displayName, AddressSpaceRealm realm)
|
||||
=> _nodeManager.EnsureFolder(folderNodeId, parentNodeId, displayName, realm);
|
||||
|
||||
/// <inheritdoc />
|
||||
public void EnsureVariable(string variableNodeId, string? parentFolderNodeId, string displayName, string dataType, bool writable, string? historianTagname = null, bool isArray = false, uint? arrayLength = null, 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)
|
||||
=> _nodeManager.EnsureVariable(variableNodeId, parentFolderNodeId, displayName, dataType, writable, historianTagname, isArray, arrayLength, realm);
|
||||
|
||||
/// <inheritdoc />
|
||||
public bool UpdateTagAttributes(string variableNodeId, bool writable, string? historianTagname, string dataType, bool isArray, uint? arrayLength, AddressSpaceRealm realm = AddressSpaceRealm.Uns)
|
||||
public bool UpdateTagAttributes(string variableNodeId, bool writable, string? historianTagname, string dataType, bool isArray, uint? arrayLength, AddressSpaceRealm realm)
|
||||
=> _nodeManager.UpdateTagAttributes(variableNodeId, writable, historianTagname, dataType, isArray, arrayLength, realm);
|
||||
|
||||
/// <inheritdoc />
|
||||
public bool UpdateFolderDisplayName(string folderNodeId, string displayName, AddressSpaceRealm realm = AddressSpaceRealm.Uns)
|
||||
public bool UpdateFolderDisplayName(string folderNodeId, string displayName, AddressSpaceRealm realm)
|
||||
=> _nodeManager.UpdateFolderDisplayName(folderNodeId, displayName, realm);
|
||||
|
||||
/// <inheritdoc />
|
||||
public bool RemoveVariableNode(string variableNodeId, AddressSpaceRealm realm = AddressSpaceRealm.Uns)
|
||||
public bool RemoveVariableNode(string variableNodeId, AddressSpaceRealm realm)
|
||||
=> _nodeManager.RemoveVariableNode(variableNodeId, realm);
|
||||
|
||||
/// <inheritdoc />
|
||||
public bool RemoveAlarmConditionNode(string alarmNodeId, AddressSpaceRealm realm = AddressSpaceRealm.Uns)
|
||||
public bool RemoveAlarmConditionNode(string alarmNodeId, AddressSpaceRealm realm)
|
||||
=> _nodeManager.RemoveAlarmConditionNode(alarmNodeId, realm);
|
||||
|
||||
/// <inheritdoc />
|
||||
public bool RemoveEquipmentSubtree(string equipmentNodeId, AddressSpaceRealm realm = AddressSpaceRealm.Uns)
|
||||
public bool RemoveEquipmentSubtree(string equipmentNodeId, AddressSpaceRealm realm)
|
||||
=> _nodeManager.RemoveEquipmentSubtree(equipmentNodeId, realm);
|
||||
|
||||
/// <inheritdoc />
|
||||
public void RebuildAddressSpace() => _nodeManager.RebuildAddressSpace();
|
||||
|
||||
/// <inheritdoc />
|
||||
public void RaiseNodesAddedModelChange(string affectedNodeId, AddressSpaceRealm realm = AddressSpaceRealm.Uns) => _nodeManager.RaiseNodesAddedModelChange(affectedNodeId, realm);
|
||||
public void RaiseNodesAddedModelChange(string affectedNodeId, AddressSpaceRealm realm) => _nodeManager.RaiseNodesAddedModelChange(affectedNodeId, realm);
|
||||
|
||||
/// <inheritdoc />
|
||||
public void AddReference(string sourceNodeId, AddressSpaceRealm sourceRealm, string targetNodeId, AddressSpaceRealm targetRealm, string referenceType = "Organizes")
|
||||
|
||||
Reference in New Issue
Block a user