/// <summary> /// Values of existing CONDITION events are updated the forwarded to TheGlobal for further processing /// per subscription per server instance /// </summary> /// <param name="EventNotification"></param> /// <param name="areas"></param> /// <param name="newCond"></param> private void ProcessCondition(EventNotification EventNotification, string[] areas, OPCCondition newCond) { try { newCond.ClearChangeMask(); newCond.SetMessage(EventNotification.Message); newCond.SetSeverity(EventNotification.Severity); newCond.SetSubconditionName(EventNotification.SubConditionName); newCond.SetActive((EventNotification.NewState & OpcRcw.Ae.Constants.CONDITION_ACTIVE) != 0); // The UA server generates unique identifiers in the form of guids. how to map these to COM-style cookies? // For now, just increment a static DWORD counter from the OPCCondition constructor and assign //newCond.Cookie = EventNotification.Cookie; newCond.SetTime(EventNotification.ActiveTime); newCond.EventId = EventNotification.EventId; newCond.EventCategory = EventNotification.EventCategory; newCond.SetQuality((short)EventNotification.Quality); newCond.ActorID = EventNotification.EventType == OpcRcw.Ae.Constants.TRACKING_EVENT ? EventNotification.ActorID : ""; newCond.SetEnable((EventNotification.NewState & OpcRcw.Ae.Constants.CONDITION_ENABLED) != 0); newCond.SetIsAcked((EventNotification.NewState & OpcRcw.Ae.Constants.CONDITION_ACKED) != 0); newCond.SetAckRequired(EventNotification.AckRequired); newCond.ConditionId = EventNotification.ConditionId; newCond.AcknowledgeMethod = EventNotification.AcknowledgeMethod; OPCSubcondition subCond = new OPCSubcondition(newCond.SubconditionName, newCond.Message, "", newCond.Severity); newCond.push_back_subcondition(subCond); for (int i = 0; i < areas.Length; i++) { newCond.push_back_area(areas[i]); } // Insert standard attribute "AckComment" newCond.push_back_attrval(Global.TheGlobal.StdAttrIds[0], newCond.AckComment); // Insert standard attribute "Areas" newCond.push_back_attrval(Global.TheGlobal.StdAttrIds[1], areas); foreach (KeyValuePair <int, object> kvp in EventNotification.EventAttributes) { newCond.push_back_attrval(kvp.Key, kvp.Value); } if (newCond.IsEnabled()) { lock (Global.TheGlobal) { Global.TheGlobal.NotifyClients(EventNotification.SourceID, EventNotification.ConditionName, newCond); } } } catch (Exception e) { Utils.Trace(e, "Unexpected error in ProcessCondition"); } // Utils.Trace("ProcessCondition - END Source: {0}, condition: {1}, conditionstate: {2}", // EventNotification.SourceID, EventNotification.ConditionName, newCond.NewState); }
/// <summary> /// If the event type is CONDITION_EVENT then update any existing record of the condition and adjust /// state and change mask. /// </summary> /// <param name="EventNotification"></param> /// <param name="areas"></param> public void ProcessEventNotificationList(EventNotification EventNotification, string[] areas) { SourceMap sourceMap = SourceMap.TheSourceMap; try { lock (sourceMap) { OPCCondition cond; if (EventNotification.EventType == OpcRcw.Ae.Constants.CONDITION_EVENT) { ConditionMap conditionMap; if (sourceMap.TryGetValue(EventNotification.SourceID, out conditionMap) == false) { conditionMap = new ConditionMap(); sourceMap.Add(EventNotification.SourceID, conditionMap); } if (conditionMap.TryGetValue(EventNotification.ConditionName, out cond) == false) { cond = new OPCCondition(); cond.EventType = EventNotification.EventType; conditionMap.Add(EventNotification.ConditionName, cond); } ProcessCondition(EventNotification, areas, cond); // When the condition has transitioned to Acked (if ack required) and inactive or disabled // then remove it from the condition source/condition database if ((!cond.IsActive() || !cond.IsEnabled()) && (cond.IsAcked() || !cond.AckRequired)) { conditionMap.Remove(EventNotification.ConditionName); } } else // a tracking or simple event { cond = new OPCCondition(); cond.EventType = EventNotification.EventType; ProcessCondition(EventNotification, areas, cond); } } } catch (Exception e) { Utils.Trace(e, "Unexpected error in ProcessEventNotificationList"); } }
/// <summary> /// If the event type is CONDITION_EVENT then update any existing record of the condition and adjust /// state and change mask. /// </summary> /// <param name="EventNotification"></param> /// <param name="areas"></param> public void ProcessEventNotificationList(EventNotification EventNotification, string[] areas) { SourceMap sourceMap = SourceMap.TheSourceMap; try { lock (sourceMap) { OPCCondition cond; if (EventNotification.EventType == OpcRcw.Ae.Constants.CONDITION_EVENT) { ConditionMap conditionMap; if (sourceMap.TryGetValue(EventNotification.SourceID, out conditionMap) == false) { conditionMap = new ConditionMap(); sourceMap.Add(EventNotification.SourceID, conditionMap); } if (conditionMap.TryGetValue(EventNotification.ConditionName, out cond) == false) { cond = new OPCCondition(); cond.EventType = EventNotification.EventType; conditionMap.Add(EventNotification.ConditionName, cond); } ProcessCondition(EventNotification, areas, cond); // When the condition has transitioned to Acked (if ack required) and inactive or disabled // then remove it from the condition source/condition database if ((!cond.IsActive() || !cond.IsEnabled()) && (cond.IsAcked() || !cond.AckRequired)) conditionMap.Remove(EventNotification.ConditionName); } else // a tracking or simple event { cond = new OPCCondition(); cond.EventType = EventNotification.EventType; ProcessCondition(EventNotification, areas, cond); } } } catch (Exception e) { Utils.Trace(e, "Unexpected error in ProcessEventNotificationList"); } }
/// <summary> /// Somewhat simplistic logic to extract the containing area associated with a received event. Just parse the /// source path and assume the final token is the unqualified source while the remainder of the string is the area /// path. /// </summary> /// <param name="ev"></param> /// <returns></returns> string[] FindEventAreas(EventNotification ev) { try { QualifiedNameCollection browseNames = SimpleAttributeOperand.Parse(ev.SourceID); String areaString = "/"; if (browseNames.Count > 0) { for (int i = 0; i < browseNames.Count - 1; i++) { areaString += browseNames[i]; areaString += "/"; } } return new string[] { areaString }; } catch (Exception e) { Utils.Trace(e, "Unexpected error in FindEventAreas"); return null; } }
/// <summary> /// Additional new event processing when the received event maps to a (COM AE) condition event type. We need to extract /// the condition name, subcondition name, changeMask, newState, Quality, AckRequired, ActiveTime and cookie. /// </summary> /// <param name="monitoredItem"></param> /// <param name="eventFields"></param> /// <param name="ev"></param> /// <param name="cat"></param> void SetConditionEventFields(MonitoredItem monitoredItem, EventFieldList eventFields, EventNotification ev, EventCategory cat) { LocalizedText localText; String ConditionName; StatusCode? Status; DateTime? TimeOfLastTransition; try { NodeId eventType = monitoredItem.GetFieldValue(eventFields, Opc.Ua.ObjectTypes.BaseEventType, Opc.Ua.BrowseNames.EventType) as NodeId; // UA events are categorized into three subsets. The first of these subsets consists of types and subtypes of ConditionType // which yields the event condition name, quality and enable/disable status. if (m_session.TypeTree.IsTypeOf(eventType, Opc.Ua.ObjectTypes.ConditionType)) { ConditionName = monitoredItem.GetFieldValue(eventFields, Opc.Ua.ObjectTypes.ConditionType, Opc.Ua.BrowseNames.ConditionName) as String; if (ConditionName != null) ev.ConditionName = ConditionName; else ev.ConditionName = cat.BrowseName; // Set the subcondition name as conditionname for now. If the event of of type AlarmconditionType and a subcondition (UA substate) // exists then this field will be set accordingly. ev.SubConditionName = ev.ConditionName; bool? enabled = monitoredItem.GetFieldValue(eventFields, Opc.Ua.ObjectTypes.ConditionType, "/EnabledState/Id", Attributes.Value) as bool?; Status = monitoredItem.GetFieldValue(eventFields, Opc.Ua.ObjectTypes.ConditionType, "/Quality", Attributes.Value) as StatusCode?; ev.Quality = MapStatusToQuality(Status); if (enabled == true) ev.NewState |= OpcRcw.Ae.Constants.CONDITION_ENABLED; else ev.NewState &= ~OpcRcw.Ae.Constants.CONDITION_ENABLED; } // The second of the three UA event subsets consists of types and subtypes of AcknowledgeableconditionType. // This categorization yields events which support acknowledgement in addition to enable/disable state if (m_session.TypeTree.IsTypeOf(eventType, Opc.Ua.ObjectTypes.AcknowledgeableConditionType)) { bool? acked = monitoredItem.GetFieldValue(eventFields, Opc.Ua.ObjectTypes.AcknowledgeableConditionType, "/AckedState/Id", Attributes.Value) as bool?; // Extract the "ConditionId" (nodeId of the condition instance) ev.ConditionId = monitoredItem.GetFieldValue(eventFields, Opc.Ua.ObjectTypes.AcknowledgeableConditionType, "", Attributes.NodeId) as NodeId; ev.AcknowledgeMethod = Opc.Ua.Methods.AcknowledgeableConditionType_Acknowledge; if (acked == true) { ev.NewState |= OpcRcw.Ae.Constants.CONDITION_ACKED; ev.AckRequired = false; } else { ev.NewState &= ~OpcRcw.Ae.Constants.CONDITION_ACKED; ev.AckRequired = true; } } // the third of the three UA event subsets consists of types and subtypes of AlarmConditionType. This // categorization yields events which support the notion of Active/Inactive and also may support substates // (subconditions). if (m_session.TypeTree.IsTypeOf(eventType, Opc.Ua.ObjectTypes.AlarmConditionType)) { bool? active = monitoredItem.GetFieldValue(eventFields, Opc.Ua.ObjectTypes.AlarmConditionType, "/ActiveState/Id", Attributes.Value) as bool?; TimeOfLastTransition = monitoredItem.GetFieldValue(eventFields, Opc.Ua.ObjectTypes.AlarmConditionType, "/ActiveState/TransitionTime", Attributes.Value) as DateTime?; if (active == true) { ev.NewState |= OpcRcw.Ae.Constants.CONDITION_ACTIVE; ev.ActiveTime = TimeOfLastTransition ?? DateTime.MinValue; } // Active subconditon. localText = monitoredItem.GetFieldValue(eventFields, Opc.Ua.ObjectTypes.AlarmConditionType, "/ActiveState/EffectiveDisplayName", Attributes.Value) as LocalizedText; if (localText != null && localText.ToString() != "") ev.SubConditionName = localText.ToString(); } else // If this is not an AlarmConditionType (thus no UA active/inactive states apply) default to Active ev.NewState |= OpcRcw.Ae.Constants.CONDITION_ACTIVE; } catch (Exception e) { Utils.Trace(e, "Unexpected error in SetConditionEventFields"); } }
/// <summary> /// Processes a Publish response from the UA server. /// </summary> /// <param name="monitoredItem"></param> /// <param name="e"></param> void MonitoredItem_Notification(MonitoredItem monitoredItem, MonitoredItemNotificationEventArgs e) { try { EventFieldList eventFields = e.NotificationValue as EventFieldList; if (eventFields == null) { return; } if (monitoredItem != null) { if (monitoredItem.ClientHandle != eventFields.ClientHandle) { return; } } INode eventUA = monitoredItem.GetEventType(eventFields); EventCategory cat = FindEventCatInfo(eventUA.BrowseName.ToString()); if (cat == null) return; // The event is not of a category that we recognize. if (cat.EventType == OpcRcw.Ae.Constants.CONDITION_EVENT) { NodeId branchId = monitoredItem.GetFieldValue(eventFields, Opc.Ua.ObjectTypes.ConditionType, Opc.Ua.BrowseNames.BranchId) as NodeId; if (!NodeId.IsNull(branchId)) return; // We don't support condition branches in the COM Proxy } EventNotification ev = new EventNotification(); ev.EventId = monitoredItem.GetFieldValue(eventFields, Opc.Ua.ObjectTypes.BaseEventType, new QualifiedName(Opc.Ua.BrowseNames.EventId)) as byte[]; ev.SourceID = System.Convert.ToString(monitoredItem.GetFieldValue(eventFields, Opc.Ua.ObjectTypes.BaseEventType, new QualifiedName(Opc.Ua.BrowseNames.SourceName))); ev.Time = System.Convert.ToDateTime(monitoredItem.GetFieldValue(eventFields, Opc.Ua.ObjectTypes.BaseEventType, new QualifiedName(Opc.Ua.BrowseNames.Time))); ev.Message = System.Convert.ToString(monitoredItem.GetFieldValue(eventFields, Opc.Ua.ObjectTypes.BaseEventType, new QualifiedName(Opc.Ua.BrowseNames.Message))); ev.EventType = cat.EventType; ev.EventCategory = cat.CategoryID; ev.Severity = System.Convert.ToInt32(monitoredItem.GetFieldValue(eventFields, Opc.Ua.ObjectTypes.BaseEventType, new QualifiedName(Opc.Ua.BrowseNames.Severity))); List<EventAttribute> Attrs = GetEventAttributes(cat.CategoryID); UniqueList<string> strEventNodeIds = new UniqueList<string>(); foreach (EventAttribute attr in Attrs) if (attr.strEventNodeId != "") strEventNodeIds.AddUnique(attr.strEventNodeId); ev.EventAttributes = new Dictionary<int, object>(); foreach (EventAttribute attr in m_configFile.Attributes) { foreach (string strEventNodeId in strEventNodeIds) { if (attr.strEventNodeId == strEventNodeId) { object value = monitoredItem.GetFieldValue(eventFields, (NodeId)attr.strEventNodeId, new QualifiedName(attr.BrowseName, attr.BrowseNameNSIndex)); if (value == null) { ev.EventAttributes.Add(attr.AttributeID, ""); } else if ((value.GetType() != null) & (short)ComUtils.GetVarType(value) != 0) { ev.EventAttributes.Add(attr.AttributeID, value); } else { // any value with a UA type that does not have a corresponding COM type will be returned as a string ev.EventAttributes.Add(attr.AttributeID, value.ToString()); } } } } //Condition-Related Event properties ev.ConditionName = ""; ev.SubConditionName = ""; ev.ChangeMask = 0; ev.NewState = OpcRcw.Ae.Constants.CONDITION_ENABLED | OpcRcw.Ae.Constants.CONDITION_ACKED; ev.Quality = OpcRcw.Da.Qualities.OPC_QUALITY_GOOD; ev.AckRequired = false; ev.ActiveTime = DateTime.Now; ev.Cookie = 0; if (ev.EventType == OpcRcw.Ae.Constants.CONDITION_EVENT) SetConditionEventFields(monitoredItem, eventFields, ev, cat); //Tracking Events and for Condition-Related Events which are acknowledgment notifications if (cat.EventType == OpcRcw.Ae.Constants.TRACKING_EVENT) ev.ActorID = System.Convert.ToString(monitoredItem.GetFieldValue(eventFields, (NodeId)eventUA.NodeId, new QualifiedName(Opc.Ua.BrowseNames.ClientUserId))); IncomingEventHandler eventHandler = new IncomingEventHandler(); //extract the area associated with this event. AreaNode areaNode; string[] areas = null; if (m_notifiers.TryGetValue(monitoredItem.ClientHandle, out areaNode)) { areas = new string[] { areaNode.AreaName }; } eventHandler.ProcessEventNotificationList(ev, areas); } catch (Exception ex) { Utils.Trace(ex, "Unexpected error in MonitoredItem_Notification"); } }
/// <summary> /// Values of existing CONDITION events are updated the forwarded to TheGlobal for further processing /// per subscription per server instance /// </summary> /// <param name="EventNotification"></param> /// <param name="areas"></param> /// <param name="newCond"></param> private void ProcessCondition(EventNotification EventNotification, string[] areas, OPCCondition newCond) { try { newCond.ClearChangeMask(); newCond.SetMessage(EventNotification.Message); newCond.SetSeverity(EventNotification.Severity); newCond.SetSubconditionName(EventNotification.SubConditionName); newCond.SetActive((EventNotification.NewState & OpcRcw.Ae.Constants.CONDITION_ACTIVE) != 0); // The UA server generates unique identifiers in the form of guids. how to map these to COM-style cookies? // For now, just increment a static DWORD counter from the OPCCondition constructor and assign //newCond.Cookie = EventNotification.Cookie; newCond.SetTime(EventNotification.ActiveTime); newCond.EventId = EventNotification.EventId; newCond.EventCategory = EventNotification.EventCategory; newCond.SetQuality((short)EventNotification.Quality); newCond.ActorID = EventNotification.EventType == OpcRcw.Ae.Constants.TRACKING_EVENT ? EventNotification.ActorID : ""; newCond.SetEnable((EventNotification.NewState & OpcRcw.Ae.Constants.CONDITION_ENABLED) != 0); newCond.SetIsAcked((EventNotification.NewState & OpcRcw.Ae.Constants.CONDITION_ACKED) != 0); newCond.SetAckRequired(EventNotification.AckRequired); newCond.ConditionId = EventNotification.ConditionId; newCond.AcknowledgeMethod = EventNotification.AcknowledgeMethod; OPCSubcondition subCond = new OPCSubcondition(newCond.SubconditionName, newCond.Message, "", newCond.Severity); newCond.push_back_subcondition(subCond); for (int i = 0; i < areas.Length; i++) newCond.push_back_area(areas[i]); // Insert standard attribute "AckComment" newCond.push_back_attrval(Global.TheGlobal.StdAttrIds[0], newCond.AckComment); // Insert standard attribute "Areas" newCond.push_back_attrval(Global.TheGlobal.StdAttrIds[1], areas); foreach (KeyValuePair<int, object> kvp in EventNotification.EventAttributes) newCond.push_back_attrval(kvp.Key, kvp.Value); if (newCond.IsEnabled()) { lock (Global.TheGlobal) { Global.TheGlobal.NotifyClients(EventNotification.SourceID, EventNotification.ConditionName, newCond); } } } catch (Exception e) { Utils.Trace(e, "Unexpected error in ProcessCondition"); } // Utils.Trace("ProcessCondition - END Source: {0}, condition: {1}, conditionstate: {2}", // EventNotification.SourceID, EventNotification.ConditionName, newCond.NewState); }