/// <summary> /// Returns value of the field name containing the event type. /// </summary> public object GetFieldValue( EventFieldList eventFields, NodeId eventTypeId, QualifiedName browseName) { QualifiedNameCollection browsePath = new QualifiedNameCollection(); browsePath.Add(browseName); return GetFieldValue(eventFields, eventTypeId, browsePath, Attributes.Value); }
/// <summary> /// Collects the fields for the instance node. /// </summary> /// <param name="session">The session.</param> /// <param name="nodeId">The node id.</param> /// <param name="parentPath">The parent path.</param> /// <param name="eventFields">The event fields.</param> /// <param name="foundNodes">The table of found nodes.</param> private void CollectFields( Session session, NodeId nodeId, QualifiedNameCollection parentPath, SimpleAttributeOperandCollection eventFields, Dictionary <NodeId, QualifiedNameCollection> foundNodes) { // find all of the children of the field. BrowseDescription nodeToBrowse = new BrowseDescription(); nodeToBrowse.NodeId = nodeId; nodeToBrowse.BrowseDirection = BrowseDirection.Forward; nodeToBrowse.ReferenceTypeId = ReferenceTypeIds.Aggregates; nodeToBrowse.IncludeSubtypes = true; nodeToBrowse.NodeClassMask = (uint)(NodeClass.Object | NodeClass.Variable); nodeToBrowse.ResultMask = (uint)BrowseResultMask.All; ReferenceDescriptionCollection children = FormUtils.Browse(session, nodeToBrowse, false); if (children == null) { return; } // process the children. for (int ii = 0; ii < children.Count; ii++) { ReferenceDescription child = children[ii]; if (child.NodeId.IsAbsolute) { continue; } // construct browse path. QualifiedNameCollection browsePath = new QualifiedNameCollection(parentPath); browsePath.Add(child.BrowseName); // check if the browse path is already in the list. if (!ContainsPath(eventFields, browsePath)) { SimpleAttributeOperand field = new SimpleAttributeOperand(); field.TypeDefinitionId = ObjectTypeIds.BaseEventType; field.BrowsePath = browsePath; field.AttributeId = (child.NodeClass == NodeClass.Variable)?Attributes.Value:Attributes.NodeId; eventFields.Add(field); } // recusively find all of the children. NodeId targetId = (NodeId)child.NodeId; // need to guard against loops. if (!foundNodes.ContainsKey(targetId)) { foundNodes.Add(targetId, browsePath); CollectFields(session, (NodeId)child.NodeId, browsePath, eventFields, foundNodes); } } }
/// <summary> /// Reads an QualifiedName array from the stream. /// </summary> public QualifiedNameCollection ReadQualifiedNameArray(string fieldName) { bool isNil = false; QualifiedNameCollection values = new QualifiedNameCollection(); if (BeginField(fieldName, true, out isNil)) { PushNamespace(Namespaces.OpcUaXsd); while (MoveToElement("QualifiedName")) { values.Add(ReadQualifiedName("QualifiedName")); } // check the length. if (m_context.MaxArrayLength > 0 && m_context.MaxArrayLength < values.Count) { throw new ServiceResultException(StatusCodes.BadEncodingLimitsExceeded); } PopNamespace(); EndField(fieldName); return values; } if (isNil) { return null; } return values; }
/// <summary> /// Parses a browse path. /// </summary> public static QualifiedNameCollection Parse(string browsePath) { QualifiedNameCollection browseNames = new QualifiedNameCollection(); if (String.IsNullOrEmpty(browsePath)) { return browseNames; } StringBuilder buffer = new StringBuilder(); bool escaped = false; for (int ii = 0; ii < browsePath.Length; ii++) { char ch = browsePath[ii]; if (escaped) { buffer.Append(ch); escaped = false; continue; } if (ch == '&') { escaped = true; continue; } if (ch == '/') { if (buffer.Length > 0) { QualifiedName browseName = QualifiedName.Parse(buffer.ToString()); browseNames.Add(browseName); } buffer.Length = 0; continue; } buffer.Append(ch); } if (buffer.Length > 0) { QualifiedName browseName = QualifiedName.Parse(buffer.ToString()); browseNames.Add(browseName); } return browseNames; }
/// <summary> /// Creates an operand that references a component/property of a type. /// </summary> public SimpleAttributeOperand( NodeId typeId, QualifiedName browsePath) { m_typeDefinitionId = typeId; m_browsePath = new QualifiedNameCollection(); m_attributeId = Attributes.Value; m_indexRange = null; m_browsePath.Add(browsePath); }
/// <summary> /// Collects the fields for the instance node. /// </summary> /// <param name="session">The session.</param> /// <param name="nodeId">The node id.</param> /// <param name="parentPath">The parent path.</param> /// <param name="fields">The event fields.</param> /// <param name="fieldNodeIds">The node id for the declaration of the field.</param> /// <param name="foundNodes">The table of found nodes.</param> private static void CollectFields( Session session, NodeId nodeId, QualifiedNameCollection parentPath, SimpleAttributeOperandCollection fields, List<NodeId> fieldNodeIds, Dictionary<NodeId, QualifiedNameCollection> foundNodes) { // find all of the children of the field. BrowseDescription nodeToBrowse = new BrowseDescription(); nodeToBrowse.NodeId = nodeId; nodeToBrowse.BrowseDirection = BrowseDirection.Forward; nodeToBrowse.ReferenceTypeId = ReferenceTypeIds.Aggregates; nodeToBrowse.IncludeSubtypes = true; nodeToBrowse.NodeClassMask = (uint)(NodeClass.Object | NodeClass.Variable); nodeToBrowse.ResultMask = (uint)BrowseResultMask.All; ReferenceDescriptionCollection children = ClientUtils.Browse(session, nodeToBrowse, false); if (children == null) { return; } // process the children. for (int ii = 0; ii < children.Count; ii++) { ReferenceDescription child = children[ii]; if (child.NodeId.IsAbsolute) { continue; } // construct browse path. QualifiedNameCollection browsePath = new QualifiedNameCollection(parentPath); browsePath.Add(child.BrowseName); // check if the browse path is already in the list. int index = ContainsPath(fields, browsePath); if (index < 0) { SimpleAttributeOperand field = new SimpleAttributeOperand(); field.TypeDefinitionId = ObjectTypeIds.BaseEventType; field.BrowsePath = browsePath; field.AttributeId = (child.NodeClass == NodeClass.Variable) ? Attributes.Value : Attributes.NodeId; fields.Add(field); fieldNodeIds.Add((NodeId)child.NodeId); } // recusively find all of the children. NodeId targetId = (NodeId)child.NodeId; // need to guard against loops. if (!foundNodes.ContainsKey(targetId)) { foundNodes.Add(targetId, browsePath); CollectFields(session, (NodeId)child.NodeId, browsePath, fields, fieldNodeIds, foundNodes); } } }
/// <summary> /// Reads an QualifiedName array from the stream. /// </summary> public QualifiedNameCollection ReadQualifiedNameArray(string fieldName) { int length = ReadArrayLength(); if (length == -1) { return null; } QualifiedNameCollection values = new QualifiedNameCollection(length); for (int ii = 0; ii < length; ii++) { values.Add(ReadQualifiedName(null)); } return values; }
public void OneItemInCollectionReturnsQualifiedNameInToString() { qualifiedNames.Add(new QualifiedName("root", "ns", "a")); Assert.AreEqual("a:root [ns]", qualifiedNames.ToString()); }
/// <summary> /// Reads an QualifiedName array from the stream. /// </summary> public QualifiedNameCollection ReadQualifiedNameArray(string fieldName) { var values = new QualifiedNameCollection(); List<object> token = null; if (!ReadArrayField(fieldName, out token)) { return values; } for (int ii = 0; ii < token.Count; ii++) { try { m_stack.Push(token[ii]); var element = ReadQualifiedName(null); values.Add(element); } finally { m_stack.Pop(); } } return values; }
public void AddElement(QualifiedName elementName) { _elements.Add(elementName); }
public void EqualsReturnsFalseWhenQualifiedNamesAreDifferent() { lhs.Add(new QualifiedName("name1", "ns1", "prefix1")); rhs.Add(new QualifiedName("name2", "ns2", "prefix2")); Assert.IsFalse(lhs.Equals(rhs)); }
public void EqualReturnsFalseWhenOneCollectionHasAnItemAndTheOtherDoesNot() { lhs.Add(new QualifiedName("root", "ns", "a")); Assert.IsFalse(lhs.Equals(rhs)); }
/// <summary> /// Monitoring for an event source starts if it is required. /// </summary> public async Task MonitorEventsAsync(CancellationToken ct) { bool sessionLocked = false; try { try { sessionLocked = await LockSessionAsync().ConfigureAwait(false); // if the session is not connected or shutdown in progress, return if (!sessionLocked || ct.IsCancellationRequested || State != SessionState.Connected) { return; } } catch (Exception) { throw; } // ensure all nodes in all subscriptions of this session are monitored. foreach (var opcEventSubscription in OpcEventSubscriptions) { // create the subscription, if it is not yet there. if (opcEventSubscription.OpcUaClientSubscription == null) { opcEventSubscription.OpcUaClientSubscription = CreateSubscription(opcEventSubscription.RequestedPublishingInterval, out int revisedPublishingInterval); opcEventSubscription.PublishingInterval = revisedPublishingInterval; Logger.Information($"Create Event subscription on endpoint '{EndpointUrl}' requested OPC publishing interval is {opcEventSubscription.RequestedPublishingInterval} ms. (revised: {revisedPublishingInterval} ms)"); } // process all unmonitored events. var unmonitoredEvents = opcEventSubscription.OpcMonitoredItems.Where(i => (i.State == OpcMonitoredItemState.Unmonitored || i.State == OpcMonitoredItemState.UnmonitoredNamespaceUpdateRequested)); int additionalMonitoredEventsCount = 0; int monitoredEventsCount = 0; bool haveUnmonitoredEvents = false; if (unmonitoredEvents.Count() != 0) { haveUnmonitoredEvents = true; monitoredEventsCount = opcEventSubscription.OpcMonitoredItems.Count(i => (i.State == OpcMonitoredItemState.Monitored)); Logger.Information($"Start monitoring events on endpoint '{EndpointUrl}'. Currently monitoring {monitoredEventsCount} events."); } // init perf data Stopwatch stopWatch = new Stopwatch(); stopWatch.Start(); foreach (var unmonitoredEvent in unmonitoredEvents) { // if the session is not connected or a shutdown is in progress, we stop trying and wait for the next cycle if (ct.IsCancellationRequested || State != SessionState.Connected) { break; } NodeId currentNodeId = null; try { // update the namespace of the node if requested. there are two cases where this is requested: // 1) publishing requests via the OPC server method are raised using a NodeId format. for those // the NodeId format is converted into an ExpandedNodeId format // 2) ExpandedNodeId configuration file entries do not have at parsing time a session to get // the namespace index. this is set now. if (unmonitoredEvent.State == OpcMonitoredItemState.UnmonitoredNamespaceUpdateRequested) { if (unmonitoredEvent.ConfigType == OpcMonitoredItemConfigurationType.ExpandedNodeId) { ExpandedNodeId expandedNodeId = ExpandedNodeId.Parse(unmonitoredEvent.Id); int namespaceIndex = _namespaceTable.GetIndex(expandedNodeId.NamespaceUri); if (namespaceIndex < 0) { Logger.Information($"The namespace URI of node '{expandedNodeId.ToString()}' can be not mapped to a namespace index."); } else { unmonitoredEvent.IdAsExpandedNodeId = expandedNodeId; } } if (unmonitoredEvent.ConfigType == OpcMonitoredItemConfigurationType.NodeId) { NodeId nodeId = NodeId.Parse(unmonitoredEvent.Id); string namespaceUri = _namespaceTable.ToArray().ElementAtOrDefault(nodeId.NamespaceIndex); if (string.IsNullOrEmpty(namespaceUri)) { Logger.Information($"The namespace index of node '{nodeId.ToString()}' is invalid and the node format can not be updated."); } else { unmonitoredEvent.IdAsExpandedNodeId = new ExpandedNodeId(nodeId.Identifier, nodeId.NamespaceIndex, namespaceUri, 0); unmonitoredEvent.ConfigType = OpcMonitoredItemConfigurationType.ExpandedNodeId; } } unmonitoredEvent.State = OpcMonitoredItemState.Unmonitored; } // lookup namespace index if ExpandedNodeId format has been used and build NodeId identifier. if (unmonitoredEvent.ConfigType == OpcMonitoredItemConfigurationType.ExpandedNodeId) { ExpandedNodeId expandedNodeId = ExpandedNodeId.Parse(unmonitoredEvent.Id); int namespaceIndex = _namespaceTable.GetIndex(expandedNodeId.NamespaceUri); if (namespaceIndex < 0) { Logger.Warning($"Syntax or namespace URI of ExpandedNodeId '{expandedNodeId.ToString()}' is invalid and will be ignored."); continue; } unmonitoredEvent.IdAsNodeId = new NodeId(expandedNodeId.Identifier, expandedNodeId.NamespaceIndex); currentNodeId = unmonitoredEvent.IdAsNodeId; } else { NodeId nodeId = NodeId.Parse(unmonitoredEvent.Id); string namespaceUri = _namespaceTable.ToArray().ElementAtOrDefault(nodeId.NamespaceIndex); if (string.IsNullOrEmpty(namespaceUri)) { Logger.Information($"The namespace index of node '{nodeId.ToString()}' is invalid and the node format can not be updated."); } else { unmonitoredEvent.IdAsExpandedNodeId = new ExpandedNodeId(nodeId.Identifier, nodeId.NamespaceIndex, namespaceUri, 0); currentNodeId = nodeId; } } // if configured, get the key for the node, otherwise use the nodeId Node node; if (string.IsNullOrEmpty(unmonitoredEvent.Key)) { if (FetchOpcNodeDisplayName == true) { node = OpcUaClientSession.ReadNode(currentNodeId); unmonitoredEvent.Key = node.DisplayName.Text ?? currentNodeId.ToString(); } else { unmonitoredEvent.Key = currentNodeId.ToString(); } } // resolve all node and namespace references in the select and where clauses EventFilter eventFilter = new EventFilter(); foreach (var selectClause in unmonitoredEvent.EventConfiguration.SelectClauses) { SimpleAttributeOperand simpleAttributeOperand = new SimpleAttributeOperand(); simpleAttributeOperand.AttributeId = selectClause.AttributeId.ResolveAttributeId(); simpleAttributeOperand.IndexRange = selectClause.IndexRange; NodeId typeId = selectClause.TypeId.ToNodeId(_namespaceTable); simpleAttributeOperand.TypeDefinitionId = new NodeId(typeId); QualifiedNameCollection browsePaths = new QualifiedNameCollection(); foreach (var browsePath in selectClause.BrowsePaths) { browsePaths.Add(QualifiedName.Parse(browsePath)); } simpleAttributeOperand.BrowsePath = browsePaths; eventFilter.SelectClauses.Add(simpleAttributeOperand); } foreach (var whereClauseElement in unmonitoredEvent.EventConfiguration.WhereClause) { ContentFilterElement contentFilterElement = new ContentFilterElement(); contentFilterElement.FilterOperator = whereClauseElement.Operator.ResolveFilterOperator(); switch (contentFilterElement.FilterOperator) { case FilterOperator.OfType: case FilterOperator.InView: if (whereClauseElement.Operands.Count != 1) { Logger.Error($"The where clause element '{whereClauseElement.ToString()}' must contain 1 operands."); continue; } FilterOperand[] filterOperands = new FilterOperand[1]; TypeInfo typeInfo = new TypeInfo(BuiltInType.NodeId, ValueRanks.Scalar); filterOperands[0] = whereClauseElement.Operands[0].GetOperand(typeInfo); eventFilter.WhereClause.Push(contentFilterElement.FilterOperator, filterOperands); break; case FilterOperator.Equals: case FilterOperator.IsNull: case FilterOperator.GreaterThan: case FilterOperator.LessThan: case FilterOperator.GreaterThanOrEqual: case FilterOperator.LessThanOrEqual: case FilterOperator.Like: case FilterOperator.Not: case FilterOperator.Between: case FilterOperator.InList: case FilterOperator.And: case FilterOperator.Or: case FilterOperator.Cast: case FilterOperator.BitwiseAnd: case FilterOperator.BitwiseOr: case FilterOperator.RelatedTo: default: Logger.Error($"The operator '{contentFilterElement.FilterOperator.ToString()}' is not supported."); break; } } // add the new monitored event. IOpcUaMonitoredItem monitoredItem = new OpcUaMonitoredItem() { StartNodeId = currentNodeId, AttributeId = Attributes.EventNotifier, DisplayName = unmonitoredEvent.Key, MonitoringMode = unmonitoredEvent.MonitoringMode, SamplingInterval = 0, QueueSize = unmonitoredEvent.QueueSize, DiscardOldest = unmonitoredEvent.DiscardOldest, Filter = eventFilter }; monitoredItem.Notification += unmonitoredEvent.NotificationEventHandler; opcEventSubscription.OpcUaClientSubscription.AddItem(monitoredItem); unmonitoredEvent.OpcUaClientMonitoredItem = monitoredItem; unmonitoredEvent.State = OpcMonitoredItemState.Monitored; unmonitoredEvent.EndpointUrl = EndpointUrl; Logger.Verbose($"Created monitored event for node '{currentNodeId.ToString()}' in subscription with id '{opcEventSubscription.OpcUaClientSubscription.Id}' on endpoint '{EndpointUrl}' (version: {NodeConfigVersion:X8})"); if (unmonitoredEvent.RequestedSamplingInterval != monitoredItem.SamplingInterval) { Logger.Information($"Sampling interval: requested: {unmonitoredEvent.RequestedSamplingInterval}; revised: {monitoredItem.SamplingInterval}"); unmonitoredEvent.SamplingInterval = monitoredItem.SamplingInterval; } if (additionalMonitoredEventsCount % 10000 == 0) { Logger.Information($"Now monitoring {monitoredEventsCount + additionalMonitoredEventsCount} events in subscription with id '{opcEventSubscription.OpcUaClientSubscription.Id}'"); } } catch (Exception e) when(e.GetType() == typeof(ServiceResultException)) { ServiceResultException sre = (ServiceResultException)e; switch ((uint)sre.Result.StatusCode) { case StatusCodes.BadSessionIdInvalid: { Logger.Information($"Session with Id {OpcUaClientSession.SessionId} is no longer available on endpoint '{EndpointUrl}'. Cleaning up."); // clean up the session InternalDisconnect(); break; } case StatusCodes.BadNodeIdInvalid: case StatusCodes.BadNodeIdUnknown: { Logger.Error($"Failed to monitor node '{currentNodeId}' on endpoint '{EndpointUrl}'."); Logger.Error($"OPC UA ServiceResultException is '{sre.Result}'. Please check your publisher configuration for this node."); break; } default: { Logger.Error($"Unhandled OPC UA ServiceResultException '{sre.Result}' when monitoring node '{currentNodeId}' on endpoint '{EndpointUrl}'. Continue."); break; } } } catch (Exception e) { Logger.Error(e, $"Failed to monitor node '{currentNodeId}' on endpoint '{EndpointUrl}'"); } } opcEventSubscription.OpcUaClientSubscription.SetPublishingMode(true); opcEventSubscription.OpcUaClientSubscription.ApplyChanges(); stopWatch.Stop(); if (haveUnmonitoredEvents == true) { monitoredEventsCount = opcEventSubscription.OpcMonitoredItems.Count(i => (i.State == OpcMonitoredItemState.Monitored)); Logger.Information($"Done processing unmonitored events on endpoint '{EndpointUrl}' took {stopWatch.ElapsedMilliseconds} msec. Now monitoring {monitoredEventsCount} events in subscription with id '{opcEventSubscription.OpcUaClientSubscription.Id}'."); } } } catch (Exception e) { Logger.Error(e, "Exception"); } finally { if (sessionLocked) { ReleaseSession(); } } }