1
0
mirror of https://github.com/FreeOpcUa/opcua-asyncio synced 2025-10-29 17:07:18 +08:00

Allow Asynchronous Method Calls (#90)

Allow Asynchronous Method Calls
This commit is contained in:
chrisjbremner 2019-10-12 08:12:30 -07:00 committed by oroulet
parent 5b4bf6961b
commit e09c160192
4 changed files with 99 additions and 46 deletions

View File

@ -2,6 +2,8 @@
High level method related functions
"""
from asyncio import iscoroutinefunction
from asyncua import ua
@ -67,14 +69,32 @@ def uamethod(func):
Method decorator to automatically convert
arguments and output to and from variants
"""
def wrapper(parent, *args):
if iscoroutinefunction(func):
async def wrapper(parent, *args):
func_args = _format_call_inputs(parent, *args)
result = await func(*func_args)
return _format_call_outputs(result)
else:
def wrapper(parent, *args):
func_args = _format_call_inputs(parent, *args)
result = func(*func_args)
return _format_call_outputs(result)
return wrapper
def _format_call_inputs(parent, *args):
if isinstance(parent, ua.NodeId):
result = func(parent, *[arg.Value for arg in args])
return (parent, *[arg.Value for arg in args])
else:
self = parent
parent = args[0]
args = args[1:]
result = func(self, parent, *[arg.Value for arg in args])
return (self, parent, *[arg.Value for arg in args])
def _format_call_outputs(result):
if result is None:
return []
elif isinstance(result, ua.CallMethodResult):
@ -86,7 +106,6 @@ def uamethod(func):
return to_variant(*result)
else:
return to_variant(result)
return wrapper
def to_variant(*args):

View File

@ -2,6 +2,7 @@ import pickle
import shelve
import logging
import collections
from asyncio import iscoroutinefunction
from datetime import datetime
from asyncua import ua
@ -454,10 +455,10 @@ class MethodService:
async def call(self, methods):
results = []
for method in methods:
results.append(self._call(method))
results.append(await self._call(method))
return results
def _call(self, method):
async def _call(self, method):
self.logger.info("Calling: %s", method)
res = ua.CallMethodResult()
if method.ObjectId not in self._aspace or method.MethodId not in self._aspace:
@ -468,7 +469,10 @@ class MethodService:
res.StatusCode = ua.StatusCode(ua.StatusCodes.BadNothingToDo)
else:
try:
result = node.call(method.ObjectId, *method.InputArguments)
if iscoroutinefunction(node.call):
result = await node.call(method.ObjectId, *method.InputArguments)
else:
result = node.call(method.ObjectId, *method.InputArguments)
if isinstance(result, ua.CallMethodResult):
res = result
elif isinstance(result, ua.StatusCode):

View File

@ -1,75 +1,83 @@
import sys
sys.path.insert(0, "..")
import asyncio
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 asyncua import ua, uamethod, Server
# method to be exposed through server
def func(parent, variant):
print("func method call with parameters: ", variant.Value)
ret = False
if variant.Value % 2 == 0:
ret = True
return [ua.Variant(ret, ua.VariantType.Boolean)]
# method to be exposed through server
async def func_async(parent, variant):
if variant.Value % 2 == 0:
print("Sleeping asynchronously for 1 second")
await asyncio.sleep(1)
else:
print("Not sleeping!")
# 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__":
@uamethod
async def multiply_async(parent, x, y):
sleep_time = x * y
print(f"Sleeping asynchronously for {x * y} seconds")
await asyncio.sleep(sleep_time)
async def main():
# optional: setup logging
logging.basicConfig(level=logging.WARN)
#logger = logging.getLogger("asyncua.address_space")
# logger = logging.getLogger("asyncua.address_space")
# logger.setLevel(logging.DEBUG)
#logger = logging.getLogger("asyncua.internal_server")
# logger = logging.getLogger("asyncua.internal_server")
# logger.setLevel(logging.DEBUG)
#logger = logging.getLogger("asyncua.binary_server_asyncio")
# logger = logging.getLogger("asyncua.binary_server_asyncio")
# logger.setLevel(logging.DEBUG)
#logger = logging.getLogger("asyncua.uaprocessor")
# logger = logging.getLogger("asyncua.uaprocessor")
# logger.setLevel(logging.DEBUG)
#logger = logging.getLogger("asyncua.subscription_service")
# logger = logging.getLogger("asyncua.subscription_service")
# logger.setLevel(logging.DEBUG)
# now setup our server
server = Server()
#server.set_endpoint("opc.tcp://localhost:4840/freeopcua/server/")
await server.init()
# 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)
idx = await 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])
await objects.add_folder(idx, "myEmptyFolder")
myobj = await objects.add_object(idx, "MyObject")
myvar = await myobj.add_variable(idx, "MyVariable", 6.7)
await myvar.set_writable() # Set MyVariable to be writable by clients
myarrayvar = await myobj.add_variable(idx, "myarrayvar", [6.7, 7.9])
await myobj.add_variable(
idx, "myStronglytTypedVariable", ua.Variant([], ua.VariantType.UInt32)
)
await myobj.add_property(idx, "myproperty", "I am a property")
await myobj.add_method(idx, "mymethod", func, [ua.VariantType.Int64], [ua.VariantType.Boolean])
inargx = ua.Argument()
inargx.Name = "x"
@ -90,13 +98,17 @@ if __name__ == "__main__":
outarg.ArrayDimensions = []
outarg.Description = ua.LocalizedText("Multiplication result")
multiply_node = myobj.add_method(idx, "multiply", multiply, [inargx, inargy], [outarg])
await myobj.add_method(idx, "multiply", multiply, [inargx, inargy], [outarg])
await myobj.add_method(idx, "multiply_async", multiply_async, [inargx, inargy], [])
await myobj.add_method(idx, "func_async", func_async, [ua.VariantType.Int64], [])
# starting!
server.start()
print("Available loggers are: ", logging.Logger.manager.loggerDict.keys())
try:
async with server:
while True:
await asyncio.sleep(1)
embed()
finally:
server.stop()
if __name__ == "__main__":
loop = asyncio.get_event_loop()
# loop.set_debug(True)
loop.run_until_complete(main())
loop.close()

View File

@ -6,11 +6,13 @@ client side since we have been carefull to have the exact
same api on server and client side
"""
import pytest
import asyncio
from datetime import datetime
from datetime import timedelta
import math
import pytest
from asyncua import ua, uamethod, Node
from asyncua.common import ua_utils
from asyncua.common.methods import call_method_full
@ -80,6 +82,15 @@ async def add_server_methods(srv):
[ua.VariantType.Int64, ua.VariantType.Int64, ua.VariantType.Int64]
)
@uamethod
async def func6(parent):
await asyncio.sleep(0)
o = srv.get_objects_node()
await o.add_method(
ua.NodeId("ServerMethodAsync", 2), ua.QualifiedName('ServerMethodAsync', 2), func6, [], []
)
async def test_find_servers(opc):
servers = await opc.opc.find_servers()
@ -618,6 +629,13 @@ async def test_method_none(opc):
assert [] == result.OutputArguments
async def test_method_async(opc):
o = opc.opc.get_objects_node()
m = await o.get_child("2:ServerMethodAsync")
await o.call_method(m)
await call_method_full(o, m)
async def test_add_nodes(opc):
objects = opc.opc.get_objects_node()
f = await objects.add_folder(3, 'MyFolder')