|
@@ -0,0 +1,627 @@
|
|
|
+#!/usr/bin/env/python
|
|
|
+# -*- coding: utf-8 -*-
|
|
|
+
|
|
|
+###
|
|
|
+### Author: Chris Iatrou (ichrispa@core-vector.net)
|
|
|
+### Version: rev 13
|
|
|
+###
|
|
|
+### This program was created for educational purposes and is released into the
|
|
|
+### public domain under the General Public Licence. A copy of the GNU GPL is
|
|
|
+### available under http://www.gnu.org/licenses/gpl-3.0.html.
|
|
|
+###
|
|
|
+### This program is not meant to be used in a production environment. The
|
|
|
+### author is not liable for any complications arising due to the use of
|
|
|
+### this program.
|
|
|
+###
|
|
|
+
|
|
|
+
|
|
|
+from time import struct_time, strftime, strptime, mktime
|
|
|
+from struct import pack as structpack
|
|
|
+
|
|
|
+from logger import *;
|
|
|
+from ua_builtin_types import *;
|
|
|
+from ua_node_types import *;
|
|
|
+from open62541_MacroHelper import open62541_MacroHelper
|
|
|
+
|
|
|
+def getNextElementNode(xmlvalue):
|
|
|
+ if xmlvalue == None:
|
|
|
+ return None
|
|
|
+ xmlvalue = xmlvalue.nextSibling
|
|
|
+ while not xmlvalue == None and not xmlvalue.nodeType == xmlvalue.ELEMENT_NODE:
|
|
|
+ xmlvalue = xmlvalue.nextSibling
|
|
|
+ return xmlvalue
|
|
|
+
|
|
|
+###
|
|
|
+### Namespace Organizer
|
|
|
+###
|
|
|
+
|
|
|
+class opcua_namespace():
|
|
|
+ """ Class holding and managing a set of OPCUA nodes.
|
|
|
+
|
|
|
+ This class handles parsing XML description of namespaces, instantiating
|
|
|
+ nodes, linking references, graphing the namespace and compiling a binary
|
|
|
+ representation.
|
|
|
+
|
|
|
+ Note that nodes assigned to this class are not restricted to having a
|
|
|
+ single namespace ID. This class represents the entire physical address
|
|
|
+ space of the binary representation and all nodes that are to be included
|
|
|
+ in that segment of memory.
|
|
|
+ """
|
|
|
+ nodes = []
|
|
|
+ nodeids = {}
|
|
|
+ aliases = {}
|
|
|
+ __linkLater__ = []
|
|
|
+ __binaryIndirectPointers__ = []
|
|
|
+ name = ""
|
|
|
+ knownNodeTypes = ""
|
|
|
+
|
|
|
+ def __init__(self, name):
|
|
|
+ self.nodes = []
|
|
|
+ self.knownNodeTypes = ['variable', 'object', 'method', 'referencetype', \
|
|
|
+ 'objecttype', 'variabletype', 'methodtype', \
|
|
|
+ 'datatype', 'referencetype', 'aliases']
|
|
|
+ self.name = name
|
|
|
+ self.nodeids = {}
|
|
|
+ self.aliases = {}
|
|
|
+ self.__binaryIndirectPointers__ = []
|
|
|
+
|
|
|
+ def linkLater(self, pointer):
|
|
|
+ """ Called by nodes or references who have parsed an XML reference to a
|
|
|
+ node represented by a string.
|
|
|
+
|
|
|
+ No return value
|
|
|
+
|
|
|
+ XML String representations of references have the form 'i=xy' or
|
|
|
+ 'ns=1;s="This unique Node"'. Since during the parsing of this attribute
|
|
|
+ only a subset of nodes are known/parsed, this reference string cannot be
|
|
|
+ linked when encountered.
|
|
|
+
|
|
|
+ References register themselves with the namespace to have their target
|
|
|
+ attribute (string) parsed by linkOpenPointers() when all nodes are
|
|
|
+ created, so that target can be dereferenced an point to an actual node.
|
|
|
+ """
|
|
|
+ self.__linkLater__.append(pointer)
|
|
|
+
|
|
|
+ def getUnlinkedPointers(self):
|
|
|
+ """ Return the list of references registered for linking during the next call
|
|
|
+ of linkOpenPointers()
|
|
|
+ """
|
|
|
+ return self.__linkLater__
|
|
|
+
|
|
|
+ def unlinkedItemCount(self):
|
|
|
+ """ Returns the number of unlinked references that will be processed during
|
|
|
+ the next call of linkOpenPointers()
|
|
|
+ """
|
|
|
+ return len(self.__linkLater__)
|
|
|
+
|
|
|
+ def buildAliasList(self, xmlelement):
|
|
|
+ """ Parses the <Alias> XML Element present in must XML NodeSet definitions.
|
|
|
+
|
|
|
+ No return value
|
|
|
+
|
|
|
+ Contents the Alias element are stored in a dictionary for further
|
|
|
+ dereferencing during pointer linkage (see linkOpenPointer()).
|
|
|
+ """
|
|
|
+ if not xmlelement.tagName == "Aliases":
|
|
|
+ log(self, "XMLElement passed is not an Aliaslist", LOG_LEVEL_ERROR)
|
|
|
+ return
|
|
|
+ for al in xmlelement.childNodes:
|
|
|
+ if al.nodeType == al.ELEMENT_NODE:
|
|
|
+ if al.hasAttribute("Alias"):
|
|
|
+ aliasst = al.getAttribute("Alias")
|
|
|
+ aliasnd = unicode(al.firstChild.data)
|
|
|
+ if not self.aliases.has_key(aliasst):
|
|
|
+ self.aliases[aliasst] = aliasnd
|
|
|
+ log(self, "Added new alias \"" + str(aliasst) + "\" == \"" + str(aliasnd) + "\"")
|
|
|
+ else:
|
|
|
+ if self.aliases[aliasst] != aliasnd:
|
|
|
+ log(self, "Alias definitions for " + aliasst + " differ. Have " + self.aliases[aliasst] + " but XML defines " + aliasnd + ". Keeping current definition.", LOG_LEVEL_ERROR)
|
|
|
+
|
|
|
+ def getNodeByBrowseName(self, idstring):
|
|
|
+ """ Returns the first node in the nodelist whose browseName matches idstring.
|
|
|
+ """
|
|
|
+ matches = []
|
|
|
+ for n in self.nodes:
|
|
|
+ if idstring==str(n.browseName()):
|
|
|
+ matches.append(n)
|
|
|
+ if len(matches) > 1:
|
|
|
+ log(self, "Found multiple nodes with same ID!?", LOG_LEVEL_ERROR)
|
|
|
+ if len(matches) == 0:
|
|
|
+ return None
|
|
|
+ else:
|
|
|
+ return matches[0]
|
|
|
+
|
|
|
+ def getNodeByIDString(self, idstring):
|
|
|
+ """ Returns the first node in the nodelist whose id string representation
|
|
|
+ matches idstring.
|
|
|
+ """
|
|
|
+ matches = []
|
|
|
+ for n in self.nodes:
|
|
|
+ if idstring==str(n.id()):
|
|
|
+ matches.append(n)
|
|
|
+ if len(matches) > 1:
|
|
|
+ log(self, "Found multiple nodes with same ID!?", LOG_LEVEL_ERROR)
|
|
|
+ if len(matches) == 0:
|
|
|
+ return None
|
|
|
+ else:
|
|
|
+ return matches[0]
|
|
|
+
|
|
|
+ def createNode(self, ndtype, xmlelement):
|
|
|
+ """ createNode is instantiates a node described by xmlelement, its type being
|
|
|
+ defined by the string ndtype.
|
|
|
+
|
|
|
+ No return value
|
|
|
+
|
|
|
+ If the xmlelement is an <Alias>, the contents will be parsed and stored
|
|
|
+ for later dereferencing during pointer linking (see linkOpenPointers).
|
|
|
+
|
|
|
+ Recognized types are:
|
|
|
+ * UAVariable
|
|
|
+ * UAObject
|
|
|
+ * UAMethod
|
|
|
+ * UAView
|
|
|
+ * UAVariableType
|
|
|
+ * UAObjectType
|
|
|
+ * UAMethodType
|
|
|
+ * UAReferenceType
|
|
|
+ * UADataType
|
|
|
+
|
|
|
+ For every recognized type, an appropriate node class is added to the node
|
|
|
+ list of the namespace. The NodeId of the given node is created and parsing
|
|
|
+ of the node attributes and elements is delegated to the parseXML() and
|
|
|
+ parseXMLSubType() functions of the instantiated class.
|
|
|
+
|
|
|
+ If the NodeID attribute is non-unique in the node list, the creation is
|
|
|
+ deferred and an error is logged.
|
|
|
+ """
|
|
|
+ if not isinstance(xmlelement, dom.Element):
|
|
|
+ log(self, "Error: Can not create node from invalid XMLElement", LOG_LEVEL_ERROR)
|
|
|
+ return
|
|
|
+
|
|
|
+ # An ID is manditory for everything but aliases!
|
|
|
+ id = None
|
|
|
+ for idname in ['NodeId', 'NodeID', 'nodeid']:
|
|
|
+ if xmlelement.hasAttribute(idname):
|
|
|
+ id = xmlelement.getAttribute(idname)
|
|
|
+ if ndtype == 'aliases':
|
|
|
+ self.buildAliasList(xmlelement)
|
|
|
+ return
|
|
|
+ elif id == None:
|
|
|
+ log(self, "Error: XMLElement has no id, node will not be created!", LOG_LEVEL_INFO)
|
|
|
+ return
|
|
|
+ else:
|
|
|
+ id = opcua_node_id_t(id)
|
|
|
+
|
|
|
+ if self.nodeids.has_key(str(id)):
|
|
|
+ log(self, "Error: XMLElement with duplicate ID " + str(id) + " found, node will not be created!", LOG_LEVEL_ERROR)
|
|
|
+ return
|
|
|
+
|
|
|
+ node = None
|
|
|
+ if (ndtype == 'variable'):
|
|
|
+ node = opcua_node_variable_t(id, self)
|
|
|
+ elif (ndtype == 'object'):
|
|
|
+ node = opcua_node_object_t(id, self)
|
|
|
+ elif (ndtype == 'method'):
|
|
|
+ node = opcua_node_method_t(id, self)
|
|
|
+ elif (ndtype == 'objecttype'):
|
|
|
+ node = opcua_node_objectType_t(id, self)
|
|
|
+ elif (ndtype == 'variabletype'):
|
|
|
+ node = opcua_node_variableType_t(id, self)
|
|
|
+ elif (ndtype == 'methodtype'):
|
|
|
+ node = opcua_node_methodType_t(id, self)
|
|
|
+ elif (ndtype == 'datatype'):
|
|
|
+ node = opcua_node_dataType_t(id, self)
|
|
|
+ elif (ndtype == 'referencetype'):
|
|
|
+ node = opcua_node_referenceType_t(id, self)
|
|
|
+ else:
|
|
|
+ log(self, "No node constructor for type " + ndtype, LOG_LEVEL_ERROR)
|
|
|
+
|
|
|
+ if node != None:
|
|
|
+ node.parseXML(xmlelement)
|
|
|
+
|
|
|
+ self.nodes.append(node)
|
|
|
+ self.nodeids[str(node.id())] = node
|
|
|
+
|
|
|
+ def registerBinaryIndirectPointer(self, node):
|
|
|
+ """ Appends a node to the list of nodes that should be contained in the
|
|
|
+ first 765 bytes (255 pointer slots a 3 bytes) in the binary
|
|
|
+ representation (indirect referencing space).
|
|
|
+
|
|
|
+ This function is reserved for references and dataType pointers.
|
|
|
+ """
|
|
|
+ if not node in self.__binaryIndirectPointers__:
|
|
|
+ self.__binaryIndirectPointers__.append(node)
|
|
|
+ return self.__binaryIndirectPointers__.index(node)
|
|
|
+
|
|
|
+ def getBinaryIndirectPointerIndex(self, node):
|
|
|
+ """ Returns the slot/index of a pointer in the indirect referencing space
|
|
|
+ (first 765 Bytes) of the binary representation.
|
|
|
+ """
|
|
|
+ if not node in self.__binaryIndirectPointers__:
|
|
|
+ return -1
|
|
|
+ return self.__binaryIndirectPointers__.index(node)
|
|
|
+
|
|
|
+
|
|
|
+ def parseXML(self, xmldoc):
|
|
|
+ """ Reads an XML Namespace definition and instantiates node.
|
|
|
+
|
|
|
+ No return value
|
|
|
+
|
|
|
+ parseXML open the file xmldoc using xml.dom.minidom and searches for
|
|
|
+ the first UANodeSet Element. For every Element encountered, createNode
|
|
|
+ is called to instantiate a node of the appropriate type.
|
|
|
+ """
|
|
|
+ typedict = {}
|
|
|
+ UANodeSet = dom.parse(xmldoc).getElementsByTagName("UANodeSet")
|
|
|
+ if len(UANodeSet) == 0:
|
|
|
+ log(self, "Error: No NodeSets found", LOG_LEVEL_ERROR)
|
|
|
+ return
|
|
|
+ if len(UANodeSet) != 1:
|
|
|
+ log(self, "Error: Found more than 1 Nodeset in XML File", LOG_LEVEL_ERROR)
|
|
|
+
|
|
|
+ UANodeSet = UANodeSet[0]
|
|
|
+ for nd in UANodeSet.childNodes:
|
|
|
+ if nd.nodeType != nd.ELEMENT_NODE:
|
|
|
+ continue
|
|
|
+
|
|
|
+ ndType = nd.tagName.lower()
|
|
|
+ if ndType[:2] == "ua":
|
|
|
+ ndType = ndType[2:]
|
|
|
+ elif not ndType in self.knownNodeTypes:
|
|
|
+ log(self, "XML Element or NodeType " + ndType + " is unknown and will be ignored", LOG_LEVEL_WARN)
|
|
|
+ continue
|
|
|
+
|
|
|
+ if not typedict.has_key(ndType):
|
|
|
+ typedict[ndType] = 1
|
|
|
+ else:
|
|
|
+ typedict[ndType] = typedict[ndType] + 1
|
|
|
+
|
|
|
+ self.createNode(ndType, nd)
|
|
|
+ log(self, "Currently " + str(len(self.nodes)) + " nodes in address space. Type distribution for this run was: " + str(typedict))
|
|
|
+
|
|
|
+ def linkOpenPointers(self):
|
|
|
+ """ Substitutes symbolic NodeIds in references for actual node instances.
|
|
|
+
|
|
|
+ No return value
|
|
|
+
|
|
|
+ References that have registered themselves with linkLater() to have
|
|
|
+ their symbolic NodeId targets ("ns=2; i=32") substited for an actual
|
|
|
+ node will be iterated by this function. For each reference encountered
|
|
|
+ in the list of unlinked/open references, the target string will be
|
|
|
+ evaluated and searched for in the node list of this namespace. If found,
|
|
|
+ the target attribute of the reference will be substituted for the
|
|
|
+ found node.
|
|
|
+
|
|
|
+ If a reference fails to get linked, it will remain in the list of
|
|
|
+ unlinked references. The individual items in this list can be
|
|
|
+ retrieved using getUnlinkedPointers().
|
|
|
+ """
|
|
|
+ linked = []
|
|
|
+
|
|
|
+ log(self, str(self.unlinkedItemCount()) + " pointers need to get linked.")
|
|
|
+ for l in self.__linkLater__:
|
|
|
+ targetLinked = False
|
|
|
+ if not l.target() == None and not isinstance(l.target(), opcua_node_t):
|
|
|
+ if isinstance(l.target(),str) or isinstance(l.target(),unicode):
|
|
|
+ # If is not a node ID, it should be an alias. Try replacing it
|
|
|
+ # with a proper node ID
|
|
|
+ if l.target() in self.aliases:
|
|
|
+ l.target(self.aliases[l.target()])
|
|
|
+ # If the link is a node ID, try to find it hopening that no ass has
|
|
|
+ # defined more than one kind of id for that sucker
|
|
|
+ if l.target()[:2] == "i=" or l.target()[:2] == "g=" or \
|
|
|
+ l.target()[:2] == "b=" or l.target()[:2] == "s=" or \
|
|
|
+ l.target()[:3] == "ns=" :
|
|
|
+ tgt = self.getNodeByIDString(str(l.target()))
|
|
|
+ if tgt == None:
|
|
|
+ log(self, "Failed to link pointer to target (node not found) " + l.target(), LOG_LEVEL_ERROR)
|
|
|
+ else:
|
|
|
+ l.target(tgt)
|
|
|
+ targetLinked = True
|
|
|
+ else:
|
|
|
+ log(self, "Failed to link pointer to target (target not Alias or Node) " + l.target(), LOG_LEVEL_ERROR)
|
|
|
+ else:
|
|
|
+ log(self, "Failed to link pointer to target (don't know dummy type + " + str(type(l.target())) + " +) " + str(l.target()), LOG_LEVEL_ERROR)
|
|
|
+ else:
|
|
|
+ log(self, "Pointer has null target: " + str(l), LOG_LEVEL_ERROR)
|
|
|
+
|
|
|
+
|
|
|
+ referenceLinked = False
|
|
|
+ if not l.referenceType() == None:
|
|
|
+ if l.referenceType() in self.aliases:
|
|
|
+ l.referenceType(self.aliases[l.referenceType()])
|
|
|
+ if l.referenceType()[:2] == "i=" or l.referenceType()[:2] == "g=" or \
|
|
|
+ l.referenceType()[:2] == "b=" or l.referenceType()[:2] == "s=":
|
|
|
+ tgt = self.getNodeByIDString(str(l.referenceType()))
|
|
|
+ if tgt == None:
|
|
|
+ log(self, "Failed to link reference type to target (node not found) " + l.referenceType(), LOG_LEVEL_ERROR)
|
|
|
+ else:
|
|
|
+ l.referenceType(tgt)
|
|
|
+ referenceLinked = True
|
|
|
+ else:
|
|
|
+ referenceLinked = True
|
|
|
+
|
|
|
+ if referenceLinked == True and targetLinked == True:
|
|
|
+ linked.append(l)
|
|
|
+
|
|
|
+ # References marked as "not forward" must be inverted (removed from source node, assigned to target node and relinked)
|
|
|
+ log(self, "Inverting reference direction for all references with isForward==False attribute (is this correct!?)", LOG_LEVEL_WARN)
|
|
|
+ for n in self.nodes:
|
|
|
+ for r in n.getReferences():
|
|
|
+ if r.isForward() == False:
|
|
|
+ tgt = r.target()
|
|
|
+ if isinstance(tgt, opcua_node_t):
|
|
|
+ nref = opcua_referencePointer_t(n, parentNode=tgt)
|
|
|
+ nref.referenceType(r.referenceType())
|
|
|
+ tgt.addReference(nref)
|
|
|
+
|
|
|
+ # Create inverse references for all nodes
|
|
|
+ log(self, "Updating all referencedBy fields in nodes for inverse lookups.")
|
|
|
+ for n in self.nodes:
|
|
|
+ n.updateInverseReferences()
|
|
|
+
|
|
|
+ for l in linked:
|
|
|
+ self.__linkLater__.remove(l)
|
|
|
+
|
|
|
+ if len(self.__linkLater__) != 0:
|
|
|
+ log(self, str(len(self.__linkLater__)) + " could not be linked.", LOG_LEVEL_WARN)
|
|
|
+
|
|
|
+ def sanitize(self):
|
|
|
+ remove = []
|
|
|
+ log(self, "Sanitizing nodes and references...")
|
|
|
+ for n in self.nodes:
|
|
|
+ if n.sanitize() == False:
|
|
|
+ remove.append(n)
|
|
|
+ if not len(remove) == 0:
|
|
|
+ log(self, str(len(remove)) + " nodes will be removed because they failed sanitation.", LOG_LEVEL_WARN)
|
|
|
+ # FIXME: Some variable ns=0 nodes fail because they don't have DataType fields...
|
|
|
+ # How should this be handles!?
|
|
|
+ log(self, "Not actually removing nodes... it's unclear if this is valid or not", LOG_LEVEL_WARN)
|
|
|
+
|
|
|
+ def getRoot(self):
|
|
|
+ """ Returns the first node instance with the browseName "Root".
|
|
|
+ """
|
|
|
+ return self.getNodeByBrowseName("Root")
|
|
|
+
|
|
|
+ def buildEncodingRules(self):
|
|
|
+ """ Calls buildEncoding() for all DataType nodes (opcua_node_dataType_t).
|
|
|
+
|
|
|
+ No return value
|
|
|
+ """
|
|
|
+ stat = {True: 0, False: 0}
|
|
|
+ for n in self.nodes:
|
|
|
+ if isinstance(n, opcua_node_dataType_t):
|
|
|
+ n.buildEncoding()
|
|
|
+ stat[n.isEncodable()] = stat[n.isEncodable()] + 1
|
|
|
+ log(self, "Type definitions built/passed: " + str(stat), LOG_LEVEL_DEBUG)
|
|
|
+
|
|
|
+ def allocateVariables(self):
|
|
|
+ for n in self.nodes:
|
|
|
+ if isinstance(n, opcua_node_variable_t):
|
|
|
+ n.allocateValue()
|
|
|
+
|
|
|
+ def printDot(self, filename="namespace.dot"):
|
|
|
+ """ Outputs a graphiz/dot description of all nodes in the namespace.
|
|
|
+
|
|
|
+ Output will written into filename to be parsed by dot/neato...
|
|
|
+
|
|
|
+ Note that for namespaces with more then 20 nodes the reference structure
|
|
|
+ will lead to a mostly illegible and huge graph. Use printDotGraphWalk()
|
|
|
+ for plotting specific portions of a large namespace.
|
|
|
+ """
|
|
|
+ file=open(filename, 'w+')
|
|
|
+
|
|
|
+ file.write("digraph ns {\n")
|
|
|
+ for n in self.nodes:
|
|
|
+ file.write(n.printDot())
|
|
|
+ file.write("}\n")
|
|
|
+ file.close()
|
|
|
+
|
|
|
+ def printDotGraphWalk(self, depth=1, filename="out.dot", rootNode=None, followInverse = False, excludeNodeIds=[]):
|
|
|
+ """ Outputs a graphiz/dot description the nodes centered around rootNode.
|
|
|
+
|
|
|
+ References beginning from rootNode will be followed for depth steps. If
|
|
|
+ "followInverse = True" is passed, then inverse (not Forward) references
|
|
|
+ will also be followed.
|
|
|
+
|
|
|
+ Nodes can be excluded from the graph by passing a list of NodeIds as
|
|
|
+ string representation using excludeNodeIds (ex ["i=53", "ns=2;i=453"]).
|
|
|
+
|
|
|
+ Output is written into filename to be parsed by dot/neato/srfp...
|
|
|
+ """
|
|
|
+ iter = depth
|
|
|
+ processed = []
|
|
|
+ if rootNode == None or \
|
|
|
+ not isinstance(rootNode, opcua_node_t) or \
|
|
|
+ not rootNode in self.nodes:
|
|
|
+ root = self.getRoot()
|
|
|
+ else:
|
|
|
+ root = rootNode
|
|
|
+
|
|
|
+ file=open(filename, 'w+')
|
|
|
+
|
|
|
+ if root == None:
|
|
|
+ return
|
|
|
+
|
|
|
+ file.write("digraph ns {\n")
|
|
|
+ file.write(root.printDot())
|
|
|
+ refs=[]
|
|
|
+ if followInverse == True:
|
|
|
+ refs = root.getReferences(); # + root.getInverseReferences()
|
|
|
+ else:
|
|
|
+ for ref in root.getReferences():
|
|
|
+ if ref.isForward():
|
|
|
+ refs.append(ref)
|
|
|
+ while iter > 0:
|
|
|
+ tmp = []
|
|
|
+ for ref in refs:
|
|
|
+ if isinstance(ref.target(), opcua_node_t):
|
|
|
+ tgt = ref.target()
|
|
|
+ if not str(tgt.id()) in excludeNodeIds:
|
|
|
+ if not tgt in processed:
|
|
|
+ file.write(tgt.printDot())
|
|
|
+ processed.append(tgt)
|
|
|
+ if ref.isForward() == False and followInverse == True:
|
|
|
+ tmp = tmp + tgt.getReferences(); # + tgt.getInverseReferences()
|
|
|
+ elif ref.isForward() == True :
|
|
|
+ tmp = tmp + tgt.getReferences();
|
|
|
+ refs = tmp
|
|
|
+ iter = iter - 1
|
|
|
+
|
|
|
+ file.write("}\n")
|
|
|
+ file.close()
|
|
|
+
|
|
|
+ def printOpen62541Header(self):
|
|
|
+ unPrintedNodes = []
|
|
|
+ unPrintedRefs = []
|
|
|
+ code = []
|
|
|
+
|
|
|
+ # Some macros (UA_EXPANDEDNODEID_MACRO()...) are easily created, but
|
|
|
+ # bulky. This class will help to offload some code.
|
|
|
+ codegen = open62541_MacroHelper()
|
|
|
+
|
|
|
+ # Populate the unPrinted-Lists with everything we have.
|
|
|
+ # Every Time a nodes printfunction is called, it will pop itself and
|
|
|
+ # all printed references from these lists.
|
|
|
+ unPrintedNodes = unPrintedNodes + self.nodes
|
|
|
+ for n in self.nodes:
|
|
|
+ for r in n.getReferences():
|
|
|
+ if (r.target() != None) and (r.target().id() != None) and (r.parent() != None):
|
|
|
+ unPrintedRefs.append(r)
|
|
|
+
|
|
|
+ log(self, str(len(unPrintedNodes)) + " Nodes, " + str(len(unPrintedRefs)) + "References need to get printed.", LOG_LEVEL_DEBUG)
|
|
|
+ code.append("/* WARNING: This is a generated file.\n * Any manual changes will be overwritten.\n */")
|
|
|
+
|
|
|
+ # Find all references necessary to create the namespace and
|
|
|
+ # "Bootstrap" them so all other nodes can safely use these referencetypes whenever
|
|
|
+ # they can locate both source and target of the reference.
|
|
|
+ log(self, "Collecting all references used in the namespace.", LOG_LEVEL_DEBUG)
|
|
|
+ refsUsed = []
|
|
|
+ for n in self.nodes:
|
|
|
+ for r in n.getReferences():
|
|
|
+ if not r.referenceType() in refsUsed:
|
|
|
+ refsUsed.append(r.referenceType())
|
|
|
+ log(self, str(len(refsUsed)) + " reference types are used in the namespace, which will now get bootstrapped.", LOG_LEVEL_DEBUG)
|
|
|
+ for r in refsUsed:
|
|
|
+ code = code + r.printOpen62541CCode(unPrintedNodes, unPrintedRefs);
|
|
|
+
|
|
|
+ # Note to self: do NOT - NOT! - try to iterate over unPrintedNodes!
|
|
|
+ # Nodes remove themselves from this list when printed.
|
|
|
+ log(self, "Printing all other nodes.", LOG_LEVEL_DEBUG)
|
|
|
+ for n in self.nodes:
|
|
|
+ code = code + n.printOpen62541CCode(unPrintedNodes, unPrintedRefs)
|
|
|
+
|
|
|
+ if len(unPrintedNodes) != 0:
|
|
|
+ log(self, "" + str(len(unPrintedNodes)) + " nodes could not be translated to code.", LOG_LEVEL_WARN)
|
|
|
+ else:
|
|
|
+ log(self, "Printing suceeded for all nodes", LOG_LEVEL_DEBUG)
|
|
|
+
|
|
|
+ if len(unPrintedRefs) != 0:
|
|
|
+ log(self, "Attempting to print " + str(len(unPrintedRefs)) + " unprinted references.", LOG_LEVEL_DEBUG)
|
|
|
+ tmprefs = []
|
|
|
+ for r in unPrintedRefs:
|
|
|
+ if not (r.target() not in unPrintedNodes) and not (r.parent() in unPrintedNodes):
|
|
|
+ if not isinstance(r.parent(), opcua_node_t):
|
|
|
+ log(self, "Reference has no parent!", LOG_LEVEL_DEBUG)
|
|
|
+ elif not isinstance(r.parent().id(), opcua_node_id_t):
|
|
|
+ log(self, "Parents nodeid is not a nodeID!", LOG_LEVEL_DEBUG)
|
|
|
+ else:
|
|
|
+ if (len(tmprefs) == 0):
|
|
|
+ code.append("// Creating leftover references:")
|
|
|
+ code = code + codegen.getCreateStandaloneReference(r.parent(), r)
|
|
|
+ code.append("")
|
|
|
+ tmprefs.append(r)
|
|
|
+ # Remove printed refs from list
|
|
|
+ for r in tmprefs:
|
|
|
+ unPrintedRefs.remove(r)
|
|
|
+ if len(unPrintedRefs) != 0:
|
|
|
+ log(self, "" + str(len(unPrintedRefs)) + " references could not be translated to code.", LOG_LEVEL_WARN)
|
|
|
+ else:
|
|
|
+ log(self, "Printing succeeded for all references", LOG_LEVEL_DEBUG)
|
|
|
+
|
|
|
+ #code.append("}")
|
|
|
+ return code
|
|
|
+
|
|
|
+###
|
|
|
+### Testing
|
|
|
+###
|
|
|
+
|
|
|
+class testing:
|
|
|
+ def __init__(self):
|
|
|
+ self.namespace = opcua_namespace("testing")
|
|
|
+
|
|
|
+ log(self, "Phase 1: Reading XML file nodessets")
|
|
|
+ self.namespace.parseXML("Opc.Ua.NodeSet2.xml")
|
|
|
+ #self.namespace.parseXML("Opc.Ua.NodeSet2.Part4.xml")
|
|
|
+ #self.namespace.parseXML("Opc.Ua.NodeSet2.Part5.xml")
|
|
|
+ #self.namespace.parseXML("Opc.Ua.SimulationNodeSet2.xml")
|
|
|
+
|
|
|
+ log(self, "Phase 2: Linking address space references and datatypes")
|
|
|
+ self.namespace.linkOpenPointers()
|
|
|
+ self.namespace.sanitize()
|
|
|
+
|
|
|
+ log(self, "Phase 3: Comprehending DataType encoding rules")
|
|
|
+ self.namespace.buildEncodingRules()
|
|
|
+
|
|
|
+ log(self, "Phase 4: Allocating variable value data")
|
|
|
+ self.namespace.allocateVariables()
|
|
|
+
|
|
|
+ bin = self.namespace.buildBinary()
|
|
|
+ f = open("binary.base64","w+")
|
|
|
+ f.write(bin.encode("base64"))
|
|
|
+ f.close()
|
|
|
+
|
|
|
+ allnodes = self.namespace.nodes;
|
|
|
+ ns = [self.namespace.getRoot()]
|
|
|
+
|
|
|
+ i = 0
|
|
|
+ print "Starting depth search on " + str(len(allnodes)) + " nodes starting with from " + str(ns)
|
|
|
+ while (len(ns) < len(allnodes)):
|
|
|
+ i = i + 1;
|
|
|
+ tmp = [];
|
|
|
+ print "Iteration: " + str(i)
|
|
|
+ for n in ns:
|
|
|
+ tmp.append(n)
|
|
|
+ for r in n.getReferences():
|
|
|
+ if (not r.target() in tmp):
|
|
|
+ tmp.append(r.target())
|
|
|
+ print "...tmp, " + str(len(tmp)) + " nodes discovered"
|
|
|
+ ns = []
|
|
|
+ for n in tmp:
|
|
|
+ ns.append(n)
|
|
|
+ print "...done, " + str(len(ns)) + " nodes discovered"
|
|
|
+
|
|
|
+ log(self, "Phase 5: Printing pretty graph")
|
|
|
+ self.namespace.printDotGraphWalk(depth=1, rootNode=self.namespace.getNodeByIDString("i=84"), followInverse=False, excludeNodeIds=["i=29", "i=22", "i=25"])
|
|
|
+ #self.namespace.printDot()
|
|
|
+
|
|
|
+class testing_open62541_header:
|
|
|
+ def __init__(self):
|
|
|
+ self.namespace = opcua_namespace("testing")
|
|
|
+
|
|
|
+ log(self, "Phase 1: Reading XML file nodessets")
|
|
|
+ self.namespace.parseXML("Opc.Ua.NodeSet2.xml")
|
|
|
+ #self.namespace.parseXML("Opc.Ua.NodeSet2.Part4.xml")
|
|
|
+ #self.namespace.parseXML("Opc.Ua.NodeSet2.Part5.xml")
|
|
|
+ #self.namespace.parseXML("Opc.Ua.SimulationNodeSet2.xml")
|
|
|
+
|
|
|
+ log(self, "Phase 2: Linking address space references and datatypes")
|
|
|
+ self.namespace.linkOpenPointers()
|
|
|
+ self.namespace.sanitize()
|
|
|
+
|
|
|
+ log(self, "Phase 3: Calling C Printers")
|
|
|
+ code = self.namespace.printOpen62541Header()
|
|
|
+
|
|
|
+ codeout = open("./open62541_namespace.c", "w+")
|
|
|
+ for line in code:
|
|
|
+ codeout.write(line + "\n")
|
|
|
+ codeout.close()
|
|
|
+ return
|
|
|
+
|
|
|
+# Call testing routine if invoked standalone.
|
|
|
+# For better debugging, it is advised to import this file using an interactive
|
|
|
+# python shell and instantiating a namespace.
|
|
|
+#
|
|
|
+# import ua_types.py as ua; ns=ua.testing().namespace
|
|
|
+if __name__ == '__main__':
|
|
|
+ tst = testing_open62541_header()
|