123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835 |
- #!/usr/bin/env/python
- # -*- coding: utf-8 -*-
- # This Source Code Form is subject to the terms of the Mozilla Public
- # License, v. 2.0. If a copy of the MPL was not distributed with this
- # file, You can obtain one at http://mozilla.org/MPL/2.0/.
- ###
- ### Author: Chris Iatrou (ichrispa@core-vector.net)
- ### Version: rev 13
- ###
- ### This program was created for educational purposes and has been
- ### contributed to the open62541 project by the author. All licensing
- ### terms for this source is inherited by the terms and conditions
- ### specified for by the open62541 project (see the projects readme
- ### file for more information on the MPLv2 terms and restrictions).
- ###
- ### 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 __future__ import print_function
- import sys
- from time import struct_time, strftime, strptime, mktime
- from struct import pack as structpack
- import logging
- from ua_builtin_types import *;
- from ua_node_types import *;
- from ua_constants import *;
- from open62541_MacroHelper import open62541_MacroHelper
- logger = logging.getLogger(__name__)
- 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 = ""
- namespaceIdentifiers = {} # list of 'int':'string' giving different namespace an array-mapable name
- 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.namespaceIdentifiers = {}
- self.__binaryIndirectPointers__ = []
- def addNamespace(self, numericId, stringURL):
- self.namespaceIdentifiers[numericId] = stringURL
- 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":
- logger.error("XMLElement passed is not an Aliaslist")
- return
- for al in xmlelement.childNodes:
- if al.nodeType == al.ELEMENT_NODE:
- if al.hasAttribute("Alias"):
- aliasst = al.getAttribute("Alias")
- if sys.version_info[0] < 3:
- aliasnd = unicode(al.firstChild.data)
- else:
- aliasnd = al.firstChild.data
- if not aliasst in self.aliases:
- self.aliases[aliasst] = aliasnd
- logger.debug("Added new alias \"" + str(aliasst) + "\" == \"" + str(aliasnd) + "\"")
- else:
- if self.aliases[aliasst] != aliasnd:
- logger.error("Alias definitions for " + aliasst + " differ. Have " + self.aliases[aliasst] + " but XML defines " + aliasnd + ". Keeping current definition.")
- 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:
- logger.error("Found multiple nodes with same ID!?")
- 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:
- logger.error("Found multiple nodes with same ID!?")
- 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):
- logger.error( "Error: Can not create node from invalid XMLElement")
- return
- # An ID is mandatory 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:
- logger.info( "Error: XMLElement has no id, node will not be created!")
- return
- else:
- id = opcua_node_id_t(id)
- if str(id) in self.nodeids:
- # Normal behavior: Do not allow duplicates, first one wins
- #logger.error( "XMLElement with duplicate ID " + str(id) + " found, node will not be created!")
- #return
- # Open62541 behavior for header generation: Replace the duplicate with the new node
- logger.info( "XMLElement with duplicate ID " + str(id) + " found, node will be replaced!")
- nd = self.getNodeByIDString(str(id))
- self.nodes.remove(nd)
- self.nodeids.pop(str(nd.id()))
- 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:
- logger.error( "No node constructor for type " + ndtype)
- if node != None:
- node.parseXML(xmlelement)
- self.nodes.append(node)
- self.nodeids[str(node.id())] = node
- def removeNodeById(self, nodeId):
- nd = self.getNodeByIDString(nodeId)
- if nd == None:
- return False
- logger.debug("Removing nodeId " + str(nodeId))
- self.nodes.remove(nd)
- if nd.getInverseReferences() != None:
- for ref in nd.getInverseReferences():
- src = ref.target();
- src.removeReferenceToNode(nd)
- return True
- 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:
- logger.error( "Error: No NodeSets found")
- return
- if len(UANodeSet) != 1:
- logger.error( "Error: Found more than 1 Nodeset in XML File")
- 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:
- logger.warn("XML Element or NodeType " + ndType + " is unknown and will be ignored")
- continue
- if not ndType in typedict:
- typedict[ndType] = 1
- else:
- typedict[ndType] = typedict[ndType] + 1
- self.createNode(ndType, nd)
- logger.debug("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") substituted 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 = []
- logger.debug( 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:
- logger.error("Failed to link pointer to target (node not found) " + l.target())
- else:
- l.target(tgt)
- targetLinked = True
- else:
- logger.error("Failed to link pointer to target (target not Alias or Node) " + l.target())
- else:
- logger.error("Failed to link pointer to target (don't know dummy type + " + str(type(l.target())) + " +) " + str(l.target()))
- else:
- logger.error("Pointer has null target: " + str(l))
- referenceLinked = False
- if not l.referenceType() == None:
- if l.referenceType() in self.aliases:
- l.referenceType(self.aliases[l.referenceType()])
- tgt = self.getNodeByIDString(str(l.referenceType()))
- if tgt == None:
- logger.error("Failed to link reference type to target (node not found) " + l.referenceType())
- 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)
- logger.warn("Inverting reference direction for all references with isForward==False attribute (is this correct!?)")
- 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
- logger.debug("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:
- logger.warn(str(len(self.__linkLater__)) + " could not be linked.")
- def sanitize(self):
- remove = []
- logger.debug("Sanitizing nodes and references...")
- for n in self.nodes:
- if n.sanitize() == False:
- remove.append(n)
- if not len(remove) == 0:
- logger.warn(str(len(remove)) + " nodes will be removed because they failed sanitation.")
- # FIXME: Some variable ns=0 nodes fail because they don't have DataType fields...
- # How should this be handles!?
- logger.warn("Not actually removing nodes... it's unclear if this is valid or not")
- 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
- logger.debug("Type definitions built/passed: " + str(stat))
- 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 getSubTypesOf(self, tdNodes = None, currentNode = None, hasSubtypeRefNode = None):
- # If this is a toplevel call, collect the following information as defaults
- if tdNodes == None:
- tdNodes = []
- if currentNode == None:
- currentNode = self.getNodeByBrowseName("HasTypeDefinition")
- tdNodes.append(currentNode)
- if len(tdNodes) < 1:
- return []
- if hasSubtypeRefNode == None:
- hasSubtypeRefNode = self.getNodeByBrowseName("HasSubtype")
- if hasSubtypeRefNode == None:
- return tdNodes
- # collect all subtypes of this node
- for ref in currentNode.getReferences():
- if ref.isForward() and ref.referenceType().id() == hasSubtypeRefNode.id():
- tdNodes.append(ref.target())
- self.getTypeDefinitionNodes(tdNodes=tdNodes, currentNode = ref.target(), hasSubtypeRefNode=hasSubtypeRefNode)
- return tdNodes
- 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 __reorder_getMinWeightNode__(self, nmatrix):
- rcind = -1
- rind = -1
- minweight = -1
- minweightnd = None
- for row in nmatrix:
- rcind += 1
- if row[0] == None:
- continue
- w = sum(row[1:])
- if minweight < 0:
- rind = rcind
- minweight = w
- minweightnd = row[0]
- elif w < minweight:
- rind = rcind
- minweight = w
- minweightnd = row[0]
- return (rind, minweightnd, minweight)
- def reorderNodesMinDependencies(self):
- # create a matrix represtantion of all node
- #
- nmatrix = []
- for n in range(0,len(self.nodes)):
- nmatrix.append([None] + [0]*len(self.nodes))
- typeRefs = []
- tn = self.getNodeByBrowseName("HasTypeDefinition")
- if tn != None:
- typeRefs.append(tn)
- typeRefs = typeRefs + self.getSubTypesOf(currentNode=tn)
- subTypeRefs = []
- tn = self.getNodeByBrowseName("HasSubtype")
- if tn != None:
- subTypeRefs.append(tn)
- subTypeRefs = subTypeRefs + self.getSubTypesOf(currentNode=tn)
- logger.debug("Building connectivity matrix for node order optimization.")
- # Set column 0 to contain the node
- for node in self.nodes:
- nind = self.nodes.index(node)
- nmatrix[nind][0] = node
- # Determine the dependencies of all nodes
- logger.debug("Determining node interdependencies.")
- for node in self.nodes:
- nind = self.nodes.index(node)
- #print "Examining node " + str(nind) + " " + str(node)
- for ref in node.getReferences():
- if isinstance(ref.target(), opcua_node_t):
- tind = self.nodes.index(ref.target())
- # Typedefinition of this node has precedence over this node
- if ref.referenceType() in typeRefs and ref.isForward():
- nmatrix[nind][tind+1] += 200 # Very big weight for typedefs
- # isSubTypeOf/typeDefinition of this node has precedence over this node
- elif ref.referenceType() in subTypeRefs and not ref.isForward():
- nmatrix[nind][tind+1] += 100 # Big weight for subtypes
- # Else the target depends on us
- elif ref.isForward():
- nmatrix[tind][nind+1] += 1 # regular weight for dependencies
- logger.debug("Using Djikstra topological sorting to determine printing order.")
- reorder = []
- while len(reorder) < len(self.nodes):
- (nind, node, w) = self.__reorder_getMinWeightNode__(nmatrix)
- #print str(100*float(len(reorder))/len(self.nodes)) + "% " + str(w) + " " + str(node) + " " + str(node.browseName())
- reorder.append(node)
- for ref in node.getReferences():
- if isinstance(ref.target(), opcua_node_t):
- tind = self.nodes.index(ref.target())
- if ref.referenceType() in typeRefs and ref.isForward():
- nmatrix[nind][tind+1] -= 200
- elif ref.referenceType() in subTypeRefs and not ref.isForward():
- nmatrix[nind][tind+1] -= 100
- elif ref.isForward():
- nmatrix[tind][nind+1] -= 1
- nmatrix[nind][0] = None
- self.nodes = reorder
- logger.debug("Nodes reordered.")
- return
- def printOpen62541Header(self, printedExternally=[], supressGenerationOfAttribute=[], outfilename=""):
- unPrintedNodes = []
- unPrintedRefs = []
- code = []
- header = []
- # Reorder our nodes to produce a bare minimum of bootstrapping dependencies
- logger.debug("Reordering nodes for minimal dependencies during printing.")
- self.reorderNodesMinDependencies()
- # Some macros (UA_EXPANDEDNODEID_MACRO()...) are easily created, but
- # bulky. This class will help to offload some code.
- codegen = open62541_MacroHelper(supressGenerationOfAttribute=supressGenerationOfAttribute)
- # 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.
- for n in self.nodes:
- if not n in printedExternally:
- unPrintedNodes.append(n)
- else:
- logger.debug("Node " + str(n.id()) + " is being ignored.")
- for n in unPrintedNodes:
- for r in n.getReferences():
- if (r.target() != None) and (r.target().id() != None) and (r.parent() != None):
- unPrintedRefs.append(r)
- logger.debug(str(len(unPrintedNodes)) + " Nodes, " + str(len(unPrintedRefs)) + "References need to get printed.")
- header.append("/* WARNING: This is a generated file.\n * Any manual changes will be overwritten.\n\n */")
- code.append("/* WARNING: This is a generated file.\n * Any manual changes will be overwritten.\n\n */")
- header.append('#ifndef '+outfilename.upper()+'_H_')
- header.append('#define '+outfilename.upper()+'_H_')
- header.append('')
- header.append('#include "server/ua_server_internal.h"')
- header.append('#include "server/ua_nodes.h"')
- header.append('#include "ua_util.h"')
- header.append('#include "ua_types_encoding_binary.h"')
- header.append('#include "ua_types_generated_encoding_binary.h"')
- header.append('#include "ua_transport_generated_encoding_binary.h"')
- header.append('')
- header.append('/* Definition that (in userspace models) may be ')
- header.append(' * - not included in the amalgamated header or')
- header.append(' * - not part of public headers or')
- header.append(' * - not exported in the shared object in combination with any of the above')
- header.append(' * but are required for value encoding.')
- header.append(' * NOTE: Userspace UA_(decode|encode)Binary /wo amalgamations requires UA_EXPORT to be appended to the appropriate definitions. */')
- header.append('#ifndef UA_ENCODINGOFFSET_BINARY')
- header.append('# define UA_ENCODINGOFFSET_BINARY 2')
- header.append('#endif')
- header.append('#ifndef NULL')
- header.append(' #define NULL ((void *)0)')
- header.append('#endif')
- header.append('#ifndef UA_malloc')
- header.append(' #define UA_malloc(_p_size) malloc(_p_size)')
- header.append('#endif')
- header.append('#ifndef UA_free')
- header.append(' #define UA_free(_p_ptr) free(_p_ptr)')
- header.append('#endif')
-
- code.append('#include "'+outfilename+'.h"')
- code.append("UA_INLINE UA_StatusCode "+outfilename+"(UA_Server *server) {")
- code.append('UA_StatusCode retval = UA_STATUSCODE_GOOD; ')
- code.append('if(retval == UA_STATUSCODE_GOOD){retval = UA_STATUSCODE_GOOD;} //ensure that retval is used');
- # Before printing nodes, we need to request additional namespace arrays from the server
- for nsid in self.namespaceIdentifiers:
- if nsid == 0 or nsid==1:
- continue
- else:
- name = self.namespaceIdentifiers[nsid]
- name = name.replace("\"","\\\"")
- code.append("if (UA_Server_addNamespace(server, \"{0}\") != {1})\n return UA_STATUSCODE_BADUNEXPECTEDERROR;".format(name, nsid))
- # 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.
- logger.debug("Collecting all references used in the namespace.")
- refsUsed = []
- for n in self.nodes:
- # Since we are already looping over all nodes, use this chance to print NodeId defines
- if n.id().ns != 0:
- nc = n.nodeClass()
- if nc != NODE_CLASS_OBJECT and nc != NODE_CLASS_VARIABLE and nc != NODE_CLASS_VIEW:
- header = header + codegen.getNodeIdDefineString(n)
- # Now for the actual references...
- for r in n.getReferences():
- # Only print valid references in namespace 0 (users will not want their refs bootstrapped)
- if not r.referenceType() in refsUsed and r.referenceType() != None and r.referenceType().id().ns == 0:
- refsUsed.append(r.referenceType())
- logger.debug(str(len(refsUsed)) + " reference types are used in the namespace, which will now get bootstrapped.")
- for r in refsUsed:
- code = code + r.printOpen62541CCode(unPrintedNodes, unPrintedRefs);
- header.append("extern UA_StatusCode "+outfilename+"(UA_Server *server);\n")
- header.append("#endif /* "+outfilename.upper()+"_H_ */")
- # Note to self: do NOT - NOT! - try to iterate over unPrintedNodes!
- # Nodes remove themselves from this list when printed.
- logger.debug("Printing all other nodes.")
- for n in self.nodes:
- code = code + n.printOpen62541CCode(unPrintedNodes, unPrintedRefs, supressGenerationOfAttribute=supressGenerationOfAttribute)
- if len(unPrintedNodes) != 0:
- logger.warn("" + str(len(unPrintedNodes)) + " nodes could not be translated to code.")
- else:
- logger.debug("Printing suceeded for all nodes")
- if len(unPrintedRefs) != 0:
- logger.debug("Attempting to print " + str(len(unPrintedRefs)) + " unprinted references.")
- 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):
- logger.debug("Reference has no parent!")
- elif not isinstance(r.parent().id(), opcua_node_id_t):
- logger.debug("Parents nodeid is not a nodeID!")
- 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:
- logger.warn("" + str(len(unPrintedRefs)) + " references could not be translated to code.")
- else:
- logger.debug("Printing succeeded for all references")
- code.append("return UA_STATUSCODE_GOOD;")
- code.append("}")
- return (header,code)
- ###
- ### Testing
- ###
- class testing:
- def __init__(self):
- self.namespace = opcua_namespace("testing")
- logger.debug("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")
- logger.debug("Phase 2: Linking address space references and datatypes")
- self.namespace.linkOpenPointers()
- self.namespace.sanitize()
- logger.debug("Phase 3: Comprehending DataType encoding rules")
- self.namespace.buildEncodingRules()
- logger.debug("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")
- logger.debug("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")
- logger.debug("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")
- logger.debug("Phase 2: Linking address space references and datatypes")
- self.namespace.linkOpenPointers()
- self.namespace.sanitize()
- logger.debug("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()
|