-
Notifications
You must be signed in to change notification settings - Fork 662
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
Open
levkevi
wants to merge
11
commits into
FreeOpcUa:master
Choose a base branch
from
levkevi:FreeOpcUa-master
base: master
Could not load branches
Branch not found: {{ refName }}
Loading
Could not load tags
Nothing to show
Loading
Are you sure you want to change the base?
Some commits from the old base branch may be removed from the timeline,
and old review comments may become outdated.
Open
Changes from all commits
Commits
Show all changes
11 commits
Select commit
Hold shift + click to select a range
f2db4b9
Ignore the "Models" xml tags as they don't contain nodes
levkevi be6df83
Fixed a bug in uamethod where returning a tuple would not work with c…
levkevi 0358328
The uamethod decorator didn't properly call the to_variant method
levkevi 5718a8f
Added tests to test complex arguments
levkevi 4830a54
Added a todo
levkevi 396ee47
Merge branch 'master' of https://github.com/FreeOpcUa/python-opcua in…
levkevi b7c0546
Moved register_extension_object to a new module to avoid circular imp…
levkevi ca60518
Refactor the example and added comments on how it works.
levkevi 660b878
Fixed the register_extension_object import in test_common.py
levkevi 625da2c
Added the nodeset file that was missing in the example
levkevi 15c21b5
Moved the register_extension_object function to common.ua_utils.
levkevi File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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.common.ua_utils 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) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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> |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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() |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -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 | ||
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 just saw that it won't work if your NamespaceIndex is not 0. I'll see what I can do to change that. |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
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!