Skip to content

How-to guide

Browse nodes

Explore the address space of a server: node by node from the ObjectsFolder, or several levels in one pass.

This content was machine translated and may contain errors. Read in German

How-to guide

Browse nodes ​

Explore the address space ​

Types usedOpcClientOpcNodeIdOpcNodeInfoOpcAttributeOpcAttributeInfoOpcObjectTypes

If you know little about the address space of a server, that is, the entirety of its nodes, first explore the nodes it provides. A graphical OPC UA client such as OPC Watch is well suited for this (page Downloads). Alternatively, explore the address space of the server in code.

The OpcObjectTypes class lets you browse predefined server nodes. The root of all nodes of the server is the standard node ObjectsFolder. If you start browsing at the ObjectsFolder, you determine the entire address space of the server:

csharp
var node = client.BrowseNode(OpcObjectTypes.ObjectsFolder);
Browse(node);
...
private void Browse(OpcNodeInfo node, int level = 0)
{
    Console.WriteLine("{0}{1}({2})",
            new string('.', level * 4),
            node.Attribute(OpcAttribute.DisplayName).Value,
            node.NodeId);
 
    level++;
 
    foreach (var childNode in node.Children())
        Browse(childNode, level);
}
vbnet
Dim node = client.BrowseNode(OpcObjectTypes.ObjectsFolder)
Browse(node)
...
Private Sub Browse(ByVal node As OpcNodeInfo, ByVal Optional level As Integer = 0)
    Console.WriteLine("{0}{1}({2})", _
            New String("."c, level * 4), _
            node.Attribute(OpcAttribute.DisplayName).Value, _
            node.NodeId)

    level += 1

    For Each childNode In node.Children()
        Browse(childNode, level)
    Next
End Sub

Among other things, the output of the code contains the NodeIds of the nodes in parentheses. The client uses these NodeIds to address the nodes of the server directly. To do so, pass the NodeId as a string in double quotes to the corresponding method of the OpcClient class. The section Read values shows what this looks like when you read the value of a node.

Query nodes and child nodes ​

Types usedOpcClientOpcNodeIdOpcNodeInfoOpcAttributeOpcAttributeInfoOpcMethodNodeInfoOpcArgumentInfoOpcNodeCategoryOpcObjectTypes

Browsing in OPC UA is comparable to reflection in .NET. With it, a client determines and explores the entire address space of a server dynamically. This includes all nodes, their references to each other, and their node types, among other things. Browsing starts with the NodeId (OpcNodeId) of the node at which the browse operation begins. From the OpcNodeInfo it receives, the client continues browsing at the child nodes, the parent nodes, or the attributes.

If a node represents a method, browsing returns an instance of OpcMethodNodeInfo. With it, you can also explore the input and output arguments of the method.

Get the OpcNodeInfo of the node you want:

csharp
OpcNodeInfo machineNode = client.BrowseNode("ns=2;s=Machine");
vbnet
Dim machineNode As OpcNodeInfo = client.BrowseNode("ns=2;s=Machine")

Browse child nodes with the Child or Children methods:

csharp
OpcNodeInfo jobNode = machineNode.Child("Job");
 
foreach (var childNode in machineNode.Children()) {
    // Your code to operate on each child node.
}
vbnet
Dim jobNode As OpcNodeInfo = machineNode.Child("Job")

For Each childNode In machineNode.Children()
    'Your code to operate on each child node.
Next

Browse attributes with the Attribute or Attributes methods:

csharp
OpcAttributeInfo displayName = machineNode.Attribute(OpcAttribute.DisplayName);
 
foreach (var attribute in machineNode.Attributes()) {
    // Your code to operate on each attribute.
}
vbnet
Dim displayName As OpcAttributeInfo = machineNode.Attribute(OpcAttribute.DisplayName)

For Each attribute In machineNode.Attributes()
    'Your code to operate on each attribute.
Next

Explore a method node:

csharp
if (childNode.Category == OpcNodeCategory.Method) {
    var methodNode = (OpcMethodNodeInfo)childNode;
 
    foreach (var argument in methodNode.GetInputArguments()) {
        // Your code to operate on each argument.
    }
}
vbnet
If childNode.Category = OpcNodeCategory.Method Then
    Dim methodNode = CType(childNode, OpcMethodNodeInfo)

    For Each argument In methodNode.GetInputArguments()
        'Your code to operate on each argument.
    Next
End If

Browse parent nodes with the Parent or Parents methods. You can also restrict the Children and Parents methods to nodes of a specific OpcNodeCategory or a specific OpcReferenceType. The AttributeValue method returns the value of an attribute directly in the expected type:

csharp
foreach (OpcNodeInfo variableNode in machineNode.Children(OpcNodeCategory.Variable)) {
    // Your code to operate on each variable node.
}
 
foreach (OpcNodeInfo parentNode in jobNode.Parents()) {
    // Your code to operate on each parent node.
}
 
int speed = jobNode.Child("Speed").AttributeValue<int>(OpcAttribute.Value);
vbnet
For Each variableNode As OpcNodeInfo In machineNode.Children(OpcNodeCategory.Variable)
    'Your code to operate on each variable node.
Next

For Each parentNode As OpcNodeInfo In jobNode.Parents()
    'Your code to operate on each parent node.
Next

Dim speed As Integer = jobNode.Child("Speed").AttributeValue(Of Integer)(OpcAttribute.Value)

Browse faster ​

Types usedOpcNodeInfoOpcNodeIdOpcBrowseNodeOpcReferenceTypeOpcBrowseNodeDegreeOpcBrowseOptions

If a server provides a very large address space, browsing all nodes is often slow. This applies in particular when the large amount of node information travels over weak hardware or a slow network. If clients traverse the entire node tree node by node, this also puts load on the server and reduces its performance.

For this reason, the SDK lets you define how many nodes and node levels the client explores at once. The OpcBrowseNodeDegree enumeration serves this purpose. Depending on how many nodes you need at once, set the corresponding value through the Degree property or in the constructor of the OpcBrowseNode class. You can also reduce the payload during browsing to a minimum or to the relevant data. The OpcBrowseOptions enumeration offers several values for this. By default, an instance of OpcBrowseNode with the degree Self explores all hierarchical references (HierarchicalReferences) and requests all node information (OpcBrowseOptions.IncludeAll). If you reduce the node information and later filter the nodes by their reference type, for example through Children(OpcReferenceType), the ReferenceTypeId must remain part of the node information. If you need further node information such as the DisplayName, include it in the Options property.

The following properties let you parameterize and optimize browsing:

csharp
// Create a browse command to browse all hierarchical references.
var browse = new OpcBrowseNode(
        nodeId: OpcNodeId.Parse("ns=2;s=Machine"),
        degree: OpcBrowseNodeDegree.Generation);

// Create a browse command to browse specific types of references.
var browse = new OpcBrowseNode(
        nodeId: OpcNodeId.Parse("ns=2;s=Machine"),
        degree: OpcBrowseNodeDegree.Generation,
        referenceTypes: new[] {
            OpcReferenceType.Organizes,
            OpcReferenceType.HasComponent,
            OpcReferenceType.HasProperty
        });

// Reduce browsing to the smallest possible amount of data.
browse.Options = OpcBrowseOptions.IncludeReferenceTypeId
        | OpcBrowseOptions.IncludeBrowseName;

var node = client.BrowseNode(browse);

foreach (var childNode in node.Children()) {
    // Continue recursively...
}
vbnet
'Create a browse command to browse all hierarchical references.
Dim browse = New OpcBrowseNode( _
        nodeId:=OpcNodeId.Parse("ns=2;s=Machine"), _
        degree:=OpcBrowseNodeDegree.Generation)

'Create a browse command to browse specific types of references.
Dim browse = New OpcBrowseNode( _
        nodeId:=OpcNodeId.Parse("ns=2;s=Machine"), _
        degree:=OpcBrowseNodeDegree.Generation, _
        referenceTypes:={ _
            OpcReferenceType.Organizes, _
            OpcReferenceType.HasComponent, _
            OpcReferenceType.HasProperty _
        })

'Reduce browsing to the smallest possible amount of data.
browse.Options = OpcBrowseOptions.IncludeReferenceTypeId _
        Or OpcBrowseOptions.IncludeBrowseName

Dim node = client.BrowseNode(browse)

For Each childNode In node.Children()
    'Continue recursively...
Next

You can optimize browsing further if you only explore nodes that have a specific relationship to each other. The ReferenceTypes express this relationship. Pass the relevant references as parameters of the browse operation, as shown in the code above. The client then only visits the nodes that use one of the specified ReferenceTypes.

Browse parameters in detail ​

With a large address space, the load on the server and the entire communication between client and server should stay as low as possible. For this, OPC UA offers several mechanisms that let a client signal to the server that it will explore far more nodes than those of a single level. The server can then prepare the information and deliver it to the client in packets. This way, the server does not have to process the node tree internally again for every request of the client. With suitable logic, a client can also query further nodes from the server in advance. This covers nodes whose information it will certainly process, as well as the nodes of their children, grandchildren, and so on.

The SDK offers several browse parameters that control the behavior during browsing. Based on these parameters and the browse history so far, the SDK decides which nodes with which information the server prepares in advance. The SDK retrieves these nodes automatically as browsing continues and caches them. After that, it delivers them from its internal cache instead of querying the server again.

OpcBrowseNodeDegree.Self: browsing A1 returns only its child nodes A1-B1 and A1-B2.A1A1-B1A1-B2A1-B2-C1A1-B2-C2A2A2-B1A2-B2A2-B2-C1A2-B2-C2Browsed nodesReturned child nodes

A value of the OpcBrowseNodeDegree enumeration controls which nodes the SDK automatically requests from the server at once. The value Self corresponds to the default behavior: the client only ever explores the children of the current node. In the diagram on the left, browsing starts at the node A1. The server only delivers the nodes directly below A1, that is, A1-B1 and A1-B2. The nodes of all other subtrees are not part of the browse operation, including those of the nodes next to the start node A1.

OpcBrowseNodeDegree.Sibling: browsing A1 also returns the child nodes of its sibling A2.A1A1-B1A1-B2A1-B2-C1A1-B2-C2A2A2-B1A2-B2A2-B2-C1A2-B2-C2Browsed nodesReturned child nodes

With the value Sibling, the SDK retrieves all children of the sibling nodes from the server while it explores the child nodes of the current node. If the browse operation starts at A1, the SDK determines not only the direct child nodes of A1, but also those of all siblings of A1. The browse operation therefore also determines the subtrees of A2 to An. The SDK caches the node information it receives. As soon as your application needs the subtree of A2, for example, the SDK delivers it from memory instead of retrieving it from the server again. The diagram on the right shows that a browse operation starting at A1 behaves as if the client browsed the nodes A1 to An at the same time.

OpcBrowseNodeDegree.Generation: browsing A1-B1 covers all nodes of the same level and returns their child nodes.A1A1-B1A1-B2A1-B2-C1A1-B2-C2A2A2-B1A2-B2A2-B2-C1A2-B2-C2Browsed nodesReturned child nodes

With the value Generation, the SDK explores all nodes of the same generation at once, that is, all nodes at the same depth. The diagram on the left shows a browse operation that starts at the node A1-B1. The SDK retrieves not only its subtree, as when browsing with OpcBrowseNodeDegree.Self. It also retrieves the subtrees of its siblings, as when browsing with OpcBrowseNodeDegree.Sibling, and the subtrees of its cousins up to the nth degree. Browsing therefore behaves as if the client browsed the nodes A1-B1, A1-B2, A2-B1, and A2-B2 at the same time. Here, too, the SDK caches the node information of the subtrees and provides it when needed.

Use the values of OpcBrowseNodeDegree with care. If a server provides a relatively flat address space with a very large number of nodes per subtree, for example, Generation can lead to long and memory-intensive browse operations. The same applies to Sibling, in particular if a node has a large number of small subtrees on the same level that in turn contain many child nodes.

We therefore recommend: if you know the complexity of the server in advance, choose the degree of the browse operations accordingly. If the server is unknown or changes and you choose the degree Sibling or Generation, reduce the node information through the Options property. Retrieve any further node information you need with a separate browse operation when required.

Find nodes by path ​

Types usedOpcClientOpcNodeIdOpcTranslatePathOpcTranslatePathResult

Instead of hard-coding identifiers, you can determine nodes by their path of BrowseNames. This is the usual approach with standardized information models (companion specifications): devices from different vendors use different identifiers but the same structure. The path follows the text format of the OPC UA specification. A / follows a hierarchical reference, a . follows an aggregation reference (component or property). The namespace index precedes the name (2:Machine). Without a start node, the path begins at the RootFolder. If the path cannot be resolved, the TranslatePath method returns null:

csharp
OpcNodeId speedId = client.TranslatePath("/Objects/2:Machine/2:Speed");
 
if (speedId == null)
    Console.WriteLine("The path does not exist.");
else
    Console.WriteLine("Speed: {0}", client.ReadNode(speedId));
vbnet
Dim speedId As OpcNodeId = client.TranslatePath("/Objects/2:Machine/2:Speed")

If speedId Is Nothing Then
    Console.WriteLine("The path does not exist.")
Else
    Console.WriteLine("Speed: {0}", client.ReadNode(speedId))
End If

You can also specify the path from a known node as individual names:

csharp
OpcNodeId temperatureId = client.TranslatePath(
        OpcObjectTypes.ObjectsFolder,
        "2:Machine",
        "2:Temperature");
vbnet
Dim temperatureId As OpcNodeId = client.TranslatePath( _
        OpcObjectTypes.ObjectsFolder, _
        "2:Machine", _
        "2:Temperature")

TranslatePaths resolves many paths in one call. The SDK splits large requests according to the limits of the server (section How the SDK splits large requests). Each result carries the resolved identifier and a status:

csharp
OpcTranslatePathResultCollection results = client.TranslatePaths(
        new OpcTranslatePath("/Objects/2:Machine/2:Speed"),
        new OpcTranslatePath("/Objects/2:Machine/2:Temperature"),
        new OpcTranslatePath("/Objects/2:Machine/2:IsRunning"));
 
foreach (OpcTranslatePathResult result in results) {
    if (result.IsGood)
        Console.WriteLine(result.NodeId);
    else
        Console.WriteLine("Not found: {0}", result.Code);
}
vbnet
Dim results As OpcTranslatePathResultCollection = client.TranslatePaths( _
        New OpcTranslatePath("/Objects/2:Machine/2:Speed"), _
        New OpcTranslatePath("/Objects/2:Machine/2:Temperature"), _
        New OpcTranslatePath("/Objects/2:Machine/2:IsRunning"))

For Each result As OpcTranslatePathResult In results
    If result.IsGood Then
        Console.WriteLine(result.NodeId)
    Else
        Console.WriteLine("Not found: {0}", result.Code)
    End If
Next

Iterate over large results ​

Types usedOpcClientOpcNodeInfoOpcNodeCategory

Children() returns the child nodes as an enumeration. If the server splits a result that is too large, the client requests the continuations (BrowseNext) itself while your application iterates over the enumeration. This is why you can search a subtree by any criteria with a recursive enumeration and LINQ. FirstOrDefault ends the search as soon as it finds a matching node. The client then no longer requests further subtrees:

csharp
OpcNodeInfo machine = client.BrowseNode("ns=2;s=Machine");
 
OpcNodeInfo firstAlarm = Descendants(machine)
        .Where(node => node.Category == OpcNodeCategory.Variable)
        .FirstOrDefault(node => node.Name.Value.StartsWith("Alarm"));
...
private static IEnumerable<OpcNodeInfo> Descendants(OpcNodeInfo node)
{
    foreach (OpcNodeInfo child in node.Children()) {
        yield return child;
 
        foreach (OpcNodeInfo descendant in Descendants(child))
            yield return descendant;
    }
}
vbnet
Dim machine As OpcNodeInfo = client.BrowseNode("ns=2;s=Machine")

Dim firstAlarm As OpcNodeInfo = Descendants(machine) _
        .Where(Function(node) node.Category = OpcNodeCategory.Variable) _
        .FirstOrDefault(Function(node) node.Name.Value.StartsWith("Alarm"))
...
Private Shared Iterator Function Descendants( _
        ByVal node As OpcNodeInfo) As IEnumerable(Of OpcNodeInfo)
    For Each child As OpcNodeInfo In node.Children()
        Yield child

        For Each descendant As OpcNodeInfo In Descendants(child)
            Yield descendant
        Next
    Next
End Function

If you explore a large subtree completely, the approach from Browse faster is usually faster, because it retrieves several levels in one request.