ua_namespace.py 23 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628
  1. #!/usr/bin/env/python
  2. # -*- coding: utf-8 -*-
  3. ###
  4. ### Author: Chris Iatrou (ichrispa@core-vector.net)
  5. ### Version: rev 13
  6. ###
  7. ### This program was created for educational purposes and is released into the
  8. ### public domain under the General Public Licence. A copy of the GNU GPL is
  9. ### available under http://www.gnu.org/licenses/gpl-3.0.html.
  10. ###
  11. ### This program is not meant to be used in a production environment. The
  12. ### author is not liable for any complications arising due to the use of
  13. ### this program.
  14. ###
  15. from time import struct_time, strftime, strptime, mktime
  16. from struct import pack as structpack
  17. from logger import *;
  18. from ua_builtin_types import *;
  19. from ua_node_types import *;
  20. from open62541_MacroHelper import open62541_MacroHelper
  21. def getNextElementNode(xmlvalue):
  22. if xmlvalue == None:
  23. return None
  24. xmlvalue = xmlvalue.nextSibling
  25. while not xmlvalue == None and not xmlvalue.nodeType == xmlvalue.ELEMENT_NODE:
  26. xmlvalue = xmlvalue.nextSibling
  27. return xmlvalue
  28. ###
  29. ### Namespace Organizer
  30. ###
  31. class opcua_namespace():
  32. """ Class holding and managing a set of OPCUA nodes.
  33. This class handles parsing XML description of namespaces, instantiating
  34. nodes, linking references, graphing the namespace and compiling a binary
  35. representation.
  36. Note that nodes assigned to this class are not restricted to having a
  37. single namespace ID. This class represents the entire physical address
  38. space of the binary representation and all nodes that are to be included
  39. in that segment of memory.
  40. """
  41. nodes = []
  42. nodeids = {}
  43. aliases = {}
  44. __linkLater__ = []
  45. __binaryIndirectPointers__ = []
  46. name = ""
  47. knownNodeTypes = ""
  48. def __init__(self, name):
  49. self.nodes = []
  50. self.knownNodeTypes = ['variable', 'object', 'method', 'referencetype', \
  51. 'objecttype', 'variabletype', 'methodtype', \
  52. 'datatype', 'referencetype', 'aliases']
  53. self.name = name
  54. self.nodeids = {}
  55. self.aliases = {}
  56. self.__binaryIndirectPointers__ = []
  57. def linkLater(self, pointer):
  58. """ Called by nodes or references who have parsed an XML reference to a
  59. node represented by a string.
  60. No return value
  61. XML String representations of references have the form 'i=xy' or
  62. 'ns=1;s="This unique Node"'. Since during the parsing of this attribute
  63. only a subset of nodes are known/parsed, this reference string cannot be
  64. linked when encountered.
  65. References register themselves with the namespace to have their target
  66. attribute (string) parsed by linkOpenPointers() when all nodes are
  67. created, so that target can be dereferenced an point to an actual node.
  68. """
  69. self.__linkLater__.append(pointer)
  70. def getUnlinkedPointers(self):
  71. """ Return the list of references registered for linking during the next call
  72. of linkOpenPointers()
  73. """
  74. return self.__linkLater__
  75. def unlinkedItemCount(self):
  76. """ Returns the number of unlinked references that will be processed during
  77. the next call of linkOpenPointers()
  78. """
  79. return len(self.__linkLater__)
  80. def buildAliasList(self, xmlelement):
  81. """ Parses the <Alias> XML Element present in must XML NodeSet definitions.
  82. No return value
  83. Contents the Alias element are stored in a dictionary for further
  84. dereferencing during pointer linkage (see linkOpenPointer()).
  85. """
  86. if not xmlelement.tagName == "Aliases":
  87. log(self, "XMLElement passed is not an Aliaslist", LOG_LEVEL_ERROR)
  88. return
  89. for al in xmlelement.childNodes:
  90. if al.nodeType == al.ELEMENT_NODE:
  91. if al.hasAttribute("Alias"):
  92. aliasst = al.getAttribute("Alias")
  93. aliasnd = unicode(al.firstChild.data)
  94. if not self.aliases.has_key(aliasst):
  95. self.aliases[aliasst] = aliasnd
  96. log(self, "Added new alias \"" + str(aliasst) + "\" == \"" + str(aliasnd) + "\"")
  97. else:
  98. if self.aliases[aliasst] != aliasnd:
  99. log(self, "Alias definitions for " + aliasst + " differ. Have " + self.aliases[aliasst] + " but XML defines " + aliasnd + ". Keeping current definition.", LOG_LEVEL_ERROR)
  100. def getNodeByBrowseName(self, idstring):
  101. """ Returns the first node in the nodelist whose browseName matches idstring.
  102. """
  103. matches = []
  104. for n in self.nodes:
  105. if idstring==str(n.browseName()):
  106. matches.append(n)
  107. if len(matches) > 1:
  108. log(self, "Found multiple nodes with same ID!?", LOG_LEVEL_ERROR)
  109. if len(matches) == 0:
  110. return None
  111. else:
  112. return matches[0]
  113. def getNodeByIDString(self, idstring):
  114. """ Returns the first node in the nodelist whose id string representation
  115. matches idstring.
  116. """
  117. matches = []
  118. for n in self.nodes:
  119. if idstring==str(n.id()):
  120. matches.append(n)
  121. if len(matches) > 1:
  122. log(self, "Found multiple nodes with same ID!?", LOG_LEVEL_ERROR)
  123. if len(matches) == 0:
  124. return None
  125. else:
  126. return matches[0]
  127. def createNode(self, ndtype, xmlelement):
  128. """ createNode is instantiates a node described by xmlelement, its type being
  129. defined by the string ndtype.
  130. No return value
  131. If the xmlelement is an <Alias>, the contents will be parsed and stored
  132. for later dereferencing during pointer linking (see linkOpenPointers).
  133. Recognized types are:
  134. * UAVariable
  135. * UAObject
  136. * UAMethod
  137. * UAView
  138. * UAVariableType
  139. * UAObjectType
  140. * UAMethodType
  141. * UAReferenceType
  142. * UADataType
  143. For every recognized type, an appropriate node class is added to the node
  144. list of the namespace. The NodeId of the given node is created and parsing
  145. of the node attributes and elements is delegated to the parseXML() and
  146. parseXMLSubType() functions of the instantiated class.
  147. If the NodeID attribute is non-unique in the node list, the creation is
  148. deferred and an error is logged.
  149. """
  150. if not isinstance(xmlelement, dom.Element):
  151. log(self, "Error: Can not create node from invalid XMLElement", LOG_LEVEL_ERROR)
  152. return
  153. # An ID is manditory for everything but aliases!
  154. id = None
  155. for idname in ['NodeId', 'NodeID', 'nodeid']:
  156. if xmlelement.hasAttribute(idname):
  157. id = xmlelement.getAttribute(idname)
  158. if ndtype == 'aliases':
  159. self.buildAliasList(xmlelement)
  160. return
  161. elif id == None:
  162. log(self, "Error: XMLElement has no id, node will not be created!", LOG_LEVEL_INFO)
  163. return
  164. else:
  165. id = opcua_node_id_t(id)
  166. if self.nodeids.has_key(str(id)):
  167. log(self, "Error: XMLElement with duplicate ID " + str(id) + " found, node will not be created!", LOG_LEVEL_ERROR)
  168. return
  169. node = None
  170. if (ndtype == 'variable'):
  171. node = opcua_node_variable_t(id, self)
  172. elif (ndtype == 'object'):
  173. node = opcua_node_object_t(id, self)
  174. elif (ndtype == 'method'):
  175. node = opcua_node_method_t(id, self)
  176. elif (ndtype == 'objecttype'):
  177. node = opcua_node_objectType_t(id, self)
  178. elif (ndtype == 'variabletype'):
  179. node = opcua_node_variableType_t(id, self)
  180. elif (ndtype == 'methodtype'):
  181. node = opcua_node_methodType_t(id, self)
  182. elif (ndtype == 'datatype'):
  183. node = opcua_node_dataType_t(id, self)
  184. elif (ndtype == 'referencetype'):
  185. node = opcua_node_referenceType_t(id, self)
  186. else:
  187. log(self, "No node constructor for type " + ndtype, LOG_LEVEL_ERROR)
  188. if node != None:
  189. node.parseXML(xmlelement)
  190. self.nodes.append(node)
  191. self.nodeids[str(node.id())] = node
  192. def registerBinaryIndirectPointer(self, node):
  193. """ Appends a node to the list of nodes that should be contained in the
  194. first 765 bytes (255 pointer slots a 3 bytes) in the binary
  195. representation (indirect referencing space).
  196. This function is reserved for references and dataType pointers.
  197. """
  198. if not node in self.__binaryIndirectPointers__:
  199. self.__binaryIndirectPointers__.append(node)
  200. return self.__binaryIndirectPointers__.index(node)
  201. def getBinaryIndirectPointerIndex(self, node):
  202. """ Returns the slot/index of a pointer in the indirect referencing space
  203. (first 765 Bytes) of the binary representation.
  204. """
  205. if not node in self.__binaryIndirectPointers__:
  206. return -1
  207. return self.__binaryIndirectPointers__.index(node)
  208. def parseXML(self, xmldoc):
  209. """ Reads an XML Namespace definition and instantiates node.
  210. No return value
  211. parseXML open the file xmldoc using xml.dom.minidom and searches for
  212. the first UANodeSet Element. For every Element encountered, createNode
  213. is called to instantiate a node of the appropriate type.
  214. """
  215. typedict = {}
  216. UANodeSet = dom.parse(xmldoc).getElementsByTagName("UANodeSet")
  217. if len(UANodeSet) == 0:
  218. log(self, "Error: No NodeSets found", LOG_LEVEL_ERROR)
  219. return
  220. if len(UANodeSet) != 1:
  221. log(self, "Error: Found more than 1 Nodeset in XML File", LOG_LEVEL_ERROR)
  222. UANodeSet = UANodeSet[0]
  223. for nd in UANodeSet.childNodes:
  224. if nd.nodeType != nd.ELEMENT_NODE:
  225. continue
  226. ndType = nd.tagName.lower()
  227. if ndType[:2] == "ua":
  228. ndType = ndType[2:]
  229. elif not ndType in self.knownNodeTypes:
  230. log(self, "XML Element or NodeType " + ndType + " is unknown and will be ignored", LOG_LEVEL_WARN)
  231. continue
  232. if not typedict.has_key(ndType):
  233. typedict[ndType] = 1
  234. else:
  235. typedict[ndType] = typedict[ndType] + 1
  236. self.createNode(ndType, nd)
  237. log(self, "Currently " + str(len(self.nodes)) + " nodes in address space. Type distribution for this run was: " + str(typedict))
  238. def linkOpenPointers(self):
  239. """ Substitutes symbolic NodeIds in references for actual node instances.
  240. No return value
  241. References that have registered themselves with linkLater() to have
  242. their symbolic NodeId targets ("ns=2; i=32") substited for an actual
  243. node will be iterated by this function. For each reference encountered
  244. in the list of unlinked/open references, the target string will be
  245. evaluated and searched for in the node list of this namespace. If found,
  246. the target attribute of the reference will be substituted for the
  247. found node.
  248. If a reference fails to get linked, it will remain in the list of
  249. unlinked references. The individual items in this list can be
  250. retrieved using getUnlinkedPointers().
  251. """
  252. linked = []
  253. log(self, str(self.unlinkedItemCount()) + " pointers need to get linked.")
  254. for l in self.__linkLater__:
  255. targetLinked = False
  256. if not l.target() == None and not isinstance(l.target(), opcua_node_t):
  257. if isinstance(l.target(),str) or isinstance(l.target(),unicode):
  258. # If is not a node ID, it should be an alias. Try replacing it
  259. # with a proper node ID
  260. if l.target() in self.aliases:
  261. l.target(self.aliases[l.target()])
  262. # If the link is a node ID, try to find it hopening that no ass has
  263. # defined more than one kind of id for that sucker
  264. if l.target()[:2] == "i=" or l.target()[:2] == "g=" or \
  265. l.target()[:2] == "b=" or l.target()[:2] == "s=" or \
  266. l.target()[:3] == "ns=" :
  267. tgt = self.getNodeByIDString(str(l.target()))
  268. if tgt == None:
  269. log(self, "Failed to link pointer to target (node not found) " + l.target(), LOG_LEVEL_ERROR)
  270. else:
  271. l.target(tgt)
  272. targetLinked = True
  273. else:
  274. log(self, "Failed to link pointer to target (target not Alias or Node) " + l.target(), LOG_LEVEL_ERROR)
  275. else:
  276. log(self, "Failed to link pointer to target (don't know dummy type + " + str(type(l.target())) + " +) " + str(l.target()), LOG_LEVEL_ERROR)
  277. else:
  278. log(self, "Pointer has null target: " + str(l), LOG_LEVEL_ERROR)
  279. referenceLinked = False
  280. if not l.referenceType() == None:
  281. if l.referenceType() in self.aliases:
  282. l.referenceType(self.aliases[l.referenceType()])
  283. if l.referenceType()[:2] == "i=" or l.referenceType()[:2] == "g=" or \
  284. l.referenceType()[:2] == "b=" or l.referenceType()[:2] == "s=":
  285. tgt = self.getNodeByIDString(str(l.referenceType()))
  286. if tgt == None:
  287. log(self, "Failed to link reference type to target (node not found) " + l.referenceType(), LOG_LEVEL_ERROR)
  288. else:
  289. l.referenceType(tgt)
  290. referenceLinked = True
  291. else:
  292. referenceLinked = True
  293. if referenceLinked == True and targetLinked == True:
  294. linked.append(l)
  295. # References marked as "not forward" must be inverted (removed from source node, assigned to target node and relinked)
  296. log(self, "Inverting reference direction for all references with isForward==False attribute (is this correct!?)", LOG_LEVEL_WARN)
  297. for n in self.nodes:
  298. for r in n.getReferences():
  299. if r.isForward() == False:
  300. tgt = r.target()
  301. if isinstance(tgt, opcua_node_t):
  302. nref = opcua_referencePointer_t(n, parentNode=tgt)
  303. nref.referenceType(r.referenceType())
  304. tgt.addReference(nref)
  305. # Create inverse references for all nodes
  306. log(self, "Updating all referencedBy fields in nodes for inverse lookups.")
  307. for n in self.nodes:
  308. n.updateInverseReferences()
  309. for l in linked:
  310. self.__linkLater__.remove(l)
  311. if len(self.__linkLater__) != 0:
  312. log(self, str(len(self.__linkLater__)) + " could not be linked.", LOG_LEVEL_WARN)
  313. def sanitize(self):
  314. remove = []
  315. log(self, "Sanitizing nodes and references...")
  316. for n in self.nodes:
  317. if n.sanitize() == False:
  318. remove.append(n)
  319. if not len(remove) == 0:
  320. log(self, str(len(remove)) + " nodes will be removed because they failed sanitation.", LOG_LEVEL_WARN)
  321. # FIXME: Some variable ns=0 nodes fail because they don't have DataType fields...
  322. # How should this be handles!?
  323. log(self, "Not actually removing nodes... it's unclear if this is valid or not", LOG_LEVEL_WARN)
  324. def getRoot(self):
  325. """ Returns the first node instance with the browseName "Root".
  326. """
  327. return self.getNodeByBrowseName("Root")
  328. def buildEncodingRules(self):
  329. """ Calls buildEncoding() for all DataType nodes (opcua_node_dataType_t).
  330. No return value
  331. """
  332. stat = {True: 0, False: 0}
  333. for n in self.nodes:
  334. if isinstance(n, opcua_node_dataType_t):
  335. n.buildEncoding()
  336. stat[n.isEncodable()] = stat[n.isEncodable()] + 1
  337. log(self, "Type definitions built/passed: " + str(stat), LOG_LEVEL_DEBUG)
  338. def allocateVariables(self):
  339. for n in self.nodes:
  340. if isinstance(n, opcua_node_variable_t):
  341. n.allocateValue()
  342. def printDot(self, filename="namespace.dot"):
  343. """ Outputs a graphiz/dot description of all nodes in the namespace.
  344. Output will written into filename to be parsed by dot/neato...
  345. Note that for namespaces with more then 20 nodes the reference structure
  346. will lead to a mostly illegible and huge graph. Use printDotGraphWalk()
  347. for plotting specific portions of a large namespace.
  348. """
  349. file=open(filename, 'w+')
  350. file.write("digraph ns {\n")
  351. for n in self.nodes:
  352. file.write(n.printDot())
  353. file.write("}\n")
  354. file.close()
  355. def printDotGraphWalk(self, depth=1, filename="out.dot", rootNode=None, followInverse = False, excludeNodeIds=[]):
  356. """ Outputs a graphiz/dot description the nodes centered around rootNode.
  357. References beginning from rootNode will be followed for depth steps. If
  358. "followInverse = True" is passed, then inverse (not Forward) references
  359. will also be followed.
  360. Nodes can be excluded from the graph by passing a list of NodeIds as
  361. string representation using excludeNodeIds (ex ["i=53", "ns=2;i=453"]).
  362. Output is written into filename to be parsed by dot/neato/srfp...
  363. """
  364. iter = depth
  365. processed = []
  366. if rootNode == None or \
  367. not isinstance(rootNode, opcua_node_t) or \
  368. not rootNode in self.nodes:
  369. root = self.getRoot()
  370. else:
  371. root = rootNode
  372. file=open(filename, 'w+')
  373. if root == None:
  374. return
  375. file.write("digraph ns {\n")
  376. file.write(root.printDot())
  377. refs=[]
  378. if followInverse == True:
  379. refs = root.getReferences(); # + root.getInverseReferences()
  380. else:
  381. for ref in root.getReferences():
  382. if ref.isForward():
  383. refs.append(ref)
  384. while iter > 0:
  385. tmp = []
  386. for ref in refs:
  387. if isinstance(ref.target(), opcua_node_t):
  388. tgt = ref.target()
  389. if not str(tgt.id()) in excludeNodeIds:
  390. if not tgt in processed:
  391. file.write(tgt.printDot())
  392. processed.append(tgt)
  393. if ref.isForward() == False and followInverse == True:
  394. tmp = tmp + tgt.getReferences(); # + tgt.getInverseReferences()
  395. elif ref.isForward() == True :
  396. tmp = tmp + tgt.getReferences();
  397. refs = tmp
  398. iter = iter - 1
  399. file.write("}\n")
  400. file.close()
  401. def printOpen62541Header(self):
  402. unPrintedNodes = []
  403. unPrintedRefs = []
  404. code = []
  405. # Some macros (UA_EXPANDEDNODEID_MACRO()...) are easily created, but
  406. # bulky. This class will help to offload some code.
  407. codegen = open62541_MacroHelper()
  408. # Populate the unPrinted-Lists with everything we have.
  409. # Every Time a nodes printfunction is called, it will pop itself and
  410. # all printed references from these lists.
  411. unPrintedNodes = unPrintedNodes + self.nodes
  412. for n in self.nodes:
  413. for r in n.getReferences():
  414. if (r.target() != None) and (r.target().id() != None) and (r.parent() != None):
  415. unPrintedRefs.append(r)
  416. log(self, str(len(unPrintedNodes)) + " Nodes, " + str(len(unPrintedRefs)) + "References need to get printed.", LOG_LEVEL_DEBUG)
  417. code.append("/* WARNING: This is a generated file.\n * Any manual changes will be overwritten.\n */")
  418. # Find all references necessary to create the namespace and
  419. # "Bootstrap" them so all other nodes can safely use these referencetypes whenever
  420. # they can locate both source and target of the reference.
  421. log(self, "Collecting all references used in the namespace.", LOG_LEVEL_DEBUG)
  422. refsUsed = []
  423. for n in self.nodes:
  424. for r in n.getReferences():
  425. if not r.referenceType() in refsUsed:
  426. refsUsed.append(r.referenceType())
  427. log(self, str(len(refsUsed)) + " reference types are used in the namespace, which will now get bootstrapped.", LOG_LEVEL_DEBUG)
  428. for r in refsUsed:
  429. code = code + r.printOpen62541CCode(unPrintedNodes, unPrintedRefs);
  430. # Note to self: do NOT - NOT! - try to iterate over unPrintedNodes!
  431. # Nodes remove themselves from this list when printed.
  432. log(self, "Printing all other nodes.", LOG_LEVEL_DEBUG)
  433. for n in self.nodes:
  434. code = code + n.printOpen62541CCode(unPrintedNodes, unPrintedRefs)
  435. if len(unPrintedNodes) != 0:
  436. log(self, "" + str(len(unPrintedNodes)) + " nodes could not be translated to code.", LOG_LEVEL_WARN)
  437. else:
  438. log(self, "Printing suceeded for all nodes", LOG_LEVEL_DEBUG)
  439. if len(unPrintedRefs) != 0:
  440. log(self, "Attempting to print " + str(len(unPrintedRefs)) + " unprinted references.", LOG_LEVEL_DEBUG)
  441. tmprefs = []
  442. for r in unPrintedRefs:
  443. if not (r.target() not in unPrintedNodes) and not (r.parent() in unPrintedNodes):
  444. if not isinstance(r.parent(), opcua_node_t):
  445. log(self, "Reference has no parent!", LOG_LEVEL_DEBUG)
  446. elif not isinstance(r.parent().id(), opcua_node_id_t):
  447. log(self, "Parents nodeid is not a nodeID!", LOG_LEVEL_DEBUG)
  448. else:
  449. if (len(tmprefs) == 0):
  450. code.append("// Creating leftover references:")
  451. code = code + codegen.getCreateStandaloneReference(r.parent(), r)
  452. code.append("")
  453. tmprefs.append(r)
  454. # Remove printed refs from list
  455. for r in tmprefs:
  456. unPrintedRefs.remove(r)
  457. if len(unPrintedRefs) != 0:
  458. log(self, "" + str(len(unPrintedRefs)) + " references could not be translated to code.", LOG_LEVEL_WARN)
  459. else:
  460. log(self, "Printing succeeded for all references", LOG_LEVEL_DEBUG)
  461. #code.append("}")
  462. return code
  463. ###
  464. ### Testing
  465. ###
  466. class testing:
  467. def __init__(self):
  468. self.namespace = opcua_namespace("testing")
  469. log(self, "Phase 1: Reading XML file nodessets")
  470. self.namespace.parseXML("Opc.Ua.NodeSet2.xml")
  471. #self.namespace.parseXML("Opc.Ua.NodeSet2.Part4.xml")
  472. #self.namespace.parseXML("Opc.Ua.NodeSet2.Part5.xml")
  473. #self.namespace.parseXML("Opc.Ua.SimulationNodeSet2.xml")
  474. log(self, "Phase 2: Linking address space references and datatypes")
  475. self.namespace.linkOpenPointers()
  476. self.namespace.sanitize()
  477. log(self, "Phase 3: Comprehending DataType encoding rules")
  478. self.namespace.buildEncodingRules()
  479. log(self, "Phase 4: Allocating variable value data")
  480. self.namespace.allocateVariables()
  481. bin = self.namespace.buildBinary()
  482. f = open("binary.base64","w+")
  483. f.write(bin.encode("base64"))
  484. f.close()
  485. allnodes = self.namespace.nodes;
  486. ns = [self.namespace.getRoot()]
  487. i = 0
  488. print "Starting depth search on " + str(len(allnodes)) + " nodes starting with from " + str(ns)
  489. while (len(ns) < len(allnodes)):
  490. i = i + 1;
  491. tmp = [];
  492. print "Iteration: " + str(i)
  493. for n in ns:
  494. tmp.append(n)
  495. for r in n.getReferences():
  496. if (not r.target() in tmp):
  497. tmp.append(r.target())
  498. print "...tmp, " + str(len(tmp)) + " nodes discovered"
  499. ns = []
  500. for n in tmp:
  501. ns.append(n)
  502. print "...done, " + str(len(ns)) + " nodes discovered"
  503. log(self, "Phase 5: Printing pretty graph")
  504. self.namespace.printDotGraphWalk(depth=1, rootNode=self.namespace.getNodeByIDString("i=84"), followInverse=False, excludeNodeIds=["i=29", "i=22", "i=25"])
  505. #self.namespace.printDot()
  506. class testing_open62541_header:
  507. def __init__(self):
  508. self.namespace = opcua_namespace("testing")
  509. log(self, "Phase 1: Reading XML file nodessets")
  510. self.namespace.parseXML("Opc.Ua.NodeSet2.xml")
  511. #self.namespace.parseXML("Opc.Ua.NodeSet2.Part4.xml")
  512. #self.namespace.parseXML("Opc.Ua.NodeSet2.Part5.xml")
  513. #self.namespace.parseXML("Opc.Ua.SimulationNodeSet2.xml")
  514. log(self, "Phase 2: Linking address space references and datatypes")
  515. self.namespace.linkOpenPointers()
  516. self.namespace.sanitize()
  517. log(self, "Phase 3: Calling C Printers")
  518. code = self.namespace.printOpen62541Header()
  519. codeout = open("./open62541_namespace.c", "w+")
  520. for line in code:
  521. codeout.write(line + "\n")
  522. codeout.close()
  523. return
  524. # Call testing routine if invoked standalone.
  525. # For better debugging, it is advised to import this file using an interactive
  526. # python shell and instantiating a namespace.
  527. #
  528. # import ua_types.py as ua; ns=ua.testing().namespace
  529. if __name__ == '__main__':
  530. tst = testing_open62541_header()