ChimeraTK-ControlSystemAdapter-OPCUAAdapter  04.00.01
ua_namespace.py
Go to the documentation of this file.
1 #!/usr/bin/env/python
2 # -*- coding: utf-8 -*-
3 
4 # This Source Code Form is subject to the terms of the Mozilla Public
5 # License, v. 2.0. If a copy of the MPL was not distributed with this
6 # file, You can obtain one at http://mozilla.org/MPL/2.0/.
7 
8 
22 
23 from __future__ import print_function
24 import sys
25 from time import struct_time, strftime, strptime, mktime
26 from struct import pack as structpack
27 
28 import logging
29 from ua_builtin_types import *;
30 from ua_node_types import *;
31 from ua_constants import *;
32 from open62541_MacroHelper import open62541_MacroHelper
33 
34 
35 logger = logging.getLogger(__name__)
36 
37 def getNextElementNode(xmlvalue):
38  if xmlvalue == None:
39  return None
40  xmlvalue = xmlvalue.nextSibling
41  while not xmlvalue == None and not xmlvalue.nodeType == xmlvalue.ELEMENT_NODE:
42  xmlvalue = xmlvalue.nextSibling
43  return xmlvalue
44 
45 
48 
50  """ Class holding and managing a set of OPCUA nodes.
51 
52  This class handles parsing XML description of namespaces, instantiating
53  nodes, linking references, graphing the namespace and compiling a binary
54  representation.
55 
56  Note that nodes assigned to this class are not restricted to having a
57  single namespace ID. This class represents the entire physical address
58  space of the binary representation and all nodes that are to be included
59  in that segment of memory.
60  """
61  nodes = []
62  nodeids = {}
63  aliases = {}
64  __linkLater__ = []
65  __binaryIndirectPointers__ = []
66  name = ""
67  knownNodeTypes = ""
68  namespaceIdentifiers = {} # list of 'int':'string' giving different namespace an array-mapable name
69 
70  def __init__(self, name):
71  self.nodes = []
72  self.knownNodeTypes = ['variable', 'object', 'method', 'referencetype', \
73  'objecttype', 'variabletype', 'methodtype', \
74  'datatype', 'referencetype', 'aliases']
75  self.name = name
76  self.nodeids = {}
77  self.aliases = {}
80 
81  def addNamespace(self, numericId, stringURL):
82  self.namespaceIdentifiers[numericId] = stringURL
83 
84  def linkLater(self, pointer):
85  """ Called by nodes or references who have parsed an XML reference to a
86  node represented by a string.
87 
88  No return value
89 
90  XML String representations of references have the form 'i=xy' or
91  'ns=1;s="This unique Node"'. Since during the parsing of this attribute
92  only a subset of nodes are known/parsed, this reference string cannot be
93  linked when encountered.
94 
95  References register themselves with the namespace to have their target
96  attribute (string) parsed by linkOpenPointers() when all nodes are
97  created, so that target can be dereferenced an point to an actual node.
98  """
99  self.__linkLater__.append(pointer)
100 
102  """ Return the list of references registered for linking during the next call
103  of linkOpenPointers()
104  """
105  return self.__linkLater__
106 
107  def unlinkedItemCount(self):
108  """ Returns the number of unlinked references that will be processed during
109  the next call of linkOpenPointers()
110  """
111  return len(self.__linkLater__)
112 
113  def buildAliasList(self, xmlelement):
114  """ Parses the <Alias> XML Element present in must XML NodeSet definitions.
115 
116  No return value
117 
118  Contents the Alias element are stored in a dictionary for further
119  dereferencing during pointer linkage (see linkOpenPointer()).
120  """
121  if not xmlelement.tagName == "Aliases":
122  logger.error("XMLElement passed is not an Aliaslist")
123  return
124  for al in xmlelement.childNodes:
125  if al.nodeType == al.ELEMENT_NODE:
126  if al.hasAttribute("Alias"):
127  aliasst = al.getAttribute("Alias")
128  if sys.version_info[0] < 3:
129  aliasnd = unicode(al.firstChild.data)
130  else:
131  aliasnd = al.firstChild.data
132  if not aliasst in self.aliases:
133  self.aliases[aliasst] = aliasnd
134  logger.debug("Added new alias \"" + str(aliasst) + "\" == \"" + str(aliasnd) + "\"")
135  else:
136  if self.aliases[aliasst] != aliasnd:
137  logger.error("Alias definitions for " + aliasst + " differ. Have " + self.aliases[aliasst] + " but XML defines " + aliasnd + ". Keeping current definition.")
138 
139  def getNodeByBrowseName(self, idstring):
140  """ Returns the first node in the nodelist whose browseName matches idstring.
141  """
142  matches = []
143  for n in self.nodes:
144  if idstring==str(n.browseName()):
145  matches.append(n)
146  if len(matches) > 1:
147  logger.error("Found multiple nodes with same ID!?")
148  if len(matches) == 0:
149  return None
150  else:
151  return matches[0]
152 
153  def getNodeByIDString(self, idstring):
154  """ Returns the first node in the nodelist whose id string representation
155  matches idstring.
156  """
157  matches = []
158  for n in self.nodes:
159  if idstring==str(n.id()):
160  matches.append(n)
161  if len(matches) > 1:
162  logger.error("Found multiple nodes with same ID!?")
163  if len(matches) == 0:
164  return None
165  else:
166  return matches[0]
167 
168  def createNode(self, ndtype, xmlelement):
169  """ createNode is instantiates a node described by xmlelement, its type being
170  defined by the string ndtype.
171 
172  No return value
173 
174  If the xmlelement is an <Alias>, the contents will be parsed and stored
175  for later dereferencing during pointer linking (see linkOpenPointers).
176 
177  Recognized types are:
178  * UAVariable
179  * UAObject
180  * UAMethod
181  * UAView
182  * UAVariableType
183  * UAObjectType
184  * UAMethodType
185  * UAReferenceType
186  * UADataType
187 
188  For every recognized type, an appropriate node class is added to the node
189  list of the namespace. The NodeId of the given node is created and parsing
190  of the node attributes and elements is delegated to the parseXML() and
191  parseXMLSubType() functions of the instantiated class.
192 
193  If the NodeID attribute is non-unique in the node list, the creation is
194  deferred and an error is logged.
195  """
196  if not isinstance(xmlelement, dom.Element):
197  logger.error( "Error: Can not create node from invalid XMLElement")
198  return
199 
200  # An ID is mandatory for everything but aliases!
201  id = None
202  for idname in ['NodeId', 'NodeID', 'nodeid']:
203  if xmlelement.hasAttribute(idname):
204  id = xmlelement.getAttribute(idname)
205  if ndtype == 'aliases':
206  self.buildAliasList(xmlelement)
207  return
208  elif id == None:
209  logger.info( "Error: XMLElement has no id, node will not be created!")
210  return
211  else:
212  id = opcua_node_id_t(id)
213 
214  if str(id) in self.nodeids:
215  # Normal behavior: Do not allow duplicates, first one wins
216  #logger.error( "XMLElement with duplicate ID " + str(id) + " found, node will not be created!")
217  #return
218  # Open62541 behavior for header generation: Replace the duplicate with the new node
219  logger.info( "XMLElement with duplicate ID " + str(id) + " found, node will be replaced!")
220  nd = self.getNodeByIDString(str(id))
221  self.nodes.remove(nd)
222  self.nodeids.pop(str(nd.id()))
223 
224  node = None
225  if (ndtype == 'variable'):
226  node = opcua_node_variable_t(id, self)
227  elif (ndtype == 'object'):
228  node = opcua_node_object_t(id, self)
229  elif (ndtype == 'method'):
230  node = opcua_node_method_t(id, self)
231  elif (ndtype == 'objecttype'):
232  node = opcua_node_objectType_t(id, self)
233  elif (ndtype == 'variabletype'):
234  node = opcua_node_variableType_t(id, self)
235  elif (ndtype == 'methodtype'):
236  node = opcua_node_methodType_t(id, self)
237  elif (ndtype == 'datatype'):
238  node = opcua_node_dataType_t(id, self)
239  elif (ndtype == 'referencetype'):
240  node = opcua_node_referenceType_t(id, self)
241  else:
242  logger.error( "No node constructor for type " + ndtype)
243 
244  if node != None:
245  node.parseXML(xmlelement)
246 
247  self.nodes.append(node)
248  self.nodeids[str(node.id())] = node
249 
250  def removeNodeById(self, nodeId):
251  nd = self.getNodeByIDString(nodeId)
252  if nd == None:
253  return False
254 
255  logger.debug("Removing nodeId " + str(nodeId))
256  self.nodes.remove(nd)
257  if nd.getInverseReferences() != None:
258  for ref in nd.getInverseReferences():
259  src = ref.target();
260  src.removeReferenceToNode(nd)
261 
262  return True
263 
265  """ Appends a node to the list of nodes that should be contained in the
266  first 765 bytes (255 pointer slots a 3 bytes) in the binary
267  representation (indirect referencing space).
268 
269  This function is reserved for references and dataType pointers.
270  """
271  if not node in self.__binaryIndirectPointers__:
272  self.__binaryIndirectPointers__.append(node)
273  return self.__binaryIndirectPointers__.index(node)
274 
276  """ Returns the slot/index of a pointer in the indirect referencing space
277  (first 765 Bytes) of the binary representation.
278  """
279  if not node in self.__binaryIndirectPointers__:
280  return -1
281  return self.__binaryIndirectPointers__.index(node)
282 
283 
284  def parseXML(self, xmldoc):
285  """ Reads an XML Namespace definition and instantiates node.
286 
287  No return value
288 
289  parseXML open the file xmldoc using xml.dom.minidom and searches for
290  the first UANodeSet Element. For every Element encountered, createNode
291  is called to instantiate a node of the appropriate type.
292  """
293  typedict = {}
294  UANodeSet = dom.parse(xmldoc).getElementsByTagName("UANodeSet")
295  if len(UANodeSet) == 0:
296  logger.error( "Error: No NodeSets found")
297  return
298  if len(UANodeSet) != 1:
299  logger.error( "Error: Found more than 1 Nodeset in XML File")
300 
301  UANodeSet = UANodeSet[0]
302  for nd in UANodeSet.childNodes:
303  if nd.nodeType != nd.ELEMENT_NODE:
304  continue
305 
306  ndType = nd.tagName.lower()
307  if ndType[:2] == "ua":
308  ndType = ndType[2:]
309  elif not ndType in self.knownNodeTypes:
310  logger.warn("XML Element or NodeType " + ndType + " is unknown and will be ignored")
311  continue
312 
313  if not ndType in typedict:
314  typedict[ndType] = 1
315  else:
316  typedict[ndType] = typedict[ndType] + 1
317 
318  self.createNode(ndType, nd)
319  logger.debug("Currently " + str(len(self.nodes)) + " nodes in address space. Type distribution for this run was: " + str(typedict))
320 
321  def linkOpenPointers(self):
322  """ Substitutes symbolic NodeIds in references for actual node instances.
323 
324  No return value
325 
326  References that have registered themselves with linkLater() to have
327  their symbolic NodeId targets ("ns=2;i=32") substituted for an actual
328  node will be iterated by this function. For each reference encountered
329  in the list of unlinked/open references, the target string will be
330  evaluated and searched for in the node list of this namespace. If found,
331  the target attribute of the reference will be substituted for the
332  found node.
333 
334  If a reference fails to get linked, it will remain in the list of
335  unlinked references. The individual items in this list can be
336  retrieved using getUnlinkedPointers().
337  """
338  linked = []
339 
340  logger.debug( str(self.unlinkedItemCount()) + " pointers need to get linked.")
341  for l in self.__linkLater__:
342  targetLinked = False
343  if not l.target() == None and not isinstance(l.target(), opcua_node_t):
344  if isinstance(l.target(),str) or isinstance(l.target(),unicode):
345  # If is not a node ID, it should be an alias. Try replacing it
346  # with a proper node ID
347  if l.target() in self.aliases:
348  l.target(self.aliases[l.target()])
349  # If the link is a node ID, try to find it hopening that no ass has
350  # defined more than one kind of id for that sucker
351  if l.target()[:2] == "i=" or l.target()[:2] == "g=" or \
352  l.target()[:2] == "b=" or l.target()[:2] == "s=" or \
353  l.target()[:3] == "ns=" :
354  tgt = self.getNodeByIDString(str(l.target()))
355  if tgt == None:
356  logger.error("Failed to link pointer to target (node not found) " + l.target())
357  else:
358  l.target(tgt)
359  targetLinked = True
360  else:
361  logger.error("Failed to link pointer to target (target not Alias or Node) " + l.target())
362  else:
363  logger.error("Failed to link pointer to target (don't know dummy type + " + str(type(l.target())) + " +) " + str(l.target()))
364  else:
365  logger.error("Pointer has null target: " + str(l))
366 
367 
368  referenceLinked = False
369  if not l.referenceType() == None:
370  if l.referenceType() in self.aliases:
371  l.referenceType(self.aliases[l.referenceType()])
372  tgt = self.getNodeByIDString(str(l.referenceType()))
373  if tgt == None:
374  logger.error("Failed to link reference type to target (node not found) " + l.referenceType())
375  else:
376  l.referenceType(tgt)
377  referenceLinked = True
378  else:
379  referenceLinked = True
380 
381  if referenceLinked == True and targetLinked == True:
382  linked.append(l)
383 
384  # References marked as "not forward" must be inverted (removed from source node, assigned to target node and relinked)
385  logger.warn("Inverting reference direction for all references with isForward==False attribute (is this correct!?)")
386  for n in self.nodes:
387  for r in n.getReferences():
388  if r.isForward() == False:
389  tgt = r.target()
390  if isinstance(tgt, opcua_node_t):
391  nref = opcua_referencePointer_t(n, parentNode=tgt)
392  nref.referenceType(r.referenceType())
393  tgt.addReference(nref)
394 
395  # Create inverse references for all nodes
396  logger.debug("Updating all referencedBy fields in nodes for inverse lookups.")
397  for n in self.nodes:
398  n.updateInverseReferences()
399 
400  for l in linked:
401  self.__linkLater__.remove(l)
402 
403  if len(self.__linkLater__) != 0:
404  logger.warn(str(len(self.__linkLater__)) + " could not be linked.")
405 
406  def sanitize(self):
407  remove = []
408  logger.debug("Sanitizing nodes and references...")
409  for n in self.nodes:
410  if n.sanitize() == False:
411  remove.append(n)
412  if not len(remove) == 0:
413  logger.warn(str(len(remove)) + " nodes will be removed because they failed sanitation.")
414  # FIXME: Some variable ns=0 nodes fail because they don't have DataType fields...
415  # How should this be handles!?
416  logger.warn("Not actually removing nodes... it's unclear if this is valid or not")
417 
418  def getRoot(self):
419  """ Returns the first node instance with the browseName "Root".
420  """
421  return self.getNodeByBrowseName("Root")
422 
424  """ Calls buildEncoding() for all DataType nodes (opcua_node_dataType_t).
425 
426  No return value
427  """
428  stat = {True: 0, False: 0}
429  for n in self.nodes:
430  if isinstance(n, opcua_node_dataType_t):
431  n.buildEncoding()
432  stat[n.isEncodable()] = stat[n.isEncodable()] + 1
433  logger.debug("Type definitions built/passed: " + str(stat))
434 
435  def allocateVariables(self):
436  for n in self.nodes:
437  if isinstance(n, opcua_node_variable_t):
438  n.allocateValue()
439 
440  def printDot(self, filename="namespace.dot"):
441  """ Outputs a graphiz/dot description of all nodes in the namespace.
442 
443  Output will written into filename to be parsed by dot/neato...
444 
445  Note that for namespaces with more then 20 nodes the reference structure
446  will lead to a mostly illegible and huge graph. Use printDotGraphWalk()
447  for plotting specific portions of a large namespace.
448  """
449  file=open(filename, 'w+')
450 
451  file.write("digraph ns {\n")
452  for n in self.nodes:
453  file.write(n.printDot())
454  file.write("}\n")
455  file.close()
456 
457  def getSubTypesOf(self, tdNodes = None, currentNode = None, hasSubtypeRefNode = None):
458  # If this is a toplevel call, collect the following information as defaults
459  if tdNodes == None:
460  tdNodes = []
461  if currentNode == None:
462  currentNode = self.getNodeByBrowseName("HasTypeDefinition")
463  tdNodes.append(currentNode)
464  if len(tdNodes) < 1:
465  return []
466  if hasSubtypeRefNode == None:
467  hasSubtypeRefNode = self.getNodeByBrowseName("HasSubtype")
468  if hasSubtypeRefNode == None:
469  return tdNodes
470 
471  # collect all subtypes of this node
472  for ref in currentNode.getReferences():
473  if ref.isForward() and ref.referenceType().id() == hasSubtypeRefNode.id():
474  tdNodes.append(ref.target())
475  self.getTypeDefinitionNodes(tdNodes=tdNodes, currentNode = ref.target(), hasSubtypeRefNode=hasSubtypeRefNode)
476 
477  return tdNodes
478 
479 
480  def printDotGraphWalk(self, depth=1, filename="out.dot", rootNode=None, followInverse = False, excludeNodeIds=[]):
481  """ Outputs a graphiz/dot description the nodes centered around rootNode.
482 
483  References beginning from rootNode will be followed for depth steps. If
484  "followInverse = True" is passed, then inverse (not Forward) references
485  will also be followed.
486 
487  Nodes can be excluded from the graph by passing a list of NodeIds as
488  string representation using excludeNodeIds (ex ["i=53", "ns=2;i=453"]).
489 
490  Output is written into filename to be parsed by dot/neato/srfp...
491  """
492  iter = depth
493  processed = []
494  if rootNode == None or \
495  not isinstance(rootNode, opcua_node_t) or \
496  not rootNode in self.nodes:
497  root = self.getRoot()
498  else:
499  root = rootNode
500 
501  file=open(filename, 'w+')
502 
503  if root == None:
504  return
505 
506  file.write("digraph ns {\n")
507  file.write(root.printDot())
508  refs=[]
509  if followInverse == True:
510  refs = root.getReferences(); # + root.getInverseReferences()
511  else:
512  for ref in root.getReferences():
513  if ref.isForward():
514  refs.append(ref)
515  while iter > 0:
516  tmp = []
517  for ref in refs:
518  if isinstance(ref.target(), opcua_node_t):
519  tgt = ref.target()
520  if not str(tgt.id()) in excludeNodeIds:
521  if not tgt in processed:
522  file.write(tgt.printDot())
523  processed.append(tgt)
524  if ref.isForward() == False and followInverse == True:
525  tmp = tmp + tgt.getReferences(); # + tgt.getInverseReferences()
526  elif ref.isForward() == True :
527  tmp = tmp + tgt.getReferences();
528  refs = tmp
529  iter = iter - 1
530 
531  file.write("}\n")
532  file.close()
533 
534  def __reorder_getMinWeightNode__(self, nmatrix):
535  rcind = -1
536  rind = -1
537  minweight = -1
538  minweightnd = None
539  for row in nmatrix:
540  rcind += 1
541  if row[0] == None:
542  continue
543  w = sum(row[1:])
544  if minweight < 0:
545  rind = rcind
546  minweight = w
547  minweightnd = row[0]
548  elif w < minweight:
549  rind = rcind
550  minweight = w
551  minweightnd = row[0]
552  return (rind, minweightnd, minweight)
553 
555  # create a matrix represtantion of all node
556  #
557  nmatrix = []
558  for n in range(0,len(self.nodes)):
559  nmatrix.append([None] + [0]*len(self.nodes))
560 
561  typeRefs = []
562  tn = self.getNodeByBrowseName("HasTypeDefinition")
563  if tn != None:
564  typeRefs.append(tn)
565  typeRefs = typeRefs + self.getSubTypesOf(currentNode=tn)
566  subTypeRefs = []
567  tn = self.getNodeByBrowseName("HasSubtype")
568  if tn != None:
569  subTypeRefs.append(tn)
570  subTypeRefs = subTypeRefs + self.getSubTypesOf(currentNode=tn)
571 
572  logger.debug("Building connectivity matrix for node order optimization.")
573  # Set column 0 to contain the node
574  for node in self.nodes:
575  nind = self.nodes.index(node)
576  nmatrix[nind][0] = node
577 
578  # Determine the dependencies of all nodes
579  logger.debug("Determining node interdependencies.")
580  for node in self.nodes:
581  nind = self.nodes.index(node)
582  #print "Examining node " + str(nind) + " " + str(node)
583  for ref in node.getReferences():
584  if isinstance(ref.target(), opcua_node_t):
585  tind = self.nodes.index(ref.target())
586  # Typedefinition of this node has precedence over this node
587  if ref.referenceType() in typeRefs and ref.isForward():
588  nmatrix[nind][tind+1] += 200 # Very big weight for typedefs
589  # isSubTypeOf/typeDefinition of this node has precedence over this node
590  elif ref.referenceType() in subTypeRefs and not ref.isForward():
591  nmatrix[nind][tind+1] += 100 # Big weight for subtypes
592  # Else the target depends on us
593  elif ref.isForward():
594  nmatrix[tind][nind+1] += 1 # regular weight for dependencies
595 
596  logger.debug("Using Djikstra topological sorting to determine printing order.")
597  reorder = []
598  while len(reorder) < len(self.nodes):
599  (nind, node, w) = self.__reorder_getMinWeightNode__(nmatrix)
600  #print str(100*float(len(reorder))/len(self.nodes)) + "% " + str(w) + " " + str(node) + " " + str(node.browseName())
601  reorder.append(node)
602  for ref in node.getReferences():
603  if isinstance(ref.target(), opcua_node_t):
604  tind = self.nodes.index(ref.target())
605  if ref.referenceType() in typeRefs and ref.isForward():
606  nmatrix[nind][tind+1] -= 200
607  elif ref.referenceType() in subTypeRefs and not ref.isForward():
608  nmatrix[nind][tind+1] -= 100
609  elif ref.isForward():
610  nmatrix[tind][nind+1] -= 1
611  nmatrix[nind][0] = None
612  self.nodes = reorder
613  logger.debug("Nodes reordered.")
614  return
615 
616  def printOpen62541Header(self, printedExternally=[], supressGenerationOfAttribute=[], outfilename=""):
617  unPrintedNodes = []
618  unPrintedRefs = []
619  code = []
620  header = []
621 
622  # Reorder our nodes to produce a bare minimum of bootstrapping dependencies
623  logger.debug("Reordering nodes for minimal dependencies during printing.")
625 
626  # Some macros (UA_EXPANDEDNODEID_MACRO()...) are easily created, but
627  # bulky. This class will help to offload some code.
628  codegen = open62541_MacroHelper(supressGenerationOfAttribute=supressGenerationOfAttribute)
629 
630  # Populate the unPrinted-Lists with everything we have.
631  # Every Time a nodes printfunction is called, it will pop itself and
632  # all printed references from these lists.
633  for n in self.nodes:
634  if not n in printedExternally:
635  unPrintedNodes.append(n)
636  else:
637  logger.debug("Node " + str(n.id()) + " is being ignored.")
638  for n in unPrintedNodes:
639  for r in n.getReferences():
640  if (r.target() != None) and (r.target().id() != None) and (r.parent() != None):
641  unPrintedRefs.append(r)
642 
643  logger.debug(str(len(unPrintedNodes)) + " Nodes, " + str(len(unPrintedRefs)) + "References need to get printed.")
644  header.append("/* WARNING: This is a generated file.\n * Any manual changes will be overwritten.\n\n */")
645  code.append("/* WARNING: This is a generated file.\n * Any manual changes will be overwritten.\n\n */")
646 
647  header.append('#ifndef '+outfilename.upper()+'_H_')
648  header.append('#define '+outfilename.upper()+'_H_')
649  header.append('#ifdef UA_NO_AMALGAMATION')
650  header.append( '#include "server/ua_server_internal.h"')
651  header.append( '#include "server/ua_nodes.h"')
652  header.append(' #include "ua_util.h"')
653  header.append(' #include "ua_types.h"')
654  header.append(' #include "ua_types_encoding_binary.h"')
655  header.append(' #include "ua_types_generated_encoding_binary.h"')
656  header.append(' #include "ua_transport_generated_encoding_binary.h"')
657  header.append('#else')
658  header.append(' #include "open62541.h"')
659  header.append('#endif')
660  header.append('')
661  header.append('/* Definition that (in userspace models) may be ')
662  header.append(' * - not included in the amalgamated header or')
663  header.append(' * - not part of public headers or')
664  header.append(' * - not exported in the shared object in combination with any of the above')
665  header.append(' * but are required for value encoding.')
666  header.append(' * NOTE: Userspace UA_(decode|encode)Binary /wo amalgamations requires UA_EXPORT to be appended to the appropriate definitions. */')
667  header.append('#ifndef UA_ENCODINGOFFSET_BINARY')
668  header.append('# define UA_ENCODINGOFFSET_BINARY 2')
669  header.append('#endif')
670  header.append('#ifndef NULL')
671  header.append(' #define NULL ((void *)0)')
672  header.append('#endif')
673  header.append('#ifndef UA_malloc')
674  header.append(' #define UA_malloc(_p_size) malloc(_p_size)')
675  header.append('#endif')
676  header.append('#ifndef UA_free')
677  header.append(' #define UA_free(_p_ptr) free(_p_ptr)')
678  header.append('#endif')
679 
680  code.append('#include "'+outfilename+'.h"')
681  code.append("UA_INLINE UA_StatusCode "+outfilename+"(UA_Server *server) {")
682  code.append('UA_StatusCode retval = UA_STATUSCODE_GOOD; ')
683  code.append('if(retval == UA_STATUSCODE_GOOD){retval = UA_STATUSCODE_GOOD;} //ensure that retval is used');
684 
685  # Before printing nodes, we need to request additional namespace arrays from the server
686  for nsid in self.namespaceIdentifiers:
687  if nsid == 0 or nsid==1:
688  continue
689  else:
690  name = self.namespaceIdentifiers[nsid]
691  name = name.replace("\"","\\\"")
692  code.append("if (UA_Server_addNamespace(server, \"{0}\") != {1})\n return UA_STATUSCODE_BADUNEXPECTEDERROR;".format(name, nsid))
693 
694  # Find all references necessary to create the namespace and
695  # "Bootstrap" them so all other nodes can safely use these referencetypes whenever
696  # they can locate both source and target of the reference.
697  logger.debug("Collecting all references used in the namespace.")
698  refsUsed = []
699  for n in self.nodes:
700  # Since we are already looping over all nodes, use this chance to print NodeId defines
701  if n.id().ns != 0:
702  nc = n.nodeClass()
703  if nc != NODE_CLASS_OBJECT and nc != NODE_CLASS_VARIABLE and nc != NODE_CLASS_VIEW:
704  header = header + codegen.getNodeIdDefineString(n)
705 
706  # Now for the actual references...
707  for r in n.getReferences():
708  # Only print valid references in namespace 0 (users will not want their refs bootstrapped)
709  if not r.referenceType() in refsUsed and r.referenceType() != None and r.referenceType().id().ns == 0:
710  refsUsed.append(r.referenceType())
711  logger.debug(str(len(refsUsed)) + " reference types are used in the namespace, which will now get bootstrapped.")
712  for r in refsUsed:
713  code = code + r.printOpen62541CCode(unPrintedNodes, unPrintedRefs);
714 
715  header.append("extern UA_StatusCode "+outfilename+"(UA_Server *server);\n")
716  header.append("#endif /* "+outfilename.upper()+"_H_ */")
717 
718  # Note to self: do NOT - NOT! - try to iterate over unPrintedNodes!
719  # Nodes remove themselves from this list when printed.
720  logger.debug("Printing all other nodes.")
721  for n in self.nodes:
722  code = code + n.printOpen62541CCode(unPrintedNodes, unPrintedRefs, supressGenerationOfAttribute=supressGenerationOfAttribute)
723 
724  if len(unPrintedNodes) != 0:
725  logger.warn("" + str(len(unPrintedNodes)) + " nodes could not be translated to code.")
726  else:
727  logger.debug("Printing suceeded for all nodes")
728 
729  if len(unPrintedRefs) != 0:
730  logger.debug("Attempting to print " + str(len(unPrintedRefs)) + " unprinted references.")
731  tmprefs = []
732  for r in unPrintedRefs:
733  if not (r.target() not in unPrintedNodes) and not (r.parent() in unPrintedNodes):
734  if not isinstance(r.parent(), opcua_node_t):
735  logger.debug("Reference has no parent!")
736  elif not isinstance(r.parent().id(), opcua_node_id_t):
737  logger.debug("Parents nodeid is not a nodeID!")
738  else:
739  if (len(tmprefs) == 0):
740  code.append("// Creating leftover references:")
741  code = code + codegen.getCreateStandaloneReference(r.parent(), r)
742  code.append("")
743  tmprefs.append(r)
744  # Remove printed refs from list
745  for r in tmprefs:
746  unPrintedRefs.remove(r)
747  if len(unPrintedRefs) != 0:
748  logger.warn("" + str(len(unPrintedRefs)) + " references could not be translated to code.")
749  else:
750  logger.debug("Printing succeeded for all references")
751 
752  code.append("return UA_STATUSCODE_GOOD;")
753  code.append("}")
754  return (header,code)
755 
756 
759 
760 class testing:
761  def __init__(self):
762  self.namespace = opcua_namespace("testing")
763 
764  logger.debug("Phase 1: Reading XML file nodessets")
765  self.namespace.parseXML("Opc.Ua.NodeSet2.xml")
766  #self.namespace.parseXML("Opc.Ua.NodeSet2.Part4.xml")
767  #self.namespace.parseXML("Opc.Ua.NodeSet2.Part5.xml")
768  #self.namespace.parseXML("Opc.Ua.SimulationNodeSet2.xml")
769 
770  logger.debug("Phase 2: Linking address space references and datatypes")
771  self.namespace.linkOpenPointers()
772  self.namespace.sanitize()
773 
774  logger.debug("Phase 3: Comprehending DataType encoding rules")
775  self.namespace.buildEncodingRules()
776 
777  logger.debug("Phase 4: Allocating variable value data")
778  self.namespace.allocateVariables()
779 
780  bin = self.namespace.buildBinary()
781  f = open("binary.base64","w+")
782  f.write(bin.encode("base64"))
783  f.close()
784 
785  allnodes = self.namespace.nodes;
786  ns = [self.namespace.getRoot()]
787 
788  i = 0
789  #print "Starting depth search on " + str(len(allnodes)) + " nodes starting with from " + str(ns)
790  while (len(ns) < len(allnodes)):
791  i = i + 1;
792  tmp = [];
793  print("Iteration: " + str(i))
794  for n in ns:
795  tmp.append(n)
796  for r in n.getReferences():
797  if (not r.target() in tmp):
798  tmp.append(r.target())
799  print("...tmp, " + str(len(tmp)) + " nodes discovered")
800  ns = []
801  for n in tmp:
802  ns.append(n)
803  print("...done, " + str(len(ns)) + " nodes discovered")
804 
805  logger.debug("Phase 5: Printing pretty graph")
806  self.namespace.printDotGraphWalk(depth=1, rootNode=self.namespace.getNodeByIDString("i=84"), followInverse=False, excludeNodeIds=["i=29", "i=22", "i=25"])
807  #self.namespace.printDot()
808 
810  def __init__(self):
811  self.namespace = opcua_namespace("testing")
812 
813  logger.debug("Phase 1: Reading XML file nodessets")
814  self.namespace.parseXML("Opc.Ua.NodeSet2.xml")
815  #self.namespace.parseXML("Opc.Ua.NodeSet2.Part4.xml")
816  #self.namespace.parseXML("Opc.Ua.NodeSet2.Part5.xml")
817  #self.namespace.parseXML("Opc.Ua.SimulationNodeSet2.xml")
818 
819  logger.debug("Phase 2: Linking address space references and datatypes")
820  self.namespace.linkOpenPointers()
821  self.namespace.sanitize()
822 
823  logger.debug("Phase 3: Calling C Printers")
824  code = self.namespace.printOpen62541Header()
825 
826  codeout = open("./open62541_namespace.c", "w+")
827  for line in code:
828  codeout.write(line + "\n")
829  codeout.close()
830  return
831 
832 # Call testing routine if invoked standalone.
833 # For better debugging, it is advised to import this file using an interactive
834 # python shell and instantiating a namespace.
835 #
836 # import ua_types.py as ua; ns=ua.testing().namespace
837 if __name__ == '__main__':
ua_namespace.opcua_namespace.__binaryIndirectPointers__
list __binaryIndirectPointers__
Definition: ua_namespace.py:65
ua_namespace.opcua_namespace.unlinkedItemCount
def unlinkedItemCount(self)
Definition: ua_namespace.py:107
ua_namespace.opcua_namespace.printDotGraphWalk
def printDotGraphWalk(self, depth=1, filename="out.dot", rootNode=None, followInverse=False, excludeNodeIds=[])
Definition: ua_namespace.py:480
ua_namespace.opcua_namespace.__init__
def __init__(self, name)
Definition: ua_namespace.py:70
ua_namespace.opcua_namespace.printOpen62541Header
def printOpen62541Header(self, printedExternally=[], supressGenerationOfAttribute=[], outfilename="")
Definition: ua_namespace.py:616
ua_node_types.opcua_node_method_t
Definition: ua_node_types.py:1116
ua_namespace.testing.__init__
def __init__(self)
Definition: ua_namespace.py:761
testMapGenerator.format
format
Definition: testMapGenerator.py:78
ua_node_types.opcua_node_variable_t
Definition: ua_node_types.py:919
ua_node_types.opcua_node_object_t
Definition: ua_node_types.py:854
ua_namespace.opcua_namespace.removeNodeById
def removeNodeById(self, nodeId)
Definition: ua_namespace.py:250
ua_node_types.opcua_node_objectType_t
Definition: ua_node_types.py:1186
ua_namespace.opcua_namespace
Namespace Organizer.
Definition: ua_namespace.py:49
ua_node_types.opcua_node_referenceType_t
Definition: ua_node_types.py:747
ua_node_types.opcua_node_dataType_t
Definition: ua_node_types.py:1371
ua_namespace.testing.namespace
namespace
Definition: ua_namespace.py:762
ua_namespace.opcua_namespace.addNamespace
def addNamespace(self, numericId, stringURL)
Definition: ua_namespace.py:81
ua_namespace.opcua_namespace.__reorder_getMinWeightNode__
def __reorder_getMinWeightNode__(self, nmatrix)
Definition: ua_namespace.py:534
ua_namespace.opcua_namespace.printDot
def printDot(self, filename="namespace.dot")
Definition: ua_namespace.py:440
ua_namespace.opcua_namespace.getNodeByBrowseName
def getNodeByBrowseName(self, idstring)
Definition: ua_namespace.py:139
ua_namespace.testing_open62541_header.__init__
def __init__(self)
Definition: ua_namespace.py:810
ua_namespace.opcua_namespace.createNode
def createNode(self, ndtype, xmlelement)
Definition: ua_namespace.py:168
ua_namespace.opcua_namespace.sanitize
def sanitize(self)
Definition: ua_namespace.py:406
ua_namespace.opcua_namespace.namespaceIdentifiers
dictionary namespaceIdentifiers
Definition: ua_namespace.py:68
open62541_MacroHelper.open62541_MacroHelper
Definition: open62541_MacroHelper.py:34
ua_namespace.opcua_namespace.linkOpenPointers
def linkOpenPointers(self)
Definition: ua_namespace.py:321
ua_namespace.opcua_namespace.nodes
list nodes
Definition: ua_namespace.py:61
ua_namespace.testing_open62541_header
Definition: ua_namespace.py:809
str
#define str(a)
Definition: ua_adapter.cpp:52
create_self-signed.type
type
Definition: create_self-signed.py:18
ua_namespace.testing_open62541_header.namespace
namespace
Definition: ua_namespace.py:811
ua_namespace.opcua_namespace.getSubTypesOf
def getSubTypesOf(self, tdNodes=None, currentNode=None, hasSubtypeRefNode=None)
Definition: ua_namespace.py:457
ua_namespace.opcua_namespace.parseXML
def parseXML(self, xmldoc)
Definition: ua_namespace.py:284
ua_namespace.getNextElementNode
def getNextElementNode(xmlvalue)
Definition: ua_namespace.py:37
ua_builtin_types.unicode
def unicode(s)
Definition: ua_builtin_types.py:43
ua_namespace.opcua_namespace.__linkLater__
list __linkLater__
Definition: ua_namespace.py:64
ua_namespace.opcua_namespace.linkLater
def linkLater(self, pointer)
Definition: ua_namespace.py:84
ua_namespace.opcua_namespace.registerBinaryIndirectPointer
def registerBinaryIndirectPointer(self, node)
Definition: ua_namespace.py:264
ua_namespace.opcua_namespace.knownNodeTypes
string knownNodeTypes
Definition: ua_namespace.py:67
ua_namespace.opcua_namespace.allocateVariables
def allocateVariables(self)
Definition: ua_namespace.py:435
ua_namespace.opcua_namespace.getUnlinkedPointers
def getUnlinkedPointers(self)
Definition: ua_namespace.py:101
ua_namespace.opcua_namespace.buildAliasList
def buildAliasList(self, xmlelement)
Definition: ua_namespace.py:113
ua_namespace.opcua_namespace.getBinaryIndirectPointerIndex
def getBinaryIndirectPointerIndex(self, node)
Definition: ua_namespace.py:275
ua_namespace.opcua_namespace.getRoot
def getRoot(self)
Definition: ua_namespace.py:418
ua_node_types.opcua_node_variableType_t
Definition: ua_node_types.py:1249
ua_namespace.opcua_namespace.buildEncodingRules
def buildEncodingRules(self)
Definition: ua_namespace.py:423
ua_namespace.opcua_namespace.nodeids
dictionary nodeids
Definition: ua_namespace.py:62
ua_namespace.opcua_namespace.getNodeByIDString
def getNodeByIDString(self, idstring)
Definition: ua_namespace.py:153
generate_open62541CCode.id
id
Definition: generate_open62541CCode.py:127
ua_namespace.opcua_namespace.reorderNodesMinDependencies
def reorderNodesMinDependencies(self)
Definition: ua_namespace.py:554
ua_namespace.testing
Definition: ua_namespace.py:760
ua_namespace.opcua_namespace.aliases
dictionary aliases
Definition: ua_namespace.py:63
ua_node_types.opcua_node_id_t
Definition: ua_node_types.py:162
ua_namespace.opcua_namespace.name
string name
Definition: ua_namespace.py:66
ua_node_types.opcua_referencePointer_t
References are not really described by OPC-UA.
Definition: ua_node_types.py:44