--- 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 && \