Skip to content

Commit 83c23e3

Browse files
committed
allow to import and export xml client side
1 parent 9375999 commit 83c23e3

8 files changed

Lines changed: 78 additions & 31 deletions

File tree

opcua/client/client.py

Lines changed: 32 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,8 @@
88

99
from opcua import ua
1010
from opcua.client.ua_client import UaClient
11+
from opcua.common.xmlimporter import XmlImporter
12+
from opcua.common.xmlexporter import XmlExporter
1113
from opcua.common.node import Node
1214
from opcua.common.manage_nodes import delete_nodes
1315
from opcua.common.subscription import Subscription
@@ -502,4 +504,33 @@ def get_namespace_index(self, uri):
502504

503505
def delete_nodes(self, nodes, recursive=False):
504506
return delete_nodes(self.uaclient, nodes, recursive)
505-
507+
508+
def import_xml(self, path):
509+
"""
510+
Import nodes defined in xml
511+
"""
512+
importer = XmlImporter(self)
513+
return importer.import_xml(path)
514+
515+
def export_xml(self, nodes, path):
516+
"""
517+
Export defined nodes to xml
518+
"""
519+
exp = XmlExporter(self)
520+
exp.build_etree(nodes)
521+
return exp.write_xml(path)
522+
523+
def register_namespace(self, uri):
524+
"""
525+
Register a new namespace. Nodes should in custom namespace, not 0.
526+
This method is mainly implemented for symetry with server
527+
"""
528+
ns_node = self.get_node(ua.NodeId(ua.ObjectIds.Server_NamespaceArray))
529+
uries = ns_node.get_value()
530+
if uri in uries:
531+
return uries.index(uri)
532+
uries.append(uri)
533+
ns_node.set_value(uries)
534+
return len(uries) - 1
535+
536+

opcua/client/ua_client.py

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -504,6 +504,16 @@ def add_nodes(self, nodestoadd):
504504
response.ResponseHeader.ServiceResult.check()
505505
return response.Results
506506

507+
def add_references(self, refs):
508+
self.logger.info("add_references")
509+
request = ua.AddReferencesRequest()
510+
request.Parameters.ReferencesToAdd = refs
511+
data = self._uasocket.send_request(request)
512+
response = ua.AddReferencesResponse.from_binary(data)
513+
self.logger.debug(response)
514+
response.ResponseHeader.ServiceResult.check()
515+
return response.Results
516+
507517
def delete_nodes(self, params):
508518
self.logger.info("delete_nodes")
509519
request = ua.DeleteNodesRequest()

opcua/common/manage_nodes.py

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -334,7 +334,8 @@ def _create_method(parent, nodeid, qname, callback, inputs, outputs):
334334
[_vtype_to_argument(vtype) for vtype in outputs],
335335
varianttype=ua.VariantType.ExtensionObject,
336336
datatype=ua.ObjectIds.Argument)
337-
parent.server.add_method_callback(method.nodeid, callback)
337+
if hasattr(parent.server, "add_method_callback"):
338+
parent.server.add_method_callback(method.nodeid, callback)
338339
return results[0].AddedNodeId
339340

340341

opcua/common/xmlexporter.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -235,7 +235,7 @@ def add_variable_common(self, node, el):
235235
dtype_name = dtype.to_string()
236236
rank = node.get_value_rank()
237237
if rank != -1:
238-
el.attrib["ValueRank"] = str(rank)
238+
el.attrib["ValueRank"] = str(int(rank))
239239
dim = node.get_attribute(ua.AttributeIds.ArrayDimensions)
240240
if dim.Value.Value:
241241
el.attrib["ArrayDimensions"] = ",".join([str(i) for i in dim.Value.Value])

opcua/common/xmlimporter.py

Lines changed: 23 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -6,8 +6,7 @@
66
import uuid
77
from copy import copy
88

9-
import dateutil.parser
10-
9+
import opcua
1110
from opcua import ua
1211
from opcua.common import xmlparser
1312

@@ -77,6 +76,18 @@ def import_xml(self, xmlpath):
7776
continue
7877
nodes.append(node)
7978
return nodes
79+
80+
def _add_node(self, node):
81+
if isinstance(self.server, opcua.server.server.Server):
82+
return self.server.iserver.isession.add_nodes([node])
83+
else:
84+
return self.server.uaclient.add_nodes([node])
85+
86+
def _add_references(self, refs):
87+
if isinstance(self.server, opcua.server.server.Server):
88+
return self.server.iserver.isession.add_references(refs)
89+
else:
90+
return self.server.uaclient.add_references(refs)
8091

8192
def make_objects(self, node_datas):
8293
new_nodes = []
@@ -142,7 +153,7 @@ def add_object(self, obj):
142153
attrs.DisplayName = ua.LocalizedText(obj.displayname)
143154
attrs.EventNotifier = obj.eventnotifier
144155
node.NodeAttributes = attrs
145-
res = self.server.iserver.isession.add_nodes([node])
156+
res = self._add_node(node)
146157
self._add_refs(obj)
147158
res[0].StatusCode.check()
148159
return res[0].AddedNodeId
@@ -155,7 +166,7 @@ def add_object_type(self, obj):
155166
attrs.DisplayName = ua.LocalizedText(obj.displayname)
156167
attrs.IsAbstract = obj.abstract
157168
node.NodeAttributes = attrs
158-
res = self.server.iserver.isession.add_nodes([node])
169+
res = self._add_node(node)
159170
self._add_refs(obj)
160171
res[0].StatusCode.check()
161172
return res[0].AddedNodeId
@@ -180,7 +191,7 @@ def add_variable(self, obj):
180191
if obj.dimensions:
181192
attrs.ArrayDimensions = obj.dimensions
182193
node.NodeAttributes = attrs
183-
res = self.server.iserver.isession.add_nodes([node])
194+
res = self._add_node(node)
184195
self._add_refs(obj)
185196
res[0].StatusCode.check()
186197
return res[0].AddedNodeId
@@ -232,7 +243,7 @@ def _add_variable_value(self, obj):
232243
for ext in obj.value:
233244
extobj = self._make_ext_obj(ext)
234245
values.append(extobj)
235-
return values
246+
return ua.Variant(values, ua.VariantType.ExtensionObject)
236247
elif obj.valuetype == 'ListOfGuid':
237248
return ua.Variant([
238249
uuid.UUID(guid) for guid in obj.value
@@ -242,7 +253,7 @@ def _add_variable_value(self, obj):
242253
if hasattr(ua.ua_binary.Primitives, vtype):
243254
return ua.Variant(obj.value, getattr(ua.VariantType, vtype))
244255
else:
245-
return [getattr(ua, vtype)(v) for v in obj.value]
256+
return ua.Variant([getattr(ua, vtype)(v) for v in obj.value])
246257
elif obj.valuetype == 'ExtensionObject':
247258
extobj = self._make_ext_obj(obj.value)
248259
return ua.Variant(extobj, getattr(ua.VariantType, obj.valuetype))
@@ -277,7 +288,7 @@ def add_variable_type(self, obj):
277288
if obj.dimensions:
278289
attrs.ArrayDimensions = obj.dimensions
279290
node.NodeAttributes = attrs
280-
res = self.server.iserver.isession.add_nodes([node])
291+
res = self._add_node(node)
281292
self._add_refs(obj)
282293
res[0].StatusCode.check()
283294
return res[0].AddedNodeId
@@ -297,7 +308,7 @@ def add_method(self, obj):
297308
if obj.dimensions:
298309
attrs.ArrayDimensions = obj.dimensions
299310
node.NodeAttributes = attrs
300-
res = self.server.iserver.isession.add_nodes([node])
311+
res = self._add_node(node)
301312
self._add_refs(obj)
302313
res[0].StatusCode.check()
303314
return res[0].AddedNodeId
@@ -315,7 +326,7 @@ def add_reference_type(self, obj):
315326
if obj.symmetric:
316327
attrs.Symmetric = obj.symmetric
317328
node.NodeAttributes = attrs
318-
res = self.server.iserver.isession.add_nodes([node])
329+
res = self._add_node(node)
319330
self._add_refs(obj)
320331
res[0].StatusCode.check()
321332
return res[0].AddedNodeId
@@ -329,7 +340,7 @@ def add_datatype(self, obj):
329340
if obj.abstract:
330341
attrs.IsAbstract = obj.abstract
331342
node.NodeAttributes = attrs
332-
res = self.server.iserver.isession.add_nodes([node])
343+
res = self._add_node(node)
333344
self._add_refs(obj)
334345
res[0].StatusCode.check()
335346
return res[0].AddedNodeId
@@ -346,7 +357,7 @@ def _add_refs(self, obj):
346357
ref.TargetNodeClass = ua.NodeClass.DataType
347358
ref.TargetNodeId = self._migrate_ns(self.to_nodeid(data.target))
348359
refs.append(ref)
349-
self.server.iserver.isession.add_references(refs)
360+
self._add_references(refs)
350361

351362
def _sort_nodes_by_parentid(self, ndatas):
352363
"""

opcua/server/server.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -17,13 +17,13 @@
1717
from opcua.server.event_generator import EventGenerator
1818
from opcua.common.node import Node
1919
from opcua.common.subscription import Subscription
20-
from opcua.common import xmlimporter
2120
from opcua.common.manage_nodes import delete_nodes
2221
from opcua.client.client import Client
2322
from opcua.crypto import security_policies
2423
from opcua.common.event_objects import BaseEvent
2524
from opcua.common.shortcuts import Shortcuts
2625
from opcua.common.xmlexporter import XmlExporter
26+
from opcua.common.xmlimporter import XmlImporter
2727
from opcua.common.ua_utils import get_nodes_of_namespace
2828
use_crypto = True
2929
try:
@@ -418,7 +418,7 @@ def import_xml(self, path):
418418
"""
419419
Import nodes defined in xml
420420
"""
421-
importer = xmlimporter.XmlImporter(self)
421+
importer = XmlImporter(self)
422422
return importer.import_xml(path)
423423

424424
def export_xml(self, nodes, path):

tests/tests_client.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@
1111
port_num1 = 48510
1212

1313

14-
class TestClient(unittest.TestCase, CommonTests, SubscriptionTests):
14+
class TestClient(unittest.TestCase, CommonTests, SubscriptionTests, XmlTests):
1515

1616
'''
1717
Run common tests on client side

tests/tests_xml.py

Lines changed: 7 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,7 @@ class XmlTests(object):
2424
assertEqual = dir
2525

2626
def test_xml_import(self):
27-
self.srv.import_xml("tests/custom_nodes.xml")
27+
self.opc.import_xml("tests/custom_nodes.xml")
2828
o = self.opc.get_objects_node()
2929
v = o.get_child(["1:MyXMLFolder", "1:MyXMLObject", "1:MyXMLVariable"])
3030
val = v.get_value()
@@ -52,9 +52,9 @@ def test_xml_import_additional_ns(self):
5252
self.srv.register_namespace('http://placeholder.toincrease.nsindex') # if not already shift the new namespaces
5353

5454
# "tests/custom_nodes.xml" isn't created with namespaces in mind, provide new test file
55-
self.srv.import_xml("tests/custom_nodesns.xml") # the ns=1 in to file now should be mapped to ns=2
55+
self.opc.import_xml("tests/custom_nodesns.xml") # the ns=1 in to file now should be mapped to ns=2
5656

57-
ns = self.srv.get_namespace_index("http://examples.freeopcua.github.io/")
57+
ns = self.opc.get_namespace_index("http://examples.freeopcua.github.io/")
5858
o = self.opc.get_objects_node()
5959

6060
o2 = o.get_child(["{0:d}:MyBaseObject".format(ns)])
@@ -102,7 +102,7 @@ def test_xml_vars(self):
102102
self.opc.register_namespace("tititi")
103103
self.opc.register_namespace("whatthexxx")
104104
o = self.opc.nodes.objects.add_object(2, "xmlexportobj")
105-
v = o.add_variable(3, "myxmlvar", 6.78, ua.VariantType.Float)
105+
v = o.add_variable(3, "myxmlvar", 6.78, ua.VariantType.Double)
106106
a = o.add_variable(3, "myxmlvar-array", [6, 1], ua.VariantType.UInt16)
107107
a2 = o.add_variable(3, "myxmlvar-2dim", [[1, 2], [3, 4]], ua.VariantType.UInt32)
108108
a3 = o.add_variable(3, "myxmlvar-2dim", [[]], ua.VariantType.ByteString)
@@ -113,7 +113,7 @@ def test_xml_vars(self):
113113
self.opc.import_xml("export-vars.xml")
114114

115115
self.assertEqual(v.get_value(), 6.78)
116-
self.assertEqual(v.get_data_type(), ua.NodeId(ua.ObjectIds.Float))
116+
self.assertEqual(v.get_data_type(), ua.NodeId(ua.ObjectIds.Double))
117117

118118
self.assertEqual(a.get_data_type(), ua.NodeId(ua.ObjectIds.UInt16))
119119
self.assertIn(a.get_value_rank(), (0, 1))
@@ -135,7 +135,6 @@ def test_xml_ns(self):
135135
ns_array = self.opc.get_namespace_array()
136136
if len(ns_array) < 3:
137137
self.opc.register_namespace("dummy_ns")
138-
print("ARRAY", self.opc.get_namespace_array())
139138

140139
ref_ns = self.opc.register_namespace("ref_namespace")
141140
new_ns = self.opc.register_namespace("my_new_namespace")
@@ -151,7 +150,6 @@ def test_xml_ns(self):
151150
o_bname = onew.add_object("ns={0};i=4000".format(new_ns), "{0}:BNAME".format(bname_ns))
152151

153152
nodes = [o, o50, o200, onew, vnew, v_no_parent, o_bname]
154-
print("CREATED", nodes, o_no_export)
155153
self.opc.export_xml(nodes, "export-ns.xml")
156154
# delete node and change index og new_ns before re-importing
157155
self.opc.delete_nodes(nodes)
@@ -165,10 +163,8 @@ def test_xml_ns(self):
165163
new_ns = self.opc.register_namespace("my_new_namespace")
166164

167165
new_nodes = self.opc.import_xml("export-ns.xml")
168-
print("NEW NODES", new_nodes)
169166

170167
for i in [o, o50, o200]:
171-
print(i)
172168
i.get_browse_name()
173169
with self.assertRaises(uaerrors.BadNodeIdUnknown):
174170
onew.get_browse_name()
@@ -179,11 +175,8 @@ def test_xml_ns(self):
179175
# get index of namespaces after import
180176
new_ns = self.opc.register_namespace("my_new_namespace")
181177
bname_ns = self.opc.register_namespace("bname_namespace")
182-
print("ARRAY 2", self.opc.get_namespace_array())
183178

184-
print("NEW NS", new_ns, onew)
185179
onew.nodeid.NamespaceIndex = new_ns
186-
print("OENE", onew)
187180
onew.get_browse_name()
188181
vnew2 = onew.get_children()[0]
189182
self.assertEqual(new_ns, vnew2.nodeid.NamespaceIndex)
@@ -313,7 +306,8 @@ def test_xml_enumvalues(self):
313306
self._test_xml_var_type(o, "enumvalues")
314307

315308
def test_xml_custom_uint32(self):
316-
t = self.opc.create_custom_data_type(2, 'MyCustomUint32', ua.ObjectIds.UInt32)
309+
#t = self.opc.nodes. create_custom_data_type(2, 'MyCustomUint32', ua.ObjectIds.UInt32)
310+
t = self.opc.get_node(ua.ObjectIds.UInt32).add_data_type(2, 'MyCustomUint32')
317311
o = self.opc.nodes.objects.add_variable(2, "xmlcustomunit32", 0, varianttype=ua.VariantType.UInt32, datatype=t.nodeid)
318312
self._test_xml_var_type(o, "cuint32")
319313

0 commit comments

Comments
 (0)