freeopcua call method input args | Python parse array to list of inputargs

1.7k views Asked by At

in order to test this you will need the freeopcua library. I want to offer the user a list of methods available on the server. The user can detect which methods exist. (through an enum)

All these functions have a variable amount of input args and output args.

now with freeopcua you call a method like

node.call_method("2:myMethod1", 1,2,3,4)

However what I have available is [1,2,3,4]. (is thats the user input I get) Would there be a way to parse this so it fits as myMethod arguments?

Minimal code to run the issue (not at all my code but it will give the idea of where I want to go:

myServer.py: (Only needed to have the methods no issue in here)

from opcua import Server, ua, uamethod
from enum import Enum


class methods(Enum):
    add = "add"
    multi = "more"
    person = "notInt"


class myServer(Server):
    def __init__(self):
        Server.__init__(self)
        self.set_endpoint("opc.tcp://0.0.0.0:4840/freeopcua/server/")
        self.set_server_name("FreeOpcUa Example Server")
        uri = "http://examples.freeopcua.github.io"
        self.idx = self.register_namespace(uri)


        # Automatically creates server methods of the methods I promise to offer
        for mymethod in methods:
            args = self.methodCreator(mymethod)
            args[1]
            self.nodes.objects.add_method(args[0], args[1], args[2], args[3], args[4])

        self.start()

    def methodCreator(self, method_type):
        inargs = None
        outargs = None
        method = None

        if method_type == methods.add:
            inargs = []
            inarg = ua.Argument()
            inarg.Name = "first_number"
            inargs.append(inarg)
            inarg = ua.Argument()
            inarg.Name = "second_number"
            inargs.append(inarg)
            method = self.multi
            return [2, method_type.value, method, inargs, outargs]

        elif method_type == methods.multi:
            inargs = []
            inarg = ua.Argument()
            inarg.Name = "first_number"
            inargs.append(inarg)
            inarg = ua.Argument()
            inarg.Name = "second_number"
            inargs.append(inarg)
            inarg = ua.Argument()
            inarg.Name = "third_number"
            inargs.append(inarg)
            method = self.add
            return [2, method_type.value, method, inargs, outargs]

        elif method_type == methods.person:
            inargs = []
            inarg = ua.Argument()
            inarg.Name = "Name"
            inargs.append(inarg)
            inarg = ua.Argument()
            inarg.Name = "Age"
            inargs.append(inarg)
            inarg = ua.Argument()
            inarg.Name = "Surname"
            inargs.append(inarg)
            inarg = ua.Argument()
            inarg.Name = "Job"
            inargs.append(inarg)
            method = self.person
            return [2, method_type.value, method, inargs, outargs]

    @uamethod
    def add(self, parent, x, y):
        print(x+y)

    @uamethod
    def multi(self, parentm, x, y, z):
        print(x*y*z)

    @uamethod
    def person(self, parent, name, age, surname, job):
        print("I'm %s %s I'm %s years old and I do %s" % (name, surname, age, job))    

Now the file it's all about:

myClient.py

from stack.server import myServer, methods
from opcua import Client

class myClient(Client):
    def call_functions(self):
        print("Implemented methods:")
        values = []
        for method in methods:
            print(method.value)
            values.append(method.value)
        #In my real code I check input but here I'll trust the user
        method = input("Select a method please: \n")

        objects = self.nodes.objects
        inarguments = objects.get_child(["2:" + method, "0:InputArguments"]).get_value()
        inargs = []
        for argument in inarguments:
            inargs.append(input("%s: " % argument.Name))
        # I disabled some methods to make sure I just need to show one case
        if method == 'notInt':
            print("Desired")
            objects.call_method("2:" + method, inargs[0], inargs[1], inargs[2], inargs[3])
            print("error")
            objects.call_method("2:" + method, inargs) # This is the line that wont work

server = myServer()
with myClient("opc.tcp://localhost:4840/freeopcua/server/") as client:
    client.call_functions()
server.stop()

So when I want to call the method generic like:

objects.call_method("2:" + method, inargs) 

Which for 'notInt' would have the desired output as if I did:

objects.call_method("2:" + method, inargs[0], inargs[1], inargs[2], inargs[3])

Is there a way in python to get this parsed from array to list of input args seperated by ,? So that I can keep my generic way to call each method? Or in freeopcua is there a way to get the desired affect (keep in mind that I use the argument names to ask the user for his input so just making it take a list as input wont be a sollution)

1

There are 1 answers

0
davy gillebert On

I've searched a bit. And yesterday at a meeting with some friends I discussed the issue. They made a point about *args and that I should investigate if that path works. And it does. To solve my issue I only had to add an * in front of my list of client responses and it just as promised in the shared link unboxes it and sends it to the server as being all individual arguments instead of 1 list object. My search keywords were wrong the other day. Anyway resolved by just doing this in de myClient.py

from stack.server import myServer, methods
from opcua import Client

class myClient(Client):
    def call_functions(self):
        print("Implemented methods:")
        values = []
        for method in methods:
            print(method.value)
            values.append(method.value)
        #In my real code I check input but here I'll trust the user
        method = input("Select a method please: \n")

        objects = self.nodes.objects
        inarguments = objects.get_child(["2:" + method, "0:InputArguments"]).get_value()
        inargs = []
        for argument in inarguments:
            inargs.append(input("%s: " % argument.Name))
        # I disabled some methods to make sure I just need to show one case
        if method == 'notInt':
            print("Desired")
            objects.call_method("2:" + method, inargs[0], inargs[1], inargs[2], inargs[3])
            print("error")
            objects.call_method("2:" + method, *inargs) # This will now work

server = myServer()
with myClient("opc.tcp://localhost:4840/freeopcua/server/") as client:
    client.call_functions()
server.stop()

Now all I need to do is remove the enum so the client asks this to the server and then I have a client that is generic for whatever function I add to my opcua-server my client code will be able to ask which are available, choose one and ask for the arguments without needing additional code. Gotta love python!