From ede5aff61aeeb9aaa05e42c47f08028abd968e5b Mon Sep 17 00:00:00 2001 From: Luis Cantero Date: Mon, 22 Jul 2024 13:47:35 +0200 Subject: [PATCH] Update src/AlarmCondition/Model/SourceState.cs --- src/AlarmCondition/Model/SourceState.cs | 1408 +++++++++++------------ 1 file changed, 704 insertions(+), 704 deletions(-) diff --git a/src/AlarmCondition/Model/SourceState.cs b/src/AlarmCondition/Model/SourceState.cs index dc3f6a7..cefb71d 100644 --- a/src/AlarmCondition/Model/SourceState.cs +++ b/src/AlarmCondition/Model/SourceState.cs @@ -1,109 +1,109 @@ -/* ======================================================================== - * Copyright (c) 2005-2019 The OPC Foundation, Inc. All rights reserved. - * - * OPC Foundation MIT License 1.00 - * - * Permission is hereby granted, free of charge, to any person - * obtaining a copy of this software and associated documentation - * files (the "Software"), to deal in the Software without - * restriction, including without limitation the rights to use, - * copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the - * Software is furnished to do so, subject to the following - * conditions: - * - * The above copyright notice and this permission notice shall be - * included in all copies or substantial portions of the Software. - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, - * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES - * OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND - * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT - * HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, - * WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING - * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR - * OTHER DEALINGS IN THE SOFTWARE. - * - * The complete license agreement can be found here: - * http://opcfoundation.org/License/MIT/1.00/ - * ======================================================================*/ - -using Microsoft.AspNetCore.Hosting.Server; -using Opc.Ua; -using Opc.Ua.Test; -using System; -using System.Collections.Generic; -using System.Reflection.Emit; - -namespace AlarmCondition -{ - /// - /// Maps an alarm source to a UA object node. - /// - public partial class SourceState : BaseObjectState - { - #region Constructors - /// - /// Initializes the area. - /// - public SourceState( - AlarmConditionServerNodeManager nodeManager, - NodeId nodeId, +/* ======================================================================== + * Copyright (c) 2005-2019 The OPC Foundation, Inc. All rights reserved. + * + * OPC Foundation MIT License 1.00 + * + * Permission is hereby granted, free of charge, to any person + * obtaining a copy of this software and associated documentation + * files (the "Software"), to deal in the Software without + * restriction, including without limitation the rights to use, + * copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the + * Software is furnished to do so, subject to the following + * conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES + * OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT + * HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, + * WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR + * OTHER DEALINGS IN THE SOFTWARE. + * + * The complete license agreement can be found here: + * http://opcfoundation.org/License/MIT/1.00/ + * ======================================================================*/ + +using Microsoft.AspNetCore.Hosting.Server; +using Opc.Ua; +using Opc.Ua.Test; +using System; +using System.Collections.Generic; +using System.Reflection.Emit; + +namespace AlarmCondition +{ + /// + /// Maps an alarm source to a UA object node. + /// + public partial class SourceState : BaseObjectState + { + #region Constructors + /// + /// Initializes the area. + /// + public SourceState( + AlarmConditionServerNodeManager nodeManager, + NodeId nodeId, string sourcePath, - DataGenerator generator) - : - base(null) - { - Initialize(nodeManager.SystemContext); - - // save the node manager that owns the source. - m_nodeManager = nodeManager; - - // create the source with the underlying system. - m_source = ((UnderlyingSystem)nodeManager.SystemContext.SystemHandle).CreateSource(sourcePath, OnAlarmChanged); - - // initialize the area with the fixed metadata. - SymbolicName = m_source.Name; - NodeId = nodeId; - BrowseName = new QualifiedName(Utils.Format("{0}", m_source.Name), nodeId.NamespaceIndex); - DisplayName = BrowseName.Name; - Description = null; - ReferenceTypeId = null; - TypeDefinitionId = ObjectTypeIds.BaseObjectType; - EventNotifier = EventNotifiers.None; - - // create a dialog. - m_generator = generator; - m_dialog = CreateDialog("OnlineState"); - - // create the table of conditions. - m_alarms = new Dictionary(); - m_events = new Dictionary(); - m_branches = new Dictionary(); - - // request an updated for all alarms. - //m_source.Refresh(); - } - #endregion - - #region Public Interface - /// - /// Returns the last event produced for any conditions belonging to the node or its children. - /// - /// The system context. - /// The list of condition events to return. - /// Whether to recursively report events for the children. - public override void ConditionRefresh(ISystemContext context, List events, bool includeChildren) - { - // need to check if this source has already been processed during this refresh operation. - for (int ii = 0; ii < events.Count; ii++) - { - if (events[ii] is InstanceStateSnapshot e && ReferenceEquals(e.Handle, this)) - { - return; - } - } - - // report the dialog. + DataGenerator generator) + : + base(null) + { + Initialize(nodeManager.SystemContext); + + // save the node manager that owns the source. + m_nodeManager = nodeManager; + + // create the source with the underlying system. + m_source = ((UnderlyingSystem)nodeManager.SystemContext.SystemHandle).CreateSource(sourcePath, OnAlarmChanged); + + // initialize the area with the fixed metadata. + SymbolicName = m_source.Name; + NodeId = nodeId; + BrowseName = new QualifiedName(Utils.Format("{0}", m_source.Name), nodeId.NamespaceIndex); + DisplayName = BrowseName.Name; + Description = null; + ReferenceTypeId = null; + TypeDefinitionId = ObjectTypeIds.BaseObjectType; + EventNotifier = EventNotifiers.None; + + // create a dialog. + m_generator = generator; + m_dialog = CreateDialog("OnlineState"); + + // create the table of conditions. + m_alarms = new Dictionary(); + m_events = new Dictionary(); + m_branches = new Dictionary(); + + // request an updated for all alarms. + //m_source.Refresh(); + } + #endregion + + #region Public Interface + /// + /// Returns the last event produced for any conditions belonging to the node or its children. + /// + /// The system context. + /// The list of condition events to return. + /// Whether to recursively report events for the children. + public override void ConditionRefresh(ISystemContext context, List events, bool includeChildren) + { + // need to check if this source has already been processed during this refresh operation. + for (int ii = 0; ii < events.Count; ii++) + { + if (events[ii] is InstanceStateSnapshot e && ReferenceEquals(e.Handle, this)) + { + return; + } + } + + // report the dialog. if (m_dialog != null && m_dialog.Retain.Value) { // create a snapshot. @@ -114,196 +114,196 @@ public override void ConditionRefresh(ISystemContext context, List - /// Called when the state of an alarm for the source has changed. - /// - private void OnAlarmChanged(UnderlyingSystemAlarm alarm) - { - lock (m_nodeManager.Lock) - { - // ignore archived alarms for now. - if (alarm.RecordNumber != 0) - { - var branchId = new NodeId(alarm.RecordNumber, NodeId.NamespaceIndex); - - // find the alarm branch. - if (!m_branches.TryGetValue(new NodeId(alarm.Name, NodeId.NamespaceIndex), out AlarmConditionState branch)) - { - m_branches[branchId] = branch = CreateAlarm(alarm, branchId); - } - - // map the system information to the UA defined alarm. - UpdateAlarm(branch, alarm); - ReportChanges(branch); - - // delete the branch. - if ((alarm.State & UnderlyingSystemAlarmStates.Deleted) != 0) - { - m_branches.Remove(branchId); - } - - return; - } - - // find the alarm node. - if (!m_alarms.TryGetValue(alarm.Name, out AlarmConditionState node)) - { - m_alarms[alarm.Name] = node = CreateAlarm(alarm, null); - } - - // map the system information to the UA defined alarm. - UpdateAlarm(node, alarm); - ReportChanges(node); - } - } - - /// - /// Creates a new dialog condition - /// - private DialogConditionState CreateDialog(string dialogName) - { - ISystemContext context = m_nodeManager.SystemContext; - + } + + // the alarm objects act as a cache for the last known state and are used to generate refresh events. + foreach (AlarmConditionState alarm in m_alarms.Values) + { + // do not refresh alarms that are not in an interesting state. + if (!alarm.Retain.Value) + { + continue; + } + + // create a snapshot. + InstanceStateSnapshot e = new(); + e.Initialize(context, alarm); + + // set the handle of the snapshot to check for duplicates. + e.Handle = this; + + events.Add(e); + } + + // report any active branches. + foreach (AlarmConditionState alarm in m_branches.Values) + { + // create a snapshot. + var e = new InstanceStateSnapshot(); + e.Initialize(context, alarm); + + // set the handle of the snapshot to check for duplicates. + e.Handle = this; + + events.Add(e); + } + } + #endregion + + #region Private Methods + /// + /// Called when the state of an alarm for the source has changed. + /// + private void OnAlarmChanged(UnderlyingSystemAlarm alarm) + { + lock (m_nodeManager.Lock) + { + // ignore archived alarms for now. + if (alarm.RecordNumber != 0) + { + var branchId = new NodeId(alarm.RecordNumber, NodeId.NamespaceIndex); + + // find the alarm branch. + if (!m_branches.TryGetValue(new NodeId(alarm.Name, NodeId.NamespaceIndex), out AlarmConditionState branch)) + { + m_branches[branchId] = branch = CreateAlarm(alarm, branchId); + } + + // map the system information to the UA defined alarm. + UpdateAlarm(branch, alarm); + ReportChanges(branch); + + // delete the branch. + if ((alarm.State & UnderlyingSystemAlarmStates.Deleted) != 0) + { + m_branches.Remove(branchId); + } + + return; + } + + // find the alarm node. + if (!m_alarms.TryGetValue(alarm.Name, out AlarmConditionState node)) + { + m_alarms[alarm.Name] = node = CreateAlarm(alarm, null); + } + + // map the system information to the UA defined alarm. + UpdateAlarm(node, alarm); + ReportChanges(node); + } + } + + /// + /// Creates a new dialog condition + /// + private DialogConditionState CreateDialog(string dialogName) + { + ISystemContext context = m_nodeManager.SystemContext; + var node = new DialogConditionState(this) { SymbolicName = dialogName, - }; - - // specify optional fields. - node.EnabledState = new TwoStateVariableState(node); - node.EnabledState.TransitionTime = new PropertyState(node.EnabledState); - node.EnabledState.EffectiveDisplayName = new PropertyState(node.EnabledState); - node.EnabledState.Create(context, null, BrowseNames.EnabledState, null, false); - - // specify reference type between the source and the alarm. - node.ReferenceTypeId = ReferenceTypeIds.HasComponent; - - // This call initializes the condition from the type model (i.e. creates all of the objects - // and variables required to store its state). The information about the type model was - // incorporated into the class when the class was created. - node.Create( - context, - null, - new QualifiedName(dialogName, BrowseName.NamespaceIndex), - null, - true); - - AddChild(node); - - // initialize event information. - node.EventId.Value = GetNextGuidAsByteArray(); - node.EventType.Value = node.TypeDefinitionId; - node.SourceNode.Value = NodeId; - node.SourceName.Value = SymbolicName; - node.ConditionName.Value = node.SymbolicName; - node.Time.Value = DateTime.UtcNow; - node.ReceiveTime.Value = node.Time.Value; - node.Message.Value = "The dialog was activated"; - node.Retain.Value = true; - - node.SetEnableState(context, true); - node.SetSeverity(context, EventSeverity.Low); - - // initialize the dialog information. - node.Prompt.Value = "Please specify a new state for the source."; - node.ResponseOptionSet.Value = s_ResponseOptions; - node.DefaultResponse.Value = 2; - node.CancelResponse.Value = 2; - node.OkResponse.Value = 0; - - // set up method handlers. - node.OnRespond = OnRespond; - - // this flag needs to be set because the underlying system does not produce these events. - node.AutoReportStateChanges = true; - - // activate the dialog. - node.Activate(context); - - // return the new node. - return node; - } - - private byte[] GetNextGuidAsByteArray() - { - // unpack the object to Uuid and then explicit cast to Guid to access the byte[] - // using RandomGenerator with known known seed to get reproducible results - return ((Guid)((Uuid)m_generator.GetRandom( - NodeId.Parse($"i={(int)BuiltInType.Guid}"), - ValueRanks.Scalar, new uint[] { 1 }, - m_nodeManager.Server.TypeTree))).ToByteArray(); - } - - /// - /// The responses used with the dialog condition. - /// - private readonly LocalizedText[] s_ResponseOptions = - [ - "Online", - "Offline", - "No Change" - ]; - - /// - /// Creates a new alarm for the source. - /// - /// The alarm. - /// The branch id. - /// The new alarm. - private AlarmConditionState CreateAlarm(UnderlyingSystemAlarm alarm, NodeId branchId) - { - ISystemContext context = m_nodeManager.SystemContext; - - AlarmConditionState node = null; - - // need to map the alarm type to a UA defined alarm type. - switch (alarm.AlarmType) - { + }; + + // specify optional fields. + node.EnabledState = new TwoStateVariableState(node); + node.EnabledState.TransitionTime = new PropertyState(node.EnabledState); + node.EnabledState.EffectiveDisplayName = new PropertyState(node.EnabledState); + node.EnabledState.Create(context, null, BrowseNames.EnabledState, null, false); + + // specify reference type between the source and the alarm. + node.ReferenceTypeId = ReferenceTypeIds.HasComponent; + + // This call initializes the condition from the type model (i.e. creates all of the objects + // and variables required to store its state). The information about the type model was + // incorporated into the class when the class was created. + node.Create( + context, + null, + new QualifiedName(dialogName, BrowseName.NamespaceIndex), + null, + true); + + AddChild(node); + + // initialize event information. + node.EventId.Value = GetNextGuidAsByteArray(); + node.EventType.Value = node.TypeDefinitionId; + node.SourceNode.Value = NodeId; + node.SourceName.Value = SymbolicName; + node.ConditionName.Value = node.SymbolicName; + node.Time.Value = DateTime.UtcNow; + node.ReceiveTime.Value = node.Time.Value; + node.Message.Value = "The dialog was activated"; + node.Retain.Value = true; + + node.SetEnableState(context, true); + node.SetSeverity(context, EventSeverity.Low); + + // initialize the dialog information. + node.Prompt.Value = "Please specify a new state for the source."; + node.ResponseOptionSet.Value = s_ResponseOptions; + node.DefaultResponse.Value = 2; + node.CancelResponse.Value = 2; + node.OkResponse.Value = 0; + + // set up method handlers. + node.OnRespond = OnRespond; + + // this flag needs to be set because the underlying system does not produce these events. + node.AutoReportStateChanges = true; + + // activate the dialog. + node.Activate(context); + + // return the new node. + return node; + } + + private byte[] GetNextGuidAsByteArray() + { + // Unpack the object to Uuid and then explicitly cast to Guid to access the byte[] + // using RandomGenerator with known known seed to get reproducible results + return ((Guid)((Uuid)m_generator.GetRandom( + NodeId.Parse($"i={(int)BuiltInType.Guid}"), + ValueRanks.Scalar, new uint[] { 1 }, + m_nodeManager.Server.TypeTree))).ToByteArray(); + } + + /// + /// The responses used with the dialog condition. + /// + private readonly LocalizedText[] s_ResponseOptions = + [ + "Online", + "Offline", + "No Change" + ]; + + /// + /// Creates a new alarm for the source. + /// + /// The alarm. + /// The branch id. + /// The new alarm. + private AlarmConditionState CreateAlarm(UnderlyingSystemAlarm alarm, NodeId branchId) + { + ISystemContext context = m_nodeManager.SystemContext; + + AlarmConditionState node = null; + + // need to map the alarm type to a UA defined alarm type. + switch (alarm.AlarmType) + { case "HighAlarm": { ExclusiveDeviationAlarmState node2 = new(this); node = node2; node2.HighLimit = new PropertyState(node2); break; - } - + } + case "HighLowAlarm": { NonExclusiveLevelAlarmState node2 = new(this); @@ -320,429 +320,429 @@ private AlarmConditionState CreateAlarm(UnderlyingSystemAlarm alarm, NodeId bran node2.LowLowState = new TwoStateVariableState(node2); break; - } - + } + case "TripAlarm": { node = new TripAlarmState(this); break; - } - + } + default: { node = new AlarmConditionState(this); break; - } - } - - node.SymbolicName = alarm.Name; - - // add optional components. - node.Comment = new ConditionVariableState(node); - node.ClientUserId = new PropertyState(node); - node.AddComment = new AddCommentMethodState(node); - node.ConfirmedState = new TwoStateVariableState(node); - node.Confirm = new AddCommentMethodState(node); - - if (NodeId.IsNull(branchId)) - { - node.SuppressedState = new TwoStateVariableState(node); - node.ShelvingState = new ShelvedStateMachineState(node); - } - - // adding optional components to children is a little more complicated since the - // necessary initialization strings defined by the class that represents the child. - // in this case we pre-create the child, add the optional components - // and call create without assigning NodeIds. The NodeIds will be assigned when the - // parent object is created. - node.EnabledState = new TwoStateVariableState(node); - node.EnabledState.TransitionTime = new PropertyState(node.EnabledState); - node.EnabledState.EffectiveDisplayName = new PropertyState(node.EnabledState); - node.EnabledState.Create(context, null, BrowseNames.EnabledState, null, false); - - // same procedure add optional components to the ActiveState component. - node.ActiveState = new TwoStateVariableState(node); - node.ActiveState.TransitionTime = new PropertyState(node.ActiveState); - node.ActiveState.EffectiveDisplayName = new PropertyState(node.ActiveState); - node.ActiveState.Create(context, null, BrowseNames.ActiveState, null, false); - - // specify reference type between the source and the alarm. - node.ReferenceTypeId = ReferenceTypeIds.HasComponent; - - // This call initializes the condition from the type model (i.e. creates all of the objects - // and variables required to store its state). The information about the type model was - // incorporated into the class when the class was created. - // - // This method also assigns new NodeIds to all of the components by calling the INodeIdFactory.New - // method on the INodeIdFactory object which is part of the system context. The NodeManager provides - // the INodeIdFactory implementation used here. - node.Create( - context, - null, - new QualifiedName(alarm.Name, BrowseName.NamespaceIndex), - null, - true); - - // don't add branches to the address space. - if (NodeId.IsNull(branchId)) - { - AddChild(node); - } - - // initialize event information.node - node.EventType.Value = node.TypeDefinitionId; - node.SourceNode.Value = NodeId; - node.SourceName.Value = SymbolicName; - node.ConditionName.Value = node.SymbolicName; - node.Time.Value = DateTime.UtcNow; - node.ReceiveTime.Value = node.Time.Value; - node.BranchId.Value = branchId; - - // set up method handlers. - node.OnEnableDisable = OnEnableDisableAlarm; - node.OnAcknowledge = OnAcknowledge; - node.OnAddComment = OnAddComment; - node.OnConfirm = OnConfirm; - node.OnShelve = OnShelve; - node.OnTimedUnshelve = OnTimedUnshelve; - - // return the new node. - return node; - } - - /// - /// Updates the alarm with a new state. - /// - /// The node. - /// The alarm. - private void UpdateAlarm(AlarmConditionState node, UnderlyingSystemAlarm alarm) - { - ISystemContext context = m_nodeManager.SystemContext; - - // remove old event. - if (node.EventId.Value != null) - { - m_events.Remove(Utils.ToHexString(node.EventId.Value)); - } - - // update the basic event information (include generating a unique id for the event). - node.EventId.Value = GetNextGuidAsByteArray(); - node.Time.Value = DateTime.UtcNow; - node.ReceiveTime.Value = node.Time.Value; - - // save the event for later lookup. - m_events[Utils.ToHexString(node.EventId.Value)] = node; - - // determine the retain state. - node.Retain.Value = true; - - if (alarm != null) - { - node.Time.Value = alarm.Time; - node.Message.Value = new LocalizedText(alarm.Reason); - - // update the states. - node.SetEnableState(context, (alarm.State & UnderlyingSystemAlarmStates.Enabled) != 0); - node.SetAcknowledgedState(context, (alarm.State & UnderlyingSystemAlarmStates.Acknowledged) != 0); - node.SetConfirmedState(context, (alarm.State & UnderlyingSystemAlarmStates.Confirmed) != 0); - node.SetActiveState(context, (alarm.State & UnderlyingSystemAlarmStates.Active) != 0); - node.SetSuppressedState(context, (alarm.State & UnderlyingSystemAlarmStates.Suppressed) != 0); - - // update other information. - node.SetComment(context, alarm.Comment, alarm.UserName); - node.SetSeverity(context, alarm.Severity); - - node.EnabledState.TransitionTime.Value = alarm.EnableTime; - node.ActiveState.TransitionTime.Value = alarm.ActiveTime; - - // check for deleted items. - if ((alarm.State & UnderlyingSystemAlarmStates.Deleted) != 0) - { - node.Retain.Value = false; - } - - // handle high alarms. - if (node is ExclusiveLimitAlarmState highAlarm) - { - highAlarm.HighLimit.Value = alarm.Limits[0]; - - if ((alarm.State & UnderlyingSystemAlarmStates.High) != 0) - { - highAlarm.SetLimitState(context, LimitAlarmStates.High); - } - } - - // handle high-low alarms. - if (node is NonExclusiveLimitAlarmState highLowAlarm) - { - highLowAlarm.HighHighLimit.Value = alarm.Limits[0]; - highLowAlarm.HighLimit.Value = alarm.Limits[1]; - highLowAlarm.LowLimit.Value = alarm.Limits[2]; - highLowAlarm.LowLowLimit.Value = alarm.Limits[3]; - - LimitAlarmStates limit = LimitAlarmStates.Inactive; - - if ((alarm.State & UnderlyingSystemAlarmStates.HighHigh) != 0) - { - limit |= LimitAlarmStates.HighHigh; - } - - if ((alarm.State & UnderlyingSystemAlarmStates.High) != 0) - { - limit |= LimitAlarmStates.High; - } - - if ((alarm.State & UnderlyingSystemAlarmStates.Low) != 0) - { - limit |= LimitAlarmStates.Low; - } - - if ((alarm.State & UnderlyingSystemAlarmStates.LowLow) != 0) - { - limit |= LimitAlarmStates.LowLow; - } - - highLowAlarm.SetLimitState(context, limit); - } - } - - // not interested in disabled or inactive alarms. - if (!node.EnabledState.Id.Value || !node.ActiveState.Id.Value) - { - node.Retain.Value = false; - } - } - - /// - /// Called when the alarm is enabled or disabled. - /// - private ServiceResult OnEnableDisableAlarm( - ISystemContext context, - ConditionState condition, - bool enabling) - { - m_source.EnableAlarm(condition.SymbolicName, enabling); - return ServiceResult.Good; - } - - /// - /// Called when the alarm has a comment added. - /// - private ServiceResult OnAddComment( - ISystemContext context, - ConditionState condition, - byte[] eventId, - LocalizedText comment) - { - AlarmConditionState alarm = FindAlarmByEventId(eventId); - - if (alarm == null) - { - return StatusCodes.BadEventIdUnknown; - } - - m_source.CommentAlarm(alarm.SymbolicName, GetRecordNumber(alarm), comment, GetUserName(context)); - - return ServiceResult.Good; - } - - /// - /// Called when the alarm is acknowledged. - /// - private ServiceResult OnAcknowledge( - ISystemContext context, - ConditionState condition, - byte[] eventId, - LocalizedText comment) - { - AlarmConditionState alarm = FindAlarmByEventId(eventId); - - if (alarm == null) - { - return StatusCodes.BadEventIdUnknown; - } - - m_source.AcknowledgeAlarm(alarm.SymbolicName, GetRecordNumber(alarm), comment, GetUserName(context)); - - return ServiceResult.Good; - } - - /// - /// Called when the alarm is confirmed. - /// - private ServiceResult OnConfirm( - ISystemContext context, - ConditionState condition, - byte[] eventId, - LocalizedText comment) - { - AlarmConditionState alarm = FindAlarmByEventId(eventId); - - if (alarm == null) - { - return StatusCodes.BadEventIdUnknown; - } - - m_source.ConfirmAlarm(alarm.SymbolicName, GetRecordNumber(alarm), comment, GetUserName(context)); - - return ServiceResult.Good; - } - - /// - /// Called when the alarm is shelved. - /// - private ServiceResult OnShelve( - ISystemContext context, - AlarmConditionState alarm, - bool shelving, - bool oneShot, - double shelvingTime) - { - alarm.SetShelvingState(context, shelving, oneShot, shelvingTime); - alarm.Message.Value = "The alarm shelved."; - - UpdateAlarm(alarm, null); - ReportChanges(alarm); - - return ServiceResult.Good; - } - - /// - /// Called when the alarm is shelved. - /// - private ServiceResult OnTimedUnshelve( - ISystemContext context, - AlarmConditionState alarm) - { - // update the alarm state and produce and event. - alarm.SetShelvingState(context, false, false, 0); - alarm.Message.Value = "The timed shelving period expired."; - - UpdateAlarm(alarm, null); - ReportChanges(alarm); - - return ServiceResult.Good; - } - - /// - /// Called when the dialog receives a response. - /// - private ServiceResult OnRespond( - ISystemContext context, - DialogConditionState dialog, - int selectedResponse) - { - // response 0 means set the source online. - if (selectedResponse == 0) - { - m_source.SetOfflineState(false); - } - - // response 1 means set the source offline. - if (selectedResponse == 1) - { - m_source.SetOfflineState(true); - } - - // other responses mean do nothing. - dialog.SetResponse(context, selectedResponse); - - // dialog no longer interesting once it is deactivated. - dialog.Message.Value = "The dialog was deactivated"; - dialog.Retain.Value = false; - - return ServiceResult.Good; - } - - /// - /// Reports the changes to the alarm. - /// - private void ReportChanges(AlarmConditionState alarm) - { - // report changes to node attributes. - alarm.ClearChangeMasks(m_nodeManager.SystemContext, true); - - // check if events are being monitored for the source. - if (AreEventsMonitored) - { - // create a snapshot. - InstanceStateSnapshot e = new(); - e.Initialize(m_nodeManager.SystemContext, alarm); - - // report the event. - alarm.ReportEvent(m_nodeManager.SystemContext, e); - } - } - - /// - /// Finds the alarm by event id. - /// - /// The event id. - /// The alarm. Null if not found. - private AlarmConditionState FindAlarmByEventId(byte[] eventId) - { - if (eventId == null) - { - return null; - } - - if (!m_events.TryGetValue(Utils.ToHexString(eventId), out AlarmConditionState alarm)) - { - return null; - } - - return alarm; - } - - /// - /// Gets the record number associated with tge alarm. - /// - /// The alarm. - /// The record number; 0 if the alarm is not an archived alarm. - private uint GetRecordNumber(AlarmConditionState alarm) - { - if (alarm == null) - { - return 0; - } - - if (alarm.BranchId == null || alarm.BranchId.Value == null) - { - return 0; - } - - uint? recordNumber = alarm.BranchId.Value.Identifier as uint?; - - if (recordNumber != null) - { - return recordNumber.Value; - } - - return 0; - } - - /// - /// Gets the user name associated with the context. - /// - private string GetUserName(ISystemContext context) - { - if (context.UserIdentity != null) - { - return context.UserIdentity.DisplayName; - } - - return null; - } - #endregion - - #region Private Fields - private readonly AlarmConditionServerNodeManager m_nodeManager; - private readonly UnderlyingSystemSource m_source; - private readonly Dictionary m_alarms; - private readonly Dictionary m_events; - private readonly Dictionary m_branches; - private readonly DialogConditionState m_dialog; - private DataGenerator m_generator; - #endregion - } -} + } + } + + node.SymbolicName = alarm.Name; + + // add optional components. + node.Comment = new ConditionVariableState(node); + node.ClientUserId = new PropertyState(node); + node.AddComment = new AddCommentMethodState(node); + node.ConfirmedState = new TwoStateVariableState(node); + node.Confirm = new AddCommentMethodState(node); + + if (NodeId.IsNull(branchId)) + { + node.SuppressedState = new TwoStateVariableState(node); + node.ShelvingState = new ShelvedStateMachineState(node); + } + + // adding optional components to children is a little more complicated since the + // necessary initialization strings defined by the class that represents the child. + // in this case we pre-create the child, add the optional components + // and call create without assigning NodeIds. The NodeIds will be assigned when the + // parent object is created. + node.EnabledState = new TwoStateVariableState(node); + node.EnabledState.TransitionTime = new PropertyState(node.EnabledState); + node.EnabledState.EffectiveDisplayName = new PropertyState(node.EnabledState); + node.EnabledState.Create(context, null, BrowseNames.EnabledState, null, false); + + // same procedure add optional components to the ActiveState component. + node.ActiveState = new TwoStateVariableState(node); + node.ActiveState.TransitionTime = new PropertyState(node.ActiveState); + node.ActiveState.EffectiveDisplayName = new PropertyState(node.ActiveState); + node.ActiveState.Create(context, null, BrowseNames.ActiveState, null, false); + + // specify reference type between the source and the alarm. + node.ReferenceTypeId = ReferenceTypeIds.HasComponent; + + // This call initializes the condition from the type model (i.e. creates all of the objects + // and variables required to store its state). The information about the type model was + // incorporated into the class when the class was created. + // + // This method also assigns new NodeIds to all of the components by calling the INodeIdFactory.New + // method on the INodeIdFactory object which is part of the system context. The NodeManager provides + // the INodeIdFactory implementation used here. + node.Create( + context, + null, + new QualifiedName(alarm.Name, BrowseName.NamespaceIndex), + null, + true); + + // don't add branches to the address space. + if (NodeId.IsNull(branchId)) + { + AddChild(node); + } + + // initialize event information.node + node.EventType.Value = node.TypeDefinitionId; + node.SourceNode.Value = NodeId; + node.SourceName.Value = SymbolicName; + node.ConditionName.Value = node.SymbolicName; + node.Time.Value = DateTime.UtcNow; + node.ReceiveTime.Value = node.Time.Value; + node.BranchId.Value = branchId; + + // set up method handlers. + node.OnEnableDisable = OnEnableDisableAlarm; + node.OnAcknowledge = OnAcknowledge; + node.OnAddComment = OnAddComment; + node.OnConfirm = OnConfirm; + node.OnShelve = OnShelve; + node.OnTimedUnshelve = OnTimedUnshelve; + + // return the new node. + return node; + } + + /// + /// Updates the alarm with a new state. + /// + /// The node. + /// The alarm. + private void UpdateAlarm(AlarmConditionState node, UnderlyingSystemAlarm alarm) + { + ISystemContext context = m_nodeManager.SystemContext; + + // remove old event. + if (node.EventId.Value != null) + { + m_events.Remove(Utils.ToHexString(node.EventId.Value)); + } + + // update the basic event information (include generating a unique id for the event). + node.EventId.Value = GetNextGuidAsByteArray(); + node.Time.Value = DateTime.UtcNow; + node.ReceiveTime.Value = node.Time.Value; + + // save the event for later lookup. + m_events[Utils.ToHexString(node.EventId.Value)] = node; + + // determine the retain state. + node.Retain.Value = true; + + if (alarm != null) + { + node.Time.Value = alarm.Time; + node.Message.Value = new LocalizedText(alarm.Reason); + + // update the states. + node.SetEnableState(context, (alarm.State & UnderlyingSystemAlarmStates.Enabled) != 0); + node.SetAcknowledgedState(context, (alarm.State & UnderlyingSystemAlarmStates.Acknowledged) != 0); + node.SetConfirmedState(context, (alarm.State & UnderlyingSystemAlarmStates.Confirmed) != 0); + node.SetActiveState(context, (alarm.State & UnderlyingSystemAlarmStates.Active) != 0); + node.SetSuppressedState(context, (alarm.State & UnderlyingSystemAlarmStates.Suppressed) != 0); + + // update other information. + node.SetComment(context, alarm.Comment, alarm.UserName); + node.SetSeverity(context, alarm.Severity); + + node.EnabledState.TransitionTime.Value = alarm.EnableTime; + node.ActiveState.TransitionTime.Value = alarm.ActiveTime; + + // check for deleted items. + if ((alarm.State & UnderlyingSystemAlarmStates.Deleted) != 0) + { + node.Retain.Value = false; + } + + // handle high alarms. + if (node is ExclusiveLimitAlarmState highAlarm) + { + highAlarm.HighLimit.Value = alarm.Limits[0]; + + if ((alarm.State & UnderlyingSystemAlarmStates.High) != 0) + { + highAlarm.SetLimitState(context, LimitAlarmStates.High); + } + } + + // handle high-low alarms. + if (node is NonExclusiveLimitAlarmState highLowAlarm) + { + highLowAlarm.HighHighLimit.Value = alarm.Limits[0]; + highLowAlarm.HighLimit.Value = alarm.Limits[1]; + highLowAlarm.LowLimit.Value = alarm.Limits[2]; + highLowAlarm.LowLowLimit.Value = alarm.Limits[3]; + + LimitAlarmStates limit = LimitAlarmStates.Inactive; + + if ((alarm.State & UnderlyingSystemAlarmStates.HighHigh) != 0) + { + limit |= LimitAlarmStates.HighHigh; + } + + if ((alarm.State & UnderlyingSystemAlarmStates.High) != 0) + { + limit |= LimitAlarmStates.High; + } + + if ((alarm.State & UnderlyingSystemAlarmStates.Low) != 0) + { + limit |= LimitAlarmStates.Low; + } + + if ((alarm.State & UnderlyingSystemAlarmStates.LowLow) != 0) + { + limit |= LimitAlarmStates.LowLow; + } + + highLowAlarm.SetLimitState(context, limit); + } + } + + // not interested in disabled or inactive alarms. + if (!node.EnabledState.Id.Value || !node.ActiveState.Id.Value) + { + node.Retain.Value = false; + } + } + + /// + /// Called when the alarm is enabled or disabled. + /// + private ServiceResult OnEnableDisableAlarm( + ISystemContext context, + ConditionState condition, + bool enabling) + { + m_source.EnableAlarm(condition.SymbolicName, enabling); + return ServiceResult.Good; + } + + /// + /// Called when the alarm has a comment added. + /// + private ServiceResult OnAddComment( + ISystemContext context, + ConditionState condition, + byte[] eventId, + LocalizedText comment) + { + AlarmConditionState alarm = FindAlarmByEventId(eventId); + + if (alarm == null) + { + return StatusCodes.BadEventIdUnknown; + } + + m_source.CommentAlarm(alarm.SymbolicName, GetRecordNumber(alarm), comment, GetUserName(context)); + + return ServiceResult.Good; + } + + /// + /// Called when the alarm is acknowledged. + /// + private ServiceResult OnAcknowledge( + ISystemContext context, + ConditionState condition, + byte[] eventId, + LocalizedText comment) + { + AlarmConditionState alarm = FindAlarmByEventId(eventId); + + if (alarm == null) + { + return StatusCodes.BadEventIdUnknown; + } + + m_source.AcknowledgeAlarm(alarm.SymbolicName, GetRecordNumber(alarm), comment, GetUserName(context)); + + return ServiceResult.Good; + } + + /// + /// Called when the alarm is confirmed. + /// + private ServiceResult OnConfirm( + ISystemContext context, + ConditionState condition, + byte[] eventId, + LocalizedText comment) + { + AlarmConditionState alarm = FindAlarmByEventId(eventId); + + if (alarm == null) + { + return StatusCodes.BadEventIdUnknown; + } + + m_source.ConfirmAlarm(alarm.SymbolicName, GetRecordNumber(alarm), comment, GetUserName(context)); + + return ServiceResult.Good; + } + + /// + /// Called when the alarm is shelved. + /// + private ServiceResult OnShelve( + ISystemContext context, + AlarmConditionState alarm, + bool shelving, + bool oneShot, + double shelvingTime) + { + alarm.SetShelvingState(context, shelving, oneShot, shelvingTime); + alarm.Message.Value = "The alarm shelved."; + + UpdateAlarm(alarm, null); + ReportChanges(alarm); + + return ServiceResult.Good; + } + + /// + /// Called when the alarm is shelved. + /// + private ServiceResult OnTimedUnshelve( + ISystemContext context, + AlarmConditionState alarm) + { + // update the alarm state and produce and event. + alarm.SetShelvingState(context, false, false, 0); + alarm.Message.Value = "The timed shelving period expired."; + + UpdateAlarm(alarm, null); + ReportChanges(alarm); + + return ServiceResult.Good; + } + + /// + /// Called when the dialog receives a response. + /// + private ServiceResult OnRespond( + ISystemContext context, + DialogConditionState dialog, + int selectedResponse) + { + // response 0 means set the source online. + if (selectedResponse == 0) + { + m_source.SetOfflineState(false); + } + + // response 1 means set the source offline. + if (selectedResponse == 1) + { + m_source.SetOfflineState(true); + } + + // other responses mean do nothing. + dialog.SetResponse(context, selectedResponse); + + // dialog no longer interesting once it is deactivated. + dialog.Message.Value = "The dialog was deactivated"; + dialog.Retain.Value = false; + + return ServiceResult.Good; + } + + /// + /// Reports the changes to the alarm. + /// + private void ReportChanges(AlarmConditionState alarm) + { + // report changes to node attributes. + alarm.ClearChangeMasks(m_nodeManager.SystemContext, true); + + // check if events are being monitored for the source. + if (AreEventsMonitored) + { + // create a snapshot. + InstanceStateSnapshot e = new(); + e.Initialize(m_nodeManager.SystemContext, alarm); + + // report the event. + alarm.ReportEvent(m_nodeManager.SystemContext, e); + } + } + + /// + /// Finds the alarm by event id. + /// + /// The event id. + /// The alarm. Null if not found. + private AlarmConditionState FindAlarmByEventId(byte[] eventId) + { + if (eventId == null) + { + return null; + } + + if (!m_events.TryGetValue(Utils.ToHexString(eventId), out AlarmConditionState alarm)) + { + return null; + } + + return alarm; + } + + /// + /// Gets the record number associated with tge alarm. + /// + /// The alarm. + /// The record number; 0 if the alarm is not an archived alarm. + private uint GetRecordNumber(AlarmConditionState alarm) + { + if (alarm == null) + { + return 0; + } + + if (alarm.BranchId == null || alarm.BranchId.Value == null) + { + return 0; + } + + uint? recordNumber = alarm.BranchId.Value.Identifier as uint?; + + if (recordNumber != null) + { + return recordNumber.Value; + } + + return 0; + } + + /// + /// Gets the user name associated with the context. + /// + private string GetUserName(ISystemContext context) + { + if (context.UserIdentity != null) + { + return context.UserIdentity.DisplayName; + } + + return null; + } + #endregion + + #region Private Fields + private readonly AlarmConditionServerNodeManager m_nodeManager; + private readonly UnderlyingSystemSource m_source; + private readonly Dictionary m_alarms; + private readonly Dictionary m_events; + private readonly Dictionary m_branches; + private readonly DialogConditionState m_dialog; + private DataGenerator m_generator; + #endregion + } +}