ua_namespace.py 26 KB

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