Skip to content

Methods

Demonstrates how to expose callable methods on the server that OPC UA clients can invoke remotely.

Topics covered:

  • Simple method with no arguments
  • Method with input and output arguments
  • Method with multiple inputs/outputs
  • Organising methods under objects
  • Error handling in method callbacks

Connect with any OPC UA client at: opc.tcp://localhost:4840

1. Method with no arguments

Methods with no arguments are useful for triggering a simple action, like a system reset. A method with no outputs still returns a one-item tuple containing just the status code.

print("--- 1. Method with no arguments ---")

def reset(node):
    """Reset the server-side counter."""
    print("Reset called")
    return (o6.StatusCode.GOOD,)

server.addMethod(
    name="Reset",
    parent=calculator,
    callback=reset,
    nodeId="ns=1;i=2001",
)

2. Method with multiple inputs

Each method argument is described with an ns0.datatypes.Argument structure: a name, an OPC UA data type (e.g. o6.Double), and a description. Multiple inputs are simply multiple Argument entries in inputArgs.

print("\n--- 2. Method with multiple inputs ---")

def add(node, a, b):
    """Add two doubles."""
    result = a + b
    print(f"Add({a}, {b}) = {result}")
    return (o6.StatusCode.GOOD, result)

server.addMethod(
    name="Add",
    parent=calculator,
    callback=add,
    inputArgs=[
        ns0.datatypes.Argument(
            name="A",
            dataType=o6.Double,
            valueRank=o6.ValueRank.SCALAR,
            description="First operand",
        ),
        ns0.datatypes.Argument(
            name="B",
            dataType=o6.Double,
            valueRank=o6.ValueRank.SCALAR,
            description="Second operand",
        ),
    ],
    outputArgs=[
        ns0.datatypes.Argument(
            name="Sum", dataType=o6.Double, valueRank=o6.ValueRank.SCALAR, description="A + B"
        ),
    ],
    nodeId="ns=1;i=2002",
)

3. String Method

Method arguments aren't limited to numbers — this method takes and returns an OPC UA String (o6.String).

print("\n--- 3. String Method ---")

def greet(node, name):
    """Return a greeting string."""
    message = f"Hello, {name}!"
    print(f"Greet('{name}') -> '{message}'")
    return (o6.StatusCode.GOOD, message)

server.addMethod(
    "Greet",
    calculator,
    greet,
    inputArgs=[
        ns0.datatypes.Argument(
            name="Name",
            dataType=o6.String,
            valueRank=o6.ValueRank.SCALAR,
            description="Name to greet",
        ),
    ],
    outputArgs=[
        ns0.datatypes.Argument(
            name="Greeting",
            dataType=o6.String,
            valueRank=o6.ValueRank.SCALAR,
            description="Greeting message",
        ),
    ],
    nodeId="ns=1;i=2003",
)

4. Method with multiple outputs

Multiple outputs work the same way as multiple inputs: list each one as its own Argument in outputArgs. Divide returns both a quotient and a remainder.

print("\n--- 4. Method with multiple outputs ---")

def divide(node, a, b):
    """Integer division returning quotient and remainder."""
    if b == 0:
        print("Divide: division by zero")
        return (o6.StatusCode.BAD_INVALID_ARGUMENT,)
    quotient = int(a // b)
    remainder = a - quotient * b
    print(f"Divide({a}, {b}) -> quotient={quotient}, remainder={remainder}")
    return (o6.StatusCode.GOOD, quotient, remainder)

server.addMethod(
    "Divide",
    calculator,
    divide,
    inputArgs=[
        ns0.datatypes.Argument(
            name="Dividend",
            dataType=o6.Double,
            valueRank=o6.ValueRank.SCALAR,
            description="Dividend",
        ),
        ns0.datatypes.Argument(
            name="Divisor",
            dataType=o6.Double,
            valueRank=o6.ValueRank.SCALAR,
            description="Divisor",
        ),
    ],
    outputArgs=[
        ns0.datatypes.Argument(
            name="Quotient",
            dataType=o6.Double,
            valueRank=o6.ValueRank.SCALAR,
            description="Integer quotient",
        ),
        ns0.datatypes.Argument(
            name="Remainder",
            dataType=o6.Double,
            valueRank=o6.ValueRank.SCALAR,
            description="Remainder",
        ),
    ],
    nodeId="ns=1;i=2004",
)

Source Code

Download S06_methods.py to run it locally.