Resolve DA, A&C, and security spec gaps with ServerCapabilities, alarm methods, and modern profiles

Add ServerCapabilities/OperationLimits node, enable diagnostics, add OnModifyMonitoredItemsComplete
override for DA compliance. Wire shelving, enable/disable, confirm, and addcomment handlers on
alarm conditions with LocalTime/Quality event fields for Part 9 compliance. Add Aes128/Aes256
security profiles, X.509 certificate authentication, and AUDIT-prefixed auth logging. Fix flaky
probe monitor test. Update docs for all changes.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
Joseph Doherty
2026-04-06 22:02:05 -04:00
parent 41f0e9ec4c
commit 6d47687573
12 changed files with 345 additions and 20 deletions

View File

@@ -327,6 +327,11 @@ namespace ZB.MOM.WW.LmxOpcUa.Host.OpcUa
condition.Retain.Value = false;
condition.OnReportEvent = (context, node, e) => Server.ReportEvent(context, e);
condition.OnAcknowledge = OnAlarmAcknowledge;
condition.OnConfirm = OnAlarmConfirm;
condition.OnAddComment = OnAlarmAddComment;
condition.OnEnableDisable = OnAlarmEnableDisable;
condition.OnShelve = OnAlarmShelve;
condition.OnTimedUnshelve = OnAlarmTimedUnshelve;
// Add HasCondition reference from source to condition
if (sourceVariable != null)
@@ -425,6 +430,48 @@ namespace ZB.MOM.WW.LmxOpcUa.Host.OpcUa
}
}
private ServiceResult OnAlarmConfirm(
ISystemContext context, ConditionState condition, byte[] eventId, LocalizedText comment)
{
Log.Information("Alarm confirmed: {Name} (Comment={Comment})",
condition.ConditionName?.Value, comment?.Text);
return ServiceResult.Good;
}
private ServiceResult OnAlarmAddComment(
ISystemContext context, ConditionState condition, byte[] eventId, LocalizedText comment)
{
Log.Information("Alarm comment added: {Name} — {Comment}",
condition.ConditionName?.Value, comment?.Text);
return ServiceResult.Good;
}
private ServiceResult OnAlarmEnableDisable(
ISystemContext context, ConditionState condition, bool enabling)
{
Log.Information("Alarm {Action}: {Name}",
enabling ? "ENABLED" : "DISABLED", condition.ConditionName?.Value);
return ServiceResult.Good;
}
private ServiceResult OnAlarmShelve(
ISystemContext context, AlarmConditionState alarm, bool shelving, bool oneShot, double shelvingTime)
{
alarm.SetShelvingState(context, shelving, oneShot, shelvingTime);
Log.Information("Alarm {Action}: {Name} (OneShot={OneShot}, Time={Time}s)",
shelving ? "SHELVED" : "UNSHELVED", alarm.ConditionName?.Value, oneShot,
shelvingTime / 1000.0);
return ServiceResult.Good;
}
private ServiceResult OnAlarmTimedUnshelve(
ISystemContext context, AlarmConditionState alarm)
{
alarm.SetShelvingState(context, false, false, 0);
Log.Information("Alarm timed unshelve: {Name}", alarm.ConditionName?.Value);
return ServiceResult.Good;
}
private void ReportAlarmEvent(AlarmInfo info, bool active)
{
var condition = info.ConditionNode;
@@ -443,6 +490,16 @@ namespace ZB.MOM.WW.LmxOpcUa.Host.OpcUa
condition.Message.Value = new LocalizedText("en", message);
condition.SetSeverity(SystemContext, (EventSeverity)severity);
// Populate additional event fields
if (condition.LocalTime != null)
condition.LocalTime.Value = new TimeZoneDataType
{
Offset = (short)TimeZoneInfo.Local.BaseUtcOffset.TotalMinutes,
DaylightSavingInOffset = TimeZoneInfo.Local.IsDaylightSavingTime(DateTime.Now)
};
if (condition.Quality != null)
condition.Quality.Value = StatusCodes.Good;
// Retain while active or unacknowledged
condition.Retain.Value = active || condition.AckedState?.Id?.Value == false;
@@ -1808,6 +1865,15 @@ namespace ZB.MOM.WW.LmxOpcUa.Host.OpcUa
RestoreTransferredSubscriptions(transferredTagRefs);
}
/// <inheritdoc />
protected override void OnModifyMonitoredItemsComplete(ServerSystemContext context,
IList<IMonitoredItem> monitoredItems)
{
foreach (var item in monitoredItems)
Log.Debug("MonitoredItem modified: Id={Id}, SamplingInterval={Interval}ms",
item.Id, item.SamplingInterval);
}
private static string? GetNodeIdString(IMonitoredItem item)
{
if (item.ManagerHandle is NodeState node)