Propagate alarm events up the full notifier chain so subscribers at any ancestor see them

Previously alarms were only reported to the immediate parent node and the Server node.
Now ReportEventUpNotifierChain walks the full parent chain so clients subscribed at
TestArea see alarms from TestMachine_001, and EventNotifier is set on all ancestors
of alarm-containing nodes.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
Joseph Doherty
2026-03-28 20:25:55 -04:00
parent d9463d6998
commit 50b9603465
3 changed files with 56 additions and 34 deletions

View File

@@ -423,14 +423,9 @@ namespace ZB.MOM.WW.LmxOpcUa.Host.OpcUa
hasAlarms = true;
}
// Enable EventNotifier on object nodes that contain alarms
// Enable EventNotifier on this node and all ancestors so alarm events propagate
if (hasAlarms && _nodeMap.TryGetValue(obj.GobjectId, out var objNode))
{
if (objNode is BaseObjectState objState)
objState.EventNotifier = EventNotifiers.SubscribeToEvents;
else if (objNode is FolderState folderState)
folderState.EventNotifier = EventNotifiers.SubscribeToEvents;
}
EnableEventNotifierUpChain(objNode);
}
// Auto-subscribe to InAlarm tags so we detect alarm transitions
@@ -519,14 +514,9 @@ namespace ZB.MOM.WW.LmxOpcUa.Host.OpcUa
if (active)
condition.SetAcknowledgedState(SystemContext, false);
// Report through the source node hierarchy so events reach subscribers on parent objects
if (_tagToVariableNode.TryGetValue(info.SourceTagReference, out var sourceVar) && sourceVar.Parent != null)
{
sourceVar.Parent.ReportEvent(SystemContext, condition);
}
// Also report to Server node for clients subscribed at server level
Server.ReportEvent(SystemContext, condition);
// Walk up the notifier chain so events reach subscribers at any ancestor level
if (_tagToVariableNode.TryGetValue(info.SourceTagReference, out var sourceVar))
ReportEventUpNotifierChain(sourceVar, condition);
Log.Information("Alarm {State}: {Source} (Severity={Severity}, Message={Message})",
active ? "ACTIVE" : "CLEARED", info.SourceName, severity, message);
@@ -879,12 +869,7 @@ namespace ZB.MOM.WW.LmxOpcUa.Host.OpcUa
}
if (hasAlarms && _nodeMap.TryGetValue(obj.GobjectId, out var objNode))
{
if (objNode is BaseObjectState objState)
objState.EventNotifier = EventNotifiers.SubscribeToEvents;
else if (objNode is FolderState folderState)
folderState.EventNotifier = EventNotifiers.SubscribeToEvents;
}
EnableEventNotifierUpChain(objNode);
}
// Subscribe alarm tags for new subtree
@@ -1192,6 +1177,23 @@ namespace ZB.MOM.WW.LmxOpcUa.Host.OpcUa
return roles != null && roles.Contains(requiredRole);
}
private static void EnableEventNotifierUpChain(NodeState node)
{
for (var current = node as BaseInstanceState; current != null; current = current.Parent as BaseInstanceState)
{
if (current is BaseObjectState obj)
obj.EventNotifier = EventNotifiers.SubscribeToEvents;
else if (current is FolderState folder)
folder.EventNotifier = EventNotifiers.SubscribeToEvents;
}
}
private void ReportEventUpNotifierChain(BaseInstanceState sourceNode, IFilterTarget eventInstance)
{
for (var current = sourceNode.Parent; current != null; current = (current as BaseInstanceState)?.Parent)
current.ReportEvent(SystemContext, eventInstance);
}
private bool TryApplyArrayElementWrite(string tagRef, object? writeValue, string indexRange, out object updatedArray)
{
updatedArray = null!;
@@ -1761,9 +1763,8 @@ namespace ZB.MOM.WW.LmxOpcUa.Host.OpcUa
condition.SetAcknowledgedState(SystemContext, acked);
condition.Retain.Value = (condition.ActiveState?.Id?.Value == true) || !acked;
if (_tagToVariableNode.TryGetValue(info.SourceTagReference, out var src) && src.Parent != null)
src.Parent.ReportEvent(SystemContext, condition);
Server.ReportEvent(SystemContext, condition);
if (_tagToVariableNode.TryGetValue(info.SourceTagReference, out var src))
ReportEventUpNotifierChain(src, condition);
Log.Information("Alarm {AckState}: {Source}",
acked ? "ACKNOWLEDGED" : "UNACKNOWLEDGED", info.SourceName);