Skip to content

Commit b00389a

Browse files
committed
make automatic decoding of custom structs really work on client
1 parent 1671288 commit b00389a

File tree

5 files changed

+53
-22
lines changed

5 files changed

+53
-22
lines changed

opcua/client/client.py

Lines changed: 18 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -557,7 +557,7 @@ def import_and_register_structures(self, nodes=None):
557557
If no no node is given, attemps to import variables from all nodes under
558558
"0:OPC Binary"
559559
the code is generated and imported on the fly. If you know the structures
560-
are not going to be modified it is safer to copy the generated files
560+
are not going to be modified it might be interresting to copy the generated files
561561
and include them in you code
562562
"""
563563
if nodes is None:
@@ -566,22 +566,29 @@ def import_and_register_structures(self, nodes=None):
566566
if desc.BrowseName != ua.QualifiedName("Opc.Ua"):
567567
nodes.append(self.get_node(desc.NodeId))
568568
self.logger.info("Importing structures from nodes: %s", nodes)
569-
569+
570+
structs_dict = {}
570571
for node in nodes:
571572
xml = node.get_value()
572573
xml = xml.decode("utf-8")
573-
#with open("titi.xml", "w") as f:
574-
#f.write(xml)
575574
name = "structures_" + node.get_browse_name().Name
576575
gen = StructGenerator()
577576
gen.make_model_from_string(xml)
578-
structs_dict = gen.save_and_import(name + ".py")
579-
# register classes
580-
for desc in node.get_children_descriptions():
581-
if desc.BrowseName.Name in structs_dict:
582-
self.logger.info("registring new structure: %: %s", desc.NodeId, desc.BrowseName.Name)
583-
ua.extension_object_classes[desc.NodeId] = structs_dict[desc.BrowseName.Name]
584-
ua.extension_object_ids[desc.BrowseName.Name] = desc.NodeId
577+
gen.save_and_import(name + ".py", append_to=structs_dict)
578+
579+
# register classes
580+
for desc in self.nodes.base_structure_type.get_children_descriptions():
581+
# FIXME: maybe we should look recursively at children
582+
# FIXME: we should get enoding and description but this is too
583+
# expensive. we take a shorcut and assume that browsename of struct
584+
# is the same as the name of the data type structure
585+
if desc.BrowseName.Name in structs_dict:
586+
struct_node = self.get_node(desc.NodeId)
587+
refs = struct_node.get_references(ua.ObjectIds.HasEncoding, ua.BrowseDirection.Forward)
588+
for ref in refs:
589+
if "Binary" in ref.BrowseName.Name:
590+
ua.register_extension_object(desc.BrowseName.Name, ref.NodeId, structs_dict[desc.BrowseName.Name])
591+
585592

586593

587594

opcua/common/shortcuts.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -25,3 +25,4 @@ def __init__(self, server):
2525
self.object_types = Node(server, ObjectIds.ObjectTypesFolder)
2626
self.namespace_array = Node(server, ObjectIds.Server_NamespaceArray)
2727
self.opc_binary = Node(server, ObjectIds.OPCBinarySchema_TypeSystem)
28+
self.base_structure_type = Node(server, ObjectIds.Structure)

opcua/common/structures_generator.py

Lines changed: 13 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -166,13 +166,23 @@ def save_to_file(self, path):
166166
_file.write(struct.get_code())
167167
_file.close()
168168

169-
def save_and_import(self, path):
169+
def save_and_import(self, path, append_to=None):
170+
"""
171+
save the new structures to a python file which be used later
172+
import the result and return resulting classes in a dict
173+
if append_to is a dict, the classes are added to the dict
174+
"""
170175
self.save_to_file(path)
171176
name = os.path.basename(path)
172177
name = os.path.splitext(name)[0]
173178
mymodule = importlib.import_module(name)
174-
mydict = {struct.name: getattr(mymodule, struct.name) for struct in self.model}
175-
return mydict
179+
if append_to is None:
180+
result = {}
181+
else:
182+
result = append_to
183+
for struct in self.model:
184+
result[struct.name] = getattr(mymodule, struct.name)
185+
return result
176186

177187
def get_structures(self):
178188
ld = {}

opcua/ua/ua_binary.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -264,7 +264,7 @@ def pack_uatype(vtype, value):
264264
# dependency loop: classes in uaprotocol_auto use Variant defined in this file,
265265
# but Variant can contain any object from uaprotocol_auto as ExtensionObject.
266266
# Using local import to avoid import loop
267-
from opcua.ua.uaprotocol_auto import extensionobject_to_binary
267+
from opcua.ua import extensionobject_to_binary
268268
return extensionobject_to_binary(value)
269269
else:
270270
try:
@@ -283,7 +283,7 @@ def unpack_uatype(vtype, data):
283283
# dependency loop: classes in uaprotocol_auto use Variant defined in this file,
284284
# but Variant can contain any object from uaprotocol_auto as ExtensionObject.
285285
# Using local import to avoid import loop
286-
from opcua.ua.uaprotocol_auto import extensionobject_from_binary
286+
from opcua.ua import extensionobject_from_binary
287287
return extensionobject_from_binary(data)
288288
else:
289289
from opcua.ua import uatypes

opcua/ua/uatypes.py

Lines changed: 19 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,10 @@
11
"""
22
implement ua datatypes
33
"""
4+
5+
import logging
46
import struct
5-
from enum import Enum, IntEnum, EnumMeta
7+
from enum import Enum, IntEnum
68
from datetime import datetime
79
import sys
810
import os
@@ -19,6 +21,9 @@
1921
from opcua.common.utils import Buffer
2022

2123

24+
logger = logging.getLogger(__name__)
25+
26+
2227
if sys.version_info.major > 2:
2328
unicode = str
2429
def get_win_epoch():
@@ -1106,12 +1111,20 @@ def get_default_value(vtype):
11061111
extension_object_ids = {}
11071112

11081113

1114+
def register_extension_object(name, nodeid, class_type):
1115+
"""
1116+
"""
1117+
logger.warning("registring new extension object: %s %s %s", name, nodeid, class_type)
1118+
extension_object_classes[nodeid] = class_type
1119+
extension_object_ids[name] = nodeid
1120+
1121+
11091122
def extensionobject_from_binary(data):
11101123
"""
11111124
Convert binary-coded ExtensionObject to a Python object.
11121125
Returns an object, or None if TypeId is zero
11131126
"""
1114-
TypeId = NodeId.from_binary(data)
1127+
typeid = NodeId.from_binary(data)
11151128
Encoding = ord(data.read(1))
11161129
body = None
11171130
if Encoding & (1 << 0):
@@ -1121,16 +1134,16 @@ def extensionobject_from_binary(data):
11211134
else:
11221135
body = data.copy(length)
11231136
data.skip(length)
1124-
if TypeId.Identifier == 0:
1137+
if typeid.Identifier == 0:
11251138
return None
1126-
elif TypeId in extension_object_classes:
1127-
klass = extension_object_classes[TypeId]
1139+
elif typeid in extension_object_classes:
1140+
klass = extension_object_classes[typeid]
11281141
if body is None:
11291142
raise UaError("parsing ExtensionObject {0} without data".format(klass.__name__))
11301143
return klass.from_binary(body)
11311144
else:
11321145
e = ExtensionObject()
1133-
e.TypeId = TypeId
1146+
e.TypeId = typeid
11341147
e.Encoding = Encoding
11351148
if body is not None:
11361149
e.Body = body.read(len(body))

0 commit comments

Comments
 (0)