Error calling OPC UA method

Hi. I got the following error when I try to call OPC UA method from Gateway script.

com.inductiveautomation.ignition.common.script.JythonExecException: Traceback (most recent call last): File "", line 1, in java.lang.ClassCastException: class org.eclipse.milo.opcua.sdk.client.nodes.UaVariableNode cannot be cast to class org.eclipse.milo.opcua.sdk.client.model.nodes.variables.PropertyTypeNode (org.eclipse.milo.opcua.sdk.client.nodes.UaVariableNode and org.eclipse.milo.opcua.sdk.client.model.nodes.variables.PropertyTypeNode are in unnamed module of loader com.inductiveautomation.ignition.gateway.modules.ModuleClassLoader @fa6080) at org.eclipse.milo.opcua.sdk.client.nodes.UaNode.lambda$null$15(UaNode.java:1064) at java.base/java.util.concurrent.CompletableFuture.uniApplyNow(Unknown Source) at java.base/java.util.concurrent.CompletableFuture.uniApplyStage(Unknown Source) at java.base/java.util.concurrent.CompletableFuture.thenApply(Unknown Source) at org.eclipse.milo.opcua.sdk.client.nodes.UaNode.lambda$null$16(UaNode.java:1064) at java.base/java.util.Optional.map(Unknown Source) at org.eclipse.milo.opcua.sdk.client.nodes.UaNode.lambda$null$17(UaNode.java:1061) at java.base/java.util.stream.ReferencePipeline$7$1.accept(Unknown Source) at java.base/java.util.stream.ReferencePipeline$2$1.accept(Unknown Source) at java.base/java.util.Spliterators$ArraySpliterator.tryAdvance(Unknown Source) at java.base/java.util.stream.ReferencePipeline.forEachWithCancel(Unknown Source) at java.base/java.util.stream.AbstractPipeline.copyIntoWithCancel(Unknown Source) at java.base/java.util.stream.AbstractPipeline.copyInto(Unknown Source) at java.base/java.util.stream.AbstractPipeline.wrapAndCopyInto(Unknown Source) at java.base/java.util.stream.FindOps$FindOp.evaluateSequential(Unknown Source) at java.base/java.util.stream.AbstractPipeline.evaluate(Unknown Source) at java.base/java.util.stream.ReferencePipeline.findFirst(Unknown Source) at org.eclipse.milo.opcua.sdk.client.nodes.UaNode.lambda$getPropertyNodeAsync$18(UaNode.java:1069) at java.base/java.util.concurrent.CompletableFuture$UniCompose.tryFire(Unknown Source) at java.base/java.util.concurrent.CompletableFuture.postComplete(Unknown Source) at java.base/java.util.concurrent.CompletableFuture.complete(Unknown Source) at org.eclipse.milo.opcua.stack.client.UaStackClient.lambda$deliverResponse$5(UaStackClient.java:318) at org.eclipse.milo.opcua.stack.core.util.ExecutionQueue$Task.run(ExecutionQueue.java:119) at java.base/java.util.concurrent.ThreadPoolExecutor.runWorker(Unknown Source) at java.base/java.util.concurrent.ThreadPoolExecutor$Worker.run(Unknown Source) at java.base/java.lang.Thread.run(Unknown Source) java.lang.ClassCastException: java.lang.ClassCastException: class org.eclipse.milo.opcua.sdk.client.nodes.UaVariableNode cannot be cast to class org.eclipse.milo.opcua.sdk.client.model.nodes.variables.PropertyTypeNode (org.eclipse.milo.opcua.sdk.client.nodes.UaVariableNode and org.eclipse.milo.opcua.sdk.client.model.nodes.variables.PropertyTypeNode are in unnamed module of loader com.inductiveautomation.ignition.gateway.modules.ModuleClassLoader @fa6080)

I tried to call the same method from UaExpert client and it worked successfully.
I used the following code to create the server and the methods.

import sys
sys.path.insert(0, "..")
import logging

try:
    from IPython import embed
except ImportError:
    import code

    def embed():
        vars = globals()
        vars.update(locals())
        shell = code.InteractiveConsole(vars)
        shell.interact()


from opcua import ua, uamethod, Server


# method to be exposed through server
def func(parent, variant):
    ret = False
    if variant.Value % 2 == 0:
        ret = True
    return [ua.Variant(ret, ua.VariantType.Boolean)]


# method to be exposed through server
# uses a decorator to automatically convert to and from variants

@uamethod
def multiply(parent, x, y):
    print("multiply method call with parameters: ", x, y)
    return x * y


if __name__ == "__main__":
    # optional: setup logging
    logging.basicConfig(level=logging.WARN)
    #logger = logging.getLogger("opcua.address_space")
    # logger.setLevel(logging.DEBUG)
    #logger = logging.getLogger("opcua.internal_server")
    # logger.setLevel(logging.DEBUG)
    #logger = logging.getLogger("opcua.binary_server_asyncio")
    # logger.setLevel(logging.DEBUG)
    #logger = logging.getLogger("opcua.uaprocessor")
    # logger.setLevel(logging.DEBUG)
    #logger = logging.getLogger("opcua.subscription_service")
    # logger.setLevel(logging.DEBUG)

    # now setup our server
    server = Server()
    #server.set_endpoint("opc.tcp://localhost:4840/freeopcua/server/")
    server.set_endpoint("opc.tcp://0.0.0.0:4840/freeopcua/server/")
    server.set_server_name("FreeOpcUa Example Server")

    # setup our own namespace
    uri = "http://examples.freeopcua.github.io"
    idx = server.register_namespace(uri)

    # get Objects node, this is where we should put our custom stuff
    objects = server.get_objects_node()

    # populating our address space
    myfolder = objects.add_folder(idx, "myEmptyFolder")
    myobj = objects.add_object(idx, "MyObject")
    myvar = myobj.add_variable(idx, "MyVariable", 6.7)
    myvar.set_writable()    # Set MyVariable to be writable by clients
    myarrayvar = myobj.add_variable(idx, "myarrayvar", [6.7, 7.9])
    myarrayvar = myobj.add_variable(idx, "myStronglytTypedVariable", ua.Variant([], ua.VariantType.UInt32))
    myprop = myobj.add_property(idx, "myproperty", "I am a property")
    mymethod = myobj.add_method(idx, "mymethod", func, [ua.VariantType.Int64], [ua.VariantType.Boolean])

    inargx = ua.Argument()
    inargx.Name = "x"
    inargx.DataType = ua.NodeId(ua.ObjectIds.Int64)
    inargx.ValueRank = -1
    inargx.ArrayDimensions = []
    inargx.Description = ua.LocalizedText("First number x")
    inargy = ua.Argument()
    inargy.Name = "y"
    inargy.DataType = ua.NodeId(ua.ObjectIds.Int64)
    inargy.ValueRank = -1
    inargy.ArrayDimensions = []
    inargy.Description = ua.LocalizedText("Second number y")
    outarg = ua.Argument()
    outarg.Name = "Result"
    outarg.DataType = ua.NodeId(ua.ObjectIds.Int64)
    outarg.ValueRank = -1
    outarg.ArrayDimensions = []
    outarg.Description = ua.LocalizedText("Multiplication result")

    multiply_node = myobj.add_method(idx, "multiply", multiply, [inargx, inargy], [outarg])

    # starting!
    server.start()
    print("Available loggers are: ", logging.Logger.manager.loggerDict.keys())
    try:

        embed()
    finally:
        server.stop()

and the script I used to call the method from Ignition Gateway tag change script is:

system.opcua.callMethod("PyServerMethods", "ns=2;i=2", "ns=2;i=10", [5, 3])

Yes, unfortunately this is due to a bug in FreeOpcUa that has not been fixed yet.

When we browse to discover the TypeDefinition of a Node FreeOpcUa returns no references, and this ends up forcing us to treat the Node as a base VariableNode, but in your case this is being called from some higher level code that expects correct modelling constraints.

It also manifests in simpler scenarios: [milo-dev] server status node has wrong type

The code in this gist reproduces the issue: Scratch.java · GitHub

Thank you Kevin for clearing things up. I need to ask as I have no much experience in this. Do you think if I tried with another OPC UA other than FreeOpcUa in Python (say on a machine), I would be able to call the methods from Ignition the same way normally, right?

Yes, it does generally work with other servers.

I reported the underlying problem to the FreeOpcUa people: Browsing for HasTypeDefinition references is broken · Issue #1529 · FreeOpcUa/opcua-asyncio · GitHub

Thanks again Kevin for the support. I would like to know if there is any free OPC UA that I can create methods in, and call them from Ignition to test and simulate before connect Ignition to the actual machine.

Apologies for posting on an old discussion, however as I am experiencing the same issue, I wanted to ask if there are any potential work arounds for this issue? I saw that the github issue has yet to be resolved by the developers over there. Any way to define the methods in asyncio such that ignition is able to call them?

If Ignition can never call asyncio methods, could I kindly ask for a suggestion as another OPCUA server that could be used for quick and dirty prototyping, or potentially whether functionality similar to methods can be achieved in some other way over OPCUA?

Many thanks for the help,
Felix

Hmm, without knowing what your prototyping needs are it's hard to recommend anything else.

What do you need besides the ability to create methods?

I just updated the issue I opened against opcua-asyncio with some triage I did to further identify the problem... I'm not hopeful that anyone over there is even paying attention, though.

We've got a fair amount liberty for the server (project is designing both the OPCUA server on device and ignition control/logging), and are still in very early prototyping stage so no particular software requirements for the OPCUA server other than it be fairly easy to modify and test. This was why we starting working with the Python one until we hit this snag, as it was super easy to have a device mock written in python that we could try to control from ignition.

Main requirement from the ignition side is ability to start a few different processes on the device running the OPCUA server that require a number of parameters (should generally be integers), that can be run in parallel.
Apart from that should mainly need to listen in on a variety of variables about the state of the device. Alarms would be a plus but honestly not necessary for a while. We're still very early in the process and not experienced with OPCUA, so if there's a guide on OPCUA design patterns for starting processes, ideally with some way of knowing when it concludes, that'd also be greatly appreciated.

Thank you very much for your help,
Felix

Your options are a little limited if free and open source are requirements of the SDK.

There's:

  • Eclipse Milo (Java)
  • opcua-asyncio (Python)
  • node-opcua (Javascript)
  • open62541 (C)
  • OPC Foundation's .NET Standard (C# / .NET)

I maintain the Eclipse Milo project, and it's what we use in Ignition, but I hesitate to recommend it right now because I'm doing a lot of work towards a 1.0 version and the APIs aren't stable until then. Alarms are technically possible, but would require significant legwork, as they aren't implemented in a first-class manner by the SDK.

Fantastic, thank you for the help