Example #1
0
        override protected void MonitoredItem_Notification(MonitoredItem monitoredItem, MonitoredItemNotificationEventArgs e)
        {
            try
            {
                var tEventTag = monitoredItem.Handle as TheOPCEvent;
                if (tEventTag == null)
                {
                    TheBaseAssets.MySYSLOG.WriteToLog(78102, TSM.L(eDEBUG_LEVELS.OFF) ? null : new TSM(MyOPCServer.GetBaseThing().EngineName, $"Internal error: invalid monitored item handle in Event", eMsgLevel.l1_Error, $"{DisplayName} {GetNodeIdForLogs()} {EventInfo.AggregateRetainedConditions}"));
                    return;
                }

                var tEventHost = tEventTag.GetHostThing();
                if (tEventHost == null)
                {
                    TheBaseAssets.MySYSLOG.WriteToLog(78102, TSM.L(eDEBUG_LEVELS.OFF) ? null : new TSM(MyOPCServer.GetBaseThing().EngineName, $"Event host thing not found", eMsgLevel.l1_Error, $"{DisplayName} {GetNodeIdForLogs()} {EventInfo.AggregateRetainedConditions}"));
                    return;
                }

                EventFieldList notification = e.NotificationValue as EventFieldList;

                if (notification == null)
                {
                    TheBaseAssets.MySYSLOG.WriteToLog(78102, TSM.L(eDEBUG_LEVELS.OFF) ? null : new TSM(MyOPCServer.GetBaseThing().EngineName, $"Internal error: notification is not an EventFieldList", eMsgLevel.l1_Error, $"{DisplayName} {GetNodeIdForLogs()} {EventInfo.AggregateRetainedConditions}"));
                    return;
                }

                // check the type of event.
                NodeId eventTypeId = ClientUtils.FindEventType(monitoredItem, notification);

                if (MyOPCServer.EnableOPCDataLogging)
                {
                    var logInfo = new Dictionary <string, object>
                    {
                        { "ReceiveTime", DateTimeOffset.Now },
                        { "TagId", DisplayName },
                        { "EventTypeId", eventTypeId },
                        { "Value", notification.EventFields.Aggregate("", (s, ef) => $"{s} [{ef.TypeInfo},{ef.Value}]") },
                        { "Server", notification.Message.PublishTime },
                        { "MonitoredItem", monitoredItem?.ClientHandle },
                        { "SequenceNumber", notification.Message?.SequenceNumber },
                    };
                    TheOPCTag.LogOPCData(logInfo, MyOPCServer.GetLogAddress(), $"{DisplayName} {GetNodeIdForLogs()} {EventInfo.AggregateRetainedConditions}");
                }


                // ignore unknown events.
                if (NodeId.IsNull(eventTypeId))
                {
                    TheBaseAssets.MySYSLOG.WriteToLog(78102, TSM.L(eDEBUG_LEVELS.OFF) ? null : new TSM(MyOPCServer.GetBaseThing().EngineName, $"Unknown eventTypeId", eMsgLevel.l1_Error, $"{DisplayName} {GetNodeIdForLogs()} {EventInfo.AggregateRetainedConditions}: received {eventTypeId}"));
                    return;
                }

                EventFilter filter = monitoredItem.Status.Filter as EventFilter;

                bool isRefreshEvent = false;

                var            eventData   = new Dictionary <string, object>();
                DateTimeOffset sourceTime  = DateTimeOffset.Now;
                string         conditionId = null;
                bool?          bRetain     = null;

                int index = 0;
                foreach (var field in filter.SelectClauses)
                {
                    var value = index < notification?.EventFields.Count ? notification?.EventFields[index].Value : null;
                    if (value is ExtensionObject || value is ExtensionObject[])
                    {
                        value = MyOPCServer.DecodeExtensionObjectToJson(value, out var ignored);
                    }
                    var name = field?.BrowsePath?.Count > 0 ? field.BrowsePath[0].Name : null;

                    if (value is NodeId)
                    {
                        if (name == "EventType")
                        {
                            var eventType = value as NodeId;
                            if (eventType == Opc.Ua.ObjectTypeIds.RefreshStartEventType)
                            {
                                TheBaseAssets.MySYSLOG.WriteToLog(78102, TSM.L(eDEBUG_LEVELS.ESSENTIALS) ? null : new TSM(MyOPCServer.GetBaseThing().EngineName, $"Received Refresh Start event", eMsgLevel.l4_Message, $"{DisplayName} {GetNodeIdForLogs()} {EventInfo.AggregateRetainedConditions}"));

                                _currentConditionsByConditionId.Clear();
                                if (_bRefreshing)
                                {
                                    // Two overlapping refresh starts received
                                    TheBaseAssets.MySYSLOG.WriteToLog(78102, TSM.L(eDEBUG_LEVELS.OFF) ? null : new TSM(MyOPCServer.GetBaseThing().EngineName, $"Received more than one Refresh Start event", eMsgLevel.l2_Warning, $"{DisplayName} {GetNodeIdForLogs()} {EventInfo.AggregateRetainedConditions}"));
                                }
                                _bRefreshing          = true;
                                _lastRefreshStartTime = DateTimeOffset.Now;
                                isRefreshEvent        = true;
                            }
                            else if (eventType == Opc.Ua.ObjectTypeIds.RefreshEndEventType)
                            {
                                TheBaseAssets.MySYSLOG.WriteToLog(78102, TSM.L(eDEBUG_LEVELS.ESSENTIALS) ? null : new TSM(MyOPCServer.GetBaseThing().EngineName, $"Received Refresh End event", eMsgLevel.l4_Message, $"{DisplayName} {GetNodeIdForLogs()} {EventInfo.AggregateRetainedConditions}"));
                                if (!_bRefreshing)
                                {
                                    TheBaseAssets.MySYSLOG.WriteToLog(78102, TSM.L(eDEBUG_LEVELS.OFF) ? null : new TSM(MyOPCServer.GetBaseThing().EngineName, $"Received Refresh End event without matching start event", eMsgLevel.l2_Warning, $"{DisplayName} {GetNodeIdForLogs()} {EventInfo.AggregateRetainedConditions}"));
                                    // refresh end received without refresh start
                                }
                                _bRefreshing   = false;
                                isRefreshEvent = true;
                                if (EventInfo.AggregateRetainedConditions)
                                {
                                    WriteAggregatedConditionsToProperty(tEventHost, TheCommonUtils.CDate(notification.Message.PublishTime));
                                }
                            }
                        }
                        value = value.ToString();
                    }
                    if (name == null)
                    {
                        if (field.TypeDefinitionId == Opc.Ua.ObjectTypeIds.BaseEventType)
                        {
                            name = "ConditionId";
                        }
                    }
                    if (name != null)
                    {
                        if (aliasMap.TryGetValue(index, out var alias))
                        {
                            eventData[alias] = value;
                        }
                        else
                        {
                            eventData[name] = value;
                        }
                        switch (name)
                        {
                        case "ConditionId":
                            conditionId = value?.ToString();
                            break;

                        case "Retain":
                            bRetain = TheCommonUtils.CBool(value);
                            break;

                        case "Time":
                            sourceTime = TheCommonUtils.CDate(value);
                            break;
                        }
                    }
                    index++;
                }
                if (!isRefreshEvent)
                {
                    var eventInfoProperties = EventInfo.GetPropertyNames();
                    var filteredEventProps  = EventInfo.Properties?.Count > 0 ? eventData.Where(pk => !requiredEventFields.Contains(pk.Key) || eventInfoProperties.Contains(pk.Key))
                                              //.Select(kv =>
                                              //{
                                              //    if (aliasMap.TryGetValue(kv.Key, out var alias))
                                              //    {
                                              //        return new KeyValuePair<string, object>(alias, kv.Value);
                                              //    }
                                              //    return kv;
                                              //})
                                              .ToDictionary(kv => kv.Key, kv => kv.Value) : eventData;

                    //if (_bRefreshing)
                    //{
                    //    filteredEventProps["Refresh"] = true;
                    //}

                    if (!EventInfo.AggregateRetainedConditions)
                    {
                        // Raw events
                        // TODO avoid resending events due to a refresh?
                        var eventAsJson = TheCommonUtils.SerializeObjectToJSONString(filteredEventProps);
                        TheBaseAssets.MySYSLOG.WriteToLog(78102, TSM.L(eDEBUG_LEVELS.VERBOSE) ? null : new TSM(MyOPCServer.GetBaseThing().EngineName, $"Received raw event {eventAsJson}", eMsgLevel.l6_Debug, $"{DisplayName} {GetNodeIdForLogs()} {EventInfo.AggregateRetainedConditions}"));
                        tEventHost.GetBaseThing().SetProperty(DisplayName, eventAsJson, TheCommonUtils.CDate(notification.Message.PublishTime));
                    }
                    else
                    {
                        // Aggregated Condition State
                        if (conditionId != null)
                        {
                            if (bRetain == false)
                            {
                                TheBaseAssets.MySYSLOG.WriteToLog(78102, TSM.L(eDEBUG_LEVELS.ESSENTIALS) ? null : new TSM(MyOPCServer.GetBaseThing().EngineName, $"Removed current event {conditionId}", eMsgLevel.l6_Debug, $"{DisplayName} {GetNodeIdForLogs()} {EventInfo.AggregateRetainedConditions}"));
                                _currentConditionsByConditionId.RemoveNoCare(conditionId);
                            }
                            else
                            {
                                TheBaseAssets.MySYSLOG.WriteToLog(78102, TSM.L(eDEBUG_LEVELS.ESSENTIALS) ? null : new TSM(MyOPCServer.GetBaseThing().EngineName, $"Added current event {conditionId}", eMsgLevel.l6_Debug, $"{DisplayName} {GetNodeIdForLogs()} {EventInfo.AggregateRetainedConditions}"));
                                _currentConditionsByConditionId[conditionId] = filteredEventProps;
                            }
                            if (!_bRefreshing)
                            {
                                WriteAggregatedConditionsToProperty(tEventHost, TheCommonUtils.CDate(notification.Message.PublishTime));
                            }
                        }
                    }

                    // Legacy format: do we still need to support this? Existing event support was not really usable...
                    //foreach (var eventField in eventData)
                    //{
                    //    string propertyName;
                    //    if (tEventHost is TheOPCUATagThing) // TODO Create a TheOPCUAEventThing
                    //    {
                    //        // If this is a dedicated event thing, use the original value name
                    //        // TODO Is this really what we want to do or do we also want to use the full browsepath for dedicated event things?
                    //        throw new NotImplementedException("Should never get here");
                    //        //propertyName = field.BrowsePath[0].Name;
                    //    }
                    //    else
                    //    {
                    //        // This is an external, multi-event thing: use the browsepath to avoid collisions with properties from multiple events
                    //        propertyName = DisplayName + "." + eventField.Key;
                    //    }
                    //    SetPropertyFromVariant(tEventHost, propertyName, eventField.Value, sourceTime);
                    //}
                }
            }
            catch (Exception ex)
            {
                TheBaseAssets.MySYSLOG.WriteToLog(78102, TSM.L(eDEBUG_LEVELS.OFF) ? null : new TSM(MyOPCServer.GetBaseThing().EngineName, $"Internal error processing event notification", eMsgLevel.l1_Error, $"{DisplayName} {GetNodeIdForLogs()} {EventInfo.AggregateRetainedConditions}: {ex.ToString()}"));
            }
        }
Example #2
0
        private void WriteAggregatedConditionsToProperty(ICDEThing tEventHost, DateTimeOffset time)
        {
            var currentConditionsAsJson = TheCommonUtils.SerializeObjectToJSONString(_currentConditionsByConditionId.Values.OrderByDescending(evnt =>
            {
                if (evnt.TryGetValue("Time", out var evntTime))
                {
                    return(evntTime);
                }
                return(null);
            }).ToList());

            if (TheThing.GetSafePropertyString(tEventHost.GetBaseThing(), DisplayName) != currentConditionsAsJson)
            {
                TheBaseAssets.MySYSLOG.WriteToLog(78102, TSM.L(eDEBUG_LEVELS.VERBOSE) ? null : new TSM(MyOPCServer.GetBaseThing().EngineName, $"Updated current events {_currentConditionsByConditionId.Count} - {currentConditionsAsJson}", eMsgLevel.l6_Debug, $"{DisplayName} {GetNodeIdForLogs()} {EventInfo.AggregateRetainedConditions}"));
                // Using publish time because the event timestamps could be way in the past (i.e. when a recent event just was removed)
                tEventHost.GetBaseThing().SetProperty(DisplayName, currentConditionsAsJson, time);
            }
            else
            {
                TheBaseAssets.MySYSLOG.WriteToLog(78102, TSM.L(eDEBUG_LEVELS.VERBOSE) ? null : new TSM(MyOPCServer.GetBaseThing().EngineName, $"No change to current events {_currentConditionsByConditionId.Count} - {currentConditionsAsJson}", eMsgLevel.l6_Debug, $"{DisplayName} {GetNodeIdForLogs()} {EventInfo.AggregateRetainedConditions}"));
            }
        }
Example #3
0
        MonitoringFilter GetFilter()
        {
            aliasMap.Clear();
            var eventFilter = new EventFilter();

            eventFilter.AddSelectClause(Opc.Ua.ObjectTypeIds.BaseEventType, null, Attributes.NodeId); // Requests condition id

            // Ensure all required properties are in the event filter
            var propsToRequest = new List <TheEventProperty>(EventInfo.Properties);

            foreach (var prop in requiredEventFields)
            {
                if (EventInfo.Properties.FirstOrDefault(p => p.Name == prop && string.IsNullOrEmpty(p.CustomTypeId)) == null)
                {
                    propsToRequest.Add(new TheEventProperty {
                        Name = prop
                    });
                }
            }

            // Remove ConditionId, because it is added by requesting the BaseEventType attibute 1 above
            var conditionIdProp = EventInfo.Properties.FirstOrDefault(p => p.Name == "ConditionId" && string.IsNullOrEmpty(p.CustomTypeId));

            if (conditionIdProp != null)
            {
                propsToRequest.Remove(conditionIdProp);
            }

            var alarmConditionProps = typeof(AlarmConditionState).GetProperties();

            int index = eventFilter.SelectClauses.Count;

            foreach (var eventProp in propsToRequest)
            {
                if (eventProp.Name == "ConditionId" && string.IsNullOrEmpty(eventProp.CustomTypeId))
                {
                    continue;
                }
                var    propertyType = Opc.Ua.ObjectTypeIds.BaseEventType;
                ushort nameSpaceId  = 0;

                var propDef = alarmConditionProps.FirstOrDefault(p => p.Name == eventProp.Name && string.IsNullOrEmpty(eventProp.CustomTypeId));
                if (propDef != null)
                {
                    switch (propDef.DeclaringType.Name)
                    {
                    case nameof(AlarmConditionState):
                        propertyType = Opc.Ua.ObjectTypeIds.AlarmConditionType;
                        break;

                    case nameof(AcknowledgeableConditionState):
                        propertyType = Opc.Ua.ObjectTypeIds.AcknowledgeableConditionType;
                        break;

                    case nameof(ConditionState):
                        propertyType = Opc.Ua.ObjectTypeIds.ConditionType;
                        break;

                    case nameof(BaseEventState):
                    default:
                        propertyType = Opc.Ua.ObjectTypeIds.BaseEventType;
                        break;
                    }
                }
                else
                {
                    if (!string.IsNullOrEmpty(eventProp.CustomTypeId))
                    {
                        // Custom Type: must be specified in the request
                        var exNodeId = ExpandedNodeId.Parse(eventProp.CustomTypeId);
                        propertyType = ExpandedNodeId.ToNodeId(exNodeId, MyOPCServer?.m_session?.NamespaceUris);
                        nameSpaceId  = propertyType.NamespaceIndex;
                    }
                    else
                    {
                        TheBaseAssets.MySYSLOG.WriteToLog(78122, TSM.L(eDEBUG_LEVELS.OFF) ? null : new TSM(MyOPCServer.GetBaseThing().EngineName, $"Unrecognized Event Property Name '{eventProp}' for {GetNodeIdForLogs()}. Use <nodeid>::<name> for custom types.", eMsgLevel.l2_Warning));
                    }
                }

                QualifiedName eventPropQName;
                var           indexOfNamespaceSeparator = eventProp.Name.LastIndexOf(':'); // namespace Uri typically contains ":" so must use last index instead of Split
                if (indexOfNamespaceSeparator > 0)
                {
                    var namespacePrefix = eventProp.Name.Substring(0, indexOfNamespaceSeparator);
                    var name            = eventProp.Name.Substring(indexOfNamespaceSeparator + 1);
                    if (!char.IsDigit(namespacePrefix[0]))
                    {
                        eventPropQName = QualifiedName.Create(name, namespacePrefix, MyOPCServer?.m_session?.NamespaceUris);
                    }
                    else
                    {
                        eventPropQName = QualifiedName.Parse(eventProp.Name);
                    }
                }
                else
                {
                    eventPropQName = new QualifiedName(eventProp.Name, nameSpaceId);
                }
                eventFilter.AddSelectClause(propertyType, eventPropQName);
                if (!string.IsNullOrEmpty(eventProp.Alias))
                {
                    aliasMap.Add(index, eventProp.Alias);
                }
                index++;
            }

            // Incomplete/untested, syntax is very cumbersome. Leaving in for now as a starting point should this become a high priority
            //if (EventInfo.WhereClauses != null)
            //{
            //    try
            //    {
            //        var whereClause = new ContentFilter();
            //        var filterElements = new ContentFilterElementCollection(EventInfo.WhereClauses.Select((clause) =>
            //        {
            //            FilterOperator filterOperator;
            //            try
            //            {
            //                filterOperator = (FilterOperator)Enum.Parse(typeof(FilterOperator), clause.Operator);
            //            }
            //            catch
            //            {
            //                filterOperator = (FilterOperator)(-1); // invalid operator: assume that it will be rejected by server or client stack
            //            }

            //            var operands = clause.Operands.Select<TheFilterOperand, ExtensionObject>(operand =>
            //            {
            //                if (operand.Value != null)
            //                {
            //                    return new ExtensionObject(new LiteralOperand(operand.Value));
            //                }
            //                if (operand.SimpleAttribute != null)
            //                {
            //                    return new ExtensionObject(new SimpleAttributeOperand
            //                    {
            //                        AttributeId = operand.SimpleAttribute.AttributeId ?? Opc.Ua.Attributes.Value, // TODO support symbolic names
            //                        BrowsePath = new QualifiedNameCollection(operand.SimpleAttribute.BrowsePaths.Select(p => new QualifiedName(p))), // TODO support namespace qualifiers etc.
            //                        TypeDefinitionId = ExpandedNodeId.ToNodeId(ExpandedNodeId.Parse(operand.SimpleAttribute.TypeId), nameTable),
            //                        // TODO Why is SimpleAttributeOperand.IndexRange a string? Should be a Range. Not supported for now
            //                    });
            //                }
            //                //if (operand.Attribute != null)
            //                //{
            //                //    return new ExtensionObject(new AttributeOperand
            //                //    {
            //                //        // TODO implement, not supported for now
            //                //           //AttributeId = operand.Attribute.AttributeId,
            //                //           // BrowsePath = operand.Attribute.BrowsePath,
            //                //    });
            //                //}
            //                return null;
            //            });

            //            var element = new ContentFilterElement
            //            {
            //                FilterOperator = filterOperator,
            //                FilterOperands = new ExtensionObjectCollection(operands),
            //            };
            //            return element;
            //        }));

            //        eventFilter.WhereClause = new ContentFilter { Elements = filterElements, };
            //    }
            //    catch (Exception e)
            //    {
            //        // Log errors, fail filter etc.
            //    }
            //}

            // Original approach relied on server providing proper type information for the events; not all OPC Servers do so
            //NodeId eventTypeId = this.TagRef;
            //var desc = new BrowseDescription
            //{
            //    NodeId = this.TagRef,
            //    ReferenceTypeId = Opc.Ua.ReferenceTypeIds.GeneratesEvent,
            //    ResultMask = (uint)BrowseResultMask.TargetInfo,
            //};
            //var references = ClientUtils.Browse(MyOPCServer.m_session, desc, false);
            //if (references.Count > 0)
            //{
            //    eventTypeId = (NodeId)references[0].NodeId;
            //}
            //TypeDeclaration type = new TypeDeclaration();
            //type.NodeId = eventTypeId;
            //type.Declarations = ClientUtils.CollectInstanceDeclarationsForType(MyOPCServer.m_session, type.NodeId);

            //// the filter to use.
            //var filter = new FilterDeclaration(type, null);
            //var eventFilter2 = filter.GetFilter();
            //eventFilter2.WhereClause = null;

            return(eventFilter);
        }