OPC-UA client : python3 + AsyncUA fixes
authorEdouard Tisserant <edouard.tisserant@gmail.com>
Sun, 18 Jun 2023 16:28:42 +0200
changeset 3820 46f3ca3f0157
parent 3819 4582f0fcf4c4
child 3821 f7bc4e5832be
OPC-UA client : python3 + AsyncUA fixes
opc_ua/client.py
opc_ua/opcua_client_maker.py
tests/cli_tests/opcua_test.bash
tests/cli_tests/opcua_test_encrypted.bash
tests/ide_tests/opcua_browse.sikuli/opcua_browse.py
tests/ide_tests/opcua_browse.sikuli/opcua_service.bash
tests/ide_tests/opcua_browse_encrypted.sikuli/opcua_browse_encrypted.py
tests/ide_tests/opcua_browse_encrypted.sikuli/opcua_service.bash
tests/projects/opcua_client_encrypted/opcua_0@opcua/confnode.xml
tests/tools/Docker/Dockerfile
--- a/opc_ua/client.py	Wed May 31 23:16:29 2023 +0200
+++ b/opc_ua/client.py	Sun Jun 18 16:28:42 2023 +0200
@@ -142,7 +142,7 @@
         c_code = '#include "beremiz.h"\n'
         c_code += self.modeldata.GenerateC(c_path, locstr, self.GetConfig())
 
-        with open(c_path, 'wb') as c_file:
+        with open(c_path, 'w') as c_file:
             c_file.write(c_code)
 
         LDFLAGS = ['"' + os.path.join(Open62541LibraryPath, "libopen62541.a") + '"', '-lcrypto']
--- a/opc_ua/opcua_client_maker.py	Wed May 31 23:16:29 2023 +0200
+++ b/opc_ua/opcua_client_maker.py	Sun Jun 18 16:28:42 2023 +0200
@@ -2,9 +2,12 @@
 
 
 import csv
-
-from opcua import Client
-from opcua import ua
+import asyncio
+import functools
+from threading import Thread
+
+from asyncua import Client
+from asyncua import ua
 
 import wx
 import wx.lib.gizmos as gizmos  # Formerly wx.gizmos in Classic
@@ -182,33 +185,28 @@
         #             splitter.        panel.      splitter
         ClientPanel = self.GetParent().GetParent().GetParent()
         nodes = ClientPanel.GetSelectedNodes()
-        for node in nodes:
-            cname = node.get_node_class().name
-            dname = node.get_display_name().Text
-            if cname != "Variable":
-                self.log("Node {} ignored (not a variable)".format(dname))
+        for node, properties in nodes:
+            if properties.cname != "Variable":
+                self.log("Node {} ignored (not a variable)".format(properties.dname))
                 continue
 
-            tname = node.get_data_type_as_variant_type().name
+            tname = properties.variant_type
             if tname not in UA_IEC_types:
-                self.log("Node {} ignored (unsupported type)".format(dname))
+                self.log("Node {} ignored (unsupported type)".format(properties.dname))
                 continue
 
-            access = node.get_access_level()
             if {"input":ua.AccessLevel.CurrentRead,
-                "output":ua.AccessLevel.CurrentWrite}[self.direction] not in access:
-                self.log("Node {} ignored because of insuficient access rights".format(dname))
+                "output":ua.AccessLevel.CurrentWrite}[self.direction] not in properties.access:
+                self.log("Node {} ignored because of insuficient access rights".format(properties.dname))
                 continue
 
-            nsid = node.nodeid.NamespaceIndex
-            nid =  node.nodeid.Identifier
-            nid_type =  type(nid).__name__
-            iecid = nid
-
-            value = [dname,
-                     nsid,
+            nid_type =  type(properties.nid).__name__
+            iecid = properties.nid
+
+            value = [properties.dname,
+                     properties.nsid,
                      nid_type,
-                     nid,
+                     properties.nid,
                      tname,
                      iecid]
             self.model.AddRow(value)
@@ -239,18 +237,41 @@
     smileidx    = il.Add(wx.ArtProvider.GetBitmap(wx.ART_ADD_BOOKMARK, wx.ART_OTHER, isz))
 
 
+AsyncUAClientLoop = None
+def AsyncUAClientLoopProc():
+    asyncio.set_event_loop(AsyncUAClientLoop)
+    AsyncUAClientLoop.run_forever()
+
+def ExecuteSychronously(func, timeout=1):
+    def AsyncSychronizer(*args, **kwargs):
+        global AsyncUAClientLoop
+        # create asyncio loop
+        if AsyncUAClientLoop is None:
+            AsyncUAClientLoop = asyncio.new_event_loop()
+            Thread(target=AsyncUAClientLoopProc, daemon=True).start()
+        # schedule work in this loop
+        future = asyncio.run_coroutine_threadsafe(func(*args, **kwargs), AsyncUAClientLoop)
+        # wait max 5sec until connection completed
+        return future.result(timeout)
+    return AsyncSychronizer
+
+def ExecuteSychronouslyWithTimeout(timeout):
+    return functools.partial(ExecuteSychronously,timeout=timeout)
+
+
 class OPCUAClientPanel(wx.SplitterWindow):
     def __init__(self, parent, modeldata, log, config_getter):
         self.log = log
         wx.SplitterWindow.__init__(self, parent, -1)
 
-        self.ordered_nodes = []
+        self.ordered_nps = []
 
         self.inout_panel = wx.Panel(self)
         self.inout_sizer = wx.FlexGridSizer(cols=1, hgap=0, rows=2, vgap=0)
         self.inout_sizer.AddGrowableCol(0)
         self.inout_sizer.AddGrowableRow(1)
 
+        self.clientloop = None
         self.client = None
         self.config_getter = config_getter
 
@@ -279,31 +300,67 @@
 
     def OnClose(self):
         if self.client is not None:
-            self.client.disconnect()
+            asyncio.run(self.client.disconnect())
             self.client = None
 
     def __del__(self):
         self.OnClose()
 
+    async def GetAsyncUANodeProperties(self, node):
+        properties = type("UANodeProperties",(),dict(
+                nsid = node.nodeid.NamespaceIndex,
+                nid =  node.nodeid.Identifier,
+                dname = (await node.read_display_name()).Text,
+                cname = (await node.read_node_class()).name,
+            ))
+        if properties.cname == "Variable":
+            properties.access = await node.get_access_level()
+            properties.variant_type = (await node.read_data_type_as_variant_type()).name
+        return properties
+
+    @ExecuteSychronouslyWithTimeout(5)
+    async def ConnectAsyncUAClient(self, config):
+        client = Client(config["URI"])
+        
+        AuthType = config["AuthType"]
+        if AuthType=="UserPasword":
+            await client.set_user(config["User"])
+            await client.set_password(config["Password"])
+        elif AuthType=="x509":
+            await client.set_security_string(
+                "{Policy},{Mode},{Certificate},{PrivateKey}".format(**config))
+
+        await client.connect()
+        self.client = client
+
+        # load definition of server specific structures/extension objects
+        await self.client.load_type_definitions()
+
+        # returns root node object and its properties
+        rootnode = self.client.get_root_node()
+        return rootnode, await self.GetAsyncUANodeProperties(rootnode)
+
+    @ExecuteSychronously
+    async def DisconnectAsyncUAClient(self):
+        if self.client is not None:
+            await self.client.disconnect()
+            self.client = None
+
+    @ExecuteSychronously
+    async def GetAsyncUANodeChildren(self, node):
+        children = await node.get_children()
+        return [ (child, await self.GetAsyncUANodeProperties(child)) for child in children]
+
     def OnConnectButton(self, event):
         if self.connect_button.GetValue():
             
             config = self.config_getter()
-            self.client = Client(config["URI"])
             self.log("OPCUA browser: connecting to {}\n".format(config["URI"]))
             
-            AuthType = config["AuthType"]
-            if AuthType=="UserPasword":
-                self.client.set_user(config["User"])
-                self.client.set_password(config["Password"])
-            elif AuthType=="x509":
-                self.client.set_security_string(
-                    "{Policy},{Mode},{Certificate},{PrivateKey}".format(**config))
-
             try :
-                self.client.connect()
+                rootnode, rootnodeproperties = self.ConnectAsyncUAClient(config)
             except Exception as e:
-                self.log("OPCUA browser: "+str(e)+"\n")
+                self.log("Exception in OPCUA browser: "+repr(e)+"\n")
                 self.client = None
                 self.connect_button.SetValue(False)
                 return
@@ -328,10 +385,7 @@
 
             self.tree.SetMainColumn(0)
 
-            self.client.load_type_definitions()  # load definition of server specific structures/extension objects
-            rootnode = self.client.get_root_node()
-
-            rootitem = self.AddNodeItem(self.tree.AddRoot, rootnode)
+            rootitem = self.AddNodeItem(self.tree.AddRoot, rootnode, rootnodeproperties)
 
             # Populate first level so that root can be expanded
             self.CreateSubItems(rootitem)
@@ -343,7 +397,7 @@
 
             self.tree.Expand(rootitem)
 
-            hint = wx.StaticText(self, label = "Drag'n'drop desired variables from tree to Input or Output list")
+            hint = wx.StaticText(self.tree_panel, label = "Drag'n'drop desired variables from tree to Input or Output list")
 
             self.tree_sizer.Add(self.tree, flag=wx.GROW)
             self.tree_sizer.Add(hint, flag=wx.GROW)
@@ -353,29 +407,23 @@
 
             self.SplitVertically(self.tree_panel, self.inout_panel, 500)
         else:
-            self.client.disconnect()
-            self.client = None
+            self.DisconnectAsyncUAClient()
             self.Unsplit(self.tree_panel)
             self.tree_panel.Destroy()
 
-
     def CreateSubItems(self, item):
-        node, browsed = self.tree.GetPyData(item)
+        node, properties, browsed = self.tree.GetPyData(item)
         if not browsed:
-            for subnode in node.get_children():
-                self.AddNodeItem(lambda n: self.tree.AppendItem(item, n), subnode)
-            self.tree.SetPyData(item,(node, True))
-
-    def AddNodeItem(self, item_creation_func, node):
-        nsid = node.nodeid.NamespaceIndex
-        nid =  node.nodeid.Identifier
-        dname = node.get_display_name().Text
-        cname = node.get_node_class().name
-
-        item = item_creation_func(dname)
-
-        if cname == "Variable":
-            access = node.get_access_level()
+            children = self.GetAsyncUANodeChildren(node)
+            for subnode, subproperties in children:
+                self.AddNodeItem(lambda n: self.tree.AppendItem(item, n), subnode, subproperties)
+            self.tree.SetPyData(item,(node, properties, True))
+
+    def AddNodeItem(self, item_creation_func, node, properties):
+        item = item_creation_func(properties.dname)
+
+        if properties.cname == "Variable":
+            access = properties.access
             normalidx = fileidx
             r = ua.AccessLevel.CurrentRead in access
             w = ua.AccessLevel.CurrentWrite in access
@@ -387,14 +435,14 @@
                 ext = "WO"  # not sure this one exist
             else:
                 ext = "no access"  # not sure this one exist
-            cname = "Var "+node.get_data_type_as_variant_type().name+" (" + ext + ")"
+            cname = "Var "+properties.variant_type+" (" + ext + ")"
         else:
             normalidx = fldridx
 
-        self.tree.SetPyData(item,(node, False))
-        self.tree.SetItemText(item, cname, 1)
-        self.tree.SetItemText(item, str(nsid), 2)
-        self.tree.SetItemText(item, type(nid).__name__+": "+str(nid), 3)
+        self.tree.SetPyData(item,(node, properties, False))
+        self.tree.SetItemText(item, properties.cname, 1)
+        self.tree.SetItemText(item, str(properties.nsid), 2)
+        self.tree.SetItemText(item, type(properties.nid).__name__+": "+str(properties.nid), 3)
         self.tree.SetItemImage(item, normalidx, which = wx.TreeItemIcon_Normal)
         self.tree.SetItemImage(item, fldropenidx, which = wx.TreeItemIcon_Expanded)
 
@@ -412,28 +460,28 @@
         items = self.tree.GetSelections()
         items_pydata = [self.tree.GetPyData(item) for item in items]
 
-        nodes = [node for node, _unused in items_pydata]
+        nps = [(node,properties) for node, properties, unused in items_pydata]
 
         # append new nodes to ordered list
-        for node in nodes:
-            if node not in self.ordered_nodes:
-                self.ordered_nodes.append(node)
+        for np in nps:
+            if np not in self.ordered_nps:
+                self.ordered_nps.append(np)
 
         # filter out vanished items
-        self.ordered_nodes = [
-            node 
-            for node in self.ordered_nodes 
-            if node in nodes]
+        self.ordered_nps = [
+            np 
+            for np in self.ordered_nps 
+            if np in nps]
 
     def GetSelectedNodes(self):
-        return self.ordered_nodes 
+        return self.ordered_nps 
 
     def OnTreeBeginDrag(self, event):
         """
         Called when a drag is started in tree
         @param event: wx.TreeEvent
         """
-        if self.ordered_nodes:
+        if self.ordered_nps:
             # Just send a recognizable mime-type, drop destination
             # will get python data from parent
             data = wx.CustomDataObject(OPCUAClientDndMagicWord)
@@ -496,7 +544,7 @@
             self[direction] = OPCUAClientList(log, change_callback)
 
     def LoadCSV(self,path):
-        with open(path, 'rb') as csvfile:
+        with open(path, 'r') as csvfile:
             reader = csv.reader(csvfile, delimiter=',', quotechar='"')
             buf = {direction:[] for direction, _model in self.items()}
             for direction, model in self.items():
@@ -507,7 +555,7 @@
                 list.append(self[direction],row[1:])
 
     def SaveCSV(self,path):
-        with open(path, 'wb') as csvfile:
+        with open(path, 'w') as csvfile:
             for direction, data in self.items():
                 writer = csv.writer(csvfile, delimiter=',',
                                 quotechar='"', quoting=csv.QUOTE_MINIMAL)
@@ -806,7 +854,7 @@
 }
 """
 
-            with open(path, 'wb') as Cfile:
+            with open(path, 'w') as Cfile:
                 Cfile.write(Ccode)
 
 
--- a/tests/cli_tests/opcua_test.bash	Wed May 31 23:16:29 2023 +0200
+++ b/tests/cli_tests/opcua_test.bash	Sun Jun 18 16:28:42 2023 +0200
@@ -19,33 +19,38 @@
 import sys
 import os
 import time
+import asyncio
 
-from opcua import ua, Server
+from asyncua import Server
 
-server = Server()
-host = os.environ.get("OPCUA_DEFAULT_HOST", "127.0.0.1")
-endpoint = "opc.tcp://"+host+":4840/freeopcua/server/"
-server.set_endpoint(endpoint)
+async def main():
+    server = Server()
+    host = os.environ.get("OPCUA_DEFAULT_HOST", "127.0.0.1")
+    endpoint = "opc.tcp://"+host+":4840/freeopcua/server/"
+    await server.init()
+    server.set_endpoint(endpoint)
 
-uri = "http://beremiz.github.io"
-idx = server.register_namespace(uri)
+    uri = "http://beremiz.github.io"
+    idx = await server.register_namespace(uri)
 
-objects = server.get_objects_node()
+    objects = server.get_objects_node()
 
-testobj = objects.add_object(idx, "TestObject")
-testvarout = testobj.add_variable(idx, "TestOut", 1.2)
-testvar = testobj.add_variable(idx, "TestIn", 5.6)
-testvar.set_writable()
+    testobj = await objects.add_object(idx, "TestObject")
+    testvarout = await testobj.add_variable(idx, "TestOut", 1.2)
+    testvar = await testobj.add_variable(idx, "TestIn", 5.6)
+    await testvar.set_writable()
 
-server.start()
+    await server.start()
+    try:
+        while True:
+            await asyncio.sleep(1)
+            print(await testvar.get_value())
+            sys.stdout.flush()
+    finally:
+        await server.stop()
 
-try:
-    while True:
-        time.sleep(1)
-        print testvar.get_value()
-        sys.stdout.flush()
-finally:
-    server.stop()
+asyncio.run(main())
+
 EOF
 SERVER_PID=$!
 
--- a/tests/cli_tests/opcua_test_encrypted.bash	Wed May 31 23:16:29 2023 +0200
+++ b/tests/cli_tests/opcua_test_encrypted.bash	Sun Jun 18 16:28:42 2023 +0200
@@ -24,37 +24,51 @@
 import sys
 import os
 import time
+import asyncio
 
-from opcua import ua, Server
+from asyncua import ua, Server
+from asyncua.server.users import User, UserRole
 
-server = Server()
-host = os.environ.get("OPCUA_DEFAULT_HOST", "127.0.0.1")
-endpoint = "opc.tcp://"+host+":4840/freeopcua/server/"
-server.set_endpoint(endpoint)
+# Asyncua can't work without (over)simple shared cerificates/privkey.
+# No user is involved in that case, but asyncua needs it.
+# Over permessive User Manager hereafter helps cuting that corner.
+class AllAdminUserManager:
+    def get_user(self, iserver, username=None, password=None, certificate=None):
+        return User(role=UserRole.Admin)
 
-server.set_security_policy([ua.SecurityPolicyType.Basic256Sha256_SignAndEncrypt])
-server.load_certificate("my_cert.der")
-server.load_private_key("my_private_key.pem")
+async def main():
+    server = Server(user_manager=AllAdminUserManager())
+    host = os.environ.get("OPCUA_DEFAULT_HOST", "127.0.0.1")
+    endpoint = "opc.tcp://"+host+":4840/freeopcua/server/"
+    await server.init()
+    server.set_endpoint(endpoint)
 
-uri = "http://beremiz.github.io"
-idx = server.register_namespace(uri)
+    server.set_security_policy([ua.SecurityPolicyType.Basic256Sha256_SignAndEncrypt])
+    await server.load_certificate("my_cert.der")
+    await server.load_private_key("my_private_key.pem")
 
-objects = server.get_objects_node()
+    uri = "http://beremiz.github.io"
+    idx = await server.register_namespace(uri)
 
-testobj = objects.add_object(idx, "TestObject")
-testvarout = testobj.add_variable(idx, "TestOut", 1.2)
-testvar = testobj.add_variable(idx, "TestIn", 5.6)
-testvar.set_writable()
+    objects = server.get_objects_node()
 
-server.start()
+    testobj = await objects.add_object(idx, "TestObject")
+    testvarout = await testobj.add_variable(idx, "TestOut", 1.2)
+    testvar = await testobj.add_variable(idx, "TestIn", 5.6)
+    await testvar.set_writable()
 
-try:
-    while True:
-        time.sleep(1)
-        print testvar.get_value()
-        sys.stdout.flush()
-finally:
-    server.stop()
+    await server.start()
+
+    try:
+        while True:
+            await asyncio.sleep(1)
+            print(await testvar.get_value())
+            sys.stdout.flush()
+    finally:
+        await server.stop()
+
+asyncio.run(main())
+
 EOF
 SERVER_PID=$!
 
--- a/tests/ide_tests/opcua_browse.sikuli/opcua_browse.py	Wed May 31 23:16:29 2023 +0200
+++ b/tests/ide_tests/opcua_browse.sikuli/opcua_browse.py	Sun Jun 18 16:28:42 2023 +0200
@@ -29,7 +29,7 @@
 
     app.doubleClick("TestObject")
 
-    app.dragNdrop(["TestIn", "Testln"], "output variables")
+    app.dragNdrop(["TestIn", "Testln","Testin"], "output variables")
 
     app.wait(1)
 
--- a/tests/ide_tests/opcua_browse.sikuli/opcua_service.bash	Wed May 31 23:16:29 2023 +0200
+++ b/tests/ide_tests/opcua_browse.sikuli/opcua_service.bash	Sun Jun 18 16:28:42 2023 +0200
@@ -8,33 +8,38 @@
 import sys
 import os
 import time
+import asyncio
 
-from opcua import ua, Server
+from asyncua import Server
 
-server = Server()
-host = os.environ.get("OPCUA_DEFAULT_HOST", "127.0.0.1")
-endpoint = "opc.tcp://"+host+":4840/freeopcua/server/"
-server.set_endpoint(endpoint)
+async def main():
+    server = Server()
+    host = os.environ.get("OPCUA_DEFAULT_HOST", "127.0.0.1")
+    endpoint = "opc.tcp://"+host+":4840/freeopcua/server/"
+    await server.init()
+    server.set_endpoint(endpoint)
 
-uri = "http://beremiz.github.io"
-idx = server.register_namespace(uri)
+    uri = "http://beremiz.github.io"
+    idx = await server.register_namespace(uri)
 
-objects = server.get_objects_node()
+    objects = server.get_objects_node()
 
-testobj = objects.add_object(idx, "TestObject")
-testvarout = testobj.add_variable(idx, "TestOut", 1.2)
-testvar = testobj.add_variable(idx, "TestIn", 5.6)
-testvar.set_writable()
+    testobj = await objects.add_object(idx, "TestObject")
+    testvarout = await testobj.add_variable(idx, "TestOut", 1.2)
+    testvar = await testobj.add_variable(idx, "TestIn", 5.6)
+    await testvar.set_writable()
 
-server.start()
+    await server.start()
+    try:
+        while True:
+            await asyncio.sleep(1)
+            inval = await testvar.get_value()
+            print(inval)
+            await testvarout.set_value(inval*2)
+            sys.stdout.flush()
+    finally:
+        await server.stop()
 
-try:
-    while True:
-        time.sleep(1)
-        inval=testvar.get_value()
-        print inval
-        testvarout.set_value(inval*2)
-        sys.stdout.flush()
-finally:
-    server.stop()
+asyncio.run(main())
+
 EOF
--- a/tests/ide_tests/opcua_browse_encrypted.sikuli/opcua_browse_encrypted.py	Wed May 31 23:16:29 2023 +0200
+++ b/tests/ide_tests/opcua_browse_encrypted.sikuli/opcua_browse_encrypted.py	Sun Jun 18 16:28:42 2023 +0200
@@ -31,7 +31,7 @@
 
     app.doubleClick("TestObject")
 
-    app.dragNdrop(["TestIn", "Testln"], "output variables")
+    app.dragNdrop(["TestIn", "Testln", "Testin"], "output variables")
 
     app.wait(1)
 
--- a/tests/ide_tests/opcua_browse_encrypted.sikuli/opcua_service.bash	Wed May 31 23:16:29 2023 +0200
+++ b/tests/ide_tests/opcua_browse_encrypted.sikuli/opcua_service.bash	Sun Jun 18 16:28:42 2023 +0200
@@ -1,13 +1,17 @@
 #!/bin/bash
 
+set -x -e
+
 echo "Instant encrypted OPC-UA server for test"
 
+rm -f my_cert.pem my_cert.der my_private_key.pem
+
 yes "" | openssl req -x509 -newkey rsa:2048 -keyout my_private_key.pem -out my_cert.pem \
         -days 355 -nodes -addext "subjectAltName = URI:urn:example.org:FreeOpcUa:python-opcua"
 openssl x509 -outform der -in my_cert.pem -out my_cert.der
 
 PROJECT_FILES_DIR=$BEREMIZPATH/tests/projects/opcua_browse_encrypted/project_files
-mkdir $PROJECT_FILES_DIR
+mkdir $PROJECT_FILES_DIR -p
 cp my_cert.der my_private_key.pem $PROJECT_FILES_DIR
 
 echo "CERTS READY"
@@ -18,37 +22,51 @@
 import sys
 import os
 import time
+import asyncio
 
-from opcua import ua, Server
+from asyncua import ua, Server
+from asyncua.server.users import User, UserRole
 
-server = Server()
-host = os.environ.get("OPCUA_DEFAULT_HOST", "127.0.0.1")
-endpoint = "opc.tcp://"+host+":4840/freeopcua/server/"
-server.set_endpoint(endpoint)
+# Asyncua can't work without (over)simple shared cerificates/privkey.
+# No user is involved in that case, but asyncua needs it.
+# Over permessive User Manager hereafter helps cuting that corner.
+class AllAdminUserManager:
+    def get_user(self, iserver, username=None, password=None, certificate=None):
+        return User(role=UserRole.Admin)
 
-server.set_security_policy([ua.SecurityPolicyType.Basic256Sha256_SignAndEncrypt])
-server.load_certificate("my_cert.der")
-server.load_private_key("my_private_key.pem")
+async def main():
+    server = Server(user_manager=AllAdminUserManager())
+    host = os.environ.get("OPCUA_DEFAULT_HOST", "127.0.0.1")
+    endpoint = "opc.tcp://"+host+":4840/freeopcua/server/"
+    await server.init()
+    server.set_endpoint(endpoint)
 
-uri = "http://beremiz.github.io"
-idx = server.register_namespace(uri)
+    server.set_security_policy([ua.SecurityPolicyType.Basic256Sha256_SignAndEncrypt])
+    await server.load_certificate("my_cert.der")
+    await server.load_private_key("my_private_key.pem")
 
-objects = server.get_objects_node()
+    uri = "http://beremiz.github.io"
+    idx = await server.register_namespace(uri)
 
-testobj = objects.add_object(idx, "TestObject")
-testvarout = testobj.add_variable(idx, "TestOut", 1.2)
-testvar = testobj.add_variable(idx, "TestIn", 5.6)
-testvar.set_writable()
+    objects = server.get_objects_node()
 
-server.start()
+    testobj = await objects.add_object(idx, "TestObject")
+    testvarout = await testobj.add_variable(idx, "TestOut", 1.2)
+    testvar = await testobj.add_variable(idx, "TestIn", 5.6)
+    await testvar.set_writable()
 
-try:
-    while True:
-        time.sleep(1)
-        inval=testvar.get_value()
-        print inval
-        testvarout.set_value(inval*2)
-        sys.stdout.flush()
-finally:
-    server.stop()
+    await server.start()
+
+    try:
+        while True:
+            await asyncio.sleep(1)
+            inval = await testvar.get_value()
+            print(inval)
+            await testvarout.set_value(inval*2)
+            sys.stdout.flush()
+    finally:
+        await server.stop()
+
+asyncio.run(main())
+
 EOF
--- a/tests/projects/opcua_client_encrypted/opcua_0@opcua/confnode.xml	Wed May 31 23:16:29 2023 +0200
+++ b/tests/projects/opcua_client_encrypted/opcua_0@opcua/confnode.xml	Sun Jun 18 16:28:42 2023 +0200
@@ -1,5 +1,5 @@
 <?xml version='1.0' encoding='utf-8'?>
-<OPCUAClient xmlns:xsd="http://www.w3.org/2001/XMLSchema">
+<OPCUAClient xmlns:xsd="http://www.w3.org/2001/XMLSchema" Server_URI="opc.tcp://127.0.0.1:4840/freeopcua/server/">
   <AuthType>
     <x509 Certificate="my_cert.der" PrivateKey="my_private_key.pem">
       <Policy/>
--- a/tests/tools/Docker/Dockerfile	Wed May 31 23:16:29 2023 +0200
+++ b/tests/tools/Docker/Dockerfile	Sun Jun 18 16:28:42 2023 +0200
@@ -77,13 +77,13 @@
 RUN ~/beremizenv/bin/pip install \
         pytest pytest-timeout ddt \
         sslpsk posix_spawn \
-        opcua \
         matplotlib lxml \
         zeroconf \
         pycountry \
         Pyro5 msgpack autobahn click
 
 RUN ~/beremizenv/bin/pip install \
+        git+https://github.com/FreeOpcUa/opcua-asyncio.git@98a64897a2d171653353de2f36d33085aef65e82 \
         git+https://github.com/beremiz/nevow-py3.git@nevow-0.14.5.dev1
 
 RUN set -xe && \