using System; using System.Collections.Generic; using System.Drawing; using System.Linq; using System.Text; using System.Threading.Tasks; using Opc.Ua; using Opc.Ua.Client; namespace OpcUaHelper { /// /// Defines numerous re-useable utility functions. /// public partial class ClientUtils { /// /// Handles an exception. /// public static void HandleException(string caption, Exception e) { //ExceptionDlg.Show(caption, e); } /// /// Returns the application icon. /// public static System.Drawing.Icon GetAppIcon() { try { return new Icon("App.ico"); } catch (Exception) { return null; } } #region DisplayText Lookup /// /// Gets the display text for the access level attribute. /// /// The access level. /// The access level formatted as a string. public static string GetAccessLevelDisplayText(byte accessLevel) { StringBuilder buffer = new StringBuilder(); if (accessLevel == AccessLevels.None) { buffer.Append("None"); } if ((accessLevel & AccessLevels.CurrentRead) == AccessLevels.CurrentRead) { buffer.Append("Read"); } if ((accessLevel & AccessLevels.CurrentWrite) == AccessLevels.CurrentWrite) { if (buffer.Length > 0) { buffer.Append(" | "); } buffer.Append("Write"); } if ((accessLevel & AccessLevels.HistoryRead) == AccessLevels.HistoryRead) { if (buffer.Length > 0) { buffer.Append(" | "); } buffer.Append("HistoryRead"); } if ((accessLevel & AccessLevels.HistoryWrite) == AccessLevels.HistoryWrite) { if (buffer.Length > 0) { buffer.Append(" | "); } buffer.Append("HistoryWrite"); } if ((accessLevel & AccessLevels.SemanticChange) == AccessLevels.SemanticChange) { if (buffer.Length > 0) { buffer.Append(" | "); } buffer.Append("SemanticChange"); } return buffer.ToString(); } /// /// Gets the display text for the event notifier attribute. /// /// The event notifier. /// The event notifier formatted as a string. public static string GetEventNotifierDisplayText(byte eventNotifier) { StringBuilder buffer = new StringBuilder(); if (eventNotifier == EventNotifiers.None) { buffer.Append("None"); } if ((eventNotifier & EventNotifiers.SubscribeToEvents) == EventNotifiers.SubscribeToEvents) { buffer.Append("Subscribe"); } if ((eventNotifier & EventNotifiers.HistoryRead) == EventNotifiers.HistoryRead) { if (buffer.Length > 0) { buffer.Append(" | "); } buffer.Append("HistoryRead"); } if ((eventNotifier & EventNotifiers.HistoryWrite) == EventNotifiers.HistoryWrite) { if (buffer.Length > 0) { buffer.Append(" | "); } buffer.Append("HistoryWrite"); } return buffer.ToString(); } /// /// Gets the display text for the value rank attribute. /// /// The value rank. /// The value rank formatted as a string. public static string GetValueRankDisplayText(int valueRank) { switch (valueRank) { case ValueRanks.Any: return "Any"; case ValueRanks.Scalar: return "Scalar"; case ValueRanks.ScalarOrOneDimension: return "ScalarOrOneDimension"; case ValueRanks.OneOrMoreDimensions: return "OneOrMoreDimensions"; case ValueRanks.OneDimension: return "OneDimension"; case ValueRanks.TwoDimensions: return "TwoDimensions"; } return valueRank.ToString(); } /// /// Gets the display text for the specified attribute. /// /// The currently active session. /// The id of the attribute. /// The value of the attribute. /// The attribute formatted as a string. public static string GetAttributeDisplayText(Session session, uint attributeId, Variant value) { if (value == Variant.Null) { return String.Empty; } switch (attributeId) { case Attributes.AccessLevel: case Attributes.UserAccessLevel: { byte? field = value.Value as byte?; if (field != null) { return GetAccessLevelDisplayText(field.Value); } break; } case Attributes.EventNotifier: { byte? field = value.Value as byte?; if (field != null) { return GetEventNotifierDisplayText(field.Value); } break; } case Attributes.DataType: { return session.NodeCache.GetDisplayText(value.Value as NodeId); } case Attributes.ValueRank: { int? field = value.Value as int?; if (field != null) { return GetValueRankDisplayText(field.Value); } break; } case Attributes.NodeClass: { int? field = value.Value as int?; if (field != null) { return ((NodeClass)field.Value).ToString(); } break; } case Attributes.NodeId: { NodeId field = value.Value as NodeId; if (!NodeId.IsNull(field)) { return field.ToString(); } return "Null"; } } // check for byte strings. if (value.Value is byte[]) { return Utils.ToHexString(value.Value as byte[]); } // use default format. return value.ToString(); } #endregion #region Browse /// /// Browses the address space and returns the references found. /// /// The session. /// The set of browse operations to perform. /// if set to true a exception will be thrown on an error. /// /// The references found. Null if an error occurred. /// public static ReferenceDescriptionCollection Browse(Session session, BrowseDescriptionCollection nodesToBrowse, bool throwOnError) { return Browse(session, null, nodesToBrowse, throwOnError); } /// /// Browses the address space and returns the references found. /// public static ReferenceDescriptionCollection Browse(Session session, ViewDescription view, BrowseDescriptionCollection nodesToBrowse, bool throwOnError) { try { ReferenceDescriptionCollection references = new ReferenceDescriptionCollection(); BrowseDescriptionCollection unprocessedOperations = new BrowseDescriptionCollection(); while (nodesToBrowse.Count > 0) { // start the browse operation. BrowseResultCollection results = null; DiagnosticInfoCollection diagnosticInfos = null; session.Browse( null, view, 0, nodesToBrowse, out results, out diagnosticInfos); ClientBase.ValidateResponse(results, nodesToBrowse); ClientBase.ValidateDiagnosticInfos(diagnosticInfos, nodesToBrowse); ByteStringCollection continuationPoints = new ByteStringCollection(); for (int ii = 0; ii < nodesToBrowse.Count; ii++) { // check for error. if (StatusCode.IsBad(results[ii].StatusCode)) { // this error indicates that the server does not have enough simultaneously active // continuation points. This request will need to be resent after the other operations // have been completed and their continuation points released. if (results[ii].StatusCode == StatusCodes.BadNoContinuationPoints) { unprocessedOperations.Add(nodesToBrowse[ii]); } continue; } // check if all references have been fetched. if (results[ii].References.Count == 0) { continue; } // save results. references.AddRange(results[ii].References); // check for continuation point. if (results[ii].ContinuationPoint != null) { continuationPoints.Add(results[ii].ContinuationPoint); } } // process continuation points. ByteStringCollection revisedContiuationPoints = new ByteStringCollection(); while (continuationPoints.Count > 0) { // continue browse operation. session.BrowseNext( null, false, continuationPoints, out results, out diagnosticInfos); ClientBase.ValidateResponse(results, continuationPoints); ClientBase.ValidateDiagnosticInfos(diagnosticInfos, continuationPoints); for (int ii = 0; ii < continuationPoints.Count; ii++) { // check for error. if (StatusCode.IsBad(results[ii].StatusCode)) { continue; } // check if all references have been fetched. if (results[ii].References.Count == 0) { continue; } // save results. references.AddRange(results[ii].References); // check for continuation point. if (results[ii].ContinuationPoint != null) { revisedContiuationPoints.Add(results[ii].ContinuationPoint); } } // check if browsing must continue; revisedContiuationPoints = continuationPoints; } // check if unprocessed results exist. nodesToBrowse = unprocessedOperations; } // return complete list. return references; } catch (Exception exception) { if (throwOnError) { throw new ServiceResultException(exception, StatusCodes.BadUnexpectedError); } return null; } } /// /// Browses the address space and returns the references found. /// /// The session. /// The NodeId for the starting node. /// if set to true a exception will be thrown on an error. /// /// The references found. Null if an error occurred. /// public static ReferenceDescriptionCollection Browse(Session session, BrowseDescription nodeToBrowse, bool throwOnError) { return Browse(session, null, nodeToBrowse, throwOnError); } /// /// Browses the address space and returns the references found. /// public static ReferenceDescriptionCollection Browse(Session session, ViewDescription view, BrowseDescription nodeToBrowse, bool throwOnError) { try { ReferenceDescriptionCollection references = new ReferenceDescriptionCollection(); // construct browse request. BrowseDescriptionCollection nodesToBrowse = new BrowseDescriptionCollection(); nodesToBrowse.Add(nodeToBrowse); // start the browse operation. BrowseResultCollection results = null; DiagnosticInfoCollection diagnosticInfos = null; session.Browse( null, view, 0, nodesToBrowse, out results, out diagnosticInfos); ClientBase.ValidateResponse(results, nodesToBrowse); ClientBase.ValidateDiagnosticInfos(diagnosticInfos, nodesToBrowse); do { // check for error. if (StatusCode.IsBad(results[0].StatusCode)) { throw new ServiceResultException(results[0].StatusCode); } // process results. for (int ii = 0; ii < results[0].References.Count; ii++) { references.Add(results[0].References[ii]); } // check if all references have been fetched. if (results[0].References.Count == 0 || results[0].ContinuationPoint == null) { break; } // continue browse operation. ByteStringCollection continuationPoints = new ByteStringCollection(); continuationPoints.Add(results[0].ContinuationPoint); session.BrowseNext( null, false, continuationPoints, out results, out diagnosticInfos); ClientBase.ValidateResponse(results, continuationPoints); ClientBase.ValidateDiagnosticInfos(diagnosticInfos, continuationPoints); } while (true); //return complete list. return references; } catch (Exception exception) { if (throwOnError) { throw new ServiceResultException(exception, StatusCodes.BadUnexpectedError); } return null; } } /// /// Browses the address space and returns all of the supertypes of the specified type node. /// /// The session. /// The NodeId for a type node in the address space. /// if set to true a exception will be thrown on an error. /// /// The references found. Null if an error occurred. /// public static ReferenceDescriptionCollection BrowseSuperTypes(Session session, NodeId typeId, bool throwOnError) { ReferenceDescriptionCollection supertypes = new ReferenceDescriptionCollection(); try { // find all of the children of the field. BrowseDescription nodeToBrowse = new BrowseDescription(); nodeToBrowse.NodeId = typeId; nodeToBrowse.BrowseDirection = BrowseDirection.Inverse; nodeToBrowse.ReferenceTypeId = ReferenceTypeIds.HasSubtype; nodeToBrowse.IncludeSubtypes = false; // more efficient to use IncludeSubtypes=False when possible. nodeToBrowse.NodeClassMask = 0; // the HasSubtype reference already restricts the targets to Types. nodeToBrowse.ResultMask = (uint)BrowseResultMask.All; ReferenceDescriptionCollection references = Browse(session, nodeToBrowse, throwOnError); while (references != null && references.Count > 0) { // should never be more than one supertype. supertypes.Add(references[0]); // only follow references within this server. if (references[0].NodeId.IsAbsolute) { break; } // get the references for the next level up. nodeToBrowse.NodeId = (NodeId)references[0].NodeId; references = Browse(session, nodeToBrowse, throwOnError); } // return complete list. return supertypes; } catch (Exception exception) { if (throwOnError) { throw new ServiceResultException(exception, StatusCodes.BadUnexpectedError); } return null; } } /// /// Returns the node ids for a set of relative paths. /// /// An open session with the server to use. /// The starting node for the relative paths. /// The namespace URIs referenced by the relative paths. /// The relative paths. /// A collection of local nodes. public static List TranslateBrowsePaths( Session session, NodeId startNodeId, NamespaceTable namespacesUris, params string[] relativePaths) { // build the list of browse paths to follow by parsing the relative paths. BrowsePathCollection browsePaths = new BrowsePathCollection(); if (relativePaths != null) { for (int ii = 0; ii < relativePaths.Length; ii++) { BrowsePath browsePath = new BrowsePath(); // The relative paths used indexes in the namespacesUris table. These must be // converted to indexes used by the server. An error occurs if the relative path // refers to a namespaceUri that the server does not recognize. // The relative paths may refer to ReferenceType by their BrowseName. The TypeTree object // allows the parser to look up the server's NodeId for the ReferenceType. browsePath.RelativePath = RelativePath.Parse( relativePaths[ii], session.TypeTree, namespacesUris, session.NamespaceUris); browsePath.StartingNode = startNodeId; browsePaths.Add(browsePath); } } // make the call to the server. BrowsePathResultCollection results; DiagnosticInfoCollection diagnosticInfos; ResponseHeader responseHeader = session.TranslateBrowsePathsToNodeIds( null, browsePaths, out results, out diagnosticInfos); // ensure that the server returned valid results. Session.ValidateResponse(results, browsePaths); Session.ValidateDiagnosticInfos(diagnosticInfos, browsePaths); // collect the list of node ids found. List nodes = new List(); for (int ii = 0; ii < results.Count; ii++) { // check if the start node actually exists. if (StatusCode.IsBad(results[ii].StatusCode)) { nodes.Add(null); continue; } // an empty list is returned if no node was found. if (results[ii].Targets.Count == 0) { nodes.Add(null); continue; } // Multiple matches are possible, however, the node that matches the type model is the // one we are interested in here. The rest can be ignored. BrowsePathTarget target = results[ii].Targets[0]; if (target.RemainingPathIndex != UInt32.MaxValue) { nodes.Add(null); continue; } // The targetId is an ExpandedNodeId because it could be node in another server. // The ToNodeId function is used to convert a local NodeId stored in a ExpandedNodeId to a NodeId. nodes.Add(ExpandedNodeId.ToNodeId(target.TargetId, session.NamespaceUris)); } // return whatever was found. return nodes; } #endregion #region Events /// /// Finds the type of the event for the notification. /// /// The monitored item. /// The notification. /// The NodeId of the EventType. public static NodeId FindEventType(MonitoredItem monitoredItem, EventFieldList notification) { EventFilter filter = monitoredItem.Status.Filter as EventFilter; if (filter != null) { for (int ii = 0; ii < filter.SelectClauses.Count; ii++) { SimpleAttributeOperand clause = filter.SelectClauses[ii]; if (clause.BrowsePath.Count == 1 && clause.BrowsePath[0] == BrowseNames.EventType) { return notification.EventFields[ii].Value as NodeId; } } } return null; } /// /// Constructs an event object from a notification. /// /// The session. /// The monitored item that produced the notification. /// The notification. /// The known event types. /// Mapping between event types and known event types. /// /// The event object. Null if the notification is not a valid event type. /// public static BaseEventState ConstructEvent( Session session, MonitoredItem monitoredItem, EventFieldList notification, Dictionary knownEventTypes, Dictionary eventTypeMappings) { // find the event type. NodeId eventTypeId = FindEventType(monitoredItem, notification); if (eventTypeId == null) { return null; } // look up the known event type. Type knownType = null; NodeId knownTypeId = null; if (eventTypeMappings.TryGetValue(eventTypeId, out knownTypeId)) { knownType = knownEventTypes[knownTypeId]; } // try again. if (knownType == null) { if (knownEventTypes.TryGetValue(eventTypeId, out knownType)) { knownTypeId = eventTypeId; eventTypeMappings.Add(eventTypeId, eventTypeId); } } // try mapping it to a known type. if (knownType == null) { // browse for the supertypes of the event type. ReferenceDescriptionCollection supertypes = ClientUtils.BrowseSuperTypes(session, eventTypeId, false); // can't do anything with unknown types. if (supertypes == null) { return null; } // find the first supertype that matches a known event type. for (int ii = 0; ii < supertypes.Count; ii++) { NodeId superTypeId = (NodeId)supertypes[ii].NodeId; if (knownEventTypes.TryGetValue(superTypeId, out knownType)) { knownTypeId = superTypeId; eventTypeMappings.Add(eventTypeId, superTypeId); } if (knownTypeId != null) { break; } } // can't do anything with unknown types. if (knownTypeId == null) { return null; } } // construct the event based on the known event type. BaseEventState e = (BaseEventState)Activator.CreateInstance(knownType, new object[] { (NodeState)null }); // get the filter which defines the contents of the notification. EventFilter filter = monitoredItem.Status.Filter as EventFilter; // initialize the event with the values in the notification. e.Update(session.SystemContext, filter.SelectClauses, notification); // save the orginal notification. e.Handle = notification; return e; } #endregion #region Type Model Browsing /// /// Collects the instance declarations for a type. /// public static List CollectInstanceDeclarationsForType(Session session, NodeId typeId) { return CollectInstanceDeclarationsForType(session, typeId, true); } /// /// Collects the instance declarations for a type. /// public static List CollectInstanceDeclarationsForType(Session session, NodeId typeId, bool includeSupertypes) { // process the types starting from the top of the tree. List instances = new List(); Dictionary map = new Dictionary(); // get the supertypes. if (includeSupertypes) { ReferenceDescriptionCollection supertypes = ClientUtils.BrowseSuperTypes(session, typeId, false); if (supertypes != null) { for (int ii = supertypes.Count - 1; ii >= 0; ii--) { CollectInstanceDeclarations(session, (NodeId)supertypes[ii].NodeId, null, instances, map); } } } // collect the fields for the selected type. CollectInstanceDeclarations(session, typeId, null, instances, map); // return the complete list. return instances; } /// /// Collects the fields for the instance node. /// private static void CollectInstanceDeclarations( Session session, NodeId typeId, InstanceDeclaration parent, List instances, IDictionary map) { // find the children. BrowseDescription nodeToBrowse = new BrowseDescription(); if (parent == null) { nodeToBrowse.NodeId = typeId; } else { nodeToBrowse.NodeId = parent.NodeId; } nodeToBrowse.BrowseDirection = BrowseDirection.Forward; nodeToBrowse.ReferenceTypeId = ReferenceTypeIds.HasChild; nodeToBrowse.IncludeSubtypes = true; nodeToBrowse.NodeClassMask = (uint)(NodeClass.Object | NodeClass.Variable | NodeClass.Method); nodeToBrowse.ResultMask = (uint)BrowseResultMask.All; // ignore any browsing errors. ReferenceDescriptionCollection references = ClientUtils.Browse(session, nodeToBrowse, false); if (references == null) { return; } // process the children. List nodeIds = new List(); List children = new List(); for (int ii = 0; ii < references.Count; ii++) { ReferenceDescription reference = references[ii]; if (reference.NodeId.IsAbsolute) { continue; } // create a new declaration. InstanceDeclaration child = new InstanceDeclaration(); child.RootTypeId = typeId; child.NodeId = (NodeId)reference.NodeId; child.BrowseName = reference.BrowseName; child.NodeClass = reference.NodeClass; if (!LocalizedText.IsNullOrEmpty(reference.DisplayName)) { child.DisplayName = reference.DisplayName.Text; } else { child.DisplayName = reference.BrowseName.Name; } if (parent != null) { child.BrowsePath = new QualifiedNameCollection(parent.BrowsePath); child.BrowsePathDisplayText = Utils.Format("{0}/{1}", parent.BrowsePathDisplayText, reference.BrowseName); child.DisplayPath = Utils.Format("{0}/{1}", parent.DisplayPath, reference.DisplayName); } else { child.BrowsePath = new QualifiedNameCollection(); child.BrowsePathDisplayText = Utils.Format("{0}", reference.BrowseName); child.DisplayPath = Utils.Format("{0}", reference.DisplayName); } child.BrowsePath.Add(reference.BrowseName); // check if reading an overridden declaration. InstanceDeclaration overriden = null; if (map.TryGetValue(child.BrowsePathDisplayText, out overriden)) { child.OverriddenDeclaration = overriden; } map[child.BrowsePathDisplayText] = child; // add to list. children.Add(child); nodeIds.Add(child.NodeId); } // check if nothing more to do. if (children.Count == 0) { return; } // find the modelling rules. List modellingRules = FindTargetOfReference(session, nodeIds, Opc.Ua.ReferenceTypeIds.HasModellingRule, false); if (modellingRules != null) { for (int ii = 0; ii < nodeIds.Count; ii++) { children[ii].ModellingRule = modellingRules[ii]; // if the modelling rule is null then the instance is not part of the type declaration. if (NodeId.IsNull(modellingRules[ii])) { map.Remove(children[ii].BrowsePathDisplayText); } } } // update the descriptions. UpdateInstanceDescriptions(session, children, false); // recusively collect instance declarations for the tree below. for (int ii = 0; ii < children.Count; ii++) { if (!NodeId.IsNull(children[ii].ModellingRule)) { instances.Add(children[ii]); CollectInstanceDeclarations(session, typeId, children[ii], instances, map); } } } /// /// Finds the targets for the specified reference. /// private static List FindTargetOfReference(Session session, List nodeIds, NodeId referenceTypeId, bool throwOnError) { try { // construct browse request. BrowseDescriptionCollection nodesToBrowse = new BrowseDescriptionCollection(); for (int ii = 0; ii < nodeIds.Count; ii++) { BrowseDescription nodeToBrowse = new BrowseDescription(); nodeToBrowse.NodeId = nodeIds[ii]; nodeToBrowse.BrowseDirection = BrowseDirection.Forward; nodeToBrowse.ReferenceTypeId = referenceTypeId; nodeToBrowse.IncludeSubtypes = false; nodeToBrowse.NodeClassMask = 0; nodeToBrowse.ResultMask = (uint)BrowseResultMask.None; nodesToBrowse.Add(nodeToBrowse); } // start the browse operation. BrowseResultCollection results = null; DiagnosticInfoCollection diagnosticInfos = null; session.Browse( null, null, 1, nodesToBrowse, out results, out diagnosticInfos); ClientBase.ValidateResponse(results, nodesToBrowse); ClientBase.ValidateDiagnosticInfos(diagnosticInfos, nodesToBrowse); List targetIds = new List(); ByteStringCollection continuationPoints = new ByteStringCollection(); for (int ii = 0; ii < nodeIds.Count; ii++) { targetIds.Add(null); // check for error. if (StatusCode.IsBad(results[ii].StatusCode)) { continue; } // check for continuation point. if (results[ii].ContinuationPoint != null && results[ii].ContinuationPoint.Length > 0) { continuationPoints.Add(results[ii].ContinuationPoint); } // get the node id. if (results[ii].References.Count > 0) { if (NodeId.IsNull(results[ii].References[0].NodeId) || results[ii].References[0].NodeId.IsAbsolute) { continue; } targetIds[ii] = (NodeId)results[ii].References[0].NodeId; } } // release continuation points. if (continuationPoints.Count > 0) { session.BrowseNext( null, true, continuationPoints, out results, out diagnosticInfos); ClientBase.ValidateResponse(results, nodesToBrowse); ClientBase.ValidateDiagnosticInfos(diagnosticInfos, nodesToBrowse); } //return complete list. return targetIds; } catch (Exception exception) { if (throwOnError) { throw new ServiceResultException(exception, StatusCodes.BadUnexpectedError); } return null; } } /// /// Finds the targets for the specified reference. /// private static void UpdateInstanceDescriptions(Session session, List instances, bool throwOnError) { try { ReadValueIdCollection nodesToRead = new ReadValueIdCollection(); for (int ii = 0; ii < instances.Count; ii++) { ReadValueId nodeToRead = new ReadValueId(); nodeToRead.NodeId = instances[ii].NodeId; nodeToRead.AttributeId = Attributes.Description; nodesToRead.Add(nodeToRead); nodeToRead = new ReadValueId(); nodeToRead.NodeId = instances[ii].NodeId; nodeToRead.AttributeId = Attributes.DataType; nodesToRead.Add(nodeToRead); nodeToRead = new ReadValueId(); nodeToRead.NodeId = instances[ii].NodeId; nodeToRead.AttributeId = Attributes.ValueRank; nodesToRead.Add(nodeToRead); } // start the browse operation. DataValueCollection results = null; DiagnosticInfoCollection diagnosticInfos = null; session.Read( null, 0, TimestampsToReturn.Neither, nodesToRead, out results, out diagnosticInfos); ClientBase.ValidateResponse(results, nodesToRead); ClientBase.ValidateDiagnosticInfos(diagnosticInfos, nodesToRead); // update the instances. for (int ii = 0; ii < nodesToRead.Count; ii += 3) { InstanceDeclaration instance = instances[ii / 3]; instance.Description = results[ii].GetValue(LocalizedText.Null).Text; instance.DataType = results[ii + 1].GetValue(NodeId.Null); instance.ValueRank = results[ii + 2].GetValue(ValueRanks.Any); if (!NodeId.IsNull(instance.DataType)) { instance.BuiltInType = DataTypes.GetBuiltInType(instance.DataType, session.TypeTree); instance.DataTypeDisplayText = session.NodeCache.GetDisplayText(instance.DataType); if (instance.ValueRank >= 0) { instance.DataTypeDisplayText += "[]"; } } } } catch (Exception exception) { if (throwOnError) { throw new ServiceResultException(exception, StatusCodes.BadUnexpectedError); } } } #endregion #region Private Methods /// /// Collects the fields for the type. /// /// The session. /// The type id. /// The fields. /// The node id for the declaration of the field. private static void CollectFieldsForType(Session session, NodeId typeId, SimpleAttributeOperandCollection fields, List fieldNodeIds) { // get the supertypes. ReferenceDescriptionCollection supertypes = ClientUtils.BrowseSuperTypes(session, typeId, false); if (supertypes == null) { return; } // process the types starting from the top of the tree. Dictionary foundNodes = new Dictionary(); QualifiedNameCollection parentPath = new QualifiedNameCollection(); for (int ii = supertypes.Count - 1; ii >= 0; ii--) { CollectFields(session, (NodeId)supertypes[ii].NodeId, parentPath, fields, fieldNodeIds, foundNodes); } // collect the fields for the selected type. CollectFields(session, typeId, parentPath, fields, fieldNodeIds, foundNodes); } /// /// Collects the fields for the instance. /// /// The session. /// The instance id. /// The fields. /// The node id for the declaration of the field. private static void CollectFieldsForInstance(Session session, NodeId instanceId, SimpleAttributeOperandCollection fields, List fieldNodeIds) { Dictionary foundNodes = new Dictionary(); QualifiedNameCollection parentPath = new QualifiedNameCollection(); CollectFields(session, instanceId, parentPath, fields, fieldNodeIds, foundNodes); } /// /// Collects the fields for the instance node. /// /// The session. /// The node id. /// The parent path. /// The event fields. /// The node id for the declaration of the field. /// The table of found nodes. private static void CollectFields( Session session, NodeId nodeId, QualifiedNameCollection parentPath, SimpleAttributeOperandCollection fields, List fieldNodeIds, Dictionary 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); } } } /// /// Determines whether the specified select clause contains the browse path. /// /// The select clause. /// The browse path. /// /// true if the specified select clause contains path; otherwise, false. /// private static int ContainsPath(SimpleAttributeOperandCollection selectClause, QualifiedNameCollection browsePath) { for (int ii = 0; ii < selectClause.Count; ii++) { SimpleAttributeOperand field = selectClause[ii]; if (field.BrowsePath.Count != browsePath.Count) { continue; } bool match = true; for (int jj = 0; jj < field.BrowsePath.Count; jj++) { if (field.BrowsePath[jj] != browsePath[jj]) { match = false; break; } } if (match) { return ii; } } return -1; } #endregion } }