From f0cba12b0a9bdcab648492eb505eee62334c999b Mon Sep 17 00:00:00 2001 From: zerox1212 Date: Wed, 3 Aug 2016 11:48:37 -0500 Subject: [PATCH 1/5] Implement XML exporter --- opcua/common/xmlexporter.py | 328 ++++++++++++++++++++++++++++++++++++ 1 file changed, 328 insertions(+) create mode 100644 opcua/common/xmlexporter.py diff --git a/opcua/common/xmlexporter.py b/opcua/common/xmlexporter.py new file mode 100644 index 000000000..fa5131eb4 --- /dev/null +++ b/opcua/common/xmlexporter.py @@ -0,0 +1,328 @@ +""" +from a list of nodes in the address space, build an XML file +format is the one from opc-ua specification +""" +import logging +import xml.etree.ElementTree as Et + +from opcua import ua +from opcua.ua import object_ids as o_ids # FIXME needed because the reverse look up isn't part of ObjectIds Class + + +class XmlExporter(object): + + def __init__(self, server, node_set_attrs): + self.logger = logging.getLogger(__name__) + self.etree = Et.ElementTree(Et.Element('UANodeSet', node_set_attrs)) + self.server = server + + def export_xml(self, node_list, xmlpath): + self.logger.info('Exporting XML file to %s', xmlpath) + + # add all nodes in the list to an XML etree + for node in node_list: + self.node_to_xml(node) + + # try to write the XML etree to a file + try: + self.etree.write(xmlpath, short_empty_elements=False) + except TypeError as e: # TODO where to find which exceptions etree.write() raises? + self.logger.error("Error writing XML to file: ", e) + + def dump_xml(self): + self.logger.info('Dumping XML file to console') + Et.dump(self.etree) + + def node_to_xml(self, node): + node_class = node.get_node_class() + + if node_class is ua.NodeClass.Object: + self.add_object(node) + elif node_class is ua.NodeClass.UaObjectType: + self.add_object_type(node) + elif node_class is ua.NodeClass.Variable: + self.add_variable(node) + elif node_class is ua.NodeClass.VariableType: + self.add_variable_type(node) + elif node_class is ua.NodeClass.RefernceType: + self.add_reference(node) + elif node_class is ua.NodeClass.DataType: + self.add_datatype(node) + elif node_class is ua.NodeClass.Method: + self.add_method(node) + else: + self.logger.info("Not implemented node type: %s ", node_class) + + def _get_node(self, obj): + # TODO not sure if this is required for exporter, check on functionality + pass + + # ORIGINAL CODE FROM IMPORTER + # node = ua.AddNodesItem() + # node.RequestedNewNodeId = ua.NodeId.from_string(obj.nodeid) + # node.BrowseName = ua.QualifiedName.from_string(obj.browsename) + # node.NodeClass = getattr(ua.NodeClass, obj.nodetype[2:]) + # if obj.parent: + # node.ParentNodeId = ua.NodeId.from_string(obj.parent) + # if obj.parentlink: + # node.ReferenceTypeId = self.to_nodeid(obj.parentlink) + # if obj.typedef: + # node.TypeDefinition = ua.NodeId.from_string(obj.typedef) + # return node + + def to_nodeid(self, nodeid): + # TODO migrate this function to _get_xml_nodeid and delete + pass + + # ORIGINAL CODE FROM IMPORTER + # if not nodeid: + # return ua.NodeId(ua.ObjectIds.String) + # elif "=" in nodeid: + # return ua.NodeId.from_string(nodeid) + # elif hasattr(ua.ObjectIds, nodeid): + # return ua.NodeId(getattr(ua.ObjectIds, nodeid)) + # else: + # if nodeid in self.parser.aliases: + # nodeid = self.parser.aliases[nodeid] + # else: + # nodeid = "i={}".format(getattr(ua.ObjectIds, nodeid)) + # return ua.NodeId.from_string(nodeid) + + def add_object(self, obj): + pass + + # ORIGINAL CODE FROM IMPORTER + # node = self._get_node(obj) + # attrs = ua.ObjectAttributes() + # if obj.desc: + # attrs.Description = ua.LocalizedText(obj.desc) + # attrs.DisplayName = ua.LocalizedText(obj.displayname) + # attrs.EventNotifier = obj.eventnotifier + # node.NodeAttributes = attrs + # self.server.add_nodes([node]) + # self._add_refs(obj) + + def add_object_type(self, obj): + pass + + # ORIGINAL CODE FROM IMPORTER + # node = self._get_node(obj) + # attrs = ua.ObjectTypeAttributes() + # if obj.desc: + # attrs.Description = ua.LocalizedText(obj.desc) + # attrs.DisplayName = ua.LocalizedText(obj.displayname) + # attrs.IsAbstract = obj.abstract + # node.NodeAttributes = attrs + # self.server.add_nodes([node]) + # self._add_refs(obj) + + def add_variable(self, obj): + """ + Add a UA variable element to the XML tree + """ + browsename = self._get_xml_browsename(obj) + datatype = o_ids.ObjectIdNames[obj.get_data_type().Identifier] + nodeid = self._get_xml_nodeid(obj) + parent = self._get_xml_parent(obj) + acccesslevel = str(obj.get_access_level().val) + useraccesslevel = str(obj.get_user_access_level().val) + + displayname = obj.get_display_name().Text.decode(encoding='UTF8') + + value = str(obj.get_value()) + + refs = [] # TODO get list of refs here + + var_el = Et.SubElement(self.etree.getroot(), + 'UAVariable', + BrowseName=browsename, + DataType=datatype, + NodeId=nodeid, + ParentNodeId=parent, + AccessLevel=acccesslevel, + UserAccessLevel=useraccesslevel) + + disp_el = Et.SubElement(var_el, 'DisplayName', ) + disp_el.text = displayname + + refs_el = Et.SubElement(var_el, 'References') + + # TODO creating XML ref elements not done yet + for ref in refs: + refx_el = Et.SubElement(refs_el, 'Reference', ReferenceType=ref) + + val_el = Et.SubElement(var_el, 'Value') + + valx_el = Et.SubElement(val_el, 'uax:' + datatype) + valx_el.text = value + + # ORIGINAL CODE FROM IMPORTER + # node = self._get_node(obj) + # attrs = ua.VariableAttributes() + # if obj.desc: + # attrs.Description = ua.LocalizedText(obj.desc) + # attrs.DisplayName = ua.LocalizedText(obj.displayname) + # attrs.DataType = self.to_nodeid(obj.datatype) + # # if obj.value and len(obj.value) == 1: + # if obj.value is not None: + # attrs.Value = self._add_variable_value(obj, ) + # if obj.rank: + # attrs.ValueRank = obj.rank + # if obj.accesslevel: + # attrs.AccessLevel = obj.accesslevel + # if obj.useraccesslevel: + # attrs.UserAccessLevel = obj.useraccesslevel + # if obj.minsample: + # attrs.MinimumSamplingInterval = obj.minsample + # if obj.dimensions: + # attrs.ArrayDimensions = obj.dimensions + # node.NodeAttributes = attrs + # self.server.add_nodes([node]) + # self._add_refs(obj) + + def _add_variable_value(self, obj): + """ + Returns the value for a Variable based on the objects valuetype. + """ + # MAY NOT BE NEEDED + pass + + # ORIGINAL CODE FROM IMPORTER + # if obj.valuetype == 'ListOfLocalizedText': + # return ua.Variant([ua.LocalizedText(txt) for txt in obj.value], None) + # elif obj.valuetype == 'EnumValueType': + # values = [] + # for ev in obj.value: + # enum_value = ua.EnumValueType() + # enum_value.DisplayName = ua.LocalizedText(ev['DisplayName']) + # enum_value.Description = ua.LocalizedText(ev['Description']) + # enum_value.Value = int(ev['Value']) + # values.append(enum_value) + # return values + # elif obj.valuetype == 'Argument': + # values = [] + # for arg in obj.value: + # argument = ua.Argument() + # argument.Name = arg['Name'] + # argument.Description = ua.LocalizedText(arg['Description']) + # argument.DataType = self.to_nodeid(arg['DataType']) + # argument.ValueRank = int(arg['ValueRank']) + # argument.ArrayDimensions = arg['ArrayDimensions'] + # values.append(argument) + # return values + # + # return ua.Variant(obj.value, getattr(ua.VariantType, obj.valuetype)) + + + def add_variable_type(self, obj): + pass + + # ORIGINAL CODE FROM IMPORTER + # node = self._get_node(obj) + # attrs = ua.VariableTypeAttributes() + # if obj.desc: + # attrs.Description = ua.LocalizedText(obj.desc) + # attrs.DisplayName = ua.LocalizedText(obj.displayname) + # attrs.DataType = self.to_nodeid(obj.datatype) + # if obj.value and len(obj.value) == 1: + # attrs.Value = obj.value[0] + # if obj.rank: + # attrs.ValueRank = obj.rank + # if obj.abstract: + # attrs.IsAbstract = obj.abstract + # if obj.dimensions: + # attrs.ArrayDimensions = obj.dimensions + # node.NodeAttributes = attrs + # self.server.add_nodes([node]) + # self._add_refs(obj) + + def add_method(self, obj): + pass + + # ORIGINAL CODE FROM IMPORTER + # node = self._get_node(obj) + # attrs = ua.MethodAttributes() + # if obj.desc: + # attrs.Description = ua.LocalizedText(obj.desc) + # attrs.DisplayName = ua.LocalizedText(obj.displayname) + # if obj.accesslevel: + # attrs.AccessLevel = obj.accesslevel + # if obj.useraccesslevel: + # attrs.UserAccessLevel = obj.useraccesslevel + # if obj.minsample: + # attrs.MinimumSamplingInterval = obj.minsample + # if obj.dimensions: + # attrs.ArrayDimensions = obj.dimensions + # node.NodeAttributes = attrs + # self.server.add_nodes([node]) + # self._add_refs(obj) + + def add_reference(self, obj): + # MAY NOT BE NEEDED + pass + + # ORIGINAL CODE FROM IMPORTER + # node = self._get_node(obj) + # attrs = ua.ReferenceTypeAttributes() + # if obj.desc: + # attrs.Description = ua.LocalizedText(obj.desc) + # attrs.DisplayName = ua.LocalizedText(obj.displayname) + # if obj. inversename: + # attrs.InverseName = ua.LocalizedText(obj.inversename) + # if obj.abstract: + # attrs.IsAbstract = obj.abstract + # if obj.symmetric: + # attrs.Symmetric = obj.symmetric + # node.NodeAttributes = attrs + # self.server.add_nodes([node]) + # self._add_refs(obj) + + def add_datatype(self, obj): + pass + + # ORIGINAL CODE FROM IMPORTER + # node = self._get_node(obj) + # attrs = ua.DataTypeAttributes() + # if obj.desc: + # attrs.Description = ua.LocalizedText(obj.desc) + # attrs.DisplayName = ua.LocalizedText(obj.displayname) + # if obj.abstract: + # attrs.IsAbstract = obj.abstract + # node.NodeAttributes = attrs + # self.server.add_nodes([node]) + # self._add_refs(obj) + + def _add_refs(self, obj): + # MAY NOT BE NEEDED + pass + + # ORIGINAL CODE FROM IMPORTER + # if not obj.refs: + # return + # refs = [] + # for data in obj.refs: + # ref = ua.AddReferencesItem() + # ref.IsForward = True + # ref.ReferenceTypeId = self.to_nodeid(data.reftype) + # ref.SourceNodeId = ua.NodeId.from_string(obj.nodeid) + # ref.TargetNodeClass = ua.NodeClass.DataType + # ref.TargetNodeId = ua.NodeId.from_string(data.target) + # refs.append(ref) + # self.server.add_references(refs) + + @staticmethod + def _get_xml_nodeid(obj): + """ + Convert a UA NodeId object to a formatted string for XML + :param obj: + :return: + """ + return 'ns=' + str(obj.nodeid.NamespaceIndex) + ';' + str(obj.nodeid.Identifier) + + @staticmethod + def _get_xml_browsename(obj): + bn = obj.get_browse_name() + return str(bn.NamespaceIndex) + ':' + bn.Name + + def _get_xml_parent(self, obj): + return self._get_xml_nodeid(obj.get_parent()) \ No newline at end of file From fa06f3acde6bde01ae748c442721c963783878f9 Mon Sep 17 00:00:00 2001 From: zerox1212 Date: Sat, 13 Aug 2016 00:48:37 -0500 Subject: [PATCH 2/5] Add support for UAObject and references Exporter can now export regular objects. First attempt at exporting references added for review. Split some methods so building etree and dump/write are separate. --- opcua/common/xmlexporter.py | 225 +++++++++++++++--------------------- 1 file changed, 93 insertions(+), 132 deletions(-) diff --git a/opcua/common/xmlexporter.py b/opcua/common/xmlexporter.py index fa5131eb4..44e7b7b40 100644 --- a/opcua/common/xmlexporter.py +++ b/opcua/common/xmlexporter.py @@ -16,40 +16,73 @@ def __init__(self, server, node_set_attrs): self.etree = Et.ElementTree(Et.Element('UANodeSet', node_set_attrs)) self.server = server - def export_xml(self, node_list, xmlpath): - self.logger.info('Exporting XML file to %s', xmlpath) + def build_etree(self, node_list): + """ + Create an XML etree object from a list of nodes + Args: + node_list: list of Node objects for export + + Returns: + """ + self.logger.info('Building XML etree') + + # add all namespace uris to the XML etree + self._get_xml_namespace_uris() # TODO not done yet - # add all nodes in the list to an XML etree + # add all required aliases to the XML etree + self._get_xml_aliases() # TODO not done yet + + # add all nodes in the list to the XML etree for node in node_list: - self.node_to_xml(node) + self.node_to_etree(node) + + def export_xml(self, xmlpath): + """ + Write the XML etree in the exporter object to a file + Args: + xmlpath: string representing the path/file name + Returns: + """ # try to write the XML etree to a file + self.logger.info('Exporting XML file to %s', xmlpath) try: self.etree.write(xmlpath, short_empty_elements=False) except TypeError as e: # TODO where to find which exceptions etree.write() raises? self.logger.error("Error writing XML to file: ", e) - def dump_xml(self): - self.logger.info('Dumping XML file to console') + def dump_etree(self): + """ + Dump etree to console for debugging + Returns: + """ + self.logger.info('Dumping XML etree to console') Et.dump(self.etree) - def node_to_xml(self, node): + def node_to_etree(self, node): + """ + Add the necessary XML sub elements to the etree for exporting the node + Args: + node: Node object which will be added to XML etree + + Returns: + """ node_class = node.get_node_class() if node_class is ua.NodeClass.Object: - self.add_object(node) - elif node_class is ua.NodeClass.UaObjectType: - self.add_object_type(node) + self.add_object_xml(node) + elif node_class is ua.NodeClass.ObjectType: + self.add_object_type_xml(node) elif node_class is ua.NodeClass.Variable: - self.add_variable(node) + self.add_variable_xml(node) elif node_class is ua.NodeClass.VariableType: - self.add_variable_type(node) + self.add_variable_type_xml(node) elif node_class is ua.NodeClass.RefernceType: - self.add_reference(node) + self.add_reference_xml(node) elif node_class is ua.NodeClass.DataType: - self.add_datatype(node) + self.add_datatype_xml(node) elif node_class is ua.NodeClass.Method: - self.add_method(node) + self.add_method_xml(node) else: self.logger.info("Not implemented node type: %s ", node_class) @@ -70,39 +103,28 @@ def _get_node(self, obj): # node.TypeDefinition = ua.NodeId.from_string(obj.typedef) # return node - def to_nodeid(self, nodeid): - # TODO migrate this function to _get_xml_nodeid and delete - pass + def add_object_xml(self, obj): + """ + Add a UA object element to the XML tree + """ + browsename = obj.get_browse_name().to_string() + nodeid = obj.nodeid.to_string() - # ORIGINAL CODE FROM IMPORTER - # if not nodeid: - # return ua.NodeId(ua.ObjectIds.String) - # elif "=" in nodeid: - # return ua.NodeId.from_string(nodeid) - # elif hasattr(ua.ObjectIds, nodeid): - # return ua.NodeId(getattr(ua.ObjectIds, nodeid)) - # else: - # if nodeid in self.parser.aliases: - # nodeid = self.parser.aliases[nodeid] - # else: - # nodeid = "i={}".format(getattr(ua.ObjectIds, nodeid)) - # return ua.NodeId.from_string(nodeid) - - def add_object(self, obj): - pass + displayname = obj.get_display_name().Text.decode(encoding='UTF8') - # ORIGINAL CODE FROM IMPORTER - # node = self._get_node(obj) - # attrs = ua.ObjectAttributes() - # if obj.desc: - # attrs.Description = ua.LocalizedText(obj.desc) - # attrs.DisplayName = ua.LocalizedText(obj.displayname) - # attrs.EventNotifier = obj.eventnotifier - # node.NodeAttributes = attrs - # self.server.add_nodes([node]) - # self._add_refs(obj) + refs = obj.get_references() + + obj_el = Et.SubElement(self.etree.getroot(), + 'UAObject', + BrowseName=browsename, + NodeId=nodeid) - def add_object_type(self, obj): + disp_el = Et.SubElement(obj_el, 'DisplayName', ) + disp_el.text = displayname + + self._add_ref_sub_els(obj_el, refs) + + def add_object_type_xml(self, obj): pass # ORIGINAL CODE FROM IMPORTER @@ -116,22 +138,22 @@ def add_object_type(self, obj): # self.server.add_nodes([node]) # self._add_refs(obj) - def add_variable(self, obj): + def add_variable_xml(self, obj): """ Add a UA variable element to the XML tree """ - browsename = self._get_xml_browsename(obj) + browsename = obj.get_browse_name().to_string() datatype = o_ids.ObjectIdNames[obj.get_data_type().Identifier] - nodeid = self._get_xml_nodeid(obj) - parent = self._get_xml_parent(obj) - acccesslevel = str(obj.get_access_level().val) - useraccesslevel = str(obj.get_user_access_level().val) + nodeid = obj.nodeid.to_string() + parent = obj.get_parent().nodeid.to_string() + acccesslevel = str(obj.get_attribute(ua.AttributeIds.AccessLevel).Value.Value) + useraccesslevel = str(obj.get_attribute(ua.AttributeIds.UserAccessLevel).Value.Value) displayname = obj.get_display_name().Text.decode(encoding='UTF8') value = str(obj.get_value()) - refs = [] # TODO get list of refs here + refs = obj.get_references() # [] # TODO get list of refs here var_el = Et.SubElement(self.etree.getroot(), 'UAVariable', @@ -145,76 +167,14 @@ def add_variable(self, obj): disp_el = Et.SubElement(var_el, 'DisplayName', ) disp_el.text = displayname - refs_el = Et.SubElement(var_el, 'References') - - # TODO creating XML ref elements not done yet - for ref in refs: - refx_el = Et.SubElement(refs_el, 'Reference', ReferenceType=ref) + self._add_ref_sub_els(var_el, refs) val_el = Et.SubElement(var_el, 'Value') valx_el = Et.SubElement(val_el, 'uax:' + datatype) valx_el.text = value - # ORIGINAL CODE FROM IMPORTER - # node = self._get_node(obj) - # attrs = ua.VariableAttributes() - # if obj.desc: - # attrs.Description = ua.LocalizedText(obj.desc) - # attrs.DisplayName = ua.LocalizedText(obj.displayname) - # attrs.DataType = self.to_nodeid(obj.datatype) - # # if obj.value and len(obj.value) == 1: - # if obj.value is not None: - # attrs.Value = self._add_variable_value(obj, ) - # if obj.rank: - # attrs.ValueRank = obj.rank - # if obj.accesslevel: - # attrs.AccessLevel = obj.accesslevel - # if obj.useraccesslevel: - # attrs.UserAccessLevel = obj.useraccesslevel - # if obj.minsample: - # attrs.MinimumSamplingInterval = obj.minsample - # if obj.dimensions: - # attrs.ArrayDimensions = obj.dimensions - # node.NodeAttributes = attrs - # self.server.add_nodes([node]) - # self._add_refs(obj) - - def _add_variable_value(self, obj): - """ - Returns the value for a Variable based on the objects valuetype. - """ - # MAY NOT BE NEEDED - pass - - # ORIGINAL CODE FROM IMPORTER - # if obj.valuetype == 'ListOfLocalizedText': - # return ua.Variant([ua.LocalizedText(txt) for txt in obj.value], None) - # elif obj.valuetype == 'EnumValueType': - # values = [] - # for ev in obj.value: - # enum_value = ua.EnumValueType() - # enum_value.DisplayName = ua.LocalizedText(ev['DisplayName']) - # enum_value.Description = ua.LocalizedText(ev['Description']) - # enum_value.Value = int(ev['Value']) - # values.append(enum_value) - # return values - # elif obj.valuetype == 'Argument': - # values = [] - # for arg in obj.value: - # argument = ua.Argument() - # argument.Name = arg['Name'] - # argument.Description = ua.LocalizedText(arg['Description']) - # argument.DataType = self.to_nodeid(arg['DataType']) - # argument.ValueRank = int(arg['ValueRank']) - # argument.ArrayDimensions = arg['ArrayDimensions'] - # values.append(argument) - # return values - # - # return ua.Variant(obj.value, getattr(ua.VariantType, obj.valuetype)) - - - def add_variable_type(self, obj): + def add_variable_type_xml(self, obj): pass # ORIGINAL CODE FROM IMPORTER @@ -236,7 +196,7 @@ def add_variable_type(self, obj): # self.server.add_nodes([node]) # self._add_refs(obj) - def add_method(self, obj): + def add_method_xml(self, obj): pass # ORIGINAL CODE FROM IMPORTER @@ -257,7 +217,7 @@ def add_method(self, obj): # self.server.add_nodes([node]) # self._add_refs(obj) - def add_reference(self, obj): + def add_reference_xml(self, obj): # MAY NOT BE NEEDED pass @@ -277,7 +237,7 @@ def add_reference(self, obj): # self.server.add_nodes([node]) # self._add_refs(obj) - def add_datatype(self, obj): + def add_datatype_xml(self, obj): pass # ORIGINAL CODE FROM IMPORTER @@ -310,19 +270,20 @@ def _add_refs(self, obj): # refs.append(ref) # self.server.add_references(refs) - @staticmethod - def _get_xml_nodeid(obj): - """ - Convert a UA NodeId object to a formatted string for XML - :param obj: - :return: - """ - return 'ns=' + str(obj.nodeid.NamespaceIndex) + ';' + str(obj.nodeid.Identifier) + def _get_xml_namespace_uris(self): + # TODO name space uris should be exported + pass + + def _get_xml_aliases(self): + # TODO aliases need to be created at the top of the xml + pass @staticmethod - def _get_xml_browsename(obj): - bn = obj.get_browse_name() - return str(bn.NamespaceIndex) + ':' + bn.Name + def _add_ref_sub_els(parent_el, refs): + refs_el = Et.SubElement(parent_el, 'References') - def _get_xml_parent(self, obj): - return self._get_xml_nodeid(obj.get_parent()) \ No newline at end of file + for ref in refs: + ref_name = o_ids.ObjectIdNames[ref.ReferenceTypeId.Identifier] + ref_forward = str(ref.IsForward) + refx_el = Et.SubElement(refs_el, 'Reference', IsForward=ref_forward, ReferenceType=ref_name) + refx_el.text = ref.NodeId.to_string() From 66144c9d3e43e579c69a3aeef6a99567425f90ef Mon Sep 17 00:00:00 2001 From: zerox1212 Date: Sat, 13 Aug 2016 13:33:15 -0500 Subject: [PATCH 3/5] Add reference aliases, add export of object type --- examples/server-xmlexporter.py | 49 +++++++++ opcua/common/xmlexporter.py | 193 +++++++++++---------------------- 2 files changed, 114 insertions(+), 128 deletions(-) create mode 100644 examples/server-xmlexporter.py diff --git a/examples/server-xmlexporter.py b/examples/server-xmlexporter.py new file mode 100644 index 000000000..c0402f499 --- /dev/null +++ b/examples/server-xmlexporter.py @@ -0,0 +1,49 @@ +import sys +sys.path.insert(0, "..") +import time +from collections import OrderedDict + +from opcua import ua, Server, instantiate +from opcua.common.xmlexporter import XmlExporter + + +if __name__ == "__main__": + + # setup our server + server = Server() + server.set_endpoint("opc.tcp://0.0.0.0:4840/freeopcua/server/") + + # setup our own namespace, not really necessary but should as spec + uri = "http://examples.freeopcua.github.io" + idx = server.register_namespace(uri) + + # get Objects node, this is where we should put our nodes + objects = server.get_objects_node() + + # populating our address space + myobj = objects.add_object(idx, "MyObject") + myvar = myobj.add_variable(idx, "MyVariable", 6.7) + myvar.set_writable() # Set MyVariable to be writable by clients + + dev = server.nodes.base_object_type.add_object_type(0, "MyDevice") + dev.add_variable(0, "sensor1", 1.0) + + mydevice = instantiate(server.nodes.objects, dev, bname="2:Device0001") + + node_list = [dev, mydevice, myobj, myvar] + + # starting! + server.start() + + node_set_attributes = OrderedDict() + node_set_attributes['xmlns:xsi'] = '"http://www.w3.org/2001/XMLSchema-instance"' + node_set_attributes['xmlns:uax'] = 'http://opcfoundation.org/UA/2008/02/Types.xsd' + node_set_attributes['xmlns:s1'] = 'http://yourorganisation.org/dataobject/Types.xsd' + node_set_attributes['xmlns:xsd'] = 'http://www.w3.org/2001/XMLSchema' + node_set_attributes['xmlns'] = 'http://opcfoundation.org/UA/2011/03/UANodeSet.xsd' + + exporter = XmlExporter(server, node_set_attributes) + exporter.build_etree(node_list) + exporter.write_xml('ua-export.xml') + + server.stop() diff --git a/opcua/common/xmlexporter.py b/opcua/common/xmlexporter.py index 44e7b7b40..191ae2840 100644 --- a/opcua/common/xmlexporter.py +++ b/opcua/common/xmlexporter.py @@ -15,6 +15,7 @@ def __init__(self, server, node_set_attrs): self.logger = logging.getLogger(__name__) self.etree = Et.ElementTree(Et.Element('UANodeSet', node_set_attrs)) self.server = server + self.aliases = {} def build_etree(self, node_list): """ @@ -27,16 +28,16 @@ def build_etree(self, node_list): self.logger.info('Building XML etree') # add all namespace uris to the XML etree - self._get_xml_namespace_uris() # TODO not done yet - - # add all required aliases to the XML etree - self._get_xml_aliases() # TODO not done yet + self._add_namespace_uri_els() # TODO not done yet # add all nodes in the list to the XML etree for node in node_list: self.node_to_etree(node) - def export_xml(self, xmlpath): + # add all required aliases to the XML etree; must be done after nodes are added + self._add_alias_els() + + def write_xml(self, xmlpath): """ Write the XML etree in the exporter object to a file Args: @@ -70,21 +71,21 @@ def node_to_etree(self, node): node_class = node.get_node_class() if node_class is ua.NodeClass.Object: - self.add_object_xml(node) + self.add_etree_object(node) elif node_class is ua.NodeClass.ObjectType: - self.add_object_type_xml(node) + self.add_etree_object_type(node) elif node_class is ua.NodeClass.Variable: - self.add_variable_xml(node) + self.add_etree_variable(node) elif node_class is ua.NodeClass.VariableType: - self.add_variable_type_xml(node) + self.add_etree_variable_type(node) elif node_class is ua.NodeClass.RefernceType: - self.add_reference_xml(node) + self.add_etree_reference(node) elif node_class is ua.NodeClass.DataType: - self.add_datatype_xml(node) + self.add_etree_datatype(node) elif node_class is ua.NodeClass.Method: - self.add_method_xml(node) + self.add_etree_method(node) else: - self.logger.info("Not implemented node type: %s ", node_class) + self.logger.info("Exporting node class not implemented: %s ", node_class) def _get_node(self, obj): # TODO not sure if this is required for exporter, check on functionality @@ -103,9 +104,9 @@ def _get_node(self, obj): # node.TypeDefinition = ua.NodeId.from_string(obj.typedef) # return node - def add_object_xml(self, obj): + def add_etree_object(self, obj): """ - Add a UA object element to the XML tree + Add a UA object element to the XML etree """ browsename = obj.get_browse_name().to_string() nodeid = obj.nodeid.to_string() @@ -122,28 +123,36 @@ def add_object_xml(self, obj): disp_el = Et.SubElement(obj_el, 'DisplayName', ) disp_el.text = displayname - self._add_ref_sub_els(obj_el, refs) + self._add_ref_els(obj_el, refs) - def add_object_type_xml(self, obj): - pass + def add_etree_object_type(self, obj): + """ + Add a UA object type element to the XML etree + """ + browsename = obj.get_browse_name().to_string() + nodeid = obj.nodeid.to_string() - # ORIGINAL CODE FROM IMPORTER - # node = self._get_node(obj) - # attrs = ua.ObjectTypeAttributes() - # if obj.desc: - # attrs.Description = ua.LocalizedText(obj.desc) - # attrs.DisplayName = ua.LocalizedText(obj.displayname) - # attrs.IsAbstract = obj.abstract - # node.NodeAttributes = attrs - # self.server.add_nodes([node]) - # self._add_refs(obj) - - def add_variable_xml(self, obj): + displayname = obj.get_display_name().Text.decode(encoding='UTF8') + + refs = obj.get_references() + + obj_el = Et.SubElement(self.etree.getroot(), + 'UAObject', + BrowseName=browsename, + NodeId=nodeid) + + disp_el = Et.SubElement(obj_el, 'DisplayName', ) + disp_el.text = displayname + + self._add_ref_els(obj_el, refs) + + def add_etree_variable(self, obj): """ - Add a UA variable element to the XML tree + Add a UA variable element to the XML etree """ browsename = obj.get_browse_name().to_string() datatype = o_ids.ObjectIdNames[obj.get_data_type().Identifier] + datatype_nodeid = obj.get_data_type().to_string() nodeid = obj.nodeid.to_string() parent = obj.get_parent().nodeid.to_string() acccesslevel = str(obj.get_attribute(ua.AttributeIds.AccessLevel).Value.Value) @@ -153,7 +162,7 @@ def add_variable_xml(self, obj): value = str(obj.get_value()) - refs = obj.get_references() # [] # TODO get list of refs here + refs = obj.get_references() var_el = Et.SubElement(self.etree.getroot(), 'UAVariable', @@ -167,123 +176,51 @@ def add_variable_xml(self, obj): disp_el = Et.SubElement(var_el, 'DisplayName', ) disp_el.text = displayname - self._add_ref_sub_els(var_el, refs) + self._add_ref_els(var_el, refs) val_el = Et.SubElement(var_el, 'Value') valx_el = Et.SubElement(val_el, 'uax:' + datatype) valx_el.text = value - def add_variable_type_xml(self, obj): - pass + # add any references that get used to aliases dict; this gets handled later + self.aliases[datatype] = datatype_nodeid - # ORIGINAL CODE FROM IMPORTER - # node = self._get_node(obj) - # attrs = ua.VariableTypeAttributes() - # if obj.desc: - # attrs.Description = ua.LocalizedText(obj.desc) - # attrs.DisplayName = ua.LocalizedText(obj.displayname) - # attrs.DataType = self.to_nodeid(obj.datatype) - # if obj.value and len(obj.value) == 1: - # attrs.Value = obj.value[0] - # if obj.rank: - # attrs.ValueRank = obj.rank - # if obj.abstract: - # attrs.IsAbstract = obj.abstract - # if obj.dimensions: - # attrs.ArrayDimensions = obj.dimensions - # node.NodeAttributes = attrs - # self.server.add_nodes([node]) - # self._add_refs(obj) - - def add_method_xml(self, obj): + def add_etree_variable_type(self, obj): pass - # ORIGINAL CODE FROM IMPORTER - # node = self._get_node(obj) - # attrs = ua.MethodAttributes() - # if obj.desc: - # attrs.Description = ua.LocalizedText(obj.desc) - # attrs.DisplayName = ua.LocalizedText(obj.displayname) - # if obj.accesslevel: - # attrs.AccessLevel = obj.accesslevel - # if obj.useraccesslevel: - # attrs.UserAccessLevel = obj.useraccesslevel - # if obj.minsample: - # attrs.MinimumSamplingInterval = obj.minsample - # if obj.dimensions: - # attrs.ArrayDimensions = obj.dimensions - # node.NodeAttributes = attrs - # self.server.add_nodes([node]) - # self._add_refs(obj) - - def add_reference_xml(self, obj): - # MAY NOT BE NEEDED + def add_etree_method(self, obj): pass - # ORIGINAL CODE FROM IMPORTER - # node = self._get_node(obj) - # attrs = ua.ReferenceTypeAttributes() - # if obj.desc: - # attrs.Description = ua.LocalizedText(obj.desc) - # attrs.DisplayName = ua.LocalizedText(obj.displayname) - # if obj. inversename: - # attrs.InverseName = ua.LocalizedText(obj.inversename) - # if obj.abstract: - # attrs.IsAbstract = obj.abstract - # if obj.symmetric: - # attrs.Symmetric = obj.symmetric - # node.NodeAttributes = attrs - # self.server.add_nodes([node]) - # self._add_refs(obj) - - def add_datatype_xml(self, obj): + def add_etree_reference(self, obj): pass - # ORIGINAL CODE FROM IMPORTER - # node = self._get_node(obj) - # attrs = ua.DataTypeAttributes() - # if obj.desc: - # attrs.Description = ua.LocalizedText(obj.desc) - # attrs.DisplayName = ua.LocalizedText(obj.displayname) - # if obj.abstract: - # attrs.IsAbstract = obj.abstract - # node.NodeAttributes = attrs - # self.server.add_nodes([node]) - # self._add_refs(obj) - - def _add_refs(self, obj): - # MAY NOT BE NEEDED + def add_etree_datatype(self, obj): pass - # ORIGINAL CODE FROM IMPORTER - # if not obj.refs: - # return - # refs = [] - # for data in obj.refs: - # ref = ua.AddReferencesItem() - # ref.IsForward = True - # ref.ReferenceTypeId = self.to_nodeid(data.reftype) - # ref.SourceNodeId = ua.NodeId.from_string(obj.nodeid) - # ref.TargetNodeClass = ua.NodeClass.DataType - # ref.TargetNodeId = ua.NodeId.from_string(data.target) - # refs.append(ref) - # self.server.add_references(refs) - - def _get_xml_namespace_uris(self): + def _add_namespace_uri_els(self): # TODO name space uris should be exported pass - def _get_xml_aliases(self): - # TODO aliases need to be created at the top of the xml - pass + def _add_alias_els(self): - @staticmethod - def _add_ref_sub_els(parent_el, refs): + aliases_el = Et.Element('Aliases') + + for k, v in self.aliases.items(): + ref_el = Et.SubElement(aliases_el, 'Alias', Alias=k) + ref_el.text = v + + self.etree.getroot().insert(0, aliases_el) + + def _add_ref_els(self, parent_el, refs): refs_el = Et.SubElement(parent_el, 'References') for ref in refs: ref_name = o_ids.ObjectIdNames[ref.ReferenceTypeId.Identifier] ref_forward = str(ref.IsForward) - refx_el = Et.SubElement(refs_el, 'Reference', IsForward=ref_forward, ReferenceType=ref_name) - refx_el.text = ref.NodeId.to_string() + ref_nodeid = ref.NodeId.to_string() + ref_el = Et.SubElement(refs_el, 'Reference', IsForward=ref_forward, ReferenceType=ref_name) + ref_el.text = ref_nodeid + + # add any references that gets used to aliases dict; this gets handled later + self.aliases[ref_name] = ref_nodeid From b5f4c8150667329d3609b1704a6b33e73ae2bf0c Mon Sep 17 00:00:00 2001 From: zerox1212 Date: Sun, 14 Aug 2016 21:36:24 -0500 Subject: [PATCH 4/5] Added UA data type, variable type, namespace uris --- examples/server-xmlexporter.py | 10 +--- opcua/common/xmlexporter.py | 99 +++++++++++++++++++++++----------- 2 files changed, 68 insertions(+), 41 deletions(-) diff --git a/examples/server-xmlexporter.py b/examples/server-xmlexporter.py index c0402f499..8540f8142 100644 --- a/examples/server-xmlexporter.py +++ b/examples/server-xmlexporter.py @@ -35,15 +35,7 @@ # starting! server.start() - node_set_attributes = OrderedDict() - node_set_attributes['xmlns:xsi'] = '"http://www.w3.org/2001/XMLSchema-instance"' - node_set_attributes['xmlns:uax'] = 'http://opcfoundation.org/UA/2008/02/Types.xsd' - node_set_attributes['xmlns:s1'] = 'http://yourorganisation.org/dataobject/Types.xsd' - node_set_attributes['xmlns:xsd'] = 'http://www.w3.org/2001/XMLSchema' - node_set_attributes['xmlns'] = 'http://opcfoundation.org/UA/2011/03/UANodeSet.xsd' - - exporter = XmlExporter(server, node_set_attributes) - exporter.build_etree(node_list) + exporter = XmlExporter(server) exporter.write_xml('ua-export.xml') server.stop() diff --git a/opcua/common/xmlexporter.py b/opcua/common/xmlexporter.py index 191ae2840..cec06d4de 100644 --- a/opcua/common/xmlexporter.py +++ b/opcua/common/xmlexporter.py @@ -3,6 +3,7 @@ format is the one from opc-ua specification """ import logging +from collections import OrderedDict import xml.etree.ElementTree as Et from opcua import ua @@ -11,25 +12,30 @@ class XmlExporter(object): - def __init__(self, server, node_set_attrs): + def __init__(self, server): self.logger = logging.getLogger(__name__) - self.etree = Et.ElementTree(Et.Element('UANodeSet', node_set_attrs)) self.server = server self.aliases = {} - def build_etree(self, node_list): + node_set_attributes = OrderedDict() + node_set_attributes['xmlns:xsi'] = 'http://www.w3.org/2001/XMLSchema-instance' + node_set_attributes['xmlns:uax'] = 'http://opcfoundation.org/UA/2008/02/Types.xsd' + node_set_attributes['xmlns:xsd'] = 'http://www.w3.org/2001/XMLSchema' + node_set_attributes['xmlns'] = 'http://opcfoundation.org/UA/2011/03/UANodeSet.xsd' + + self.etree = Et.ElementTree(Et.Element('UANodeSet', node_set_attributes)) + + def build_etree(self, node_list, uris=None): """ - Create an XML etree object from a list of nodes + Create an XML etree object from a list of nodes; custom namespace uris are optional Args: node_list: list of Node objects for export + uris: list of namespace uri strings Returns: """ self.logger.info('Building XML etree') - # add all namespace uris to the XML etree - self._add_namespace_uri_els() # TODO not done yet - # add all nodes in the list to the XML etree for node in node_list: self.node_to_etree(node) @@ -37,6 +43,10 @@ def build_etree(self, node_list): # add all required aliases to the XML etree; must be done after nodes are added self._add_alias_els() + if uris: + # add all namespace uris to the XML etree; must be done after aliases are added + self._add_namespace_uri_els(uris) + def write_xml(self, xmlpath): """ Write the XML etree in the exporter object to a file @@ -87,23 +97,6 @@ def node_to_etree(self, node): else: self.logger.info("Exporting node class not implemented: %s ", node_class) - def _get_node(self, obj): - # TODO not sure if this is required for exporter, check on functionality - pass - - # ORIGINAL CODE FROM IMPORTER - # node = ua.AddNodesItem() - # node.RequestedNewNodeId = ua.NodeId.from_string(obj.nodeid) - # node.BrowseName = ua.QualifiedName.from_string(obj.browsename) - # node.NodeClass = getattr(ua.NodeClass, obj.nodetype[2:]) - # if obj.parent: - # node.ParentNodeId = ua.NodeId.from_string(obj.parent) - # if obj.parentlink: - # node.ReferenceTypeId = self.to_nodeid(obj.parentlink) - # if obj.typedef: - # node.TypeDefinition = ua.NodeId.from_string(obj.typedef) - # return node - def add_etree_object(self, obj): """ Add a UA object element to the XML etree @@ -157,6 +150,7 @@ def add_etree_variable(self, obj): parent = obj.get_parent().nodeid.to_string() acccesslevel = str(obj.get_attribute(ua.AttributeIds.AccessLevel).Value.Value) useraccesslevel = str(obj.get_attribute(ua.AttributeIds.UserAccessLevel).Value.Value) + symbolicname = None # TODO when to export this? displayname = obj.get_display_name().Text.decode(encoding='UTF8') @@ -187,23 +181,64 @@ def add_etree_variable(self, obj): self.aliases[datatype] = datatype_nodeid def add_etree_variable_type(self, obj): - pass + """ + Add a UA variable type element to the XML etree + """ + browsename = obj.get_browse_name().to_string() + nodeid = obj.nodeid.to_string() + valuerank = None # TODO when to export this? + + displayname = obj.get_display_name().Text.decode(encoding='UTF8') + + refs = obj.get_references() + + obj_el = Et.SubElement(self.etree.getroot(), + 'UAObject', + BrowseName=browsename, + NodeId=nodeid) + + disp_el = Et.SubElement(obj_el, 'DisplayName', ) + disp_el.text = displayname + + self._add_ref_els(obj_el, refs) def add_etree_method(self, obj): - pass + raise NotImplemented def add_etree_reference(self, obj): - pass + raise NotImplemented def add_etree_datatype(self, obj): - pass + """ + Add a UA data type element to the XML etree + """ + browsename = obj.get_browse_name().to_string() + nodeid = obj.nodeid.to_string() - def _add_namespace_uri_els(self): - # TODO name space uris should be exported - pass + displayname = obj.get_display_name().Text.decode(encoding='UTF8') - def _add_alias_els(self): + refs = obj.get_references() + + obj_el = Et.SubElement(self.etree.getroot(), + 'UAObject', + BrowseName=browsename, + NodeId=nodeid) + + disp_el = Et.SubElement(obj_el, 'DisplayName', ) + disp_el.text = displayname + self._add_ref_els(obj_el, refs) + + def _add_namespace_uri_els(self, uris): + nuris_el = Et.Element('NamespaceUris') + + for uri in uris: + uri_el = Et.SubElement(nuris_el, 'Uri') + uri_el.text = uri + + self.etree.getroot().insert(0, nuris_el) + + def _add_alias_els(self): aliases_el = Et.Element('Aliases') for k, v in self.aliases.items(): From 4a8b29cb74fe1b9ece4e2c5314df18502cd2b0bd Mon Sep 17 00:00:00 2001 From: zerox1212 Date: Sun, 14 Aug 2016 21:37:16 -0500 Subject: [PATCH 5/5] Example for custom namespace uri --- examples/server-xmlexporter.py | 1 + 1 file changed, 1 insertion(+) diff --git a/examples/server-xmlexporter.py b/examples/server-xmlexporter.py index 8540f8142..3bb5ae5a8 100644 --- a/examples/server-xmlexporter.py +++ b/examples/server-xmlexporter.py @@ -36,6 +36,7 @@ server.start() exporter = XmlExporter(server) + exporter.build_etree(node_list, ['http://myua.org/test/']) exporter.write_xml('ua-export.xml') server.stop()