Skip to content

Commit

Permalink
make automatic decoding of custom structs really work on client
Browse files Browse the repository at this point in the history
  • Loading branch information
oroulet committed Mar 19, 2017
1 parent 1671288 commit b00389a
Show file tree
Hide file tree
Showing 5 changed files with 53 additions and 22 deletions.
29 changes: 18 additions & 11 deletions opcua/client/client.py
Original file line number Diff line number Diff line change
Expand Up @@ -557,7 +557,7 @@ def import_and_register_structures(self, nodes=None):
If no no node is given, attemps to import variables from all nodes under
"0:OPC Binary"
the code is generated and imported on the fly. If you know the structures
are not going to be modified it is safer to copy the generated files
are not going to be modified it might be interresting to copy the generated files
and include them in you code
"""
if nodes is None:
Expand All @@ -566,22 +566,29 @@ def import_and_register_structures(self, nodes=None):
if desc.BrowseName != ua.QualifiedName("Opc.Ua"):
nodes.append(self.get_node(desc.NodeId))
self.logger.info("Importing structures from nodes: %s", nodes)


structs_dict = {}
for node in nodes:
xml = node.get_value()
xml = xml.decode("utf-8")
#with open("titi.xml", "w") as f:
#f.write(xml)
name = "structures_" + node.get_browse_name().Name
gen = StructGenerator()
gen.make_model_from_string(xml)
structs_dict = gen.save_and_import(name + ".py")
# register classes
for desc in node.get_children_descriptions():
if desc.BrowseName.Name in structs_dict:
self.logger.info("registring new structure: %: %s", desc.NodeId, desc.BrowseName.Name)
ua.extension_object_classes[desc.NodeId] = structs_dict[desc.BrowseName.Name]
ua.extension_object_ids[desc.BrowseName.Name] = desc.NodeId
gen.save_and_import(name + ".py", append_to=structs_dict)

# register classes
for desc in self.nodes.base_structure_type.get_children_descriptions():
# FIXME: maybe we should look recursively at children
# FIXME: we should get enoding and description but this is too
# expensive. we take a shorcut and assume that browsename of struct
# is the same as the name of the data type structure
if desc.BrowseName.Name in structs_dict:
struct_node = self.get_node(desc.NodeId)
refs = struct_node.get_references(ua.ObjectIds.HasEncoding, ua.BrowseDirection.Forward)
for ref in refs:
if "Binary" in ref.BrowseName.Name:
ua.register_extension_object(desc.BrowseName.Name, ref.NodeId, structs_dict[desc.BrowseName.Name])




Expand Down
1 change: 1 addition & 0 deletions opcua/common/shortcuts.py
Original file line number Diff line number Diff line change
Expand Up @@ -25,3 +25,4 @@ def __init__(self, server):
self.object_types = Node(server, ObjectIds.ObjectTypesFolder)
self.namespace_array = Node(server, ObjectIds.Server_NamespaceArray)
self.opc_binary = Node(server, ObjectIds.OPCBinarySchema_TypeSystem)
self.base_structure_type = Node(server, ObjectIds.Structure)
16 changes: 13 additions & 3 deletions opcua/common/structures_generator.py
Original file line number Diff line number Diff line change
Expand Up @@ -166,13 +166,23 @@ def save_to_file(self, path):
_file.write(struct.get_code())
_file.close()

def save_and_import(self, path):
def save_and_import(self, path, append_to=None):
"""
save the new structures to a python file which be used later
import the result and return resulting classes in a dict
if append_to is a dict, the classes are added to the dict
"""
self.save_to_file(path)
name = os.path.basename(path)
name = os.path.splitext(name)[0]
mymodule = importlib.import_module(name)
mydict = {struct.name: getattr(mymodule, struct.name) for struct in self.model}
return mydict
if append_to is None:
result = {}
else:
result = append_to
for struct in self.model:
result[struct.name] = getattr(mymodule, struct.name)
return result

def get_structures(self):
ld = {}
Expand Down
4 changes: 2 additions & 2 deletions opcua/ua/ua_binary.py
Original file line number Diff line number Diff line change
Expand Up @@ -264,7 +264,7 @@ def pack_uatype(vtype, value):
# dependency loop: classes in uaprotocol_auto use Variant defined in this file,
# but Variant can contain any object from uaprotocol_auto as ExtensionObject.
# Using local import to avoid import loop
from opcua.ua.uaprotocol_auto import extensionobject_to_binary
from opcua.ua import extensionobject_to_binary
return extensionobject_to_binary(value)
else:
try:
Expand All @@ -283,7 +283,7 @@ def unpack_uatype(vtype, data):
# dependency loop: classes in uaprotocol_auto use Variant defined in this file,
# but Variant can contain any object from uaprotocol_auto as ExtensionObject.
# Using local import to avoid import loop
from opcua.ua.uaprotocol_auto import extensionobject_from_binary
from opcua.ua import extensionobject_from_binary
return extensionobject_from_binary(data)
else:
from opcua.ua import uatypes
Expand Down
25 changes: 19 additions & 6 deletions opcua/ua/uatypes.py
Original file line number Diff line number Diff line change
@@ -1,8 +1,10 @@
"""
implement ua datatypes
"""

import logging
import struct
from enum import Enum, IntEnum, EnumMeta
from enum import Enum, IntEnum
from datetime import datetime
import sys
import os
Expand All @@ -19,6 +21,9 @@
from opcua.common.utils import Buffer


logger = logging.getLogger(__name__)


if sys.version_info.major > 2:
unicode = str
def get_win_epoch():
Expand Down Expand Up @@ -1106,12 +1111,20 @@ def get_default_value(vtype):
extension_object_ids = {}


def register_extension_object(name, nodeid, class_type):
"""
"""
logger.warning("registring new extension object: %s %s %s", name, nodeid, class_type)
extension_object_classes[nodeid] = class_type
extension_object_ids[name] = nodeid


def extensionobject_from_binary(data):
"""
Convert binary-coded ExtensionObject to a Python object.
Returns an object, or None if TypeId is zero
"""
TypeId = NodeId.from_binary(data)
typeid = NodeId.from_binary(data)
Encoding = ord(data.read(1))
body = None
if Encoding & (1 << 0):
Expand All @@ -1121,16 +1134,16 @@ def extensionobject_from_binary(data):
else:
body = data.copy(length)
data.skip(length)
if TypeId.Identifier == 0:
if typeid.Identifier == 0:
return None
elif TypeId in extension_object_classes:
klass = extension_object_classes[TypeId]
elif typeid in extension_object_classes:
klass = extension_object_classes[typeid]
if body is None:
raise UaError("parsing ExtensionObject {0} without data".format(klass.__name__))
return klass.from_binary(body)
else:
e = ExtensionObject()
e.TypeId = TypeId
e.TypeId = typeid
e.Encoding = Encoding
if body is not None:
e.Body = body.read(len(body))
Expand Down

0 comments on commit b00389a

Please sign in to comment.