Code Library
Machine DataPythonPython 3.8+ · asyncuaMIT licenseIntermediate

Browse an OPC UA server and read a machine's variables (Python)

Most OPC UA examples hardcode a NodeId like ns=2;i=1005 — which is exactly the number that's different on every PLC and every machine builder's server. This script does the more useful thing: it browses the standard Objects folder for an object matching a name you give it, then reads back every variable under that object, whatever they're called. Tested end to end against a local OPC UA test server before this ever needed real hardware.

Before you run it

  • pip install asyncua
  • An OPC UA server endpoint URL and the browse name of the object it exposes for the machine

The code

GitHub
"""Browse an OPC UA server for a named object and print its variable values.

Usage:  python opcua_reader.py opc.tcp://192.168.0.20:4840/machine/ CNC-01
"""

import asyncio
import sys

from asyncua import Client


async def read_machine(url, object_name):
    async with Client(url=url) as client:
        objects = client.get_objects_node()
        children = await objects.get_children()

        target = None
        for child in children:
            name = (await child.read_browse_name()).Name
            if name == object_name:
                target = child
                break

        if target is None:
            names = [(await c.read_browse_name()).Name for c in children]
            sys.exit(f"'{object_name}' not found under Objects. Available: {names}")

        for var in await target.get_children():
            name = (await var.read_browse_name()).Name
            try:
                value = await var.read_value()
            except Exception as e:
                value = f"<not readable: {e}>"
            print(f"{name}: {value}")


def main():
    if len(sys.argv) < 3:
        sys.exit("Usage: python opcua_reader.py <endpoint-url> <object-name>")
    asyncio.run(read_machine(sys.argv[1], sys.argv[2]))


if __name__ == "__main__":
    main()

What you get

$ python opcua_reader.py opc.tcp://192.168.0.20:4840/machine/ CNC-01
ActualFeed: 320.5
SpindleSpeed: 4200
ExecutionStatus: ACTIVE
PartCount: 742
 
$ python opcua_reader.py opc.tcp://192.168.0.20:4840/machine/ CNC-99
'CNC-99' not found under Objects. Available: ['Locations', 'Server', 'Aliases', 'CNC-01']

How it works

  • client.get_objects_node() starts at OPC UA's standard Objects folder — every compliant server has one, which is what makes browsing-by-name portable across completely different PLCs and machine builders.
  • Matching on read_browse_name().Name instead of a NodeId is the whole point: NodeIds are server-specific and often numeric; browse names are the human-readable labels an integrator actually chose.
  • The second get_children() call — on the matched object, not on Objects — walks that machine's own variables. Nesting is exactly this simple in a flat OPC UA model; deeper hierarchies just repeat the pattern one level further down.
  • Failed reads are caught per-variable, not for the whole script — one write-only or momentarily bad node shouldn't stop you from seeing every other value.

Gotchas & honest limits

  • Server security matters more here than in FOCAS or MTConnect — this script uses Client(url=...) with no security policy, which many production OPC UA servers reject outright. Real machines usually need a certificate and a signed/encrypted policy; check your PLC vendor's OPC UA setup guide.
  • Browse names aren't guaranteed unique across a whole server, only within their parent — matching under Objects directly is safe; searching deeper trees needs a path-aware browse, not this flat loop.
  • Value types come back as native Python types via asyncua's automatic conversion — a UInt16 and a Double both just print as numbers, so if you need to distinguish them, read the VariantType instead of the bare value.
  • This is a read-only browser. Writing values (var.write_value(...)) uses the same node objects but is a bigger safety conversation — don't wire it to anything until you've read your server's write-access model.

Goes deeper

Want this adapted to your shop — or built into a real tool?

Samples are the free 80%. The last 20% is the part I do for a living.

Get in touch
Home
Blog
Tools
Code
Email
LinkedIn
Résumé