public BrowsePathResult GetBrowseResultForOneNode(string nodeId) { var result = new BrowsePathResult { StatusCode = StatusCodes.Bad }; try { SysLog.Debug($"Trying to locate {nodeId}.\n"); //This call is a synchronous call (Polling). This type of call for some nodes it is too quickly var node = this._session?.ReadNode(nodeId); var target = new BrowsePathTarget { TargetId = node?.NodeId }; result.StatusCode = node?.NodeId is null ? StatusCodes.Bad : StatusCodes.Good; result.Targets.Add(target); } catch (ServiceResultException ex) { if (StatusCodes.BadNodeIdUnknown == ex.StatusCode || StatusCodes.BadNodeIdInvalid == ex.StatusCode) { //We know the nodeId does not exist because the OPC server has reported that if (SysLog.IsDebugEnabled) { SysLog.Error($"Failed to retrieve node id for item {nodeId}. "); } result.StatusCode = ex.StatusCode; } //We know the Node is not bad because the OPC Server has not report that . //We continue searching for the Node. SysLog.Debug($"Node {nodeId} was not located in a normal way.\n"); } catch (Exception ex) { SysLog.Error($"Failed to retrieve node id for item {nodeId}. ", ex); } return(result); }
/// <summary> /// Returns the node ids for a set of relative paths. /// </summary> /// <param name="session">An open session with the server to use.</param> /// <param name="startNodeId">The starting node for the relative paths.</param> /// <param name="namespacesUris">The namespace URIs referenced by the relative paths.</param> /// <param name="relativePaths">The relative paths.</param> /// <returns>A collection of local nodes.</returns> public static List <NodeId> 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 <NodeId> nodes = new List <NodeId>(); 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); }
/// <summary> /// Finds an element identified by the path from the root. /// </summary> private AeBrowseElement Find(Session session, string itemId, AeBrowseElement root, Stack <string> names, bool isArea) { string browseText = null; BrowsePath browsePath = new BrowsePath(); browsePath.StartingNode = root.NodeId; while (names.Count > 0) { RelativePathElement path = new RelativePathElement(); path.ReferenceTypeId = Opc.Ua.ReferenceTypeIds.HasNotifier; path.IsInverse = false; path.IncludeSubtypes = true; // final hop can be HasEventSource for sources. if (!isArea && names.Count == 1) { path.ReferenceTypeId = Opc.Ua.ReferenceTypeIds.HasEventSource; } browseText = names.Pop(); path.TargetName = m_mapper.GetRemoteBrowseName(browseText); browsePath.RelativePath.Elements.Add(path); } BrowsePathCollection browsePaths = new BrowsePathCollection(); 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); // check if the start node actually exists. if (StatusCode.IsBad(results[0].StatusCode)) { return(null); } // must be exact one target. if (results[0].Targets.Count != 1) { return(null); } // can't be an external reference. BrowsePathTarget target = results[0].Targets[0]; if (target.RemainingPathIndex != UInt32.MaxValue) { return(null); } // need to check if at the end of the tree. BrowseDescription nodeToBrowse = new BrowseDescription(); nodeToBrowse.NodeId = (NodeId)target.TargetId; nodeToBrowse.ReferenceTypeId = Opc.Ua.ReferenceTypeIds.HasEventSource; nodeToBrowse.BrowseDirection = BrowseDirection.Forward; nodeToBrowse.IncludeSubtypes = true; ReferenceDescriptionCollection children = ComAeUtils.Browse(session, nodeToBrowse, false); if (!isArea) { if (children != null && children.Count > 0) { return(null); } } else { if (children == null || children.Count == 0) { return(null); } } // construct the element. AeBrowseElement element = new AeBrowseElement(); element.NodeId = (NodeId)target.TargetId; element.ItemId = itemId; element.BrowseText = browseText; element.IsArea = isArea; return(element); }
/// <summary> /// Verifies that the timestamps match the requested filter. /// </summary> private bool VerifyPaths( Node node, BrowsePath request, BrowsePathResult result) { // check empty path. if (request.RelativePath.Elements == null || request.RelativePath.Elements.Count == 0) { if (result.StatusCode != StatusCodes.BadBrowseNameInvalid) { Log( "Unexpected error returned during translate for Node '{0}'. NodeId = {1}, Expected = {2}, Actual = {3}", node, node.NodeId, (StatusCode)StatusCodes.BadBrowseNameInvalid, result.StatusCode); return(false); } } BrowsePathResult expectedResult = new BrowsePathResult(); GetTargets(node, request.RelativePath.Elements, 0, expectedResult); if (result.StatusCode == StatusCodes.BadNoMatch) { if (expectedResult.Targets.Count > 0) { Log( "Translate returned BadNoMatch when targets expected '{0}'. NodeId = {1}, Path = {2}, ExpectedCount = {3}", node, node.NodeId, GetRelativePath(request.RelativePath.Elements), expectedResult.Targets.Count); return(false); } if (result.Targets.Count > 0) { Log( "Translate returned targets with a BadNoMatch code '{0}'. NodeId = {1}, Path = {2}, ActualCount = {3}", node, node.NodeId, GetRelativePath(request.RelativePath.Elements), result.Targets.Count); return(false); } return(true); } if (expectedResult.Targets.Count == 0) { Log( "Translate returned invalided error code when no targets exist '{0}'. NodeId = {1}, Path = {2}, StatusCode = {3}", node, node.NodeId, GetRelativePath(request.RelativePath.Elements), (StatusCode)result.StatusCode); return(false); } // check status code. if (result.StatusCode != StatusCodes.Good) { Log( "Translate returned an error for Node '{0}'. NodeId = {1}, Path = {2}, StatusCode = {3}", node, node.NodeId, GetRelativePath(request.RelativePath.Elements), (StatusCode)result.StatusCode); return(false); } // check for expected targets. for (int ii = 0; ii < expectedResult.Targets.Count; ii++) { BrowsePathTarget expectedTarget = expectedResult.Targets[ii]; bool found = false; for (int jj = 0; jj < result.Targets.Count; jj++) { BrowsePathTarget actualTarget = result.Targets[jj]; if (actualTarget.TargetId != expectedTarget.TargetId) { continue; } found = true; if (actualTarget.RemainingPathIndex != expectedTarget.RemainingPathIndex) { Log( "Translate did not return correct remaining path index for target Node '{0}'. NodeId = {1}, Path = {2}, Expected = {3}, Actual = {4}", node, node.NodeId, GetRelativePath(request.RelativePath.Elements), expectedTarget.RemainingPathIndex, actualTarget.RemainingPathIndex); return(false); } break; } if (!found) { Log( "Translate did not return expected target Node '{0}'. NodeId = {1}, Path = {2}, TargetId = {3}", node, node.NodeId, GetRelativePath(request.RelativePath.Elements), expectedTarget.TargetId); return(false); } } // check for unexpected targets. for (int ii = 0; ii < result.Targets.Count; ii++) { BrowsePathTarget actualTarget = result.Targets[ii]; bool found = false; for (int jj = 0; jj < expectedResult.Targets.Count; jj++) { BrowsePathTarget expectedTarget = expectedResult.Targets[jj]; if (actualTarget.TargetId == expectedTarget.TargetId) { found = true; break; } } if (!found) { Log( "Translate returned unexpected target Node '{0}'. NodeId = {1}, Path = {2}, TargetId = {3}", node, node.NodeId, GetRelativePath(request.RelativePath.Elements), actualTarget.TargetId); return(false); } } // all ok. return(true); }
/// <summary> /// Recursively finds the targets of the specified path. /// </summary> private void GetTargets(Node start, IList <RelativePathElement> path, int index, BrowsePathResult result) { // check for invalid parameters. if (index >= path.Count) { return; } // look for list of references for node. ReferenceDescriptionCollection references = start.Handle as ReferenceDescriptionCollection; if (references == null || references.Count == 0) { return; } RelativePathElement element = path[index]; // each list of references. for (int ii = 0; ii < references.Count; ii++) { ReferenceDescription reference = references[ii]; // check for a reference match. if (element.IsInverse == reference.IsForward) { continue; } if (element.ReferenceTypeId != reference.ReferenceTypeId) { if (!element.IncludeSubtypes) { continue; } if (!Session.TypeTree.IsTypeOf(reference.ReferenceTypeId, element.ReferenceTypeId)) { continue; } } // check for a browse name match. if (element.TargetName != reference.BrowseName) { continue; } // check for end of list. if (index == path.Count - 1) { BrowsePathTarget item = new BrowsePathTarget(); item.TargetId = reference.NodeId; item.RemainingPathIndex = UInt32.MaxValue; result.Targets.Add(item); continue; } // check for external reference. if (reference.NodeId.IsAbsolute) { BrowsePathTarget item = new BrowsePathTarget(); item.TargetId = reference.NodeId; item.RemainingPathIndex = (uint)index + 1; result.Targets.Add(item); continue; } // check for targets. Node target = null; if (!AvailableNodes.TryGetValue((NodeId)reference.NodeId, out target)) { BrowsePathTarget item = new BrowsePathTarget(); item.TargetId = reference.NodeId; item.RemainingPathIndex = (uint)index + 1; result.Targets.Add(item); continue; } // recursively follow targets. GetTargets(target, path, index + 1, result); } }
/// <summary> /// Gets the available attributes for an HDA item. /// </summary> /// <param name="session">The session.</param> /// <param name="nodeId">The node id.</param> /// <returns></returns> private ReadValueIdCollection GetAvailableAttributes(Session session, NodeId nodeId) { ReadValueIdCollection supportedAttributes = new ReadValueIdCollection(); // add mandatory HDA attributes. supportedAttributes.Add(Construct(nodeId, OpcRcw.Hda.Constants.OPCHDA_ITEMID, Attributes.DisplayName)); supportedAttributes.Add(Construct(nodeId, OpcRcw.Hda.Constants.OPCHDA_DATA_TYPE, Attributes.DataType)); supportedAttributes.Add(Construct(nodeId, ComHdaProxy.INTERNAL_ATTRIBUTE_VALUE_RANK, Attributes.ValueRank)); supportedAttributes.Add(Construct(nodeId, OpcRcw.Hda.Constants.OPCHDA_DESCRIPTION, Attributes.Description)); supportedAttributes.Add(Construct(nodeId, OpcRcw.Hda.Constants.OPCHDA_ARCHIVING, Attributes.Historizing)); // check if nodes are defined for all optional HDA attributes. BrowsePathCollection pathsToRead = new BrowsePathCollection(); pathsToRead.Add(Construct(nodeId, ComHdaProxy.INTERNAL_ATTRIBUTE_ANNOTATION, Opc.Ua.BrowseNames.Annotations)); pathsToRead.Add(Construct(nodeId, OpcRcw.Hda.Constants.OPCHDA_ENG_UNITS, Opc.Ua.BrowseNames.EngineeringUnits));; pathsToRead.Add(Construct(nodeId, OpcRcw.Hda.Constants.OPCHDA_DERIVE_EQUATION, Opc.Ua.BrowseNames.Definition)); pathsToRead.Add(Construct(nodeId, OpcRcw.Hda.Constants.OPCHDA_NORMAL_MAXIMUM, Opc.Ua.BrowseNames.EURange)); pathsToRead.Add(Construct(nodeId, OpcRcw.Hda.Constants.OPCHDA_NORMAL_MINIMUM, Opc.Ua.BrowseNames.EURange)); pathsToRead.Add(Construct(nodeId, OpcRcw.Hda.Constants.OPCHDA_HIGH_ENTRY_LIMIT, Opc.Ua.BrowseNames.InstrumentRange)); pathsToRead.Add(Construct(nodeId, OpcRcw.Hda.Constants.OPCHDA_LOW_ENTRY_LIMIT, Opc.Ua.BrowseNames.InstrumentRange)); pathsToRead.Add(Construct(nodeId, OpcRcw.Hda.Constants.OPCHDA_STEPPED, Opc.Ua.BrowseNames.HAConfiguration, Opc.Ua.BrowseNames.Stepped)); pathsToRead.Add(Construct(nodeId, OpcRcw.Hda.Constants.OPCHDA_MAX_TIME_INT, Opc.Ua.BrowseNames.HAConfiguration, Opc.Ua.BrowseNames.MaxTimeInterval)); pathsToRead.Add(Construct(nodeId, OpcRcw.Hda.Constants.OPCHDA_MIN_TIME_INT, Opc.Ua.BrowseNames.HAConfiguration, Opc.Ua.BrowseNames.MinTimeInterval)); pathsToRead.Add(Construct(nodeId, OpcRcw.Hda.Constants.OPCHDA_EXCEPTION_DEV, Opc.Ua.BrowseNames.HAConfiguration, Opc.Ua.BrowseNames.ExceptionDeviation)); pathsToRead.Add(Construct(nodeId, OpcRcw.Hda.Constants.OPCHDA_EXCEPTION_DEV_TYPE, Opc.Ua.BrowseNames.HAConfiguration, Opc.Ua.BrowseNames.ExceptionDeviationFormat)); BrowsePathResultCollection results = null; DiagnosticInfoCollection diagnosticInfos = null; session.TranslateBrowsePathsToNodeIds( null, pathsToRead, out results, out diagnosticInfos); Session.ValidateResponse(results, pathsToRead); Session.ValidateDiagnosticInfos(diagnosticInfos, pathsToRead); for (int ii = 0; ii < pathsToRead.Count; ii++) { uint attributeId = (uint)pathsToRead[ii].Handle; // path does not exist. if (StatusCode.IsBad(results[ii].StatusCode)) { continue; } // nothing found. if (results[ii].Targets.Count == 0) { continue; } // choose the first valid target. for (int jj = 0; jj < results[ii].Targets.Count; jj++) { BrowsePathTarget target = results[ii].Targets[jj]; if (target.RemainingPathIndex == UInt32.MaxValue && !NodeId.IsNull(target.TargetId) && !target.TargetId.IsAbsolute) { supportedAttributes.Add(Construct((NodeId)target.TargetId, attributeId, Attributes.Value)); break; } } } return(supportedAttributes); }
/// <summary> /// Recursively finds the targets of the specified path. /// </summary> private void GetTargets(Node start, IList<RelativePathElement> path, int index, BrowsePathResult result) { // check for invalid parameters. if (index >= path.Count) { return; } // look for list of references for node. ReferenceDescriptionCollection references = start.Handle as ReferenceDescriptionCollection; if (references == null || references.Count == 0) { return; } RelativePathElement element = path[index]; // each list of references. for (int ii = 0; ii < references.Count; ii++) { ReferenceDescription reference = references[ii]; // check for a reference match. if (element.IsInverse == reference.IsForward) { continue; } if (element.ReferenceTypeId != reference.ReferenceTypeId) { if (!element.IncludeSubtypes) { continue; } if (!Session.TypeTree.IsTypeOf(reference.ReferenceTypeId, element.ReferenceTypeId)) { continue; } } // check for a browse name match. if (element.TargetName != reference.BrowseName) { continue; } // check for end of list. if (index == path.Count-1) { BrowsePathTarget item = new BrowsePathTarget(); item.TargetId = reference.NodeId; item.RemainingPathIndex = UInt32.MaxValue; result.Targets.Add(item); continue; } // check for external reference. if (reference.NodeId.IsAbsolute) { BrowsePathTarget item = new BrowsePathTarget(); item.TargetId = reference.NodeId; item.RemainingPathIndex = (uint)index+1; result.Targets.Add(item); continue; } // check for targets. Node target = null; if (!AvailableNodes.TryGetValue((NodeId)reference.NodeId, out target)) { BrowsePathTarget item = new BrowsePathTarget(); item.TargetId = reference.NodeId; item.RemainingPathIndex = (uint)index+1; result.Targets.Add(item); continue; } // recursively follow targets. GetTargets(target, path, index+1, result); } }
/// <summary> /// Recursively processes the elements in the RelativePath starting at the specified index. /// </summary> private void TranslateBrowsePath( OperationContext context, INodeManager nodeManager, object sourceHandle, RelativePath relativePath, BrowsePathTargetCollection targets, int index) { Debug.Assert(nodeManager != null); Debug.Assert(sourceHandle != null); Debug.Assert(relativePath != null); Debug.Assert(targets != null); // check for end of list. if (index < 0 || index >= relativePath.Elements.Count) { return; } // follow the next hop. RelativePathElement element = relativePath.Elements[index]; // check for valid reference type. if (!element.IncludeSubtypes && NodeId.IsNull(element.ReferenceTypeId)) { return; } // check for valid target name. if (QualifiedName.IsNull(element.TargetName)) { throw new ServiceResultException(StatusCodes.BadBrowseNameInvalid); } List<ExpandedNodeId> targetIds = new List<ExpandedNodeId>(); List<NodeId> externalTargetIds = new List<NodeId>(); try { nodeManager.TranslateBrowsePath( context, sourceHandle, element, targetIds, externalTargetIds); } catch (Exception e) { Utils.Trace(e, "Unexpected error translating browse path."); return; } // must check the browse name on all external targets. for (int ii = 0; ii < externalTargetIds.Count; ii++) { // get the browse name from another node manager. ReferenceDescription description = new ReferenceDescription(); UpdateReferenceDescription( context, externalTargetIds[ii], NodeClass.Unspecified, BrowseResultMask.BrowseName, description); // add to list if target name matches. if (description.BrowseName == element.TargetName) { bool found = false; for (int jj = 0; jj < targetIds.Count; jj++) { if (targetIds[jj] == externalTargetIds[ii]) { found = true; break; } } if (!found) { targetIds.Add(externalTargetIds[ii]); } } } // check if done after a final hop. if (index == relativePath.Elements.Count-1) { for (int ii = 0; ii < targetIds.Count; ii++) { BrowsePathTarget target = new BrowsePathTarget(); target.TargetId = targetIds[ii]; target.RemainingPathIndex = UInt32.MaxValue; targets.Add(target); } return; } // process next hops. for (int ii = 0; ii < targetIds.Count; ii++) { ExpandedNodeId targetId = targetIds[ii]; // check for external reference. if (targetId.IsAbsolute) { BrowsePathTarget target = new BrowsePathTarget(); target.TargetId = targetId; target.RemainingPathIndex = (uint)(index+1); targets.Add(target); continue; } // check for valid start node. sourceHandle = GetManagerHandle((NodeId)targetId, out nodeManager); if (sourceHandle == null) { continue; } // recusively follow hops. TranslateBrowsePath( context, nodeManager, sourceHandle, relativePath, targets, index+1); } }
/// <summary> /// Follows the browse path and returns any targets found. /// </summary> public void TranslateBrowsePath( BrowsePath request, BrowsePathResult result, DiagnosticInfo diagnosticInfo) { lock (m_lock) { // find the starting node. Node source = m_nodes.Find(request.StartingNode); if (source == null) { result.StatusCode = new StatusCode(StatusCodes.BadNodeIdUnknown); return; } // check if there is nothing to do. if (request.RelativePath.Elements == null || request.RelativePath.Elements.Count == 0) { result.StatusCode = new StatusCode(StatusCodes.BadNothingToDo); return; } result.Targets = new ListOfBrowsePathTarget(); Node current = source; // follow each element in the browse path. for (int ii = 0; ii < request.RelativePath.Elements.Count; ii++) { RelativePathElement element = request.RelativePath.Elements[ii]; bool found = false; // need to find any matching reference. foreach (ReferenceNode reference in current.References) { // inverse is a quick check - do that first. if (reference.IsInverse != element.IsInverse) { continue; } // check for reference type matches. if (reference.ReferenceTypeId != element.ReferenceTypeId) { if (!element.IncludeSubtypes) { continue; } if (!IsTypeOf(reference.ReferenceTypeId, element.ReferenceTypeId)) { continue; } } // The UA type model requires that the browse names of all targets of hierarchial references // be unique within a parent. This means most browse paths will point to no more than one node. // However, instances are allowed to add additional nodes with duplicate browse names. If the server // allows this it must keep track of the child that matches the type model and return it as the // first target. // need to find the target to check the browse name. Node target = m_nodes.Find(reference.TargetId); if (target != null) { if (element.TargetName == target.BrowseName) { found = true; current = target; break; } } } if (found) { // check if the complete path has been followed. if (ii == request.RelativePath.Elements.Count - 1) { BrowsePathTarget item = new BrowsePathTarget(); item.TargetId = new ExpandedNodeId(current.NodeId); item.RemainingPathIndex = UInt32.MaxValue; result.Targets.Add(item); } } } } }
/// <summary> /// Provides a way to move �up� or �down� in a hierarchical space from the current position, or a way to move to a specific position in the area space tree. The target szString must represent an area, rather than a source. /// </summary> /// <param name="dwBrowseDirection">OPCAE_BROWSE_UP, OPCAE_BROWSE_DOWN, or OPCAE_BROWSE_TO</param> /// <param name="szString"> /// For DOWN, the partial area name of the area to move into. This would be one of the strings returned from BrowseOPCAreas. /// For UP this parameter is ignored and should point to a NULL string. /// For BROWSE_TO, the fully qualified area name (as obtained from GetQualifiedAreaName method) or a pointer to a NUL string to go to the root. /// </param> public void ChangeBrowsePosition(OPCAEBROWSEDIRECTION dwBrowseDirection, string szString) { lock (m_lock) { try { switch (dwBrowseDirection) { // move to a specified position or root. case OPCAEBROWSEDIRECTION.OPCAE_BROWSE_TO: { try { // move to root. if (String.IsNullOrEmpty(szString)) { m_browseStack.Clear(); m_browseStack.Push(m_session.NodeCache.Find(Objects.Server)); break; } // Translate the fully qualified area name to NodeId List <string> szAreas = new List <string>(); szAreas.Add(szString); BrowsePathResultCollection results = m_server.GetBrowseTargets(szAreas); if (StatusCode.IsBad(results[0].StatusCode)) { throw ComUtils.CreateComException(ResultIds.E_INVALIDBRANCHNAME); } BrowsePathTarget target = results[0].Targets[0]; INode node = m_session.NodeCache.Find(target.TargetId); if (node == null) { throw ComUtils.CreateComException(ResultIds.E_INVALIDBRANCHNAME); } // build a new browse stack. Stack <INode> browseStack = new Stack <INode>(); if (!FindPathToNode(node, browseStack)) { throw ComUtils.CreateComException(ResultIds.E_INVALIDBRANCHNAME); } // push target node onto stack. browseStack.Push(node); m_browseStack = browseStack; } catch { throw ComUtils.CreateComException(ResultIds.E_INVALIDBRANCHNAME); } break; } // move to a child branch. case OPCAEBROWSEDIRECTION.OPCAE_BROWSE_DOWN: { // check for invalid name. if (String.IsNullOrEmpty(szString)) { throw ComUtils.CreateComException(ResultIds.E_INVALIDBRANCHNAME); } // find the current node. INode parent = m_browseStack.Peek(); if (parent == null) { throw ComUtils.CreateComException(ResultIds.E_FAIL); } // find the child. INode child = FindChildByName(parent.NodeId, szString); if (child == null) { throw ComUtils.CreateComException(ResultIds.E_INVALIDBRANCHNAME); } // save the new position. m_browseStack.Push(child); break; } // move to a parent branch. case OPCAEBROWSEDIRECTION.OPCAE_BROWSE_UP: { // check for invalid name. if (!String.IsNullOrEmpty(szString)) { throw ComUtils.CreateComException(ResultIds.E_FAIL); } // can't move up from root. if (m_browseStack.Count <= 1) { throw ComUtils.CreateComException(ResultIds.E_FAIL); } // move up the stack. m_browseStack.Pop(); break; } default: { throw ComUtils.CreateComException(ResultIds.E_INVALIDARG); } } } catch (COMException e) { throw ComUtils.CreateComException(e); } catch (Exception e) { Utils.Trace(e, "Unexpected error in ChangeBrowsePosition"); throw ComUtils.CreateComException(e); } } }