23 from __future__
import print_function
25 from time
import struct_time, strftime, strptime, mktime
26 from struct
import pack
as structpack
29 from ua_builtin_types
import *;
30 from ua_node_types
import *;
31 from ua_constants
import *;
32 from open62541_MacroHelper
import open62541_MacroHelper
35 logger = logging.getLogger(__name__)
40 xmlvalue = xmlvalue.nextSibling
41 while not xmlvalue ==
None and not xmlvalue.nodeType == xmlvalue.ELEMENT_NODE:
42 xmlvalue = xmlvalue.nextSibling
50 """ Class holding and managing a set of OPCUA nodes.
52 This class handles parsing XML description of namespaces, instantiating
53 nodes, linking references, graphing the namespace and compiling a binary
56 Note that nodes assigned to this class are not restricted to having a
57 single namespace ID. This class represents the entire physical address
58 space of the binary representation and all nodes that are to be included
59 in that segment of memory.
65 __binaryIndirectPointers__ = []
68 namespaceIdentifiers = {}
73 'objecttype',
'variabletype',
'methodtype', \
74 'datatype',
'referencetype',
'aliases']
85 """ Called by nodes or references who have parsed an XML reference to a
86 node represented by a string.
90 XML String representations of references have the form 'i=xy' or
91 'ns=1;s="This unique Node"'. Since during the parsing of this attribute
92 only a subset of nodes are known/parsed, this reference string cannot be
93 linked when encountered.
95 References register themselves with the namespace to have their target
96 attribute (string) parsed by linkOpenPointers() when all nodes are
97 created, so that target can be dereferenced an point to an actual node.
102 """ Return the list of references registered for linking during the next call
103 of linkOpenPointers()
108 """ Returns the number of unlinked references that will be processed during
109 the next call of linkOpenPointers()
114 """ Parses the <Alias> XML Element present in must XML NodeSet definitions.
118 Contents the Alias element are stored in a dictionary for further
119 dereferencing during pointer linkage (see linkOpenPointer()).
121 if not xmlelement.tagName ==
"Aliases":
122 logger.error(
"XMLElement passed is not an Aliaslist")
124 for al
in xmlelement.childNodes:
125 if al.nodeType == al.ELEMENT_NODE:
126 if al.hasAttribute(
"Alias"):
127 aliasst = al.getAttribute(
"Alias")
128 if sys.version_info[0] < 3:
129 aliasnd =
unicode(al.firstChild.data)
131 aliasnd = al.firstChild.data
132 if not aliasst
in self.
aliases:
133 self.
aliases[aliasst] = aliasnd
134 logger.debug(
"Added new alias \"" +
str(aliasst) +
"\" == \"" +
str(aliasnd) +
"\"")
136 if self.
aliases[aliasst] != aliasnd:
137 logger.error(
"Alias definitions for " + aliasst +
" differ. Have " + self.
aliases[aliasst] +
" but XML defines " + aliasnd +
". Keeping current definition.")
140 """ Returns the first node in the nodelist whose browseName matches idstring.
144 if idstring==
str(n.browseName()):
147 logger.error(
"Found multiple nodes with same ID!?")
148 if len(matches) == 0:
154 """ Returns the first node in the nodelist whose id string representation
159 if idstring==
str(n.id()):
162 logger.error(
"Found multiple nodes with same ID!?")
163 if len(matches) == 0:
169 """ createNode is instantiates a node described by xmlelement, its type being
170 defined by the string ndtype.
174 If the xmlelement is an <Alias>, the contents will be parsed and stored
175 for later dereferencing during pointer linking (see linkOpenPointers).
177 Recognized types are:
188 For every recognized type, an appropriate node class is added to the node
189 list of the namespace. The NodeId of the given node is created and parsing
190 of the node attributes and elements is delegated to the parseXML() and
191 parseXMLSubType() functions of the instantiated class.
193 If the NodeID attribute is non-unique in the node list, the creation is
194 deferred and an error is logged.
196 if not isinstance(xmlelement, dom.Element):
197 logger.error(
"Error: Can not create node from invalid XMLElement")
202 for idname
in [
'NodeId',
'NodeID',
'nodeid']:
203 if xmlelement.hasAttribute(idname):
204 id = xmlelement.getAttribute(idname)
205 if ndtype ==
'aliases':
209 logger.info(
"Error: XMLElement has no id, node will not be created!")
219 logger.info(
"XMLElement with duplicate ID " +
str(id) +
" found, node will be replaced!")
221 self.
nodes.remove(nd)
225 if (ndtype ==
'variable'):
227 elif (ndtype ==
'object'):
229 elif (ndtype ==
'method'):
231 elif (ndtype ==
'objecttype'):
233 elif (ndtype ==
'variabletype'):
235 elif (ndtype ==
'methodtype'):
236 node = opcua_node_methodType_t(id, self)
237 elif (ndtype ==
'datatype'):
239 elif (ndtype ==
'referencetype'):
242 logger.error(
"No node constructor for type " + ndtype)
245 node.parseXML(xmlelement)
247 self.
nodes.append(node)
255 logger.debug(
"Removing nodeId " +
str(nodeId))
256 self.
nodes.remove(nd)
257 if nd.getInverseReferences() !=
None:
258 for ref
in nd.getInverseReferences():
260 src.removeReferenceToNode(nd)
265 """ Appends a node to the list of nodes that should be contained in the
266 first 765 bytes (255 pointer slots a 3 bytes) in the binary
267 representation (indirect referencing space).
269 This function is reserved for references and dataType pointers.
276 """ Returns the slot/index of a pointer in the indirect referencing space
277 (first 765 Bytes) of the binary representation.
285 """ Reads an XML Namespace definition and instantiates node.
289 parseXML open the file xmldoc using xml.dom.minidom and searches for
290 the first UANodeSet Element. For every Element encountered, createNode
291 is called to instantiate a node of the appropriate type.
294 UANodeSet = dom.parse(xmldoc).getElementsByTagName(
"UANodeSet")
295 if len(UANodeSet) == 0:
296 logger.error(
"Error: No NodeSets found")
298 if len(UANodeSet) != 1:
299 logger.error(
"Error: Found more than 1 Nodeset in XML File")
301 UANodeSet = UANodeSet[0]
302 for nd
in UANodeSet.childNodes:
303 if nd.nodeType != nd.ELEMENT_NODE:
306 ndType = nd.tagName.lower()
307 if ndType[:2] ==
"ua":
310 logger.warn(
"XML Element or NodeType " + ndType +
" is unknown and will be ignored")
313 if not ndType
in typedict:
316 typedict[ndType] = typedict[ndType] + 1
319 logger.debug(
"Currently " +
str(len(self.
nodes)) +
" nodes in address space. Type distribution for this run was: " +
str(typedict))
322 """ Substitutes symbolic NodeIds in references for actual node instances.
326 References that have registered themselves with linkLater() to have
327 their symbolic NodeId targets ("ns=2;i=32") substituted for an actual
328 node will be iterated by this function. For each reference encountered
329 in the list of unlinked/open references, the target string will be
330 evaluated and searched for in the node list of this namespace. If found,
331 the target attribute of the reference will be substituted for the
334 If a reference fails to get linked, it will remain in the list of
335 unlinked references. The individual items in this list can be
336 retrieved using getUnlinkedPointers().
343 if not l.target() ==
None and not isinstance(l.target(), opcua_node_t):
344 if isinstance(l.target(),str)
or isinstance(l.target(),unicode):
348 l.target(self.
aliases[l.target()])
351 if l.target()[:2] ==
"i=" or l.target()[:2] ==
"g=" or \
352 l.target()[:2] ==
"b=" or l.target()[:2] ==
"s=" or \
353 l.target()[:3] ==
"ns=" :
356 logger.error(
"Failed to link pointer to target (node not found) " + l.target())
361 logger.error(
"Failed to link pointer to target (target not Alias or Node) " + l.target())
363 logger.error(
"Failed to link pointer to target (don't know dummy type + " +
str(
type(l.target())) +
" +) " +
str(l.target()))
365 logger.error(
"Pointer has null target: " +
str(l))
368 referenceLinked =
False
369 if not l.referenceType() ==
None:
370 if l.referenceType()
in self.
aliases:
371 l.referenceType(self.
aliases[l.referenceType()])
374 logger.error(
"Failed to link reference type to target (node not found) " + l.referenceType())
377 referenceLinked =
True
379 referenceLinked =
True
381 if referenceLinked ==
True and targetLinked ==
True:
385 logger.warn(
"Inverting reference direction for all references with isForward==False attribute (is this correct!?)")
387 for r
in n.getReferences():
388 if r.isForward() ==
False:
390 if isinstance(tgt, opcua_node_t):
392 nref.referenceType(r.referenceType())
393 tgt.addReference(nref)
396 logger.debug(
"Updating all referencedBy fields in nodes for inverse lookups.")
398 n.updateInverseReferences()
408 logger.debug(
"Sanitizing nodes and references...")
410 if n.sanitize() ==
False:
412 if not len(remove) == 0:
413 logger.warn(
str(len(remove)) +
" nodes will be removed because they failed sanitation.")
416 logger.warn(
"Not actually removing nodes... it's unclear if this is valid or not")
419 """ Returns the first node instance with the browseName "Root".
424 """ Calls buildEncoding() for all DataType nodes (opcua_node_dataType_t).
428 stat = {
True: 0,
False: 0}
430 if isinstance(n, opcua_node_dataType_t):
432 stat[n.isEncodable()] = stat[n.isEncodable()] + 1
433 logger.debug(
"Type definitions built/passed: " +
str(stat))
437 if isinstance(n, opcua_node_variable_t):
441 """ Outputs a graphiz/dot description of all nodes in the namespace.
443 Output will written into filename to be parsed by dot/neato...
445 Note that for namespaces with more then 20 nodes the reference structure
446 will lead to a mostly illegible and huge graph. Use printDotGraphWalk()
447 for plotting specific portions of a large namespace.
449 file=open(filename,
'w+')
451 file.write(
"digraph ns {\n")
453 file.write(n.printDot())
457 def getSubTypesOf(self, tdNodes = None, currentNode = None, hasSubtypeRefNode = None):
461 if currentNode ==
None:
463 tdNodes.append(currentNode)
466 if hasSubtypeRefNode ==
None:
468 if hasSubtypeRefNode ==
None:
472 for ref
in currentNode.getReferences():
473 if ref.isForward()
and ref.referenceType().
id() == hasSubtypeRefNode.id():
474 tdNodes.append(ref.target())
475 self.getTypeDefinitionNodes(tdNodes=tdNodes, currentNode = ref.target(), hasSubtypeRefNode=hasSubtypeRefNode)
480 def printDotGraphWalk(self, depth=1, filename="out.dot", rootNode=None, followInverse = False, excludeNodeIds=[]):
481 """ Outputs a graphiz/dot description the nodes centered around rootNode.
483 References beginning from rootNode will be followed for depth steps. If
484 "followInverse = True" is passed, then inverse (not Forward) references
485 will also be followed.
487 Nodes can be excluded from the graph by passing a list of NodeIds as
488 string representation using excludeNodeIds (ex ["i=53", "ns=2;i=453"]).
490 Output is written into filename to be parsed by dot/neato/srfp...
494 if rootNode ==
None or \
495 not isinstance(rootNode, opcua_node_t)
or \
496 not rootNode
in self.
nodes:
501 file=open(filename,
'w+')
506 file.write(
"digraph ns {\n")
507 file.write(root.printDot())
509 if followInverse ==
True:
510 refs = root.getReferences();
512 for ref
in root.getReferences():
518 if isinstance(ref.target(), opcua_node_t):
520 if not str(tgt.id())
in excludeNodeIds:
521 if not tgt
in processed:
522 file.write(tgt.printDot())
523 processed.append(tgt)
524 if ref.isForward() ==
False and followInverse ==
True:
525 tmp = tmp + tgt.getReferences();
526 elif ref.isForward() ==
True :
527 tmp = tmp + tgt.getReferences();
552 return (rind, minweightnd, minweight)
558 for n
in range(0,len(self.
nodes)):
559 nmatrix.append([
None] + [0]*len(self.
nodes))
569 subTypeRefs.append(tn)
570 subTypeRefs = subTypeRefs + self.
getSubTypesOf(currentNode=tn)
572 logger.debug(
"Building connectivity matrix for node order optimization.")
574 for node
in self.
nodes:
575 nind = self.
nodes.index(node)
576 nmatrix[nind][0] = node
579 logger.debug(
"Determining node interdependencies.")
580 for node
in self.
nodes:
581 nind = self.
nodes.index(node)
583 for ref
in node.getReferences():
584 if isinstance(ref.target(), opcua_node_t):
585 tind = self.
nodes.index(ref.target())
587 if ref.referenceType()
in typeRefs
and ref.isForward():
588 nmatrix[nind][tind+1] += 200
590 elif ref.referenceType()
in subTypeRefs
and not ref.isForward():
591 nmatrix[nind][tind+1] += 100
593 elif ref.isForward():
594 nmatrix[tind][nind+1] += 1
596 logger.debug(
"Using Djikstra topological sorting to determine printing order.")
598 while len(reorder) < len(self.
nodes):
602 for ref
in node.getReferences():
603 if isinstance(ref.target(), opcua_node_t):
604 tind = self.
nodes.index(ref.target())
605 if ref.referenceType()
in typeRefs
and ref.isForward():
606 nmatrix[nind][tind+1] -= 200
607 elif ref.referenceType()
in subTypeRefs
and not ref.isForward():
608 nmatrix[nind][tind+1] -= 100
609 elif ref.isForward():
610 nmatrix[tind][nind+1] -= 1
611 nmatrix[nind][0] =
None
613 logger.debug(
"Nodes reordered.")
623 logger.debug(
"Reordering nodes for minimal dependencies during printing.")
634 if not n
in printedExternally:
635 unPrintedNodes.append(n)
637 logger.debug(
"Node " +
str(n.id()) +
" is being ignored.")
638 for n
in unPrintedNodes:
639 for r
in n.getReferences():
640 if (r.target() !=
None)
and (r.target().
id() !=
None)
and (r.parent() !=
None):
641 unPrintedRefs.append(r)
643 logger.debug(
str(len(unPrintedNodes)) +
" Nodes, " +
str(len(unPrintedRefs)) +
"References need to get printed.")
644 header.append(
"/* WARNING: This is a generated file.\n * Any manual changes will be overwritten.\n\n */")
645 code.append(
"/* WARNING: This is a generated file.\n * Any manual changes will be overwritten.\n\n */")
647 header.append(
'#ifndef '+outfilename.upper()+
'_H_')
648 header.append(
'#define '+outfilename.upper()+
'_H_')
649 header.append(
'#ifdef UA_NO_AMALGAMATION')
650 header.append(
'#include "server/ua_server_internal.h"')
651 header.append(
'#include "server/ua_nodes.h"')
652 header.append(
' #include "ua_util.h"')
653 header.append(
' #include "ua_types.h"')
654 header.append(
' #include "ua_types_encoding_binary.h"')
655 header.append(
' #include "ua_types_generated_encoding_binary.h"')
656 header.append(
' #include "ua_transport_generated_encoding_binary.h"')
657 header.append(
'#else')
658 header.append(
' #include "open62541.h"')
659 header.append(
'#endif')
661 header.append(
'/* Definition that (in userspace models) may be ')
662 header.append(
' * - not included in the amalgamated header or')
663 header.append(
' * - not part of public headers or')
664 header.append(
' * - not exported in the shared object in combination with any of the above')
665 header.append(
' * but are required for value encoding.')
666 header.append(
' * NOTE: Userspace UA_(decode|encode)Binary /wo amalgamations requires UA_EXPORT to be appended to the appropriate definitions. */')
667 header.append(
'#ifndef UA_ENCODINGOFFSET_BINARY')
668 header.append(
'# define UA_ENCODINGOFFSET_BINARY 2')
669 header.append(
'#endif')
670 header.append(
'#ifndef NULL')
671 header.append(
' #define NULL ((void *)0)')
672 header.append(
'#endif')
673 header.append(
'#ifndef UA_malloc')
674 header.append(
' #define UA_malloc(_p_size) malloc(_p_size)')
675 header.append(
'#endif')
676 header.append(
'#ifndef UA_free')
677 header.append(
' #define UA_free(_p_ptr) free(_p_ptr)')
678 header.append(
'#endif')
680 code.append(
'#include "'+outfilename+
'.h"')
681 code.append(
"UA_INLINE UA_StatusCode "+outfilename+
"(UA_Server *server) {")
682 code.append(
'UA_StatusCode retval = UA_STATUSCODE_GOOD; ')
683 code.append(
'if(retval == UA_STATUSCODE_GOOD){retval = UA_STATUSCODE_GOOD;} //ensure that retval is used');
687 if nsid == 0
or nsid==1:
691 name = name.replace(
"\"",
"\\\"")
692 code.append(
"if (UA_Server_addNamespace(server, \"{0}\") != {1})\n return UA_STATUSCODE_BADUNEXPECTEDERROR;".
format(name, nsid))
697 logger.debug(
"Collecting all references used in the namespace.")
703 if nc != NODE_CLASS_OBJECT
and nc != NODE_CLASS_VARIABLE
and nc != NODE_CLASS_VIEW:
704 header = header + codegen.getNodeIdDefineString(n)
707 for r
in n.getReferences():
709 if not r.referenceType()
in refsUsed
and r.referenceType() !=
None and r.referenceType().
id().ns == 0:
710 refsUsed.append(r.referenceType())
711 logger.debug(
str(len(refsUsed)) +
" reference types are used in the namespace, which will now get bootstrapped.")
713 code = code + r.printOpen62541CCode(unPrintedNodes, unPrintedRefs);
715 header.append(
"extern UA_StatusCode "+outfilename+
"(UA_Server *server);\n")
716 header.append(
"#endif /* "+outfilename.upper()+
"_H_ */")
720 logger.debug(
"Printing all other nodes.")
722 code = code + n.printOpen62541CCode(unPrintedNodes, unPrintedRefs, supressGenerationOfAttribute=supressGenerationOfAttribute)
724 if len(unPrintedNodes) != 0:
725 logger.warn(
"" +
str(len(unPrintedNodes)) +
" nodes could not be translated to code.")
727 logger.debug(
"Printing suceeded for all nodes")
729 if len(unPrintedRefs) != 0:
730 logger.debug(
"Attempting to print " +
str(len(unPrintedRefs)) +
" unprinted references.")
732 for r
in unPrintedRefs:
733 if not (r.target()
not in unPrintedNodes)
and not (r.parent()
in unPrintedNodes):
734 if not isinstance(r.parent(), opcua_node_t):
735 logger.debug(
"Reference has no parent!")
736 elif not isinstance(r.parent().
id(), opcua_node_id_t):
737 logger.debug(
"Parents nodeid is not a nodeID!")
739 if (len(tmprefs) == 0):
740 code.append(
"// Creating leftover references:")
741 code = code + codegen.getCreateStandaloneReference(r.parent(), r)
746 unPrintedRefs.remove(r)
747 if len(unPrintedRefs) != 0:
748 logger.warn(
"" +
str(len(unPrintedRefs)) +
" references could not be translated to code.")
750 logger.debug(
"Printing succeeded for all references")
752 code.append(
"return UA_STATUSCODE_GOOD;")
764 logger.debug(
"Phase 1: Reading XML file nodessets")
765 self.
namespace.parseXML(
"Opc.Ua.NodeSet2.xml")
770 logger.debug(
"Phase 2: Linking address space references and datatypes")
774 logger.debug(
"Phase 3: Comprehending DataType encoding rules")
777 logger.debug(
"Phase 4: Allocating variable value data")
781 f = open(
"binary.base64",
"w+")
782 f.write(bin.encode(
"base64"))
790 while (len(ns) < len(allnodes)):
793 print(
"Iteration: " +
str(i))
796 for r
in n.getReferences():
797 if (
not r.target()
in tmp):
798 tmp.append(r.target())
799 print(
"...tmp, " +
str(len(tmp)) +
" nodes discovered")
803 print(
"...done, " +
str(len(ns)) +
" nodes discovered")
805 logger.debug(
"Phase 5: Printing pretty graph")
806 self.
namespace.printDotGraphWalk(depth=1, rootNode=self.
namespace.getNodeByIDString(
"i=84"), followInverse=
False, excludeNodeIds=[
"i=29",
"i=22",
"i=25"])
813 logger.debug(
"Phase 1: Reading XML file nodessets")
814 self.
namespace.parseXML(
"Opc.Ua.NodeSet2.xml")
819 logger.debug(
"Phase 2: Linking address space references and datatypes")
823 logger.debug(
"Phase 3: Calling C Printers")
824 code = self.
namespace.printOpen62541Header()
826 codeout = open(
"./open62541_namespace.c",
"w+")
828 codeout.write(line +
"\n")
837 if __name__ ==
'__main__':