Skip to content

Commit 86537ce

Browse files
committed
make automatic decoding of custom structs really work on client
1 parent e92abd5 commit 86537ce

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
@@ -540,7 +540,7 @@ def import_and_register_structures(self, nodes=None):
540540
If no no node is given, attemps to import variables from all nodes under
541541
"0:OPC Binary"
542542
the code is generated and imported on the fly. If you know the structures
543-
are not going to be modified it is safer to copy the generated files
543+
are not going to be modified it might be interresting to copy the generated files
544544
and include them in you code
545545
"""
546546
if nodes is None:
@@ -549,22 +549,29 @@ def import_and_register_structures(self, nodes=None):
549549
if desc.BrowseName != ua.QualifiedName("Opc.Ua"):
550550
nodes.append(self.get_node(desc.NodeId))
551551
self.logger.info("Importing structures from nodes: %s", nodes)
552-
552+
553+
structs_dict = {}
553554
for node in nodes:
554555
xml = node.get_value()
555556
xml = xml.decode("utf-8")
556-
#with open("titi.xml", "w") as f:
557-
#f.write(xml)
558557
name = "structures_" + node.get_browse_name().Name
559558
gen = StructGenerator()
560559
gen.make_model_from_string(xml)
561-
structs_dict = gen.save_and_import(name + ".py")
562-
# register classes
563-
for desc in node.get_children_descriptions():
564-
if desc.BrowseName.Name in structs_dict:
565-
self.logger.info("registring new structure: %: %s", desc.NodeId, desc.BrowseName.Name)
566-
ua.extension_object_classes[desc.NodeId] = structs_dict[desc.BrowseName.Name]
567-
ua.extension_object_ids[desc.BrowseName.Name] = desc.NodeId
560+
gen.save_and_import(name + ".py", append_to=structs_dict)
561+
562+
# register classes
563+
for desc in self.nodes.base_structure_type.get_children_descriptions():
564+
# FIXME: maybe we should look recursively at children
565+
# FIXME: we should get enoding and description but this is too
566+
# expensive. we take a shorcut and assume that browsename of struct
567+
# is the same as the name of the data type structure
568+
if desc.BrowseName.Name in structs_dict:
569+
struct_node = self.get_node(desc.NodeId)
570+
refs = struct_node.get_references(ua.ObjectIds.HasEncoding, ua.BrowseDirection.Forward)
571+
for ref in refs:
572+
if "Binary" in ref.BrowseName.Name:
573+
ua.register_extension_object(desc.BrowseName.Name, ref.NodeId, structs_dict[desc.BrowseName.Name])
574+
568575

569576

570577

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)