-
Notifications
You must be signed in to change notification settings - Fork 663
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Custom type methods with examples #368
base: master
Are you sure you want to change the base?
Changes from 10 commits
f2db4b9
be6df83
0358328
5718a8f
4830a54
396ee47
b7c0546
ca60518
660b878
625da2c
15c21b5
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,44 @@ | ||
from opcua import Client | ||
from common import KeyValuePair | ||
|
||
|
||
class HelloClient(object): | ||
def __init__(self, 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): | ||
# __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() | ||
|
||
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 hello_client: | ||
complex_error, complex_error_list = hello_client.say_hello( | ||
KeyValuePair("foo", "bar"), | ||
[KeyValuePair("toto", "tata"), KeyValuePair("Hello", "World")], | ||
) | ||
|
||
print(complex_error, complex_error_list) |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,75 @@ | ||
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): | ||
# The DEFAULT_BINARY is the NodeId of the custom type | ||
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): | ||
# 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)) | ||
packet.append(uabin.Primitives.String.pack(self.value)) | ||
return b''.join(packet) | ||
|
||
@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) | ||
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)) | ||
|
||
# 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()) | ||
|
||
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) | ||
|
||
# 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) |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,151 @@ | ||
<?xml version='1.0' encoding='utf-8'?> | ||
<UANodeSet xmlns="http://opcfoundation.org/UA/2011/03/UANodeSet.xsd" xmlns:uax="http://opcfoundation.org/UA/2008/02/Types.xsd" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"> | ||
<Aliases> | ||
<Alias Alias="Organizes">i=35</Alias> | ||
<Alias Alias="HasTypeDefinition">i=40</Alias> | ||
<Alias Alias="HasSubtype">i=45</Alias> | ||
<Alias Alias="HasProperty">i=46</Alias> | ||
<Alias Alias="HasComponent">i=47</Alias> | ||
<Alias Alias="Argument">i=296</Alias> | ||
</Aliases> | ||
<NamespaceUris /> | ||
<UADataType BrowseName="0:KeyValue" NodeId="i=20001"> | ||
<DisplayName>KeyValue</DisplayName> | ||
<Description>KeyValue</Description> | ||
<References> | ||
<Reference IsForward="false" ReferenceType="HasSubtype">i=22</Reference> | ||
</References> | ||
</UADataType> | ||
<UADataType BrowseName="0:ErrorKeyValue" NodeId="i=20002"> | ||
<DisplayName>ErrorKeyValue</DisplayName> | ||
<Description>ErrorKeyValue</Description> | ||
<References> | ||
<Reference IsForward="false" ReferenceType="HasSubtype">i=22</Reference> | ||
</References> | ||
</UADataType> | ||
<UAObjectType BrowseName="0:HellowerType" NodeId="i=20004"> | ||
<DisplayName>HellowerType</DisplayName> | ||
<Description>HellowerType</Description> | ||
<References> | ||
<Reference IsForward="false" ReferenceType="HasSubtype">i=58</Reference> | ||
</References> | ||
</UAObjectType> | ||
<UAObject BrowseName="0:Hellower" NodeId="i=20005" ParentNodeId="i=85"> | ||
<DisplayName>HellowerType</DisplayName> | ||
<Description>HellowerType</Description> | ||
<References> | ||
<Reference IsForward="false" ReferenceType="Organizes">i=85</Reference> | ||
<Reference ReferenceType="HasTypeDefinition">i=20004</Reference> | ||
<Reference ReferenceType="HasComponent">i=20006</Reference> | ||
</References> | ||
</UAObject> | ||
<UAMethod BrowseName="0:SayComplexHello" NodeId="i=20006" ParentNodeId="i=20005"> | ||
<DisplayName>SayComplexHello</DisplayName> | ||
<Description>SayComplexHello</Description> | ||
<References> | ||
<Reference IsForward="false" ReferenceType="HasComponent">i=20005</Reference> | ||
<Reference ReferenceType="HasProperty">i=20007</Reference> | ||
<Reference ReferenceType="HasProperty">i=20008</Reference> | ||
</References> | ||
</UAMethod> | ||
<UAVariable BrowseName="0:InputArguments" DataType="Argument" NodeId="i=20007" ParentNodeId="i=20006" ValueRank="0"> | ||
<DisplayName>InputArguments</DisplayName> | ||
<Description>InputArguments</Description> | ||
<Value> | ||
<uax:ListOfExtensionObject> | ||
<uax:ExtensionObject> | ||
<uax:TypeId> | ||
<uax:Identifier>i=296</uax:Identifier> | ||
</uax:TypeId> | ||
<uax:Body> | ||
<uax:Argument> | ||
<uax:Description> | ||
<uax:Locale /> | ||
<uax:Text /> | ||
</uax:Description> | ||
<uax:ArrayDimensions /> | ||
<uax:DataType> | ||
<uax:Identifier>i=20001</uax:Identifier> | ||
</uax:DataType> | ||
<uax:ValueRank>-1</uax:ValueRank> | ||
<uax:Name>complex_variable</uax:Name> | ||
</uax:Argument> | ||
</uax:Body> | ||
</uax:ExtensionObject> | ||
<uax:ExtensionObject> | ||
<uax:TypeId> | ||
<uax:Identifier>i=296</uax:Identifier> | ||
</uax:TypeId> | ||
<uax:Body> | ||
<uax:Argument> | ||
<uax:Description> | ||
<uax:Locale /> | ||
<uax:Text /> | ||
</uax:Description> | ||
<uax:ArrayDimensions /> | ||
<uax:DataType> | ||
<uax:Identifier>i=20001</uax:Identifier> | ||
</uax:DataType> | ||
<uax:ValueRank>-1</uax:ValueRank> | ||
<uax:Name>complex_variable_list</uax:Name> | ||
</uax:Argument> | ||
</uax:Body> | ||
</uax:ExtensionObject> | ||
</uax:ListOfExtensionObject> | ||
</Value> | ||
<References> | ||
<Reference IsForward="false" ReferenceType="HasProperty">i=20006</Reference> | ||
<Reference ReferenceType="HasTypeDefinition">i=68</Reference> | ||
</References> | ||
</UAVariable> | ||
<UAVariable BrowseName="0:OutputArguments" DataType="Argument" NodeId="i=20008" ParentNodeId="i=20006" ValueRank="0"> | ||
<DisplayName>OutputArguments</DisplayName> | ||
<Description>OutputArguments</Description> | ||
<Value> | ||
<uax:ListOfExtensionObject> | ||
<uax:ExtensionObject> | ||
<uax:TypeId> | ||
<uax:Identifier>i=296</uax:Identifier> | ||
</uax:TypeId> | ||
<uax:Body> | ||
<uax:Argument> | ||
<uax:Description> | ||
<uax:Locale /> | ||
<uax:Text /> | ||
</uax:Description> | ||
<uax:ArrayDimensions /> | ||
<uax:DataType> | ||
<uax:Identifier>i=20002</uax:Identifier> | ||
</uax:DataType> | ||
<uax:ValueRank>-1</uax:ValueRank> | ||
<uax:Name>complex_error</uax:Name> | ||
</uax:Argument> | ||
</uax:Body> | ||
</uax:ExtensionObject> | ||
<uax:ExtensionObject> | ||
<uax:TypeId> | ||
<uax:Identifier>i=296</uax:Identifier> | ||
</uax:TypeId> | ||
<uax:Body> | ||
<uax:Argument> | ||
<uax:Description> | ||
<uax:Locale /> | ||
<uax:Text /> | ||
</uax:Description> | ||
<uax:ArrayDimensions /> | ||
<uax:DataType> | ||
<uax:Identifier>i=20002</uax:Identifier> | ||
</uax:DataType> | ||
<uax:ValueRank>-1</uax:ValueRank> | ||
<uax:Name>complex_error_list</uax:Name> | ||
</uax:Argument> | ||
</uax:Body> | ||
</uax:ExtensionObject> | ||
</uax:ListOfExtensionObject> | ||
</Value> | ||
<References> | ||
<Reference IsForward="false" ReferenceType="HasProperty">i=20006</Reference> | ||
<Reference ReferenceType="HasTypeDefinition">i=68</Reference> | ||
</References> | ||
</UAVariable> | ||
</UANodeSet> |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -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): | ||
# 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")]) | ||
|
||
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) | ||
|
||
# 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() | ||
hellower = objects.get_child("0:Hellower") | ||
|
||
say_hello_node = hellower.get_child("0:SayComplexHello") | ||
|
||
self.server.link_method(say_hello_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() |
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -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): | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. what is that? you cannot test for the value of Encoding without first unpacking????? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Good call, I don't event remember removing that. I'm putting it back. |
||
obj.Body = uabin.Primitives.ByteString.unpack(data) | ||
return obj | ||
|
@@ -1083,5 +1082,3 @@ def get_default_value(vtype): | |
return Variant() | ||
else: | ||
raise RuntimeError("function take a uatype as argument, got:", vtype) | ||
|
||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -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 | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. we already have a file called ua_utils.py under common. Maybe you could put that method in uatypes.py or ua_prototcol_hans.py There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I'll move it to that file. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
this is good!