Scope alarm tracking to selected templates and surface endpoint/security state on the dashboard so operators can deploy in large galaxies without drowning clients in irrelevant alarms or guessing what the server is advertising
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -29,6 +29,8 @@ namespace ZB.MOM.WW.LmxOpcUa.Host.OpcUa
|
||||
private readonly Dictionary<string, AlarmInfo> _alarmPriorityTags = new(StringComparer.OrdinalIgnoreCase);
|
||||
private readonly Dictionary<string, AlarmInfo> _alarmDescTags = new(StringComparer.OrdinalIgnoreCase);
|
||||
private readonly bool _alarmTrackingEnabled;
|
||||
private readonly AlarmObjectFilter? _alarmObjectFilter;
|
||||
private int _alarmFilterIncludedObjectCount;
|
||||
private readonly bool _anonymousCanWrite;
|
||||
private readonly AutoResetEvent _dataChangeSignal = new(false);
|
||||
private readonly Dictionary<int, List<string>> _gobjectToTagRefs = new();
|
||||
@@ -88,6 +90,9 @@ namespace ZB.MOM.WW.LmxOpcUa.Host.OpcUa
|
||||
/// <param name="metrics">The metrics collector used to track node manager activity.</param>
|
||||
/// <param name="historianDataSource">The optional historian adapter used to satisfy OPC UA history read requests.</param>
|
||||
/// <param name="alarmTrackingEnabled">Enables alarm-condition state generation for Galaxy attributes modeled as alarms.</param>
|
||||
/// <param name="alarmObjectFilter">Optional template-based object filter. When supplied and enabled, only Galaxy
|
||||
/// objects whose template derivation chain matches a pattern (and their descendants) contribute alarm conditions.
|
||||
/// A <see langword="null"/> or disabled filter preserves the current unfiltered behavior.</param>
|
||||
public LmxNodeManager(
|
||||
IServerInternal server,
|
||||
ApplicationConfiguration configuration,
|
||||
@@ -100,7 +105,8 @@ namespace ZB.MOM.WW.LmxOpcUa.Host.OpcUa
|
||||
NodeId? writeOperateRoleId = null,
|
||||
NodeId? writeTuneRoleId = null,
|
||||
NodeId? writeConfigureRoleId = null,
|
||||
NodeId? alarmAckRoleId = null)
|
||||
NodeId? alarmAckRoleId = null,
|
||||
AlarmObjectFilter? alarmObjectFilter = null)
|
||||
: base(server, configuration, namespaceUri)
|
||||
{
|
||||
_namespaceUri = namespaceUri;
|
||||
@@ -108,6 +114,7 @@ namespace ZB.MOM.WW.LmxOpcUa.Host.OpcUa
|
||||
_metrics = metrics;
|
||||
_historianDataSource = historianDataSource;
|
||||
_alarmTrackingEnabled = alarmTrackingEnabled;
|
||||
_alarmObjectFilter = alarmObjectFilter;
|
||||
_anonymousCanWrite = anonymousCanWrite;
|
||||
_writeOperateRoleId = writeOperateRoleId;
|
||||
_writeTuneRoleId = writeTuneRoleId;
|
||||
@@ -161,6 +168,21 @@ namespace ZB.MOM.WW.LmxOpcUa.Host.OpcUa
|
||||
/// </summary>
|
||||
public bool AlarmTrackingEnabled => _alarmTrackingEnabled;
|
||||
|
||||
/// <summary>
|
||||
/// Gets a value indicating whether the template-based alarm object filter is enabled.
|
||||
/// </summary>
|
||||
public bool AlarmFilterEnabled => _alarmObjectFilter?.Enabled ?? false;
|
||||
|
||||
/// <summary>
|
||||
/// Gets the number of compiled alarm filter patterns.
|
||||
/// </summary>
|
||||
public int AlarmFilterPatternCount => _alarmObjectFilter?.PatternCount ?? 0;
|
||||
|
||||
/// <summary>
|
||||
/// Gets the number of Galaxy objects included by the alarm filter during the most recent address-space build.
|
||||
/// </summary>
|
||||
public int AlarmFilterIncludedObjectCount => _alarmFilterIncludedObjectCount;
|
||||
|
||||
/// <summary>
|
||||
/// Gets the number of distinct alarm conditions currently tracked (one per alarm attribute).
|
||||
/// </summary>
|
||||
@@ -337,9 +359,12 @@ namespace ZB.MOM.WW.LmxOpcUa.Host.OpcUa
|
||||
|
||||
// Build alarm tracking: create AlarmConditionState for each alarm attribute
|
||||
if (_alarmTrackingEnabled)
|
||||
{
|
||||
var includedIds = ResolveAlarmFilterIncludedIds(sorted);
|
||||
foreach (var obj in sorted)
|
||||
{
|
||||
if (obj.IsArea) continue;
|
||||
if (includedIds != null && !includedIds.Contains(obj.GobjectId)) continue;
|
||||
if (!attrsByObject.TryGetValue(obj.GobjectId, out var objAttrs)) continue;
|
||||
|
||||
var hasAlarms = false;
|
||||
@@ -419,6 +444,7 @@ namespace ZB.MOM.WW.LmxOpcUa.Host.OpcUa
|
||||
if (hasAlarms && _nodeMap.TryGetValue(obj.GobjectId, out var objNode))
|
||||
EnableEventNotifierUpChain(objNode);
|
||||
}
|
||||
}
|
||||
|
||||
// Auto-subscribe to InAlarm tags so we detect alarm transitions
|
||||
if (_alarmTrackingEnabled)
|
||||
@@ -433,6 +459,32 @@ namespace ZB.MOM.WW.LmxOpcUa.Host.OpcUa
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Resolves the alarm object filter against the given hierarchy, updates the published include count,
|
||||
/// emits a one-line summary log when the filter is active, and warns about patterns that matched nothing.
|
||||
/// Returns <see langword="null"/> when no filter is configured so the alarm loop continues unfiltered.
|
||||
/// </summary>
|
||||
private HashSet<int>? ResolveAlarmFilterIncludedIds(IReadOnlyList<GalaxyObjectInfo> sorted)
|
||||
{
|
||||
if (_alarmObjectFilter == null || !_alarmObjectFilter.Enabled)
|
||||
{
|
||||
_alarmFilterIncludedObjectCount = 0;
|
||||
return null;
|
||||
}
|
||||
|
||||
var includedIds = _alarmObjectFilter.ResolveIncludedObjects(sorted);
|
||||
_alarmFilterIncludedObjectCount = includedIds?.Count ?? 0;
|
||||
|
||||
Log.Information(
|
||||
"Alarm filter: {IncludedCount} of {TotalCount} objects included ({PatternCount} pattern(s))",
|
||||
_alarmFilterIncludedObjectCount, sorted.Count, _alarmObjectFilter.PatternCount);
|
||||
|
||||
foreach (var unmatched in _alarmObjectFilter.UnmatchedPatterns)
|
||||
Log.Warning("Alarm filter pattern matched zero objects: {Pattern}", unmatched);
|
||||
|
||||
return includedIds;
|
||||
}
|
||||
|
||||
private void SubscribeAlarmTags()
|
||||
{
|
||||
foreach (var kvp in _alarmInAlarmTags)
|
||||
@@ -863,9 +915,11 @@ namespace ZB.MOM.WW.LmxOpcUa.Host.OpcUa
|
||||
// Alarm tracking for the new subtree
|
||||
if (_alarmTrackingEnabled)
|
||||
{
|
||||
var includedIds = ResolveAlarmFilterIncludedIds(sorted);
|
||||
foreach (var obj in sorted)
|
||||
{
|
||||
if (obj.IsArea) continue;
|
||||
if (includedIds != null && !includedIds.Contains(obj.GobjectId)) continue;
|
||||
if (!attrsByObject.TryGetValue(obj.GobjectId, out var objAttrs)) continue;
|
||||
|
||||
var hasAlarms = false;
|
||||
|
||||
Reference in New Issue
Block a user