00001 """
00002 add nodes defined in XML to address space
00003 format is the one from opc-ua specification
00004 """
00005 import logging
00006 import uuid
00007 from copy import copy
00008
00009 import dateutil.parser
00010
00011 from opcua import ua
00012 from opcua.common import xmlparser
00013
00014
00015 class XmlImporter(object):
00016
00017 def __init__(self, server):
00018 self.logger = logging.getLogger(__name__)
00019 self.parser = None
00020 self.server = server
00021 self.namespaces = {}
00022 self.aliases = {}
00023
00024 def _map_namespaces(self, namespaces_uris):
00025 """
00026 creates a mapping between the namespaces in the xml file and in the server.
00027 if not present the namespace is registered.
00028 """
00029 namespaces = {}
00030 for ns_index, ns_uri in enumerate(namespaces_uris):
00031 ns_server_index = self.server.register_namespace(ns_uri)
00032 namespaces[ns_index + 1] = ns_server_index
00033 return namespaces
00034
00035 def _map_aliases(self, aliases):
00036 """
00037 maps the import aliases to the correct namespaces
00038 """
00039 aliases_mapped = {}
00040 for alias, node_id in aliases.items():
00041 aliases_mapped[alias] = self._migrate_ns(self.to_nodeid(node_id))
00042 return aliases_mapped
00043
00044 def import_xml(self, xmlpath):
00045 """
00046 import xml and return added nodes
00047 """
00048 self.logger.info("Importing XML file %s", xmlpath)
00049 self.parser = xmlparser.XMLParser(xmlpath)
00050
00051 dnodes = self.parser.get_node_datas()
00052 dnodes = self.make_objects(dnodes)
00053
00054 self.namespaces = self._map_namespaces(self.parser.get_used_namespaces())
00055 self.aliases = self._map_aliases(self.parser.get_aliases())
00056
00057 nodes_parsed = self._sort_nodes_by_parentid(dnodes)
00058
00059 nodes = []
00060 for nodedata in nodes_parsed:
00061 if nodedata.nodetype == 'UAObject':
00062 node = self.add_object(nodedata)
00063 elif nodedata.nodetype == 'UAObjectType':
00064 node = self.add_object_type(nodedata)
00065 elif nodedata.nodetype == 'UAVariable':
00066 node = self.add_variable(nodedata)
00067 elif nodedata.nodetype == 'UAVariableType':
00068 node = self.add_variable_type(nodedata)
00069 elif nodedata.nodetype == 'UAReferenceType':
00070 node = self.add_reference_type(nodedata)
00071 elif nodedata.nodetype == 'UADataType':
00072 node = self.add_datatype(nodedata)
00073 elif nodedata.nodetype == 'UAMethod':
00074 node = self.add_method(nodedata)
00075 else:
00076 self.logger.warning("Not implemented node type: %s ", nodedata.nodetype)
00077 continue
00078 nodes.append(node)
00079 return nodes
00080
00081 def make_objects(self, node_datas):
00082 new_nodes = []
00083 for ndata in node_datas:
00084 ndata.nodeid = ua.NodeId.from_string(ndata.nodeid)
00085 ndata.browsename = ua.QualifiedName.from_string(ndata.browsename)
00086 if ndata.parent:
00087 ndata.parent = ua.NodeId.from_string(ndata.parent)
00088 if ndata.parentlink:
00089 ndata.parentlink = self.to_nodeid(ndata.parentlink)
00090 if ndata.typedef:
00091 ndata.typedef = self.to_nodeid(ndata.typedef)
00092 new_nodes.append(ndata)
00093 return new_nodes
00094
00095 def _migrate_ns(self, nodeid):
00096 """
00097 Check if the index of nodeid or browsename given in the xml model file
00098 must be converted to a already existing namespace id based on the files
00099 namespace uri
00100
00101 :returns: NodeId (str)
00102 """
00103 if nodeid.NamespaceIndex in self.namespaces:
00104 nodeid = copy(nodeid)
00105 nodeid.NamespaceIndex = self.namespaces[nodeid.NamespaceIndex]
00106 return nodeid
00107
00108 def _get_node(self, obj):
00109 node = ua.AddNodesItem()
00110 node.RequestedNewNodeId = self._migrate_ns(obj.nodeid)
00111 node.BrowseName = self._migrate_ns(obj.browsename)
00112 self.logger.info("Importing xml node (%s, %s) as (%s %s)", obj.browsename, obj.nodeid, node.BrowseName, node.RequestedNewNodeId)
00113 node.NodeClass = getattr(ua.NodeClass, obj.nodetype[2:])
00114 if obj.parent:
00115 node.ParentNodeId = self._migrate_ns(obj.parent)
00116 if obj.parentlink:
00117 node.ReferenceTypeId = self._migrate_ns(obj.parentlink)
00118 if obj.typedef:
00119 node.TypeDefinition = self._migrate_ns(obj.typedef)
00120 return node
00121
00122 def to_nodeid(self, nodeid):
00123 if isinstance(nodeid, ua.NodeId):
00124 return nodeid
00125 elif not nodeid:
00126 return ua.NodeId(ua.ObjectIds.String)
00127 elif "=" in nodeid:
00128 return ua.NodeId.from_string(nodeid)
00129 elif hasattr(ua.ObjectIds, nodeid):
00130 return ua.NodeId(getattr(ua.ObjectIds, nodeid))
00131 else:
00132 if nodeid in self.aliases:
00133 return self.aliases[nodeid]
00134 else:
00135 return ua.NodeId(getattr(ua.ObjectIds, nodeid))
00136
00137 def add_object(self, obj):
00138 node = self._get_node(obj)
00139 attrs = ua.ObjectAttributes()
00140 if obj.desc:
00141 attrs.Description = ua.LocalizedText(obj.desc)
00142 attrs.DisplayName = ua.LocalizedText(obj.displayname)
00143 attrs.EventNotifier = obj.eventnotifier
00144 node.NodeAttributes = attrs
00145 res = self.server.iserver.isession.add_nodes([node])
00146 self._add_refs(obj)
00147 res[0].StatusCode.check()
00148 return res[0].AddedNodeId
00149
00150 def add_object_type(self, obj):
00151 node = self._get_node(obj)
00152 attrs = ua.ObjectTypeAttributes()
00153 if obj.desc:
00154 attrs.Description = ua.LocalizedText(obj.desc)
00155 attrs.DisplayName = ua.LocalizedText(obj.displayname)
00156 attrs.IsAbstract = obj.abstract
00157 node.NodeAttributes = attrs
00158 res = self.server.iserver.isession.add_nodes([node])
00159 self._add_refs(obj)
00160 res[0].StatusCode.check()
00161 return res[0].AddedNodeId
00162
00163 def add_variable(self, obj):
00164 node = self._get_node(obj)
00165 attrs = ua.VariableAttributes()
00166 if obj.desc:
00167 attrs.Description = ua.LocalizedText(obj.desc)
00168 attrs.DisplayName = ua.LocalizedText(obj.displayname)
00169 attrs.DataType = self.to_nodeid(obj.datatype)
00170 if obj.value is not None:
00171 attrs.Value = self._add_variable_value(obj,)
00172 if obj.rank:
00173 attrs.ValueRank = obj.rank
00174 if obj.accesslevel:
00175 attrs.AccessLevel = obj.accesslevel
00176 if obj.useraccesslevel:
00177 attrs.UserAccessLevel = obj.useraccesslevel
00178 if obj.minsample:
00179 attrs.MinimumSamplingInterval = obj.minsample
00180 if obj.dimensions:
00181 attrs.ArrayDimensions = obj.dimensions
00182 node.NodeAttributes = attrs
00183 res = self.server.iserver.isession.add_nodes([node])
00184 self._add_refs(obj)
00185 res[0].StatusCode.check()
00186 return res[0].AddedNodeId
00187
00188 def _make_ext_obj(self, obj):
00189 ext = getattr(ua, obj.objname)()
00190 for name, val in obj.body:
00191 if isinstance(val, str):
00192 raise Exception("Error val should a dict", name, val)
00193 else:
00194 for attname, v in val:
00195 self._set_attr(ext, attname, v)
00196 return ext
00197
00198 def _set_attr(self, obj, attname, val):
00199
00200
00201
00202 if isinstance(val, str):
00203 pval = xmlparser.ua_type_to_python(val, obj.ua_types[attname])
00204 setattr(obj, attname, pval)
00205 else:
00206
00207 obj2 = getattr(obj, attname)
00208 if isinstance(obj2, ua.NodeId):
00209 for attname2, v2 in val:
00210 if attname2 == "Identifier":
00211 obj2 = ua.NodeId.from_string(v2)
00212 setattr(obj, attname, obj2)
00213 break
00214 elif not isinstance(obj2, ua.NodeId) and not hasattr(obj2, "ua_types"):
00215
00216 my_list = []
00217 for vtype, v2 in val:
00218 my_list.append(xmlparser.ua_type_to_python(v2, vtype))
00219 setattr(obj, attname, my_list)
00220 else:
00221 for attname2, v2 in val:
00222 self._set_attr(obj2, attname2, v2)
00223 setattr(obj, attname, obj2)
00224
00225 def _add_variable_value(self, obj):
00226 """
00227 Returns the value for a Variable based on the objects value type.
00228 """
00229 self.logger.debug("Setting value with type %s and value %s", obj.valuetype, obj.value)
00230 if obj.valuetype == 'ListOfExtensionObject':
00231 values = []
00232 for ext in obj.value:
00233 extobj = self._make_ext_obj(ext)
00234 values.append(extobj)
00235 return values
00236 elif obj.valuetype.startswith("ListOf"):
00237 vtype = obj.valuetype[6:]
00238 if hasattr(ua.ua_binary.Primitives, vtype):
00239 return ua.Variant(obj.value, getattr(ua.VariantType, vtype))
00240 else:
00241 return [getattr(ua, vtype)(v) for v in obj.value]
00242 elif obj.valuetype == 'ExtensionObject':
00243 extobj = self._make_ext_obj(obj.value)
00244 return ua.Variant(extobj, getattr(ua.VariantType, obj.valuetype))
00245 elif obj.valuetype == 'DateTime':
00246 return ua.Variant(dateutil.parser.parse(obj.value), getattr(ua.VariantType, obj.valuetype))
00247 elif obj.valuetype == 'Guid':
00248 return ua.Variant(uuid.UUID(obj.value), getattr(ua.VariantType, obj.valuetype))
00249 elif obj.valuetype == 'LocalizedText':
00250 ltext = ua.LocalizedText()
00251 for name, val in obj.value:
00252 if name == "Text":
00253 ltext.Text = val.encode("utf-8")
00254 else:
00255 self.logger.warning("While parsing localizedText value, unkown element: %s with val: %s", name, val)
00256 return ua.Variant(ltext, ua.VariantType.LocalizedText)
00257 elif obj.valuetype == 'NodeId':
00258 return ua.Variant(ua.NodeId.from_string(obj.value))
00259 else:
00260 return ua.Variant(obj.value, getattr(ua.VariantType, obj.valuetype))
00261
00262 def add_variable_type(self, obj):
00263 node = self._get_node(obj)
00264 attrs = ua.VariableTypeAttributes()
00265 if obj.desc:
00266 attrs.Description = ua.LocalizedText(obj.desc)
00267 attrs.DisplayName = ua.LocalizedText(obj.displayname)
00268 attrs.DataType = self.to_nodeid(obj.datatype)
00269 if obj.value and len(obj.value) == 1:
00270 attrs.Value = obj.value[0]
00271 if obj.rank:
00272 attrs.ValueRank = obj.rank
00273 if obj.abstract:
00274 attrs.IsAbstract = obj.abstract
00275 if obj.dimensions:
00276 attrs.ArrayDimensions = obj.dimensions
00277 node.NodeAttributes = attrs
00278 res = self.server.iserver.isession.add_nodes([node])
00279 self._add_refs(obj)
00280 res[0].StatusCode.check()
00281 return res[0].AddedNodeId
00282
00283 def add_method(self, obj):
00284 node = self._get_node(obj)
00285 attrs = ua.MethodAttributes()
00286 if obj.desc:
00287 attrs.Description = ua.LocalizedText(obj.desc)
00288 attrs.DisplayName = ua.LocalizedText(obj.displayname)
00289 if obj.accesslevel:
00290 attrs.AccessLevel = obj.accesslevel
00291 if obj.useraccesslevel:
00292 attrs.UserAccessLevel = obj.useraccesslevel
00293 if obj.minsample:
00294 attrs.MinimumSamplingInterval = obj.minsample
00295 if obj.dimensions:
00296 attrs.ArrayDimensions = obj.dimensions
00297 node.NodeAttributes = attrs
00298 res = self.server.iserver.isession.add_nodes([node])
00299 self._add_refs(obj)
00300 res[0].StatusCode.check()
00301 return res[0].AddedNodeId
00302
00303 def add_reference_type(self, obj):
00304 node = self._get_node(obj)
00305 attrs = ua.ReferenceTypeAttributes()
00306 if obj.desc:
00307 attrs.Description = ua.LocalizedText(obj.desc)
00308 attrs.DisplayName = ua.LocalizedText(obj.displayname)
00309 if obj. inversename:
00310 attrs.InverseName = ua.LocalizedText(obj.inversename)
00311 if obj.abstract:
00312 attrs.IsAbstract = obj.abstract
00313 if obj.symmetric:
00314 attrs.Symmetric = obj.symmetric
00315 node.NodeAttributes = attrs
00316 res = self.server.iserver.isession.add_nodes([node])
00317 self._add_refs(obj)
00318 res[0].StatusCode.check()
00319 return res[0].AddedNodeId
00320
00321 def add_datatype(self, obj):
00322 node = self._get_node(obj)
00323 attrs = ua.DataTypeAttributes()
00324 if obj.desc:
00325 attrs.Description = ua.LocalizedText(obj.desc)
00326 attrs.DisplayName = ua.LocalizedText(obj.displayname)
00327 if obj.abstract:
00328 attrs.IsAbstract = obj.abstract
00329 node.NodeAttributes = attrs
00330 res = self.server.iserver.isession.add_nodes([node])
00331 self._add_refs(obj)
00332 res[0].StatusCode.check()
00333 return res[0].AddedNodeId
00334
00335 def _add_refs(self, obj):
00336 if not obj.refs:
00337 return
00338 refs = []
00339 for data in obj.refs:
00340 ref = ua.AddReferencesItem()
00341 ref.IsForward = True
00342 ref.ReferenceTypeId = self.to_nodeid(data.reftype)
00343 ref.SourceNodeId = self._migrate_ns(obj.nodeid)
00344 ref.TargetNodeClass = ua.NodeClass.DataType
00345 ref.TargetNodeId = self._migrate_ns(self.to_nodeid(data.target))
00346 refs.append(ref)
00347 self.server.iserver.isession.add_references(refs)
00348
00349 def _sort_nodes_by_parentid(self, ndatas):
00350 """
00351 Sort the list of nodes according their parent node in order to respect
00352 the dependency between nodes.
00353
00354 :param nodes: list of NodeDataObjects
00355 :returns: list of sorted nodes
00356 """
00357 _ndatas = list(ndatas)
00358
00359 sorted_nodes_ids = []
00360
00361 sorted_ndatas = []
00362 all_node_ids = [data.nodeid for data in ndatas]
00363
00364
00365
00366
00367
00368 while len(_ndatas) > 0:
00369 pop_nodes = []
00370 for ndata in _ndatas:
00371
00372
00373
00374 if ndata.nodeid.NamespaceIndex not in self.namespaces or \
00375 ndata.parent is None or \
00376 ndata.parent not in all_node_ids:
00377 sorted_ndatas.append(ndata)
00378 sorted_nodes_ids.append(ndata.nodeid)
00379 pop_nodes.append(ndata)
00380 else:
00381
00382
00383 if ndata.parent in sorted_nodes_ids:
00384 sorted_ndatas.append(ndata)
00385 sorted_nodes_ids.append(ndata.nodeid)
00386 pop_nodes.append(ndata)
00387
00388 for ndata in pop_nodes:
00389 _ndatas.pop(_ndatas.index(ndata))
00390 return sorted_ndatas