From f2db4b9076510011e34a6180bdf3e1e5aa9bc785 Mon Sep 17 00:00:00 2001 From: Kevin Levesque Date: Tue, 15 Nov 2016 16:42:40 -0500 Subject: [PATCH 01/10] Ignore the "Models" xml tags as they don't contain nodes --- opcua/common/xmlparser.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/opcua/common/xmlparser.py b/opcua/common/xmlparser.py index 1069af7ea..63448f14e 100644 --- a/opcua/common/xmlparser.py +++ b/opcua/common/xmlparser.py @@ -136,7 +136,7 @@ def get_node_datas(self): nodes = [] for child in self.root: tag = self._retag.match(child.tag).groups()[1] - if tag not in ["Aliases", "NamespaceUris", "Extensions"]: # these XML tags don't contain nodes + if tag not in ["Aliases", "NamespaceUris", "Extensions", "Models"]: # these XML tags don't contain nodes node = self._parse_node(tag, child) nodes.append(node) return nodes From be6df83e2ecae740db39a3bcf7d1d6923345687b Mon Sep 17 00:00:00 2001 From: Kevin Levesque Date: Thu, 17 Nov 2016 16:58:41 -0500 Subject: [PATCH 02/10] Fixed a bug in uamethod where returning a tuple would not work with custom objects Added a helper tool to be able to add a custom object type more easily --- opcua/ua/uatypes.py | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/opcua/ua/uatypes.py b/opcua/ua/uatypes.py index b4aff17bd..4b5fa523b 100644 --- a/opcua/ua/uatypes.py +++ b/opcua/ua/uatypes.py @@ -12,7 +12,7 @@ from opcua.ua import ua_binary as uabin from opcua.ua import status_codes -from opcua.ua import ObjectIds +from opcua.ua import ObjectIds, ObjectIdNames from opcua.ua.uaerrors import UaError from opcua.ua.uaerrors import UaStatusCodeError from opcua.ua.uaerrors import UaStringParsingError @@ -629,7 +629,6 @@ def to_binary(self): def from_binary(data): obj = ExtensionObject() obj.TypeId = NodeId.from_binary(data) - obj.Encoding = uabin.Primitives.UInt8.unpack(data) if obj.Encoding & (1 << 0): obj.Body = uabin.Primitives.ByteString.unpack(data) return obj @@ -1085,3 +1084,8 @@ def get_default_value(vtype): raise RuntimeError("function take a uatype as argument, got:", vtype) +def register_extension_object(object_factory): + from opcua.ua.uaprotocol_auto import ExtensionClasses + setattr(ObjectIds, "{}_Encoding_DefaultBinary".format(object_factory.__name__), object_factory.DEFAULT_BINARY) + ObjectIdNames[object_factory.DEFAULT_BINARY] = object_factory.__name__ + ExtensionClasses[object_factory.DEFAULT_BINARY] = object_factory From 035832833b9d1153b90ae53e44a0ffe3065f6a24 Mon Sep 17 00:00:00 2001 From: Kevin Levesque Date: Thu, 17 Nov 2016 17:00:12 -0500 Subject: [PATCH 03/10] The uamethod decorator didn't properly call the to_variant method --- opcua/common/methods.py | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/opcua/common/methods.py b/opcua/common/methods.py index dd9545249..8a29a695b 100644 --- a/opcua/common/methods.py +++ b/opcua/common/methods.py @@ -60,7 +60,8 @@ def wrapper(parent, *args): parent = args[0] args = args[1:] result = func(self, parent, *[arg.Value for arg in args]) - + if isinstance(result, (list, tuple)): + return to_variant(*result) return to_variant(result) return wrapper @@ -70,5 +71,3 @@ def to_variant(*args): for arg in args: uaargs.append(ua.Variant(arg)) return uaargs - - From 5718a8f12648162d1209c57306f9416fa6e749ea Mon Sep 17 00:00:00 2001 From: Kevin Levesque Date: Fri, 18 Nov 2016 13:20:02 -0500 Subject: [PATCH 04/10] Added tests to test complex arguments --- tests/tests_common.py | 88 ++++++++++++++++++++++++++++++++++++++++++- 1 file changed, 87 insertions(+), 1 deletion(-) diff --git a/tests/tests_common.py b/tests/tests_common.py index 8002aefa8..4b3fecbe2 100644 --- a/tests/tests_common.py +++ b/tests/tests_common.py @@ -1,5 +1,6 @@ # encoding: utf-8 from concurrent.futures import Future, TimeoutError +import io import time from datetime import datetime from datetime import timedelta @@ -9,6 +10,9 @@ from opcua import uamethod from opcua import instantiate from opcua import copy_node +from opcua.ua.uatypes import register_extension_object + +from opcua.ua import ua_binary as uabin def add_server_methods(srv): @@ -654,4 +658,86 @@ def test_enum(self): self.assertEqual(myvar.get_data_type(), myenum_type.nodeid) myvar.set_value(ua.LocalizedText("String2")) - + def test_ua_method_unique(self): + def unique_response(parent): + return 42 + + decorated_unique_response = uamethod(unique_response) + response = decorated_unique_response(ua.NodeId("ServerMethod", 2)) + + self.assertEqual(len(response), 1) + self.assertEqual(type(response[0]), ua.Variant) + + def test_ua_method_multiple(self): + def unique_response(parent): + return 42, 49 + + decorated_unique_response = uamethod(unique_response) + response = decorated_unique_response(ua.NodeId("ServerMethod", 2)) + + self.assertEqual(len(response), 2) + self.assertEqual(type(response[0]), ua.Variant) + + def test_ua_method_complex(self): + register_extension_object(KeyValuePair) + + def unique_response(parent): + return KeyValuePair("Key", "Value") + + decorated_unique_response = uamethod(unique_response) + response = decorated_unique_response(ua.NodeId("ServerMethod", 2)) + + self.assertEqual(len(response), 1) + self.assertEqual(type(response[0]), ua.Variant) + + def test_ua_method_complex_multiple(self): + register_extension_object(KeyValuePair) + + def unique_response(parent): + return [KeyValuePair("Key", "Value"), + KeyValuePair("Hello", "World")] + + decorated_unique_response = uamethod(unique_response) + response = decorated_unique_response(ua.NodeId("ServerMethod", 2)) + + self.assertEqual(len(response), 2) + self.assertEqual(type(response[0]), ua.Variant) + + # def test_decode_custom_object(self): + # register_extension_object(KeyValuePair) + # + # def unique_response(parent): + # return KeyValuePair("Key", "Value") + # + # decorated_unique_response = uamethod(unique_response) + # response = decorated_unique_response(ua.NodeId("ServerMethod", 2)) + # + # self.assertEqual(len(response), 1) + # self.assertEqual(type(response[0]), ua.Variant) + # + # key_value = uabin.unpack_uatype_array(response[0].VariantType, io.BytesIO(response[0].to_binary())) + # self.assertEqual(key_value.key, "Key") + # self.assertEqual(key_value.value, "Value") + + +class KeyValuePair(object): + DEFAULT_BINARY = 20001 + + def __init__(self, key, value, namespace_id=0): + self.key = key + self.value = value + self.NamespaceIndex = namespace_id + + def to_binary(self): + packet = [] + packet.append(uabin.Primitives.UInt16.pack(self.NamespaceIndex)) + packet.append(uabin.Primitives.String.pack(self.key)) + packet.append(uabin.Primitives.String.pack(self.value)) + return b''.join(packet) + + @staticmethod + def from_binary(data): + namespace_index = uabin.Primitives.UInt16.unpack(data) + key = uabin.Primitives.String.unpack(data) + value = uabin.Primitives.String.unpack(data) + return KeyValuePair(key, value, namespace_index) From 4830a546fc7732bb37933ae0deed1df692844a6a Mon Sep 17 00:00:00 2001 From: Kevin Levesque Date: Fri, 18 Nov 2016 13:25:36 -0500 Subject: [PATCH 05/10] Added a todo --- tests/tests_common.py | 1 + 1 file changed, 1 insertion(+) diff --git a/tests/tests_common.py b/tests/tests_common.py index 4b3fecbe2..83ec3a3fd 100644 --- a/tests/tests_common.py +++ b/tests/tests_common.py @@ -703,6 +703,7 @@ def unique_response(parent): self.assertEqual(len(response), 2) self.assertEqual(type(response[0]), ua.Variant) + # TODO: Fix the test # def test_decode_custom_object(self): # register_extension_object(KeyValuePair) # From b7c05468a84698d0c25030eb6e2c299e4d3e33f0 Mon Sep 17 00:00:00 2001 From: Kevin Levesque Date: Fri, 18 Nov 2016 14:47:47 -0500 Subject: [PATCH 06/10] Moved register_extension_object to a new module to avoid circular imports Added an example that show how to use complex types --- examples/complex-type-client-server/client.py | 33 ++++++++++ examples/complex-type-client-server/common.py | 66 +++++++++++++++++++ examples/complex-type-client-server/server.py | 58 ++++++++++++++++ opcua/ua/uatypes.py | 9 +-- opcua/ua/uautils.py | 8 +++ 5 files changed, 166 insertions(+), 8 deletions(-) create mode 100644 examples/complex-type-client-server/client.py create mode 100644 examples/complex-type-client-server/common.py create mode 100644 examples/complex-type-client-server/server.py create mode 100644 opcua/ua/uautils.py diff --git a/examples/complex-type-client-server/client.py b/examples/complex-type-client-server/client.py new file mode 100644 index 000000000..c55496547 --- /dev/null +++ b/examples/complex-type-client-server/client.py @@ -0,0 +1,33 @@ +from opcua import Client +from common import KeyValuePair + + +class HelloClient(object): + def __init__(self, endpoint): + self.client = Client(endpoint) + + def __enter__(self): + self.client.connect() + return self.client + + def __exit__(self, exc_type, exc_val, exc_tb): + self.client.disconnect() + + +if __name__ == '__main__': + with HelloClient("opc.tcp://localhost:40840/freeopcua/server/") as client: + root = client.get_root_node() + for obj in root.get_children(): + print(obj.get_browse_name()) + + objects = root.get_child("0:Objects") + for obj in objects.get_children(): + print(obj.get_browse_name()) + serial_manager = objects.get_child("0:Hellower") + complex_error, complex_error_list = serial_manager.call_method( + "0:SayComplexHello", + KeyValuePair("foo", "bar"), + [KeyValuePair("toto", "tata"), KeyValuePair("Hello", "World")], + ) + + print(complex_error, complex_error_list) diff --git a/examples/complex-type-client-server/common.py b/examples/complex-type-client-server/common.py new file mode 100644 index 000000000..36f9aa23b --- /dev/null +++ b/examples/complex-type-client-server/common.py @@ -0,0 +1,66 @@ +from opcua.common.methods import to_variant +from opcua.ua import ua_binary as uabin +from opcua.ua.uatypes import Variant +from opcua.ua.uautils import register_extension_object + + +class KeyValuePair(object): + DEFAULT_BINARY = 20001 + + def __init__(self, key, value, namespace_id=0): + self.key = key + self.value = value + self.NamespaceIndex = namespace_id + + def __repr__(self): + return "KeyValuePair(key={}, value={})".format(self.key, self.value) + + def to_binary(self): + packet = [] + packet.append(uabin.Primitives.UInt16.pack(self.NamespaceIndex)) + packet.append(uabin.Primitives.String.pack(self.key)) + packet.append(uabin.Primitives.String.pack(self.value)) + return b''.join(packet) + + @staticmethod + def from_binary(data): + namespace_index = uabin.Primitives.UInt16.unpack(data) + key = uabin.Primitives.String.unpack(data) + value = uabin.Primitives.String.unpack(data) + return KeyValuePair(key, value, namespace_index) + + +class ErrorKeyValue(object): + DEFAULT_BINARY = 20002 + + def __init__(self, code, description, extensions, namespace_id=0): + self.code = code + self.description = description + self.extensions = extensions + self.NamespaceIndex = namespace_id + + def __repr__(self): + return "ErrorKeyValue(code='{}', description='{}', extensions={})".format( + self.code, self.description, self.extensions) + + def to_binary(self): + packet = [] + packet.append(uabin.Primitives.UInt16.pack(self.NamespaceIndex)) + packet.append(uabin.Primitives.String.pack(self.code)) + packet.append(uabin.Primitives.String.pack(self.description)) + for i in to_variant(self.extensions): + packet.append(i.to_binary()) + + return b''.join(packet) + + @staticmethod + def from_binary(data): + namespace_index = uabin.Primitives.UInt16.unpack(data) + code = uabin.Primitives.String.unpack(data) + description = uabin.Primitives.String.unpack(data) + extensions = Variant.from_binary(data) + extensions = [ext for ext in extensions.Value] + return ErrorKeyValue(code, description, extensions, namespace_index) + +register_extension_object(KeyValuePair) +register_extension_object(ErrorKeyValue) diff --git a/examples/complex-type-client-server/server.py b/examples/complex-type-client-server/server.py new file mode 100644 index 000000000..d9eb41242 --- /dev/null +++ b/examples/complex-type-client-server/server.py @@ -0,0 +1,58 @@ +import os +from common import KeyValuePair, ErrorKeyValue +from opcua import uamethod, Server +try: + from IPython import embed +except ImportError: + import code + + def embed(): + vars = globals() + vars.update(locals()) + shell = code.InteractiveConsole(vars) + shell.interact() + + +@uamethod +def say_complex_hello(parent, complex_variable, complex_variable_list): + print("say_complex_hello called: {}, {}".format(complex_variable, complex_variable_list)) + complex_error = ErrorKeyValue("0", "foo", [KeyValuePair("key", "value"), KeyValuePair("hello", "world")]) + complex_error_list = ErrorKeyValue("1", "bar", [KeyValuePair("key", "value")]) + + return complex_error, complex_error_list + + +class HellowerServer(object): + def __init__(self, endpoint, name, model_filepath): + self.server = Server() + + self.server.import_xml(model_filepath) + + self.server.set_endpoint(endpoint) + self.server.set_server_name(name) + + objects = self.server.get_objects_node() + serial_manager = objects.get_child("0:Hellower") + + for child in serial_manager.get_children(): + print(dir(child)) + print("Got a child: {}".format(child.get_browse_name())) + get_serial_node = serial_manager.get_child("0:SayComplexHello") + + self.server.link_method(get_serial_node, say_complex_hello) + + def __enter__(self): + self.server.start() + return self.server + + def __exit__(self, exc_type, exc_val, exc_tb): + self.server.stop() + + +if __name__ == '__main__': + script_dir = os.path.dirname(__file__) + with HellowerServer( + "opc.tcp://0.0.0.0:40840/freeopcua/server/", + "FreeOpcUa Example Server", + os.path.join(script_dir, "nodeset.xml")) as server: + embed() diff --git a/opcua/ua/uatypes.py b/opcua/ua/uatypes.py index 4b5fa523b..653fcd16d 100644 --- a/opcua/ua/uatypes.py +++ b/opcua/ua/uatypes.py @@ -12,7 +12,7 @@ from opcua.ua import ua_binary as uabin from opcua.ua import status_codes -from opcua.ua import ObjectIds, ObjectIdNames +from opcua.ua import ObjectIds from opcua.ua.uaerrors import UaError from opcua.ua.uaerrors import UaStatusCodeError from opcua.ua.uaerrors import UaStringParsingError @@ -1082,10 +1082,3 @@ def get_default_value(vtype): return Variant() else: raise RuntimeError("function take a uatype as argument, got:", vtype) - - -def register_extension_object(object_factory): - from opcua.ua.uaprotocol_auto import ExtensionClasses - setattr(ObjectIds, "{}_Encoding_DefaultBinary".format(object_factory.__name__), object_factory.DEFAULT_BINARY) - ObjectIdNames[object_factory.DEFAULT_BINARY] = object_factory.__name__ - ExtensionClasses[object_factory.DEFAULT_BINARY] = object_factory diff --git a/opcua/ua/uautils.py b/opcua/ua/uautils.py new file mode 100644 index 000000000..a43fe173c --- /dev/null +++ b/opcua/ua/uautils.py @@ -0,0 +1,8 @@ +from opcua.ua import ObjectIds, ObjectIdNames +from opcua.ua.uaprotocol_auto import ExtensionClasses + + +def register_extension_object(object_factory): + setattr(ObjectIds, "{}_Encoding_DefaultBinary".format(object_factory.__name__), object_factory.DEFAULT_BINARY) + ObjectIdNames[object_factory.DEFAULT_BINARY] = object_factory.__name__ + ExtensionClasses[object_factory.DEFAULT_BINARY] = object_factory From ca60518ad61a9e6cf0b920ed1a9c511cd64235f3 Mon Sep 17 00:00:00 2001 From: Kevin Levesque Date: Fri, 18 Nov 2016 15:12:45 -0500 Subject: [PATCH 07/10] Refactor the example and added comments on how it works. --- examples/complex-type-client-server/client.py | 41 ++++++++++++------- examples/complex-type-client-server/common.py | 9 ++++ examples/complex-type-client-server/server.py | 12 +++--- 3 files changed, 41 insertions(+), 21 deletions(-) diff --git a/examples/complex-type-client-server/client.py b/examples/complex-type-client-server/client.py index c55496547..adbda538d 100644 --- a/examples/complex-type-client-server/client.py +++ b/examples/complex-type-client-server/client.py @@ -4,28 +4,39 @@ class HelloClient(object): def __init__(self, endpoint): - self.client = Client(endpoint) + self._client = Client(endpoint) + + # We cannot set them properly as we are still not connected to the server + self._root = None + self._objects = None + self._hellower = None def __enter__(self): - self.client.connect() - return self.client + # __enter__ and __exit__ are called when getting the object with the with keyword. See context manager + # documentation for more information + self._client.connect() + + # As soon as we are connected to the server, we set the variables + self._root = self._client.get_root_node() + self._objects = self._client.get_objects_node() + self._hellower = self._objects.get_child("0:Hellower") + return self def __exit__(self, exc_type, exc_val, exc_tb): - self.client.disconnect() + self._client.disconnect() + + def say_hello(self, complex_variable, complex_variable_list): + """Adapt the method call so it is used like a normal python method""" + return self._hellower.call_method( + "0:SayComplexHello", + complex_variable, + complex_variable_list + ) if __name__ == '__main__': - with HelloClient("opc.tcp://localhost:40840/freeopcua/server/") as client: - root = client.get_root_node() - for obj in root.get_children(): - print(obj.get_browse_name()) - - objects = root.get_child("0:Objects") - for obj in objects.get_children(): - print(obj.get_browse_name()) - serial_manager = objects.get_child("0:Hellower") - complex_error, complex_error_list = serial_manager.call_method( - "0:SayComplexHello", + with HelloClient("opc.tcp://localhost:40840/freeopcua/server/") as hello_client: + complex_error, complex_error_list = hello_client.say_hello( KeyValuePair("foo", "bar"), [KeyValuePair("toto", "tata"), KeyValuePair("Hello", "World")], ) diff --git a/examples/complex-type-client-server/common.py b/examples/complex-type-client-server/common.py index 36f9aa23b..7b4cdb708 100644 --- a/examples/complex-type-client-server/common.py +++ b/examples/complex-type-client-server/common.py @@ -5,6 +5,7 @@ class KeyValuePair(object): + # The DEFAULT_BINARY is the NodeId of the custom type DEFAULT_BINARY = 20001 def __init__(self, key, value, namespace_id=0): @@ -16,6 +17,7 @@ def __repr__(self): return "KeyValuePair(key={}, value={})".format(self.key, self.value) def to_binary(self): + # We need to define to_binary. It will be used when serializing the object packet = [] packet.append(uabin.Primitives.UInt16.pack(self.NamespaceIndex)) packet.append(uabin.Primitives.String.pack(self.key)) @@ -24,6 +26,7 @@ def to_binary(self): @staticmethod def from_binary(data): + # This is how we deserialize the object namespace_index = uabin.Primitives.UInt16.unpack(data) key = uabin.Primitives.String.unpack(data) value = uabin.Primitives.String.unpack(data) @@ -48,6 +51,9 @@ def to_binary(self): packet.append(uabin.Primitives.UInt16.pack(self.NamespaceIndex)) packet.append(uabin.Primitives.String.pack(self.code)) packet.append(uabin.Primitives.String.pack(self.description)) + + # When we want to serialize a list, we need to transform the objects to a Variant and then manually call + # to_binary() to serialize them for i in to_variant(self.extensions): packet.append(i.to_binary()) @@ -58,9 +64,12 @@ def from_binary(data): namespace_index = uabin.Primitives.UInt16.unpack(data) code = uabin.Primitives.String.unpack(data) description = uabin.Primitives.String.unpack(data) + + # When descerialising, you'll get a Variant object back. This is how get the object's value back extensions = Variant.from_binary(data) extensions = [ext for ext in extensions.Value] return ErrorKeyValue(code, description, extensions, namespace_index) +# For each custom type defined, we need to register it so the script know how to serialize / deserialise them register_extension_object(KeyValuePair) register_extension_object(ErrorKeyValue) diff --git a/examples/complex-type-client-server/server.py b/examples/complex-type-client-server/server.py index d9eb41242..535258051 100644 --- a/examples/complex-type-client-server/server.py +++ b/examples/complex-type-client-server/server.py @@ -15,6 +15,8 @@ def embed(): @uamethod def say_complex_hello(parent, complex_variable, complex_variable_list): + # The uamethod decorator will take care of converting the data for us. We only work with python objects inside it + # For it to work, you need to register your DataType like in common.py print("say_complex_hello called: {}, {}".format(complex_variable, complex_variable_list)) complex_error = ErrorKeyValue("0", "foo", [KeyValuePair("key", "value"), KeyValuePair("hello", "world")]) complex_error_list = ErrorKeyValue("1", "bar", [KeyValuePair("key", "value")]) @@ -28,18 +30,16 @@ def __init__(self, endpoint, name, model_filepath): self.server.import_xml(model_filepath) + # Those need to be done after importing the xml file or it will be overwritten self.server.set_endpoint(endpoint) self.server.set_server_name(name) objects = self.server.get_objects_node() - serial_manager = objects.get_child("0:Hellower") + hellower = objects.get_child("0:Hellower") - for child in serial_manager.get_children(): - print(dir(child)) - print("Got a child: {}".format(child.get_browse_name())) - get_serial_node = serial_manager.get_child("0:SayComplexHello") + say_hello_node = hellower.get_child("0:SayComplexHello") - self.server.link_method(get_serial_node, say_complex_hello) + self.server.link_method(say_hello_node, say_complex_hello) def __enter__(self): self.server.start() From 660b878066f954176d64487b34cdd4f3218ef8f0 Mon Sep 17 00:00:00 2001 From: Kevin Levesque Date: Fri, 18 Nov 2016 15:17:10 -0500 Subject: [PATCH 08/10] Fixed the register_extension_object import in test_common.py --- tests/tests_common.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/tests_common.py b/tests/tests_common.py index 64cb911ea..752a8aeef 100644 --- a/tests/tests_common.py +++ b/tests/tests_common.py @@ -7,7 +7,7 @@ from opcua import uamethod from opcua import instantiate from opcua import copy_node -from opcua.ua.uatypes import register_extension_object +from opcua.ua.uautils import register_extension_object from opcua.ua import ua_binary as uabin from opcua.common import ua_utils From 625da2cc889248fe440027bc6dbd499b09acc539 Mon Sep 17 00:00:00 2001 From: Kevin Levesque Date: Fri, 18 Nov 2016 15:18:21 -0500 Subject: [PATCH 09/10] Added the nodeset file that was missing in the example --- .../complex-type-client-server/nodeset.xml | 151 ++++++++++++++++++ 1 file changed, 151 insertions(+) create mode 100644 examples/complex-type-client-server/nodeset.xml diff --git a/examples/complex-type-client-server/nodeset.xml b/examples/complex-type-client-server/nodeset.xml new file mode 100644 index 000000000..419ed54af --- /dev/null +++ b/examples/complex-type-client-server/nodeset.xml @@ -0,0 +1,151 @@ + + + + i=35 + i=40 + i=45 + i=46 + i=47 + i=296 + + + + KeyValue + KeyValue + + i=22 + + + + ErrorKeyValue + ErrorKeyValue + + i=22 + + + + HellowerType + HellowerType + + i=58 + + + + HellowerType + HellowerType + + i=85 + i=20004 + i=20006 + + + + SayComplexHello + SayComplexHello + + i=20005 + i=20007 + i=20008 + + + + InputArguments + InputArguments + + + + + i=296 + + + + + + + + + + i=20001 + + -1 + complex_variable + + + + + + i=296 + + + + + + + + + + i=20001 + + -1 + complex_variable_list + + + + + + + i=20006 + i=68 + + + + OutputArguments + OutputArguments + + + + + i=296 + + + + + + + + + + i=20002 + + -1 + complex_error + + + + + + i=296 + + + + + + + + + + i=20002 + + -1 + complex_error_list + + + + + + + i=20006 + i=68 + + + From 15c21b57f83f2712ce51ab872bfd025e4cdacb3c Mon Sep 17 00:00:00 2001 From: Kevin Levesque Date: Mon, 21 Nov 2016 09:37:02 -0500 Subject: [PATCH 10/10] Moved the register_extension_object function to common.ua_utils. Changed the unittest custom error type so it is easier to know that it's a custom class. --- examples/complex-type-client-server/common.py | 2 +- opcua/common/ua_utils.py | 8 +++ opcua/ua/uatypes.py | 1 + opcua/ua/uautils.py | 8 --- tests/tests_common.py | 52 ++++++++++--------- 5 files changed, 37 insertions(+), 34 deletions(-) delete mode 100644 opcua/ua/uautils.py diff --git a/examples/complex-type-client-server/common.py b/examples/complex-type-client-server/common.py index 7b4cdb708..6f2739f46 100644 --- a/examples/complex-type-client-server/common.py +++ b/examples/complex-type-client-server/common.py @@ -1,7 +1,7 @@ from opcua.common.methods import to_variant from opcua.ua import ua_binary as uabin from opcua.ua.uatypes import Variant -from opcua.ua.uautils import register_extension_object +from opcua.common.ua_utils import register_extension_object class KeyValuePair(object): diff --git a/opcua/common/ua_utils.py b/opcua/common/ua_utils.py index 10dad752b..36a02ff52 100644 --- a/opcua/common/ua_utils.py +++ b/opcua/common/ua_utils.py @@ -9,6 +9,8 @@ from opcua import ua from opcua.ua.uaerrors import UaError +from opcua.ua import ObjectIds, ObjectIdNames +from opcua.ua.uaprotocol_auto import ExtensionClasses def val_to_string(val): @@ -256,3 +258,9 @@ def get_nodes_of_namespace(server, namespaces=[]): nodes = [server.get_node(nodeid) for nodeid in server.iserver.aspace.keys() if nodeid.NamespaceIndex != 0 and nodeid.NamespaceIndex in namespace_indexes] return nodes + + +def register_extension_object(object_factory): + setattr(ObjectIds, "{}_Encoding_DefaultBinary".format(object_factory.__name__), object_factory.DEFAULT_BINARY) + ObjectIdNames[object_factory.DEFAULT_BINARY] = object_factory.__name__ + ExtensionClasses[object_factory.DEFAULT_BINARY] = object_factory diff --git a/opcua/ua/uatypes.py b/opcua/ua/uatypes.py index 653fcd16d..dd80bf913 100644 --- a/opcua/ua/uatypes.py +++ b/opcua/ua/uatypes.py @@ -629,6 +629,7 @@ def to_binary(self): def from_binary(data): obj = ExtensionObject() obj.TypeId = NodeId.from_binary(data) + obj.Encoding = uabin.Primitives.UInt8.unpack(data) if obj.Encoding & (1 << 0): obj.Body = uabin.Primitives.ByteString.unpack(data) return obj diff --git a/opcua/ua/uautils.py b/opcua/ua/uautils.py deleted file mode 100644 index a43fe173c..000000000 --- a/opcua/ua/uautils.py +++ /dev/null @@ -1,8 +0,0 @@ -from opcua.ua import ObjectIds, ObjectIdNames -from opcua.ua.uaprotocol_auto import ExtensionClasses - - -def register_extension_object(object_factory): - setattr(ObjectIds, "{}_Encoding_DefaultBinary".format(object_factory.__name__), object_factory.DEFAULT_BINARY) - ObjectIdNames[object_factory.DEFAULT_BINARY] = object_factory.__name__ - ExtensionClasses[object_factory.DEFAULT_BINARY] = object_factory diff --git a/tests/tests_common.py b/tests/tests_common.py index 752a8aeef..3bd85da7d 100644 --- a/tests/tests_common.py +++ b/tests/tests_common.py @@ -7,11 +7,13 @@ from opcua import uamethod from opcua import instantiate from opcua import copy_node -from opcua.ua.uautils import register_extension_object +from opcua.common.ua_utils import register_extension_object from opcua.ua import ua_binary as uabin from opcua.common import ua_utils +import io + def add_server_methods(srv): @uamethod @@ -720,10 +722,10 @@ def unique_response(parent): self.assertEqual(type(response[0]), ua.Variant) def test_ua_method_complex(self): - register_extension_object(KeyValuePair) + register_extension_object(MyCustomClass) def unique_response(parent): - return KeyValuePair("Key", "Value") + return MyCustomClass("Key", "Value") decorated_unique_response = uamethod(unique_response) response = decorated_unique_response(ua.NodeId("ServerMethod", 2)) @@ -732,11 +734,11 @@ def unique_response(parent): self.assertEqual(type(response[0]), ua.Variant) def test_ua_method_complex_multiple(self): - register_extension_object(KeyValuePair) + register_extension_object(MyCustomClass) def unique_response(parent): - return [KeyValuePair("Key", "Value"), - KeyValuePair("Hello", "World")] + return [MyCustomClass("Key", "Value"), + MyCustomClass("Hello", "World")] decorated_unique_response = uamethod(unique_response) response = decorated_unique_response(ua.NodeId("ServerMethod", 2)) @@ -745,24 +747,24 @@ def unique_response(parent): self.assertEqual(type(response[0]), ua.Variant) # TODO: Fix the test - # def test_decode_custom_object(self): - # register_extension_object(KeyValuePair) - # - # def unique_response(parent): - # return KeyValuePair("Key", "Value") - # - # decorated_unique_response = uamethod(unique_response) - # response = decorated_unique_response(ua.NodeId("ServerMethod", 2)) - # - # self.assertEqual(len(response), 1) - # self.assertEqual(type(response[0]), ua.Variant) - # - # key_value = uabin.unpack_uatype_array(response[0].VariantType, io.BytesIO(response[0].to_binary())) - # self.assertEqual(key_value.key, "Key") - # self.assertEqual(key_value.value, "Value") - - -class KeyValuePair(object): + def test_decode_custom_object(self): + register_extension_object(MyCustomClass) + + def unique_response(parent): + return MyCustomClass("Key", "Value") + + decorated_unique_response = uamethod(unique_response) + response = decorated_unique_response(ua.NodeId("ServerMethod", 2)) + + self.assertEqual(len(response), 1) + self.assertEqual(type(response[0]), ua.Variant) + + key_value = uabin.unpack_uatype_array(response[0].VariantType, io.BytesIO(response[0].to_binary())) + self.assertEqual(key_value.key, "Key") + self.assertEqual(key_value.value, "Value") + + +class MyCustomClass(object): DEFAULT_BINARY = 20001 def __init__(self, key, value, namespace_id=0): @@ -782,4 +784,4 @@ def from_binary(data): namespace_index = uabin.Primitives.UInt16.unpack(data) key = uabin.Primitives.String.unpack(data) value = uabin.Primitives.String.unpack(data) - return KeyValuePair(key, value, namespace_index) + return MyCustomClass(key, value, namespace_index)