Skip to content

Browse & Explore

Before you can read a value, call a method, or subscribe to anything, you need to know it's NodeID. Relying on statically assigned nodeids on the server side is in practice very error prone. The browse services let you traverse through the server's address space and retrive the NodeId for an element you are looking for.

This page walks through browsing primitives:

  • browsing interactively
  • using the browse service api
  • the client __getitem__ syntax

This tutorial expects the example server running in the background.


Browsing Interactively

When you're poking at a server you don't yet know well — discovering what it has is one of the first steps.

The o6\Python Client ships with a very simple, light weight interactive browser for the command line.

from o6 import Client

with Client("opc.tcp://localhost:4840") as client:
    selected = client.browseInteractive()
    # Arrow keys to explore the address space,
    # 's' to select the node under the curser and exit with it's nodeid/browse path
    # 'q' quits and returns None
    print(selected)

Info

Requires the curses module (install windows-curses on Windows).

browse interactive

You should see a command line program that lets you explore the server address space, starting at the server's root node. On the top you see the current node's full browse path, the right hand side lists this node's child references. The left hand side provides details about the selected node on the left side. Use the arrow keys to navigate your curser and explore the server. Move into the Objects node and you will see the DistillingSystem and a few details. You can quit the browser by pressing q.

Tip

Get familiar with the distilling example server and explore its structure

You can also pass a NodeId to drop straight into a known subtree:

client.browseInteractive("ns=1;i=1000")   # start inside DistillingSystem

On quitting the interactive browser you can choose to return a NodeId as string or the full browse path from root as string as well:

selected = client.browseInteractive()
from o6 import Client

with Client("opc.tcp://localhost:4840") as client:
    selected = client.browseInteractive()
    # Arrow keys to explore the address space,
    # 's' to select the node under the curser and exit with it's nodeid/browse path
    # 'q' quits and returns None
    print(selected)

The browse service

client.browse(target) is the no-frills browsing call: hand it a NodeId, get back a list[ReferenceDescription] of every reference coming out of that node. The default direction is forward and the default reference type is HierarchicalReferences (which includes HasComponent, HasProperty, Organizes, and their subtypes).

from o6.ns.ns0.datatypes import BrowseResultMask

refs = client.browse("ns=1;i=1000", resultMask=BrowseResultMask.BROWSE_NAME | BrowseResultMask.NODE_CLASS)   # DistillingSystem
for ref in refs:
    print(ref.browseName, "→", ref.nodeId, ref.nodeClass)

Output (abbreviated):

Identification → ns=1;i=1100 Object
Status         → ns=1;i=1200 Object
Kettle         → ns=1;i=1300 Object
Distillate     → ns=1;i=1400 Object
Actuators      → ns=1;i=1500 Object
Events         → ns=1;i=1600 Object
Start          → ns=1;i=2001 Method
Shutdown       → ns=1;i=2002 Method

This is, what the interactive browser does in the background to fetch and display the child references of the currently selected node.

Info

Per the OPC UA spec, a ReferenceDescription's optional fields (browseName, displayName, nodeClass, typeDefinition) are only populated if you ask for them via resultMask — the default resultMask=0 leaves them empty. reftype and nodeId are always returned regardless of the mask.

Putting it all together

from o6 import Client
from o6.ns.ns0.datatypes import BrowseResultMask

with Client("opc.tcp://localhost:4840") as client:
    # 1. Start at DistillingSystem
    refs = client.browse("ns=1;i=1000", resultMask=BrowseResultMask.BROWSE_NAME | BrowseResultMask.NODE_CLASS)

    # 2. Pick the Kettle object and walk one level into it
    kettle = next(r.nodeId for r in refs if r.browseName.name == "Kettle")
    for ref in client.browse(kettle, resultMask=BrowseResultMask.BROWSE_NAME | BrowseResultMask.NODE_CLASS):
        print(ref.browseName.name, "—", ref.nodeClass.name)

Info

client.browse() transparently follows server-issued continuation points by calling BrowseNext until the result is exhausted, so the list you get back is always complete even when the server splits a large result into multiple batches.


Filtering the result

The real address space of a server is much busier than what you see in the interactive browser — every node also has HasTypeDefinition, HasModellingRule, inverse references, non-hierarchical references, and so on. The four filter parameters on browse() are how you cut that down.

direction=...FORWARD (the default), INVERSE, or BOTH. Inverse is "who points at me?". Forward is "who do I point at?".

# Who points at DistillingSystem? (answer: Objects folder)
refs = client.browse("ns=1;i=1000", direction=o6.ns.ns0.datatypes.BrowseDirection.INVERSE)

reftype=... — restrict to a specific reference type. The default is HierarchicalReferences, for example:

# Only HasComponent edges (variables, methods, nested objects)
client.browse(
    "ns=1;i=1000",
    reftype=o6.ns.ns0.reftypes.HasComponent,
)

nodeClassMask=... — restrict the returned targets to specific node classes (Object, Variable, Method, …). Useful for "give me all the variables under this object":

vars_only = client.browse(
    "ns=1;i=1000",
    nodeClassMask=NodeClass.VARIABLE,
)

Putting it all together — list every writable variable

A single browse, filtered to Variable nodes, gives you the writable surface of the still:

import o6
from o6 import Client
from o6.ns.ns0.datatypes import BrowseResultMask, NodeClass

with Client("opc.tcp://localhost:4840") as client:
    writable = client.browse(
        "ns=1;i=1000",
        direction=o6.ns.ns0.datatypes.BrowseDirection.FORWARD,
        nodeClassMask=NodeClass.VARIABLE,
        resultMask=BrowseResultMask.BROWSE_NAME,
    )
    for ref in writable:
        print(ref.browseName.name, "→", ref.nodeId)

Info

Filtering at the server with nodeClassMask is cheaper than fetching everything and filtering in Python — the server only returns matches in it's response.

What is next

You have learned in this chapter: - Quickly getting an oveview of a server with the interactive browser - Using client.browse(...) to get a node's references - Server side filtering of references with nodeClassMask and reftype

Next you will learn how to read and write node values and attributes.