Przeglądaj źródła

Modified to encode VariableNode data contents

ichrispa 10 lat temu
rodzic
commit
aaffcbd637

+ 91 - 0
tools/pyUANamespace/generate_open62541CCode.py

@@ -0,0 +1,91 @@
+#!/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 sys import argv, exit
+from os import path
+from ua_namespace import *
+
+def usage():
+  print "Skript usage:"
+  print "generate_open62541CCode <namespace XML> [namespace.xml[ namespace.xml[...]]] <output file>"
+  print ""
+  print "generate_open62541CCode will first read all XML files passed on the command line, then "
+  print "link and check the namespace. All nodes that fullfill the basic requirements will then be"
+  print "printed as C-Code intended to be included in the open62541 OPC-UA Server that will"
+  print "initialize the corresponding name space."
+
+if __name__ == '__main__':
+  # Check if the parameters given correspond to actual files
+  infiles = []
+  ouffile = ""
+  if len(argv) < 2:
+    usage()
+    exit(1)
+  for filename in argv[1:-1]:
+    if path.exists(filename):
+      infiles.append(filename)
+    else:
+      print "File " + str(filename) + " does not exist."
+      usage()
+      exit(1)
+
+  # Creating the header is tendious. We can skip the entire process if
+  # the header exists.
+#  if path.exists(argv[-1]):
+#    print "Header " + str(argv[-1]) + " already exists."
+#    print "Header generation is a lengthy process, please delete the header"
+#    print "  if you really want namespace 0 to be recompiled from the XML"
+#    print "  file."
+#    exit(0)
+
+  # Open the output file
+  outfile = open(argv[-1], r"w+")
+
+  # Create a new namespace
+  # Note that the name is actually completely symbolic, it has no other
+  # function but to distinguish this specific class.
+  # A namespace class acts as a container for nodes. The nodes may belong
+  # to any number of different OPC-UA namespaces.
+  ns = opcua_namespace("open62541")
+
+  # Parse the XML files
+  for xmlfile in infiles:
+    print "Parsing " + xmlfile
+    ns.parseXML(xmlfile)
+
+  # Link the references in the namespace
+  ns.linkOpenPointers()
+
+  # Remove nodes that are not printable or contain parsing errors, such as
+  # unresolvable or no references or invalid NodeIDs
+  ns.sanitize()
+
+  # Parse Datatypes in order to find out what the XML keyed values actually
+  # represent.
+  # Ex. <rpm>123</rpm> is not encodable
+  #     only after parsing the datatypes, it is known that
+  #     rpm is encoded as a double
+  ns.buildEncodingRules()
+
+  # Allocate/Parse the data values. In order to do this, we must have run
+  # buidEncodingRules.
+  ns.allocateVariables()
+
+  # Create the C Code
+  for line in ns.printOpen62541Header():
+    outfile.write(line+"\n")
+
+  outfile.close()

+ 43 - 0
tools/pyUANamespace/logger.py

@@ -0,0 +1,43 @@
+#!/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.
+###
+
+import inspect
+
+###
+### Tidy logging helpers
+###
+
+LOG_LEVEL_DEBUG  = 4
+LOG_LEVEL_INFO   = 2
+LOG_LEVEL_WARN   = 1
+LOG_LEVEL_ERROR  = 0
+LOG_LEVEL_SILENT = -1
+
+# Change the following to filter logging output
+GLOBAL_LOG_LEVEL = LOG_LEVEL_DEBUG
+
+def log(callee, logstr, level=LOG_LEVEL_DEBUG):
+  prefixes = { LOG_LEVEL_DEBUG : "DBG: ",
+               LOG_LEVEL_INFO  : "INF: ",
+               LOG_LEVEL_WARN  : "WRN: ",
+               LOG_LEVEL_ERROR : "ERR: ",
+               LOG_LEVEL_SILENT: ""
+            }
+  if level <= GLOBAL_LOG_LEVEL:
+    if prefixes.has_key(level):
+      print str(prefixes[level]) + callee.__class__.__name__ + "." + inspect.stack()[1][3] +  "(): " + logstr
+    else:
+      print callee.__class__.__name__  + "." + inspect.stack()[1][3] + "(): " + logstr

+ 131 - 0
tools/pyUANamespace/open62541_MacroHelper.py

@@ -0,0 +1,131 @@
+#!/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 logger import *
+from ua_constants import *
+
+__unique_item_id = 0
+
+class open62541_MacroHelper():
+  def __init__(self):
+    pass
+
+  def getCreateExpandedNodeIDMacro(self, node):
+    if node.id().i != None:
+      return "UA_EXPANDEDNODEID_NUMERIC(" + str(node.id().ns) + ", " + str(node.id().i) + ")"
+    elif node.id().s != None:
+      return "UA_EXPANDEDNODEID_STRING("  + str(node.id().ns) + ", " + node.id().s + ")"
+    elif node.id().b != None:
+      log(self, "NodeID Generation macro for bytestrings has not been implemented.")
+      return ""
+    elif node.id().g != None:
+      log(self, "NodeID Generation macro for guids has not been implemented.")
+      return ""
+    else:
+      return ""
+
+  def getCreateNodeIDMacro(self, node):
+    if node.id().i != None:
+      return "UA_NODEID_NUMERIC(" + str(node.id().ns) + ", " + str(node.id().i) + ")"
+    elif node.id().s != None:
+      return "UA_NODEID_STRING("  + str(node.id().ns) + ", " + node.id().s + ")"
+    elif node.id().b != None:
+      log(self, "NodeID Generation macro for bytestrings has not been implemented.")
+      return ""
+    elif node.id().g != None:
+      log(self, "NodeID Generation macro for guids has not been implemented.")
+      return ""
+    else:
+      return ""
+
+  def getCreateStandaloneReference(self, sourcenode, reference):
+  # As reference from open62541 (we need to alter the attributes)
+  #    #define ADDREFERENCE(NODEID, REFTYPE_NODEID, TARGET_EXPNODEID) do {     \
+  #        UA_AddReferencesItem item;                                      \
+  #        UA_AddReferencesItem_init(&item);                               \
+  #        item.sourceNodeId = NODEID;                                     \
+  #        item.referenceTypeId = REFTYPE_NODEID;                          \
+  #        item.isForward = UA_TRUE;                                       \
+  #        item.targetNodeId = TARGET_EXPNODEID;                           \
+  #        UA_Server_addReference(server, &item);                          \
+  #    } while(0)
+    code = []
+    refid = "ref_" + reference.getCodePrintableID()
+    code.append("UA_AddReferencesItem " + refid + ";")
+    code.append("UA_AddReferencesItem_init(&" + refid + ");")
+    code.append(refid + ".sourceNodeId = " + self.getCreateNodeIDMacro(sourcenode) + ";")
+    code.append(refid + ".referenceTypeId = " + self.getCreateNodeIDMacro(reference.referenceType()) + ";")
+    if reference.isForward():
+      code.append(refid + ".isForward = UA_TRUE;")
+    else:
+      code.append(refid + ".isForward = UA_FALSE;")
+    code.append(refid + ".targetNodeId = " + self.getCreateExpandedNodeIDMacro(reference.target()) + ";")
+    code.append("UA_Server_addReference(server, &" + refid + ");")
+    return code
+
+  def getCreateNode(self, node):
+    nodetype = ""
+    code = []
+
+    code.append("// Node: " + str(node) + ", " + str(node.browseName()))
+
+    if node.nodeClass() == NODE_CLASS_OBJECT:
+      nodetype = "UA_ObjectNode"
+    elif node.nodeClass() == NODE_CLASS_VARIABLE:
+      nodetype = "UA_VariableNode"
+    elif node.nodeClass() == NODE_CLASS_METHOD:
+      nodetype = "UA_MethodNode"
+    elif node.nodeClass() == NODE_CLASS_OBJECTTYPE:
+      nodetype = "UA_ObjectTypeNode"
+    elif node.nodeClass() == NODE_CLASS_REFERENCETYPE:
+      nodetype = "UA_ReferenceTypeNode"
+    elif node.nodeClass() == NODE_CLASS_VARIABLETYPE:
+      nodetype = "UA_VariableTypeNode"
+    elif node.nodeClass() == NODE_CLASS_DATATYPE:
+      nodetype = "UA_DataTypeNode"
+    elif node.nodeClass() == NODE_CLASS_VIEW:
+      nodetype = "UA_ViewNode"
+    elif node.nodeClass() == NODE_CLASS_METHODTYPE:
+      nodetype = "UA_MethodTypeNode"
+    else:
+      nodetype = "UA_NodeTypeNotFoundorGeneric"
+
+    code.append(nodetype + " *" + node.getCodePrintableID() + " = " + nodetype + "_new();")
+    code.append(node.getCodePrintableID() + "->browseName = UA_QUALIFIEDNAME_ALLOC(" +  str(node.id().ns) + ", \"" + node.browseName() + "\");")
+    code.append(node.getCodePrintableID() + "->displayName = UA_LOCALIZEDTEXT_ALLOC(\"en_US\", \"" +  node.displayName() + "\");")
+    code.append(node.getCodePrintableID() + "->description = UA_LOCALIZEDTEXT_ALLOC(\"en_US\", \"" +  node.description() + "\");")
+    code.append(node.getCodePrintableID() + "->writeMask = (UA_Int32) " +  str(node.__node_writeMask__) + ";")
+    code.append(node.getCodePrintableID() + "->userWriteMask = (UA_Int32) " + str(node.__node_userWriteMask__) + ";")
+    #FIXME: Allocate descriptions, etc.
+
+    if node.id().i != None:
+      code.append(node.getCodePrintableID() + "->nodeId.identifier.numeric = " + str(node.id().i) + ";")
+    elif node.id().b != None:
+      log(self, "ByteString IDs for nodes has not been implemented yet.", LOG_LEVEL_ERROR)
+      return []
+    elif node.id().g != None:
+      #<jpfr> the string is sth like { .length = 111, .data = <ptr> }
+      #<jpfr> there you _may_ alloc the <ptr> on the heap
+      #<jpfr> for the guid, just set it to {.data1 = 111, .data2 = 2222, ....
+      log(self, "GUIDs for nodes has not been implemented yet.", LOG_LEVEL_ERROR)
+      return []
+    elif node.id().s != None:
+      code.append(node.getCodePrintableID() + "->nodeId.identifier.numeric = UA_STRING_ALLOC(\"" + str(node.id().i) + "\");")
+    else:
+      log(self, "Node ID is not numeric, bytestring, guid or string. I do not know how to create c code for that...", LOG_LEVEL_ERROR)
+      return []
+
+    return code

Plik diff jest za duży
+ 1080 - 0
tools/pyUANamespace/ua_builtin_types.py


+ 55 - 0
tools/pyUANamespace/ua_constants.py

@@ -0,0 +1,55 @@
+#!/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.
+###
+
+NODE_CLASS_GENERERIC        = 0
+NODE_CLASS_OBJECT           = 1
+NODE_CLASS_VARIABLE         = 2
+NODE_CLASS_METHOD           = 4
+NODE_CLASS_OBJECTTYPE       = 8
+NODE_CLASS_VARIABLETYPE     = 16
+NODE_CLASS_REFERENCETYPE    = 32
+NODE_CLASS_DATATYPE         = 64
+NODE_CLASS_VIEW             = 128
+
+# Not in OPC-UA, but exists in XML
+NODE_CLASS_METHODTYPE       = 256
+
+##
+## Numeric codes used to encode binary type fields:
+##
+BUILTINTYPE_TYPEID_EXTENSIONOBJECT = 1
+BUILTINTYPE_TYPEID_LOCALIZEDTEXT = 2
+BUILTINTYPE_TYPEID_EXPANDEDNODEID = 3
+BUILTINTYPE_TYPEID_NODEID = 4
+BUILTINTYPE_TYPEID_DATETIME = 5
+BUILTINTYPE_TYPEID_QUALIFIEDNAME = 6
+BUILTINTYPE_TYPEID_STATUSCODE = 7
+BUILTINTYPE_TYPEID_GUID = 8
+BUILTINTYPE_TYPEID_BOOLEAN = 9
+BUILTINTYPE_TYPEID_BYTE = 10
+BUILTINTYPE_TYPEID_SBYTE = 11
+BUILTINTYPE_TYPEID_INT16 = 12
+BUILTINTYPE_TYPEID_UINT16 = 13
+BUILTINTYPE_TYPEID_INT32 = 14
+BUILTINTYPE_TYPEID_UINT32 = 15
+BUILTINTYPE_TYPEID_INT64 = 16
+BUILTINTYPE_TYPEID_UINT64 = 17
+BUILTINTYPE_TYPEID_FLOAT = 18
+BUILTINTYPE_TYPEID_DOUBLE = 19
+BUILTINTYPE_TYPEID_STRING = 20
+BUILTINTYPE_TYPEID_XMLELEMENT = 21
+BUILTINTYPE_TYPEID_BYTESTRING = 22
+BUILTINTYPE_TYPEID_DIAGNOSTICINFO = 23

+ 627 - 0
tools/pyUANamespace/ua_namespace.py

@@ -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()

Plik diff jest za duży
+ 1486 - 0
tools/pyUANamespace/ua_node_types.py