ua_node_types.py 66 KB

12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394959697989910010110210310410510610710810911011111211311411511611711811912012112212312412512612712812913013113213313413513613713813914014114214314414514614714814915015115215315415515615715815916016116216316416516616716816917017117217317417517617717817918018118218318418518618718818919019119219319419519619719819920020120220320420520620720820921021121221321421521621721821922022122222322422522622722822923023123223323423523623723823924024124224324424524624724824925025125225325425525625725825926026126226326426526626726826927027127227327427527627727827928028128228328428528628728828929029129229329429529629729829930030130230330430530630730830931031131231331431531631731831932032132232332432532632732832933033133233333433533633733833934034134234334434534634734834935035135235335435535635735835936036136236336436536636736836937037137237337437537637737837938038138238338438538638738838939039139239339439539639739839940040140240340440540640740840941041141241341441541641741841942042142242342442542642742842943043143243343443543643743843944044144244344444544644744844945045145245345445545645745845946046146246346446546646746846947047147247347447547647747847948048148248348448548648748848949049149249349449549649749849950050150250350450550650750850951051151251351451551651751851952052152252352452552652752852953053153253353453553653753853954054154254354454554654754854955055155255355455555655755855956056156256356456556656756856957057157257357457557657757857958058158258358458558658758858959059159259359459559659759859960060160260360460560660760860961061161261361461561661761861962062162262362462562662762862963063163263363463563663763863964064164264364464564664764864965065165265365465565665765865966066166266366466566666766866967067167267367467567667767867968068168268368468568668768868969069169269369469569669769869970070170270370470570670770870971071171271371471571671771871972072172272372472572672772872973073173273373473573673773873974074174274374474574674774874975075175275375475575675775875976076176276376476576676776876977077177277377477577677777877978078178278378478578678778878979079179279379479579679779879980080180280380480580680780880981081181281381481581681781881982082182282382482582682782882983083183283383483583683783883984084184284384484584684784884985085185285385485585685785885986086186286386486586686786886987087187287387487587687787887988088188288388488588688788888989089189289389489589689789889990090190290390490590690790890991091191291391491591691791891992092192292392492592692792892993093193293393493593693793893994094194294394494594694794894995095195295395495595695795895996096196296396496596696796896997097197297397497597697797897998098198298398498598698798898999099199299399499599699799899910001001100210031004100510061007100810091010101110121013101410151016101710181019102010211022102310241025102610271028102910301031103210331034103510361037103810391040104110421043104410451046104710481049105010511052105310541055105610571058105910601061106210631064106510661067106810691070107110721073107410751076107710781079108010811082108310841085108610871088108910901091109210931094109510961097109810991100110111021103110411051106110711081109111011111112111311141115111611171118111911201121112211231124112511261127112811291130113111321133113411351136113711381139114011411142114311441145114611471148114911501151115211531154115511561157115811591160116111621163116411651166116711681169117011711172117311741175117611771178117911801181118211831184118511861187118811891190119111921193119411951196119711981199120012011202120312041205120612071208120912101211121212131214121512161217121812191220122112221223122412251226122712281229123012311232123312341235123612371238123912401241124212431244124512461247124812491250125112521253125412551256125712581259126012611262126312641265126612671268126912701271127212731274127512761277127812791280128112821283128412851286128712881289129012911292129312941295129612971298129913001301130213031304130513061307130813091310131113121313131413151316131713181319132013211322132313241325132613271328132913301331133213331334133513361337133813391340134113421343134413451346134713481349135013511352135313541355135613571358135913601361136213631364136513661367136813691370137113721373137413751376137713781379138013811382138313841385138613871388138913901391139213931394139513961397139813991400140114021403140414051406140714081409141014111412141314141415141614171418141914201421142214231424142514261427142814291430143114321433143414351436143714381439144014411442144314441445144614471448144914501451145214531454145514561457145814591460146114621463146414651466146714681469147014711472147314741475147614771478147914801481148214831484148514861487148814891490149114921493149414951496149714981499150015011502150315041505150615071508150915101511151215131514151515161517151815191520152115221523152415251526152715281529153015311532153315341535153615371538153915401541154215431544154515461547154815491550155115521553155415551556155715581559156015611562156315641565156615671568156915701571157215731574157515761577157815791580158115821583158415851586158715881589159015911592159315941595159615971598159916001601160216031604160516061607160816091610161116121613161416151616161716181619162016211622162316241625162616271628162916301631163216331634163516361637163816391640164116421643164416451646164716481649165016511652165316541655165616571658165916601661166216631664166516661667166816691670167116721673167416751676167716781679168016811682168316841685168616871688168916901691169216931694169516961697169816991700170117021703170417051706170717081709171017111712171317141715171617171718171917201721172217231724172517261727172817291730173117321733173417351736173717381739174017411742174317441745174617471748174917501751175217531754175517561757175817591760176117621763176417651766176717681769177017711772177317741775177617771778177917801781
  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 MPL v2 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. import sys
  21. import logging
  22. from ua_builtin_types import *;
  23. from open62541_MacroHelper import open62541_MacroHelper
  24. from ua_constants import *
  25. logger = logging.getLogger(__name__)
  26. def getNextElementNode(xmlvalue):
  27. if xmlvalue == None:
  28. return None
  29. xmlvalue = xmlvalue.nextSibling
  30. while not xmlvalue == None and not xmlvalue.nodeType == xmlvalue.ELEMENT_NODE:
  31. xmlvalue = xmlvalue.nextSibling
  32. return xmlvalue
  33. ###
  34. ### References are not really described by OPC-UA. This is how we
  35. ### use them here.
  36. ###
  37. class opcua_referencePointer_t():
  38. """ Representation of a pointer.
  39. A pointer consists of a target (which should be a node class),
  40. an optional reference type (which should be an instance of
  41. opcua_node_referenceType_t) and an optional isForward flag.
  42. """
  43. __reference_type__ = None
  44. __target__ = None
  45. __isForward__ = True
  46. __addr__ = 0
  47. __parentNode__ = None
  48. def __init__(self, target, hidden=False, parentNode=None):
  49. self.__target__ = target
  50. self.__reference_type__ = None
  51. self.__isForward__ = True
  52. self.__isHidden__ = hidden
  53. self.__parentNode__ = parentNode
  54. self.__addr__ = 0
  55. def isHidden(self, data=None):
  56. if isinstance(data, bool):
  57. self.__isHidden__ = data
  58. return self.__isHidden__
  59. def isForward(self, data=None):
  60. if isinstance(data, bool):
  61. self.__isForward__ = data
  62. return self.__isForward__
  63. def referenceType(self, type=None):
  64. if not type == None:
  65. self.__reference_type__ = type
  66. return self.__reference_type__
  67. def target(self, data=None):
  68. if not data == None:
  69. self.__target__ = data
  70. return self.__target__
  71. def address(self, data=None):
  72. if data != None:
  73. self.__addr__ = data
  74. return self.__addr__
  75. def parent(self):
  76. return self.__parentNode__
  77. def getCodePrintableID(self):
  78. src = "None"
  79. tgt = "None"
  80. type = "Unknown"
  81. if self.parent() != None:
  82. src = str(self.parent().id())
  83. if self.target() != None:
  84. tgt = str(self.target().id())
  85. if self.referenceType() != None:
  86. type = str(self.referenceType().id())
  87. tmp = src+"_"+type+"_"+tgt
  88. tmp = tmp.lower()
  89. refid = ""
  90. for i in tmp:
  91. if not i in "ABCDEFGHIJKLMOPQRSTUVWXYZ0123456789".lower():
  92. refid = refid + ("_")
  93. else:
  94. refid = refid + i
  95. return refid
  96. def __str__(self):
  97. retval=""
  98. if isinstance(self.parent(), opcua_node_t):
  99. if isinstance(self.parent().id(), opcua_node_id_t):
  100. retval=retval + str(self.parent().id()) + "--["
  101. else:
  102. retval=retval + "(?) --["
  103. else:
  104. retval=retval + "(?) --["
  105. if isinstance(self.referenceType(), opcua_node_t):
  106. retval=retval + str(self.referenceType().browseName()) + "]-->"
  107. else:
  108. retval=retval + "?]-->"
  109. if isinstance(self.target(), opcua_node_t):
  110. if isinstance(self.target().id(), opcua_node_id_t):
  111. retval=retval + str(self.target().id())
  112. else:
  113. retval=retval + "(?) "
  114. else:
  115. retval=retval + "(?) "
  116. if self.isForward() or self.isHidden():
  117. retval = retval + " <"
  118. if self.isForward():
  119. retval = retval + "F"
  120. if self.isHidden():
  121. retval = retval + "H"
  122. retval = retval + ">"
  123. return retval
  124. def __repr__(self):
  125. return self.__str__()
  126. def __cmp__(self, other):
  127. if not isinstance(other, opcua_referencePointer_t):
  128. return -1
  129. if other.target() == self.target():
  130. if other.referenceType() == self.referenceType():
  131. if other.isForward() == self.isForward():
  132. return 0
  133. return 1
  134. ###
  135. ### Node ID's as a builtin type are useless. using this one instead.
  136. ###
  137. class opcua_node_id_t():
  138. """ Implementation of a node ID.
  139. The ID will encoding itself appropriatly as string. If multiple ID's (numeric, string, guid)
  140. are defined, the order of preference for the ID string is always numeric, guid,
  141. bytestring, string. Binary encoding only applies to numeric values (UInt16).
  142. """
  143. i = -1
  144. o = ""
  145. g = ""
  146. s = ""
  147. ns = 0
  148. __mystrname__ = ""
  149. def __init__(self, idstring):
  150. idparts = idstring.split(";")
  151. self.i = None
  152. self.b = None
  153. self.g = None
  154. self.s = None
  155. self.ns = 0
  156. for p in idparts:
  157. if p[:2] == "ns":
  158. self.ns = int(p[3:])
  159. elif p[:2] == "i=":
  160. self.i = int(p[2:])
  161. elif p[:2] == "o=":
  162. self.b = p[2:]
  163. elif p[:2] == "g=":
  164. tmp = []
  165. self.g = p[2:].split("-")
  166. for i in self.g:
  167. i = "0x"+i
  168. tmp.append(int(i,16))
  169. self.g = tmp
  170. elif p[:2] == "s=":
  171. self.s = p[2:]
  172. self.__mystrname__ = ""
  173. self.toString()
  174. def toString(self):
  175. self.__mystrname__ = ""
  176. if self.ns != 0:
  177. self.__mystrname__ = "ns="+str(self.ns)+";"
  178. # Order of preference is numeric, guid, bytestring, string
  179. if self.i != None:
  180. self.__mystrname__ = self.__mystrname__ + "i="+str(self.i)
  181. elif self.g != None:
  182. self.__mystrname__ = self.__mystrname__ + "g="
  183. tmp = []
  184. for i in self.g:
  185. tmp.append(hex(i).replace("0x",""))
  186. for i in tmp:
  187. self.__mystrname__ = self.__mystrname__ + "-" + i
  188. self.__mystrname__ = self.__mystrname__.replace("g=-","g=")
  189. elif self.b != None:
  190. self.__mystrname__ = self.__mystrname__ + "b="+str(self.b)
  191. elif self.s != None:
  192. self.__mystrname__ = self.__mystrname__ + "s="+str(self.s)
  193. def __str__(self):
  194. return self.__mystrname__
  195. def __eq__(self, nodeId2):
  196. if not nodeId2:
  197. return False
  198. return (self.toString() == nodeId2.toString())
  199. def __repr__(self):
  200. return self.__mystrname__
  201. ###
  202. ### Actually existing node types
  203. ###
  204. class opcua_node_t:
  205. __node_id__ = None
  206. __node_class__ = 0
  207. __node_browseName__ = ""
  208. __node_displayName__ = ""
  209. __node_description__ = ""
  210. __node_writeMask__ = 0
  211. __node_userWriteMask__ = 0
  212. __node_namespace__ = None
  213. __node_references__ = []
  214. __node_referencedBy__ = []
  215. __binary__ = ""
  216. __address__ = 0
  217. def __init__(self, id, ns):
  218. self.__node_namespace__ = ns
  219. self.__node_id__ = id
  220. self.__node_class__ = 0
  221. self.__node_browseName__ = ""
  222. self.__node_displayName__ = ""
  223. self.__node_description__ = ""
  224. self.__node_writeMask__ = 0
  225. self.__node_userWriteMask__ = 0
  226. self.__node_references__ = []
  227. self.__node_referencedBy__ = []
  228. self.__init_subType__()
  229. self.FLAG_ISABSTRACT = 128
  230. self.FLAG_SYMMETRIC = 64
  231. self.FLAG_CONTAINSNOLOOPS = 32
  232. self.FLAG_EXECUTABLE = 16
  233. self.FLAG_USEREXECUTABLE = 8
  234. self.FLAG_HISTORIZING = 4
  235. self.__binary__ = ""
  236. def __init_subType__(self):
  237. self.nodeClass(0)
  238. def __str__(self):
  239. if isinstance(self.id(), opcua_node_id_t):
  240. return self.__class__.__name__ + "(" + str(self.id()) + ")"
  241. return self.__class__.__name__ + "( no ID )"
  242. def __repr__(self):
  243. if isinstance(self.id(), opcua_node_id_t):
  244. return self.__class__.__name__ + "(" + str(self.id()) + ")"
  245. return self.__class__.__name__ + "( no ID )"
  246. def getCodePrintableID(self):
  247. CodePrintable="NODE_"
  248. if isinstance(self.id(), opcua_node_id_t):
  249. CodePrintable = self.__class__.__name__ + "_" + str(self.id())
  250. else:
  251. CodePrintable = self.__class__.__name__ + "_unknown_nid"
  252. CodePrintable = CodePrintable.lower()
  253. cleanPrintable = ""
  254. for i in range(0,len(CodePrintable)):
  255. if not CodePrintable[i] in "ABCDEFGHIJKLMNOPQRSTUVWXYZ1234567890_".lower():
  256. cleanPrintable = cleanPrintable + "_"
  257. else:
  258. cleanPrintable = cleanPrintable + CodePrintable[i]
  259. return cleanPrintable
  260. def addReference(self, ref):
  261. """ Add a opcua_referencePointer_t to the list of
  262. references this node carries.
  263. """
  264. if not ref in self.__node_references__:
  265. self.__node_references__.append(ref)
  266. def removeReference(self, ref):
  267. if ref in self.__node_references__:
  268. self.__node_references__.remove(ref)
  269. def removeReferenceToNode(self, targetNode):
  270. tmp = []
  271. if ref in self.__node_references__:
  272. if ref.target() != targetNode:
  273. tmp.append(ref)
  274. self.__node_references__ = tmp
  275. def addInverseReferenceTarget(self, node):
  276. """ Adds a reference to the inverse reference list of this node.
  277. Inverse references are considered as "this node is referenced by"
  278. and facilitate lookups when between nodes that reference this node,
  279. but are not referenced by this node. These references would
  280. require the namespace to be traversed by references to be found
  281. if this node was not aware of them.
  282. """
  283. # Only add this target if it is not already referenced
  284. if not node in self.__node_referencedBy__:
  285. if not self.hasReferenceTarget(node):
  286. self.__node_referencedBy__.append(opcua_referencePointer_t(node, hidden=True, parentNode=self))
  287. # logger.debug(self.__node_browseName__ + " added reverse reference to " + str(node.__node_browseName__))
  288. # else:
  289. # logger.debug(self.__node_browseName__ + " refusing reverse reference to " + str(node.__node_browseName__) + " (referenced normally)")
  290. # else:
  291. # logger.debug(self.__node_browseName__ + " refusing reverse reference to " + str(node.__node_browseName__) + " (already reversed referenced)")
  292. def getReferences(self):
  293. return self.__node_references__
  294. def getInverseReferences(self):
  295. return self.__node_referencedBy__
  296. def hasInverseReferenceTarget(self, node):
  297. for r in self.getInverseReferences():
  298. if node == r.target():
  299. return True
  300. return False
  301. def hasReferenceTarget(self, node):
  302. for r in self.getReferences():
  303. if node == r.target():
  304. return True
  305. return False
  306. def getFirstParentNode(self):
  307. """ getFirstParentNode
  308. return a tuple of (opcua_node_t, opcua_referencePointer_t) indicating
  309. the first node found that references this node. If this node is not
  310. referenced at all, None will be returned.
  311. This function requires a linked namespace.
  312. Note that there may be more than one nodes that reference this node.
  313. The parent returned will be determined by the first isInverse()
  314. Reference of this node found. If none exists, the first hidden
  315. reference will be returned.
  316. """
  317. parent = None
  318. revref = None
  319. for hiddenstatus in [False, True]:
  320. for r in self.getReferences():
  321. if r.isHidden() == hiddenstatus and r.isForward() == False:
  322. parent = r.target()
  323. for r in parent.getReferences():
  324. if r.target() == self:
  325. revref = r
  326. break
  327. if revref != None:
  328. return (parent, revref)
  329. return (parent, revref)
  330. def updateInverseReferences(self):
  331. """ Updates inverse references in all nodes referenced by this node.
  332. The function will look up all referenced nodes and check if they
  333. have a reference that points back at this node. If none is found,
  334. that means that the target is not aware that this node references
  335. it. In that case an inverse reference will be registered with
  336. the target node to point back to this node instance.
  337. """
  338. # Update inverse references in all nodes we have referenced
  339. for r in self.getReferences():
  340. if isinstance(r.target(), opcua_node_t):
  341. if not r.target().hasInverseReferenceTarget(self):
  342. #logger.debug(self.__node_browseName__ + " req. rev. referencing in" + str(r.target().__node_browseName__))
  343. r.target().addInverseReferenceTarget(self)
  344. #else:
  345. #logger.debug("Cannot register inverse link to " + str(r.target()) + " (not a node)")
  346. def id(self):
  347. return self.__node_id__
  348. def getNamespace(self):
  349. return self.__node_namespace__
  350. def nodeClass(self, c = 0):
  351. """ Sets the node class attribute if c is passed.
  352. Returns the current node class.
  353. """
  354. # Allow overwriting only if it is not set
  355. if isinstance(c, int):
  356. if self.__node_class__ == 0 and c < 256:
  357. self.__node_class__ = c
  358. return self.__node_class__
  359. def browseName(self, data=0):
  360. """ Sets the browse name attribute if data is passed.
  361. Returns the current browse name.
  362. """
  363. if isinstance(data, str):
  364. self.__node_browseName__ = data
  365. if sys.version_info[0] < 3:
  366. return self.__node_browseName__.encode('utf-8')
  367. return self.__node_browseName__
  368. def displayName(self, data=None):
  369. """ Sets the display name attribute if data is passed.
  370. Returns the current display name.
  371. """
  372. if data != None:
  373. self.__node_displayName__ = data
  374. return self.__node_displayName__.encode('utf-8')
  375. def description(self, data=None):
  376. """ Sets the description attribute if data is passed.
  377. Returns the current description.
  378. """
  379. if data != None:
  380. self.__node_description__ = data
  381. return self.__node_description__.encode('utf-8')
  382. def writeMask(self, data=None):
  383. """ Sets the write mask attribute if data is passed.
  384. Returns the current write mask.
  385. """
  386. if data != None:
  387. self.__node_writeMask__ = data
  388. return self.__node_writeMask__
  389. def userWriteMask(self, data=None):
  390. """ Sets the user write mask attribute if data is passed.
  391. Returns the current user write mask.
  392. """
  393. if data != None:
  394. self.__node_userWriteMask__ = data
  395. return self.__node_userWriteMask__
  396. def initiateDummyXMLReferences(self, xmlelement):
  397. """ Initiates references found in the XML <References> element.
  398. All references initiated will be registered with this node, but
  399. their targets will be strings extracted from the XML description
  400. (hence "dummy").
  401. References created will however be registered with the namespace
  402. for linkLater(), which will eventually replace the string target
  403. with an actual instance of an opcua_node_t.
  404. """
  405. if not xmlelement.tagName == "References":
  406. logger.error("XMLElement passed is not a reference list")
  407. return
  408. for ref in xmlelement.childNodes:
  409. if ref.nodeType == ref.ELEMENT_NODE:
  410. dummy = opcua_referencePointer_t(unicode(ref.firstChild.data), parentNode=self)
  411. self.addReference(dummy)
  412. self.getNamespace().linkLater(dummy)
  413. for (at, av) in ref.attributes.items():
  414. if at == "ReferenceType":
  415. dummy.referenceType(av)
  416. elif at == "IsForward":
  417. if "false" in av.lower():
  418. dummy.isForward(False)
  419. else:
  420. logger.error("Don't know how to process attribute " + at + "(" + av + ") for references.")
  421. def printDot(self):
  422. cleanname = "node_" + str(self.id()).replace(";","").replace("=","")
  423. dot = cleanname + " [label = \"{" + str(self.id()) + "|" + str(self.browseName()) + "}\", shape=\"record\"]"
  424. for r in self.__node_references__:
  425. if isinstance(r.target(), opcua_node_t):
  426. tgtname = "node_" + str(r.target().id()).replace(";","").replace("=","")
  427. dot = dot + "\n"
  428. if r.isForward() == True:
  429. dot = dot + cleanname + " -> " + tgtname + " [label=\"" + str(r.referenceType().browseName()) + "\"]\n"
  430. else:
  431. if len(r.referenceType().inverseName()) == 0:
  432. logger.warn("Inverse name of reference is null " + str(r.referenceType().id()))
  433. dot = dot + cleanname + " -> " + tgtname + " [label=\"" + str(r.referenceType().inverseName()) + "\"]\n"
  434. return dot
  435. def sanitize(self):
  436. """ Check the health of this node.
  437. Return True if all mandatory attributes are valid and all references have been
  438. correclty linked to nodes. Returns False on failure, which should indicate
  439. that this node needs to be removed from the namespace.
  440. """
  441. # Do we have an id?
  442. if not isinstance(self.id(), opcua_node_id_t):
  443. logger.error("HELP! I'm an id'less node!")
  444. return False
  445. # Remove unlinked references
  446. tmp = []
  447. for r in self.getReferences():
  448. if not isinstance(r, opcua_referencePointer_t):
  449. logger.error("Reference is not a reference!?.")
  450. elif not isinstance(r.referenceType(), opcua_node_t):
  451. logger.error("Reference has no valid reference type and will be removed.")
  452. elif not isinstance(r.target(), opcua_node_t):
  453. logger.warn("Reference to " + str(r.target()) + " is not a node. It has been removed.")
  454. else:
  455. tmp.append(r)
  456. self.__node_references__ = tmp
  457. # Make sure that every inverse referenced node actually does reference us
  458. tmp = []
  459. for r in self.getInverseReferences():
  460. if not isinstance(r.target(), opcua_node_t):
  461. logger.warn("Invers reference to " + str(r.target()) + " does not reference a real node. It has been removed.")
  462. else:
  463. if r.target().hasReferenceTarget(self):
  464. tmp.append(r)
  465. else:
  466. logger.warn("Node " + str(self.id()) + " was falsely under the impression that it is referenced by " + str(r.target().id()))
  467. self.__node_referencedBy__ = tmp
  468. # Remove references from inverse list if we can reach this not "the regular way"
  469. # over a normal reference
  470. tmp=[]
  471. for r in self.getInverseReferences():
  472. if not self.hasReferenceTarget(r.target()):
  473. tmp.append(r)
  474. else:
  475. logger.debug("Removing unnecessary inverse reference to " + str(r.target.id()))
  476. self.__node_referencedBy__ = tmp
  477. return self.sanitizeSubType()
  478. def sanitizeSubType(self):
  479. pass
  480. def address(self, addr = None):
  481. """ If addr is passed, the address of this node within the binary
  482. representation will be set.
  483. If an address within the binary representation is known/set, this
  484. function will return it. NoneType is returned if no address has been
  485. set.
  486. """
  487. if addr != None:
  488. self.__address__ = addr
  489. return self.__address__
  490. def parseXML(self, xmlelement):
  491. """ Extracts base attributes from the XML description of an element.
  492. Parsed basetype attributes are:
  493. * browseName
  494. * displayName
  495. * Description
  496. * writeMask
  497. * userWriteMask
  498. * eventNotifier
  499. ParentNodeIds are ignored.
  500. If recognized, attributes and elements found will be removed from
  501. the XML Element passed. Type-specific attributes and child elements
  502. are handled by the parseXMLSubType() functions of all classes deriving
  503. from this base type and will be called automatically.
  504. """
  505. thisxml = xmlelement
  506. for (at, av) in thisxml.attributes.items():
  507. if at == "NodeId":
  508. xmlelement.removeAttribute(at)
  509. elif at == "BrowseName":
  510. self.browseName(str(av))
  511. xmlelement.removeAttribute(at)
  512. elif at == "DisplayName":
  513. self.displayName(av)
  514. xmlelement.removeAttribute(at)
  515. elif at == "Description":
  516. self.description(av)
  517. xmlelement.removeAttribute(at)
  518. elif at == "WriteMask":
  519. self.writeMask(int(av))
  520. xmlelement.removeAttribute(at)
  521. elif at == "UserWriteMask":
  522. self.userWriteMask(int(av))
  523. xmlelement.removeAttribute(at)
  524. elif at == "EventNotifier":
  525. self.eventNotifier(int(av))
  526. xmlelement.removeAttribute(at)
  527. elif at == "ParentNodeId":
  528. # Silently ignore this one..
  529. xmlelement.removeAttribute(at)
  530. for x in thisxml.childNodes:
  531. if x.nodeType == x.ELEMENT_NODE:
  532. if x.firstChild:
  533. if x.tagName == "BrowseName":
  534. self.browseName(unicode(x.firstChild.data))
  535. xmlelement.removeChild(x)
  536. elif x.tagName == "DisplayName":
  537. self.displayName(unicode(x.firstChild.data))
  538. xmlelement.removeChild(x)
  539. elif x.tagName == "Description":
  540. self.description(unicode(x.firstChild.data))
  541. xmlelement.removeChild(x)
  542. elif x.tagName == "WriteMask":
  543. self.writeMask(int(unicode(x.firstChild.data)))
  544. xmlelement.removeChild(x)
  545. elif x.tagName == "UserWriteMask":
  546. self.userWriteMask(int(unicode(x.firstChild.data)))
  547. xmlelement.removeChild(x)
  548. if x.tagName == "References":
  549. self.initiateDummyXMLReferences(x)
  550. xmlelement.removeChild(x)
  551. self.parseXMLSubType(xmlelement)
  552. def parseXMLSubType(self, xmlelement):
  553. pass
  554. def printXML(self):
  555. pass
  556. def printOpen62541CCode_SubtypeEarly(self, bootstrapping = True):
  557. """ printOpen62541CCode_SubtypeEarly
  558. Initiate code segments for the nodes instantiotion that preceed
  559. the actual UA_Server_addNode or UA_NodeStore_insert calls.
  560. """
  561. return []
  562. def printOpen62541CCode_Subtype(self, unPrintedReferences=[], bootstrapping = True):
  563. """ printOpen62541CCode_Subtype
  564. Appends node type specific information to the nodes UA_Server_addNode
  565. or UA_NodeStore_insert calls.
  566. """
  567. return []
  568. def printOpen62541CCode(self, unPrintedNodes=[], unPrintedReferences=[], supressGenerationOfAttribute=[]):
  569. """ printOpen62541CCode
  570. Returns a list of strings containing the C-code necessary to intialize
  571. this node for the open62541 OPC-UA Stack.
  572. Note that this function will fail if the nodeid is non-numeric, as
  573. there is no UA_EXPANDEDNNODEID_[STRING|GUID|BYTESTRING] macro.
  574. """
  575. codegen = open62541_MacroHelper(supressGenerationOfAttribute=supressGenerationOfAttribute)
  576. code = []
  577. code.append("")
  578. code.append("do {")
  579. # Just to be sure...
  580. if not (self in unPrintedNodes):
  581. logger.warn(str(self) + " attempted to reprint already printed node " + str(self)+ ".")
  582. return []
  583. # If we are being passed a parent node by the namespace, use that for registering ourselves in the namespace
  584. # Note: getFirstParentNode will return [parentNode, referenceToChild]
  585. (parentNode, parentRef) = self.getFirstParentNode()
  586. if not (parentNode in unPrintedNodes) and (parentNode != None) and (parentRef.referenceType() != None):
  587. code.append("// Referencing node found and declared as parent: " + str(parentNode .id()) + "/" +
  588. str(parentNode .__node_browseName__) + " using " + str(parentRef.referenceType().id()) +
  589. "/" + str(parentRef.referenceType().__node_browseName__))
  590. code = code + codegen.getCreateNodeNoBootstrap(self, parentNode, parentRef, unPrintedNodes)
  591. # Parent to child reference is added by the server, do not reprint that reference
  592. if parentRef in unPrintedReferences:
  593. unPrintedReferences.remove(parentRef)
  594. # the UA_Server_addNode function will use addReference which creates a bidirectional reference; remove any inverse
  595. # references to our parent to avoid duplicate refs
  596. for ref in self.getReferences():
  597. if ref.target() == parentNode and ref.referenceType() == parentRef.referenceType() and ref.isForward() == False:
  598. while ref in unPrintedReferences:
  599. unPrintedReferences.remove(ref)
  600. # Otherwise use the "Bootstrapping" method and we will get registered with other nodes later.
  601. else:
  602. code = code + self.printOpen62541CCode_SubtypeEarly(bootstrapping = True)
  603. code = code + codegen.getCreateNodeBootstrap(self)
  604. code = code + self.printOpen62541CCode_Subtype(unPrintedReferences = unPrintedReferences, bootstrapping = True)
  605. code.append("// Parent node does not exist yet. This node will be bootstrapped and linked later.")
  606. code.append("UA_RCU_LOCK();")
  607. code.append("UA_NodeStore_insert(server->nodestore, (UA_Node*) " + self.getCodePrintableID() + ");")
  608. code.append("UA_RCU_UNLOCK();")
  609. # Try to print all references to nodes that already exist
  610. # Note: we know the reference types exist, because the namespace class made sure they were
  611. # the first ones being printed
  612. tmprefs = []
  613. for r in self.getReferences():
  614. #logger.debug("Checking if reference from " + str(r.parent()) + "can be created...")
  615. if not (r.target() in unPrintedNodes):
  616. if r in unPrintedReferences:
  617. if (len(tmprefs) == 0):
  618. code.append("// This node has the following references that can be created:")
  619. code = code + codegen.getCreateStandaloneReference(self, r)
  620. tmprefs.append(r)
  621. # Remove printed refs from list
  622. for r in tmprefs:
  623. unPrintedReferences.remove(r)
  624. # Again, but this time check if other nodes deffered their node creation because this node did
  625. # not exist...
  626. tmprefs = []
  627. for r in unPrintedReferences:
  628. #logger.debug("Checking if another reference " + str(r.target()) + "can be created...")
  629. if (r.target() == self) and not (r.parent() in unPrintedNodes):
  630. if not isinstance(r.parent(), opcua_node_t):
  631. logger.debug("Reference has no parent!")
  632. elif not isinstance(r.parent().id(), opcua_node_id_t):
  633. logger.debug("Parents nodeid is not a nodeID!")
  634. else:
  635. if (len(tmprefs) == 0):
  636. code.append("// Creating this node has resolved the following open references:")
  637. code = code + codegen.getCreateStandaloneReference(r.parent(), r)
  638. tmprefs.append(r)
  639. # Remove printed refs from list
  640. for r in tmprefs:
  641. unPrintedReferences.remove(r)
  642. # Again, just to be sure...
  643. if self in unPrintedNodes:
  644. # This is necessery to make printing work at all!
  645. unPrintedNodes.remove(self)
  646. code.append("} while(0);")
  647. return code
  648. class opcua_node_referenceType_t(opcua_node_t):
  649. __isAbstract__ = False
  650. __symmetric__ = False
  651. __reference_inverseName__ = ""
  652. __reference_referenceType__ = None
  653. def __init_subType__(self):
  654. self.nodeClass(NODE_CLASS_REFERENCETYPE)
  655. self.__reference_isAbstract__ = False
  656. self.__reference_symmetric__ = False
  657. self.__reference_inverseName__ = ""
  658. self.__reference_referenceType__ = None
  659. def referenceType(self,data=None):
  660. if isinstance(data, opcua_node_t):
  661. self.__reference_referenceType__ = data
  662. return self.__reference_referenceType__
  663. def isAbstract(self,data=None):
  664. if isinstance(data, bool):
  665. self.__isAbstract__ = data
  666. return self.__isAbstract__
  667. def symmetric(self,data=None):
  668. if isinstance(data, bool):
  669. self.__symmetric__ = data
  670. return self.__symmetric__
  671. def inverseName(self,data=None):
  672. if isinstance(data, str):
  673. self.__reference_inverseName__ = data
  674. return self.__reference_inverseName__
  675. def sanitizeSubType(self):
  676. if not isinstance(self.referenceType(), opcua_referencePointer_t):
  677. logger.error("ReferenceType " + str(self.referenceType()) + " of " + str(self.id()) + " is not a pointer (ReferenceType is mandatory for references).")
  678. self.__reference_referenceType__ = None
  679. return False
  680. return True
  681. def parseXMLSubType(self, xmlelement):
  682. for (at, av) in xmlelement.attributes.items():
  683. if at == "Symmetric":
  684. if "false" in av.lower():
  685. self.symmetric(False)
  686. else:
  687. self.symmetric(True)
  688. xmlelement.removeAttribute(at)
  689. elif at == "InverseName":
  690. self.inverseName(str(av))
  691. xmlelement.removeAttribute(at)
  692. elif at == "IsAbstract":
  693. if "false" in str(av).lower():
  694. self.isAbstract(False)
  695. else:
  696. self.isAbstract(True)
  697. xmlelement.removeAttribute(at)
  698. else:
  699. logger.warn("Don't know how to process attribute " + at + " (" + av + ")")
  700. for x in xmlelement.childNodes:
  701. if x.nodeType == x.ELEMENT_NODE:
  702. if x.tagName == "InverseName" and x.firstChild:
  703. self.inverseName(str(unicode(x.firstChild.data)))
  704. else:
  705. logger.warn( "Unprocessable XML Element: " + x.tagName)
  706. def printOpen62541CCode_Subtype(self, unPrintedReferences=[], bootstrapping = True):
  707. code = []
  708. codegen = open62541_MacroHelper()
  709. # Detect if this is bootstrapping or if we are attempting to use userspace...
  710. if bootstrapping == False:
  711. typeDefs = self.getNamespace().getSubTypesOf() # defaults to TypeDefinition
  712. myTypeRef = None
  713. for ref in self.getReferences():
  714. if ref.referenceType() in typeDefs:
  715. myTypeRef = ref
  716. break
  717. if myTypeRef==None:
  718. for ref in self.getReferences():
  719. if ref.referenceType().browseName() == "HasSubtype" and ref.isForward() == False:
  720. myTypeRef = ref
  721. break
  722. if myTypeRef==None:
  723. logger.warn(str(self) + " failed to locate a type definition, assuming BaseDataType.")
  724. code.append(" // No valid typeDefinition found; assuming BaseDataType")
  725. code.append(" UA_EXPANDEDNODEID_NUMERIC(0, UA_NS0ID_BASEDATATYPE),")
  726. else:
  727. code.append(" " + codegen.getCreateExpandedNodeIDMacro(myTypeRef.target()) + ",")
  728. while myTypeRef in unPrintedReferences:
  729. unPrintedReferences.remove(myTypeRef)
  730. code.append(" UA_LOCALIZEDTEXT(\"\",\"" + str(self.inverseName()) + "\"),");
  731. code.append(" // FIXME: Missing, isAbstract")
  732. code.append(" // FIXME: Missing, symmetric")
  733. return code
  734. if self.isAbstract():
  735. code.append(self.getCodePrintableID() + "->isAbstract = true;")
  736. if self.symmetric():
  737. code.append(self.getCodePrintableID() + "->symmetric = true;")
  738. if self.__reference_inverseName__ != "":
  739. code.append(self.getCodePrintableID() + "->inverseName = UA_LOCALIZEDTEXT_ALLOC(\"en_US\", \"" + self.__reference_inverseName__ + "\");")
  740. return code;
  741. class opcua_node_object_t(opcua_node_t):
  742. __object_eventNotifier__ = 0
  743. def __init_subType__(self):
  744. self.nodeClass(NODE_CLASS_OBJECT)
  745. self.__object_eventNotifier__ = 0
  746. def eventNotifier(self, data=""):
  747. if isinstance(data, int):
  748. self.__object_eventNotifier__ == data
  749. return self.__object_eventNotifier__
  750. def parseXMLSubType(self, xmlelement):
  751. for (at, av) in xmlelement.attributes.items():
  752. if at == "EventNotifier":
  753. self.eventNotifier(int(av))
  754. xmlelement.removeAttribute(at)
  755. elif at == "SymbolicName":
  756. # Silently ignore this one
  757. xmlelement.removeAttribute(at)
  758. else:
  759. logger.error("Don't know how to process attribute " + at + " (" + av + ")")
  760. for x in xmlelement.childNodes:
  761. if x.nodeType == x.ELEMENT_NODE:
  762. logger.info( "Unprocessable XML Element: " + x.tagName)
  763. def printOpen62541CCode_Subtype(self, unPrintedReferences=[], bootstrapping = True):
  764. code = []
  765. codegen = open62541_MacroHelper()
  766. # Detect if this is bootstrapping or if we are attempting to use userspace...
  767. if bootstrapping == False:
  768. typeDefs = self.getNamespace().getSubTypesOf() # defaults to TypeDefinition
  769. myTypeRef = None
  770. for ref in self.getReferences():
  771. if ref.referenceType() in typeDefs:
  772. myTypeRef = ref
  773. break
  774. if myTypeRef==None:
  775. for ref in self.getReferences():
  776. if ref.referenceType().browseName() == "HasSubtype" and ref.isForward() == False:
  777. myTypeRef = ref
  778. break
  779. if myTypeRef==None:
  780. logger.warn(str(self) + " failed to locate a type definition, assuming BaseObjectType.")
  781. code.append(" // No valid typeDefinition found; assuming BaseObjectType")
  782. code.append(" UA_EXPANDEDNODEID_NUMERIC(0, UA_NS0ID_BASEOBJECTTYPE),")
  783. else:
  784. code.append(" " + codegen.getCreateExpandedNodeIDMacro(myTypeRef.target()) + ",")
  785. while myTypeRef in unPrintedReferences:
  786. unPrintedReferences.remove(myTypeRef)
  787. #FIXME: No event notifier in UA_Server_addNode call!
  788. return code
  789. # We are being bootstrapped! Add the raw attributes to the node.
  790. code.append(self.getCodePrintableID() + "->eventNotifier = (UA_Byte) " + str(self.eventNotifier()) + ";")
  791. return code
  792. if sys.version_info[0] >= 3:
  793. # strings are already parsed to unicode
  794. def unicode(s):
  795. return s
  796. class opcua_node_variable_t(opcua_node_t):
  797. __value__ = 0
  798. __dataType__ = None
  799. __valueRank__ = 0
  800. __arrayDimensions__ = 0
  801. __accessLevel__ = 0
  802. __userAccessLevel__ = 0
  803. __minimumSamplingInterval__ = 0
  804. __historizing__ = False
  805. def __init_subType__(self):
  806. self.nodeClass(NODE_CLASS_VARIABLE)
  807. self.__value__ = None
  808. self.__dataType__ = None
  809. self.__valueRank__ = -1
  810. self.__arrayDimensions__ = []
  811. self.__accessLevel__ = 3
  812. self.__userAccessLevel__ = 3
  813. self.__minimumSamplingInterval__ = 0.0
  814. self.__historizing__ = False
  815. self.__xmlValueDef__ = None
  816. def value(self, data=0):
  817. if isinstance(data, opcua_value_t):
  818. self.__value__ = data
  819. return self.__value__
  820. def dataType(self, data=None):
  821. if data != None:
  822. self.__dataType__ = data
  823. return self.__dataType__
  824. def valueRank(self, data=""):
  825. if isinstance(data, int):
  826. self.__valueRank__ = data
  827. return self.__valueRank__
  828. def arrayDimensions(self, data=None):
  829. if not data==None:
  830. self.__arrayDimensions__ = data
  831. return self.__arrayDimensions__
  832. def accessLevel(self, data=None):
  833. if not data==None:
  834. self.__accessLevel__ = data
  835. return self.__accessLevel__
  836. def userAccessLevel(self, data=None):
  837. if not data==None:
  838. self.__userAccessLevel__ = data
  839. return self.__userAccessLevel__
  840. def minimumSamplingInterval(self, data=None):
  841. if not data==None:
  842. self.__minimumSamplingInterval__ = data
  843. return self.__minimumSamplingInterval__
  844. def historizing(self, data=None):
  845. if data != None:
  846. self.__historizing__ = data
  847. return self.__historizing__
  848. def sanitizeSubType(self):
  849. if not isinstance(self.dataType(), opcua_referencePointer_t):
  850. logger.error("DataType " + str(self.dataType()) + " of " + str(self.id()) + " is not a pointer (DataType is mandatory for variables).")
  851. self.__dataType__ = None
  852. return False
  853. if not isinstance(self.dataType().target(), opcua_node_t):
  854. logger.error("DataType " + str(self.dataType().target()) + " of " + str(self.id()) + " does not point to a node (DataType is mandatory for variables).")
  855. self.__dataType__ = None
  856. return False
  857. return True
  858. def allocateValue(self):
  859. if not isinstance(self.dataType(), opcua_referencePointer_t):
  860. logger.error("Variable " + self.browseName() + "/" + str(self.id()) + " does not reference a valid dataType.")
  861. return False
  862. if not isinstance(self.dataType().target(), opcua_node_dataType_t):
  863. logger.error("Variable " + self.browseName() + "/" + str(self.id()) + " does not have a valid dataType reference.")
  864. return False
  865. if not self.dataType().target().isEncodable():
  866. logger.error("DataType for Variable " + self.browseName() + "/" + str(self.id()) + " is not encodable.")
  867. return False
  868. # FIXME: Don't build at all or allocate "defaults"? I'm for not building at all.
  869. if self.__xmlValueDef__ == None:
  870. #logger.warn("Variable " + self.browseName() + "/" + str(self.id()) + " is not initialized. No memory will be allocated.")
  871. return False
  872. self.value(opcua_value_t(self))
  873. self.value().parseXML(self.__xmlValueDef__)
  874. # Array Dimensions must accurately represent the value and will be patched
  875. # reflect the exaxt dimensions attached binary stream.
  876. if not isinstance(self.value(), opcua_value_t) or len(self.value().value) == 0:
  877. self.arrayDimensions([])
  878. else:
  879. # Parser only permits 1-d arrays, which means we do not have to check further dimensions
  880. self.arrayDimensions([len(self.value().value)])
  881. return True
  882. def parseXMLSubType(self, xmlelement):
  883. for (at, av) in xmlelement.attributes.items():
  884. if at == "ValueRank":
  885. self.valueRank(int(av))
  886. xmlelement.removeAttribute(at)
  887. elif at == "AccessLevel":
  888. self.accessLevel(int(av))
  889. xmlelement.removeAttribute(at)
  890. elif at == "UserAccessLevel":
  891. self.userAccessLevel(int(av))
  892. xmlelement.removeAttribute(at)
  893. elif at == "MinimumSamplingInterval":
  894. self.minimumSamplingInterval(float(av))
  895. xmlelement.removeAttribute(at)
  896. elif at == "DataType":
  897. self.dataType(opcua_referencePointer_t(str(av), parentNode=self))
  898. # dataType needs to be linked to a node once the namespace is read
  899. self.getNamespace().linkLater(self.dataType())
  900. xmlelement.removeAttribute(at)
  901. elif at == "SymbolicName":
  902. # Silently ignore this one
  903. xmlelement.removeAttribute(at)
  904. else:
  905. logger.error("Don't know how to process attribute " + at + " (" + av + ")")
  906. for x in xmlelement.childNodes:
  907. if x.nodeType == x.ELEMENT_NODE:
  908. if x.tagName == "Value":
  909. # We need to be able to parse the DataType to build the variable value,
  910. # which can only be done if the namespace is linked.
  911. # Store the Value for later parsing
  912. self.__xmlValueDef__ = x
  913. #logger.debug( "Value description stored for later elaboration.")
  914. elif x.tagName == "DataType":
  915. self.dataType(opcua_referencePointer_t(str(av), parentNode=self))
  916. # dataType needs to be linked to a node once the namespace is read
  917. self.getNamespace().linkLater(self.dataType())
  918. elif x.tagName == "ValueRank":
  919. self.valueRank(int(unicode(x.firstChild.data)))
  920. elif x.tagName == "ArrayDimensions":
  921. self.arrayDimensions(int(unicode(x.firstChild.data)))
  922. elif x.tagName == "AccessLevel":
  923. self.accessLevel(int(unicode(x.firstChild.data)))
  924. elif x.tagName == "UserAccessLevel":
  925. self.userAccessLevel(int(unicode(x.firstChild.data)))
  926. elif x.tagName == "MinimumSamplingInterval":
  927. self.minimumSamplingInterval(float(unicode(x.firstChild.data)))
  928. elif x.tagName == "Historizing":
  929. if "true" in x.firstChild.data.lower():
  930. self.historizing(True)
  931. else:
  932. logger.info( "Unprocessable XML Element: " + x.tagName)
  933. def printOpen62541CCode_SubtypeEarly(self, bootstrapping = True):
  934. code = []
  935. # If we have an encodable value, try to encode that
  936. if self.dataType() != None and isinstance(self.dataType().target(), opcua_node_dataType_t):
  937. # Delegate the encoding of the datavalue to the helper if we have
  938. # determined a valid encoding
  939. if self.dataType().target().isEncodable():
  940. if self.value() != None:
  941. code = code + self.value().printOpen62541CCode(bootstrapping)
  942. return code
  943. if(bootstrapping):
  944. code.append("UA_Variant *" + self.getCodePrintableID() + "_variant = UA_alloca(sizeof(UA_Variant));")
  945. code.append("UA_Variant_init(" + self.getCodePrintableID() + "_variant);")
  946. return code
  947. def printOpen62541CCode_Subtype(self, unPrintedReferences=[], bootstrapping = True):
  948. code = []
  949. codegen = open62541_MacroHelper()
  950. # Detect if this is bootstrapping or if we are attempting to use userspace...
  951. if bootstrapping == False:
  952. code.append(" " + self.getCodePrintableID() + "_variant, ")
  953. code.append(" // FIXME: missing minimumSamplingInterval")
  954. code.append(" // FIXME: missing accessLevel")
  955. code.append(" // FIXME: missing userAccessLevel")
  956. code.append(" // FIXME: missing valueRank")
  957. return code
  958. if self.historizing():
  959. code.append(self.getCodePrintableID() + "->historizing = true;")
  960. code.append(self.getCodePrintableID() + "->minimumSamplingInterval = (UA_Double) " + str(self.minimumSamplingInterval()) + ";")
  961. code.append(self.getCodePrintableID() + "->userAccessLevel = (UA_Int32) " + str(self.userAccessLevel()) + ";")
  962. code.append(self.getCodePrintableID() + "->accessLevel = (UA_Int32) " + str(self.accessLevel()) + ";")
  963. code.append(self.getCodePrintableID() + "->valueRank = (UA_Int32) " + str(self.valueRank()) + ";")
  964. # The variant is guaranteed to exist by SubtypeEarly()
  965. code.append("UA_Variant_copy(" + self.getCodePrintableID() + "_variant, &" + self.getCodePrintableID() + "->value.data.value.value );")
  966. code.append(self.getCodePrintableID() + "->valueSource = UA_VALUESOURCE_DATA;")
  967. return code
  968. class opcua_node_method_t(opcua_node_t):
  969. __executable__ = True
  970. __userExecutable__ = True
  971. __methodDecalaration__ = None
  972. def __init_subType__(self):
  973. self.nodeClass(NODE_CLASS_METHOD)
  974. self.__executable__ = True
  975. self.__userExecutable__ = True
  976. self.__methodDecalaration__ = None
  977. def methodDeclaration(self, data=None):
  978. if not data==None:
  979. self.__methodDecalaration__ = data
  980. return self.__methodDecalaration__
  981. def executable(self, data=None):
  982. if isinstance(data, bool):
  983. self.__executable__ == data
  984. return self.__executable__
  985. def userExecutable(self, data=None):
  986. if isinstance(data, bool):
  987. self.__userExecutable__ == data
  988. return self.__userExecutable__
  989. def sanitizeSubType(self):
  990. if self.methodDeclaration() != None:
  991. if not isinstance(self.methodDeclaration().target(), opcua_node_t):
  992. return False
  993. else:
  994. #FIXME: Is this even permitted!?
  995. pass
  996. def parseXMLSubType(self, xmlelement):
  997. for (at, av) in xmlelement.attributes.items():
  998. if at == "MethodDeclarationId":
  999. self.methodDeclaration(opcua_referencePointer_t(str(av), parentNode=self))
  1000. # dataType needs to be linked to a node once the namespace is read
  1001. self.getNamespace().linkLater(self.methodDeclaration())
  1002. else:
  1003. logger.error("Don't know how to process attribute " + at + " (" + av + ")")
  1004. for x in xmlelement.childNodes:
  1005. if x.nodeType == x.ELEMENT_NODE:
  1006. logger.info( "Unprocessable XML Element: " + x.tagName)
  1007. def printOpen62541CCode_Subtype(self, unPrintedReferences=[], bootstrapping = True):
  1008. code = []
  1009. # Detect if this is bootstrapping or if we are attempting to use userspace...
  1010. if bootstrapping == False:
  1011. code.append(" // Note: in/outputArguments are added by attaching the variable nodes,")
  1012. code.append(" // not by including the in the addMethodNode() call.")
  1013. code.append(" NULL,")
  1014. code.append(" NULL,")
  1015. code.append(" 0, NULL,")
  1016. code.append(" 0, NULL,")
  1017. code.append(" // FIXME: Missing executable")
  1018. code.append(" // FIXME: Missing userExecutable")
  1019. return code
  1020. # UA_False is default for booleans on _init()
  1021. if self.executable():
  1022. code.append(self.getCodePrintableID() + "->executable = true;")
  1023. if self.userExecutable():
  1024. code.append(self.getCodePrintableID() + "->userExecutable = true;")
  1025. return code
  1026. class opcua_node_objectType_t(opcua_node_t):
  1027. __isAbstract__ = False
  1028. def __init_subType__(self):
  1029. self.nodeClass(NODE_CLASS_OBJECTTYPE)
  1030. self.__isAbstract__ == False
  1031. def isAbstract(self, data=None):
  1032. if isinstance(data, bool):
  1033. self.__isAbstract__ = data
  1034. return self.__isAbstract__
  1035. def parseXMLSubType(self, xmlelement):
  1036. for (at, av) in xmlelement.attributes.items():
  1037. if at == "IsAbstract":
  1038. if "false" in av.lower():
  1039. self.isAbstract(False)
  1040. xmlelement.removeAttribute(at)
  1041. else:
  1042. logger.error("Don't know how to process attribute " + at + " (" + av + ")")
  1043. for x in xmlelement.childNodes:
  1044. if x.nodeType == x.ELEMENT_NODE:
  1045. logger.info( "Unprocessable XML Element: " + x.tagName)
  1046. def printOpen62541CCode_Subtype(self, unPrintedReferences=[], bootstrapping = True):
  1047. code = []
  1048. codegen = open62541_MacroHelper();
  1049. # Detect if this is bootstrapping or if we are attempting to use userspace...
  1050. if bootstrapping == False:
  1051. typeDefs = self.getNamespace().getSubTypesOf() # defaults to TypeDefinition
  1052. myTypeRef = None
  1053. for ref in self.getReferences():
  1054. if ref.referenceType() in typeDefs:
  1055. myTypeRef = ref
  1056. break
  1057. if myTypeRef==None:
  1058. for ref in self.getReferences():
  1059. if ref.referenceType().browseName() == "HasSubtype" and ref.isForward() == False:
  1060. myTypeRef = ref
  1061. break
  1062. if myTypeRef==None:
  1063. logger.warn(str(self) + " failed to locate a type definition, assuming BaseObjectType.")
  1064. code.append(" // No valid typeDefinition found; assuming BaseObjectType")
  1065. code.append(" UA_EXPANDEDNODEID_NUMERIC(0, UA_NS0ID_BASEOBJECTTYPE),")
  1066. else:
  1067. code.append(" " + codegen.getCreateExpandedNodeIDMacro(myTypeRef.target()) + ",")
  1068. while myTypeRef in unPrintedReferences:
  1069. code.append(" // removed " + str(myTypeRef))
  1070. unPrintedReferences.remove(myTypeRef)
  1071. if (self.isAbstract()):
  1072. code.append(" true,")
  1073. else:
  1074. code.append(" false,")
  1075. # Fallback mode for bootstrapping
  1076. if (self.isAbstract()):
  1077. code.append(self.getCodePrintableID() + "->isAbstract = true;")
  1078. return code
  1079. class opcua_node_variableType_t(opcua_node_t):
  1080. __value__ = 0
  1081. __dataType__ = None
  1082. __valueRank__ = 0
  1083. __arrayDimensions__ = 0
  1084. __isAbstract__ = False
  1085. __xmlDefinition__ = None
  1086. def __init_subType__(self):
  1087. self.nodeClass(NODE_CLASS_VARIABLETYPE)
  1088. self.__value__ = 0
  1089. self.__dataType__ = None
  1090. self.__valueRank__ = -1
  1091. self.__arrayDimensions__ = 0
  1092. self.__isAbstract__ = False
  1093. self.__xmlDefinition__ = None
  1094. def value(self, data=None):
  1095. logger.error("Setting data not implemented!")
  1096. def dataType(self, data=None):
  1097. if data != None:
  1098. self.__dataType__ = data
  1099. return self.__dataType__
  1100. def valueRank(self,data=None):
  1101. if isinstance(data, int):
  1102. self.__valueRank__ = data
  1103. return self.__valueRank__
  1104. def arrayDimensions(self,data=None):
  1105. if isinstance(data, int):
  1106. self.__arrayDimensions__ = data
  1107. return self.__arrayDimensions__
  1108. def isAbstract(self,data=None):
  1109. if isinstance(data, bool):
  1110. self.__isAbstract__ = data
  1111. return self.__isAbstract__
  1112. def sanitizeSubType(self):
  1113. # DataType fields appear to be optional for VariableTypes
  1114. # but if it does have a node set, it must obviously be a valid node
  1115. if not self.dataType() != None:
  1116. if not isinstance(self.dataType(), opcua_referencePointer_t):
  1117. logger.error("DataType attribute of " + str(self.id()) + " is not a pointer")
  1118. return False
  1119. else:
  1120. if not isinstance(self.dataType().target(), opcua_node_t):
  1121. logger.error("DataType attribute of " + str(self.id()) + " does not point to a node")
  1122. return False
  1123. else:
  1124. # FIXME: It's unclear wether this is ok or not.
  1125. logger.warn("DataType attribute of variableType " + str(self.id()) + " is not defined.")
  1126. return False
  1127. def parseXMLSubType(self, xmlelement):
  1128. for (at, av) in xmlelement.attributes.items():
  1129. if at == "IsAbstract":
  1130. if "false" in av.lower():
  1131. self.isAbstract(False)
  1132. else:
  1133. self.isAbstract(True)
  1134. xmlelement.removeAttribute(at)
  1135. elif at == "ValueRank":
  1136. self.valueRank(int(av))
  1137. if self.valueRank() != -1:
  1138. logger.warn("Array's or matrices are only permitted in variables and not supported for variableTypes. This attribute (" + at + "=" + av + ") will effectively be ignored.")
  1139. xmlelement.removeAttribute(at)
  1140. elif at == "DataType":
  1141. self.dataType(opcua_referencePointer_t(str(av), parentNode=self))
  1142. # dataType needs to be linked to a node once the namespace is read
  1143. self.getNamespace().linkLater(self.dataType())
  1144. else:
  1145. logger.error("Don't know how to process attribute " + at + " (" + av + ")")
  1146. for x in xmlelement.childNodes:
  1147. if x.nodeType == x.ELEMENT_NODE:
  1148. if x.tagName == "Definition":
  1149. self.__xmlDefinition__ = x
  1150. logger.debug( "Definition stored for future processing")
  1151. else:
  1152. logger.info( "Unprocessable XML Element: " + x.tagName)
  1153. def printOpen62541CCode_SubtypeEarly(self, bootstrapping = True):
  1154. code = []
  1155. # If we have an encodable value, try to encode that
  1156. if self.dataType() != None and isinstance(self.dataType().target(), opcua_node_dataType_t):
  1157. # Delegate the encoding of the datavalue to the helper if we have
  1158. # determined a valid encoding
  1159. if self.dataType().target().isEncodable():
  1160. if self.value() != None:
  1161. code = code + self.value().printOpen62541CCode(bootstrapping)
  1162. return code
  1163. if(bootstrapping):
  1164. code.append("UA_Variant *" + self.getCodePrintableID() + "_variant = UA_alloca(sizeof(UA_Variant));")
  1165. code.append("UA_Variant_init(" + self.getCodePrintableID() + "_variant);")
  1166. return code
  1167. def printOpen62541CCode_Subtype(self, unPrintedReferences=[], bootstrapping = True):
  1168. code = []
  1169. codegen = open62541_MacroHelper()
  1170. if bootstrapping == False:
  1171. code.append(" " + self.getCodePrintableID() + "_variant, ")
  1172. code.append(" " + str(self.valueRank()) + ",")
  1173. if self.isAbstract():
  1174. code.append(" true,")
  1175. else:
  1176. code.append(" false,")
  1177. return code
  1178. if (self.isAbstract()):
  1179. code.append(self.getCodePrintableID() + "->isAbstract = true;")
  1180. else:
  1181. code.append(self.getCodePrintableID() + "->isAbstract = false;")
  1182. # The variant is guaranteed to exist by SubtypeEarly()
  1183. code.append("UA_Variant_copy(" + self.getCodePrintableID() + "_variant, &" + self.getCodePrintableID() + "->value.data.value.value );")
  1184. code.append(self.getCodePrintableID() + "->valueSource = UA_VALUESOURCE_DATA;")
  1185. return code
  1186. class opcua_node_dataType_t(opcua_node_t):
  1187. """ opcua_node_dataType_t is a subtype of opcua_note_t describing DataType nodes.
  1188. DataType contain definitions and structure information usable for Variables.
  1189. The format of this structure is determined by buildEncoding()
  1190. Two definition styles are distinguished in XML:
  1191. 1) A DataType can be a structure of fields, each field having a name and a type.
  1192. The type must be either an encodable builtin node (ex. UInt32) or point to
  1193. another DataType node that inherits its encoding from a builtin type using
  1194. a inverse "hasSubtype" (hasSuperType) reference.
  1195. 2) A DataType may be an enumeration, in which each field has a name and a numeric
  1196. value.
  1197. The definition is stored as an ordered list of tuples. Depending on which
  1198. definition style was used, the __definition__ will hold
  1199. 1) A list of ("Fieldname", opcua_node_t) tuples.
  1200. 2) A list of ("Fieldname", int) tuples.
  1201. A DataType (and in consequence all Variables using it) shall be deemed not
  1202. encodable if any of its fields cannot be traced to an encodable builtin type.
  1203. A DataType shall be further deemed not encodable if it contains mixed structure/
  1204. enumaration definitions.
  1205. If encodable, the encoding can be retrieved using getEncoding().
  1206. """
  1207. __isAbstract__ = False
  1208. __isEnum__ = False
  1209. __xmlDefinition__ = None
  1210. __baseTypeEncoding__ = []
  1211. __encodable__ = False
  1212. __encodingBuilt__ = False
  1213. __definition__ = []
  1214. def __init_subType__(self):
  1215. self.nodeClass(NODE_CLASS_DATATYPE)
  1216. self.__isAbstract__ == False
  1217. self.__xmlDefinition__ = None
  1218. self.__baseTypeEncoding__ = []
  1219. self.__encodable__ = None
  1220. self.__encodingBuilt__ = False
  1221. self.__definition__ = []
  1222. self.__isEnum__ = False
  1223. def isAbstract(self,data=None):
  1224. """ Will return True if isAbstract was defined.
  1225. Calling this function with an arbitrary data parameter will set
  1226. isAbstract = data.
  1227. """
  1228. if isinstance(data, bool):
  1229. self.__isAbstract__ = data
  1230. return self.__isAbstract__
  1231. def isEncodable(self):
  1232. """ Will return True if buildEncoding() was able to determine which builtin
  1233. type corresponds to all fields of this DataType.
  1234. If no encoding has been build yet, this function will call buildEncoding()
  1235. and return True if it succeeds.
  1236. """
  1237. return self.__encodable__
  1238. def getEncoding(self):
  1239. """ If the dataType is encodable, getEncoding() returns a nested list
  1240. containing the encoding the structure definition for this type.
  1241. If no encoding has been build yet, this function will call buildEncoding()
  1242. and return the encoding if buildEncoding() succeeds.
  1243. If buildEncoding() fails or has failed, an empty list will be returned.
  1244. """
  1245. if self.__encodable__ == False:
  1246. if self.__encodingBuilt__ == False:
  1247. return self.buildEncoding()
  1248. return []
  1249. else:
  1250. return self.__baseTypeEncoding__
  1251. def buildEncoding(self, indent=0, force=False):
  1252. """ buildEncoding() determines the structure and aliases used for variables
  1253. of this DataType.
  1254. The function will parse the XML <Definition> of the dataType and extract
  1255. "Name"-"Type" tuples. If successfull, buildEncoding will return a nested
  1256. list of the following format:
  1257. [['Alias1', ['Alias2', ['BuiltinType']]], [Alias2, ['BuiltinType']], ...]
  1258. Aliases are fieldnames defined by this DataType or DataTypes referenced. A
  1259. list such as ['DataPoint', ['Int32']] indicates that a value will encode
  1260. an Int32 with the alias 'DataPoint' such as <DataPoint>12827</DataPoint>.
  1261. Only the first Alias of a nested list is considered valid for the BuiltinType.
  1262. Single-Elemented lists are always BuiltinTypes. Every nested list must
  1263. converge in a builtin type to be encodable. buildEncoding will follow
  1264. the first type inheritance reference (hasSupertype) of the dataType if
  1265. necessary;
  1266. If instead to "DataType" a numeric "Value" attribute is encountered,
  1267. the DataType will be considered an enumeration and all Variables using
  1268. it will be encoded as Int32.
  1269. DataTypes can be either structures or enumeration - mixed definitions will
  1270. be unencodable.
  1271. Calls to getEncoding() will be iterative. buildEncoding() can be called
  1272. only once per dataType, with all following calls returning the predetermined
  1273. value. Use of the 'force=True' parameter will force the Definition to be
  1274. reparsed.
  1275. After parsing, __definition__ holds the field definition as a list. Note
  1276. that this might deviate from the encoding, especially if inheritance was
  1277. used.
  1278. """
  1279. proxy = opcua_value_t(None)
  1280. prefix = " " + "|"*indent+ "+"
  1281. if force==True:
  1282. self.__encodingBuilt__ = False
  1283. if self.__encodingBuilt__ == True:
  1284. if self.isEncodable():
  1285. logger.debug(prefix + str(self.__baseTypeEncoding__) + " (already analyzed)")
  1286. else:
  1287. logger.debug( prefix + str(self.__baseTypeEncoding__) + "(already analyzed, not encodable!)")
  1288. return self.__baseTypeEncoding__
  1289. self.__encodingBuilt__ = True # signify that we have attempted to built this type
  1290. self.__encodable__ = True
  1291. if indent==0:
  1292. logger.debug("Parsing DataType " + self.browseName() + " (" + str(self.id()) + ")")
  1293. if proxy.isBuiltinByString(self.browseName()):
  1294. self.__baseTypeEncoding__ = [self.browseName()]
  1295. self.__encodable__ = True
  1296. logger.debug( prefix + self.browseName() + "*")
  1297. logger.debug("Encodable as: " + str(self.__baseTypeEncoding__))
  1298. logger.debug("")
  1299. return self.__baseTypeEncoding__
  1300. if self.__xmlDefinition__ == None:
  1301. # Check if there is a supertype available
  1302. for ref in self.getReferences():
  1303. if "hassubtype" in ref.referenceType().browseName().lower() and ref.isForward() == False:
  1304. if isinstance(ref.target(), opcua_node_dataType_t):
  1305. logger.debug( prefix + "Attempting definition using supertype " + ref.target().browseName() + " for DataType " + " " + self.browseName())
  1306. subenc = ref.target().buildEncoding(indent=indent+1)
  1307. if not ref.target().isEncodable():
  1308. self.__encodable__ = False
  1309. break
  1310. else:
  1311. self.__baseTypeEncoding__ = self.__baseTypeEncoding__ + [self.browseName(), subenc, 0]
  1312. if len(self.__baseTypeEncoding__) == 0:
  1313. logger.debug(prefix + "No viable definition for " + self.browseName() + " " + str(self.id()) + " found.")
  1314. self.__encodable__ = False
  1315. if indent==0:
  1316. if not self.__encodable__:
  1317. logger.debug("Not encodable (partial): " + str(self.__baseTypeEncoding__))
  1318. else:
  1319. logger.debug("Encodable as: " + str(self.__baseTypeEncoding__))
  1320. logger.debug( "")
  1321. return self.__baseTypeEncoding__
  1322. isEnum = True
  1323. isSubType = True
  1324. hasValueRank = 0
  1325. # We need to store the definition as ordered data, but can't use orderedDict
  1326. # for backward compatibility with Python 2.6 and 3.4
  1327. enumDict = []
  1328. typeDict = []
  1329. # An XML Definition is provided and will be parsed... now
  1330. for x in self.__xmlDefinition__.childNodes:
  1331. if x.nodeType == x.ELEMENT_NODE:
  1332. fname = ""
  1333. fdtype = ""
  1334. enumVal = ""
  1335. hasValueRank = 0
  1336. for at,av in x.attributes.items():
  1337. if at == "DataType":
  1338. fdtype = str(av)
  1339. isEnum = False
  1340. elif at == "Name":
  1341. fname = str(av)
  1342. elif at == "Value":
  1343. enumVal = int(av)
  1344. isSubType = False
  1345. elif at == "ValueRank":
  1346. hasValueRank = int(av)
  1347. logger.warn("Arrays or matrices (ValueRank) are not supported for datatypes. This DT will become scalar.")
  1348. else:
  1349. logger.warn("Unknown Field Attribute " + str(at))
  1350. # This can either be an enumeration OR a structure, not both.
  1351. # Figure out which of the dictionaries gets the newly read value pair
  1352. if isEnum == isSubType:
  1353. # This is an error
  1354. logger.warn("DataType contains both enumeration and subtype (or neither)")
  1355. self.__encodable__ = False
  1356. break
  1357. elif isEnum:
  1358. # This is an enumeration
  1359. enumDict.append((fname, enumVal))
  1360. continue
  1361. else:
  1362. # This might be a subtype... follow the node defined as datatype to find out
  1363. # what encoding to use
  1364. dtnode = self.getNamespace().getNodeByIDString(fdtype)
  1365. if dtnode == None:
  1366. # Node found in datatype element is invalid
  1367. logger.debug( prefix + fname + " ?? " + av + " ??")
  1368. self.__encodable__ = False
  1369. else:
  1370. # The node in the datatype element was found. we inherit its encoding,
  1371. # but must still ensure that the dtnode is itself validly encodable
  1372. typeDict.append([fname, dtnode])
  1373. if hasValueRank < 0:
  1374. hasValueRank = 0
  1375. fdtype = str(dtnode.browseName()) + "+"*hasValueRank
  1376. logger.debug( prefix + fname + " : " + fdtype + " -> " + str(dtnode.id()))
  1377. subenc = dtnode.buildEncoding(indent=indent+1)
  1378. self.__baseTypeEncoding__ = self.__baseTypeEncoding__ + [[fname, subenc, hasValueRank]]
  1379. if not dtnode.isEncodable():
  1380. # If we inherit an encoding from an unencodable not, this node is
  1381. # also not encodable
  1382. self.__encodable__ = False
  1383. break
  1384. # If we used inheritance to determine an encoding without alias, there is a
  1385. # the possibility that lists got double-nested despite of only one element
  1386. # being encoded, such as [['Int32']] or [['alias',['int32']]]. Remove that
  1387. # enclosing list.
  1388. while len(self.__baseTypeEncoding__) == 1 and isinstance(self.__baseTypeEncoding__[0], list):
  1389. self.__baseTypeEncoding__ = self.__baseTypeEncoding__[0]
  1390. if isEnum == True:
  1391. self.__baseTypeEncoding__ = self.__baseTypeEncoding__ + ['Int32']
  1392. self.__definition__ = enumDict
  1393. self.__isEnum__ = True
  1394. logger.debug( prefix+"Int32* -> enumeration with dictionary " + str(enumDict) + " encodable " + str(self.__encodable__))
  1395. return self.__baseTypeEncoding__
  1396. if indent==0:
  1397. if not self.__encodable__:
  1398. logger.debug( "Not encodable (partial): " + str(self.__baseTypeEncoding__))
  1399. else:
  1400. logger.debug( "Encodable as: " + str(self.__baseTypeEncoding__))
  1401. self.__isEnum__ = False
  1402. self.__definition__ = typeDict
  1403. logger.debug( "")
  1404. return self.__baseTypeEncoding__
  1405. def parseXMLSubType(self, xmlelement):
  1406. """ Parses all XML data that is not considered part of the base node attributes.
  1407. XML attributes fields processed are "isAbstract"
  1408. XML elements processed are "Definition"
  1409. """
  1410. for (at, av) in xmlelement.attributes.items():
  1411. if at == "IsAbstract":
  1412. if "true" in str(av).lower():
  1413. self.isAbstract(True)
  1414. else:
  1415. self.isAbstract(False)
  1416. xmlelement.removeAttribute(at)
  1417. else:
  1418. logger.warn("Don't know how to process attribute " + at + " (" + av + ")")
  1419. for x in xmlelement.childNodes:
  1420. if x.nodeType == x.ELEMENT_NODE:
  1421. if x.tagName == "Definition":
  1422. self.__xmlDefinition__ = x
  1423. #logger.debug( "Definition stored for future processing")
  1424. else:
  1425. logger.warn( "Unprocessable XML Element: " + x.tagName)
  1426. def encodedTypeId(self):
  1427. """ Returns a number of the builtin Type that should be used
  1428. to represent this datatype.
  1429. """
  1430. if self.isEncodable() != True or len(self.getEncoding()) == 0:
  1431. # Encoding is []
  1432. return 0
  1433. else:
  1434. enc = self.getEncoding()
  1435. if len(enc) > 1 and isinstance(enc[0], list):
  1436. # [ [?], [?], [?] ]
  1437. # Encoding is a list representing an extensionobject
  1438. return opcua_BuiltinType_extensionObject_t(None).getNumericRepresentation()
  1439. else:
  1440. if len(enc)==1 and isinstance(enc[0], str):
  1441. # [ 'BuiltinType' ]
  1442. return opcua_value_t(None).getTypeByString(enc[0]).getNumericRepresentation()
  1443. else:
  1444. # [ ['Alias', [?]] ]
  1445. # Determine if [?] is reducable to a builtin type or if [?] is an aliased
  1446. # extensionobject
  1447. while len(enc) > 1 and isinstance(enc[0], str):
  1448. enc = enc[1]
  1449. if len(enc) > 1:
  1450. return opcua_BuiltinType_extensionObject_t(None).getNumericRepresentation()
  1451. else:
  1452. return opcua_value_t(None).getTypeByString(enc[0]).getNumericRepresentation()
  1453. def printOpen62541CCode_Subtype(self, unPrintedReferences=[], bootstrapping = True):
  1454. code = []
  1455. codegen = open62541_MacroHelper()
  1456. # Detect if this is bootstrapping or if we are attempting to use userspace...
  1457. if bootstrapping == False:
  1458. typeDefs = self.getNamespace().getSubTypesOf() # defaults to TypeDefinition
  1459. myTypeRef = None
  1460. for ref in self.getReferences():
  1461. if ref.referenceType() in typeDefs:
  1462. myTypeRef = ref
  1463. break
  1464. if myTypeRef==None:
  1465. for ref in self.getReferences():
  1466. if ref.referenceType().browseName() == "HasSubtype" and ref.isForward() == False:
  1467. myTypeRef = ref
  1468. break
  1469. if myTypeRef==None:
  1470. logger.warn(str(self) + " failed to locate a type definition, assuming BaseDataType.")
  1471. code.append(" // No valid typeDefinition found; assuming BaseDataType")
  1472. code.append(" UA_EXPANDEDNODEID_NUMERIC(0, UA_NS0ID_BASEDATATYPE),")
  1473. else:
  1474. code.append(" " + codegen.getCreateExpandedNodeIDMacro(myTypeRef.target()) + ",")
  1475. while myTypeRef in unPrintedReferences:
  1476. unPrintedReferences.remove(myTypeRef)
  1477. if (self.isAbstract()):
  1478. code.append(" true,")
  1479. else:
  1480. code.append(" false,")
  1481. return code
  1482. if (self.isAbstract()):
  1483. code.append(self.getCodePrintableID() + "->isAbstract = true;")
  1484. else:
  1485. code.append(self.getCodePrintableID() + "->isAbstract = false;")
  1486. return code
  1487. class opcua_node_view_t(opcua_node_t):
  1488. __containsNoLoops__ = True
  1489. __eventNotifier__ = 0
  1490. def __init_subType__(self):
  1491. self.nodeClass(NODE_CLASS_VIEW)
  1492. self.__containsNoLoops__ == False
  1493. self.__eventNotifier__ == False
  1494. def containsNoLoops(self,data=None):
  1495. if isinstance(data, bool):
  1496. self.__containsNoLoops__ = data
  1497. return self.__containsNoLoops__
  1498. def eventNotifier(self,data=None):
  1499. if isinstance(data, int):
  1500. self.__eventNotifier__ = data
  1501. return self.__eventNotifier__
  1502. def parseXMLSubtype(self, xmlelement):
  1503. for (at, av) in xmlelement.attributes.items():
  1504. logger.error("Don't know how to process attribute " + at + " (" + av + ")")
  1505. for x in xmlelement.childNodes:
  1506. if x.nodeType == x.ELEMENT_NODE:
  1507. logger.info( "Unprocessable XML Element: " + x.tagName)
  1508. def printOpen62541CCode_Subtype(self, unPrintedReferences=[], bootstrapping = True):
  1509. code = []
  1510. codegen = open62541_MacroHelper()
  1511. # Detect if this is bootstrapping or if we are attempting to use userspace...
  1512. if bootstrapping == False:
  1513. typeDefs = self.getNamespace().getSubTypesOf() # defaults to TypeDefinition
  1514. myTypeRef = None
  1515. for ref in self.getReferences():
  1516. if ref.referenceType() in typeDefs:
  1517. myTypeRef = ref
  1518. break
  1519. if myTypeRef==None:
  1520. for ref in self.getReferences():
  1521. if ref.referenceType().browseName() == "HasSubtype" and ref.isForward() == False:
  1522. myTypeRef = ref
  1523. break
  1524. if myTypeRef==None:
  1525. logger.warn(str(self) + " failed to locate a type definition, assuming BaseViewType.")
  1526. code.append(" // No valid typeDefinition found; assuming BaseViewType")
  1527. code.append(" UA_EXPANDEDNODEID_NUMERIC(0, UA_NS0ID_BASEViewTYPE),")
  1528. else:
  1529. code.append(" " + codegen.getCreateExpandedNodeIDMacro(myTypeRef.target()) + ",")
  1530. while myTypeRef in unPrintedReferences:
  1531. unPrintedReferences.remove(myTypeRef)
  1532. code.append(" // FIXME: Missing eventNotifier")
  1533. code.append(" // FIXME: Missing containsNoLoops")
  1534. return code
  1535. if self.containsNoLoops():
  1536. code.append(self.getCodePrintableID() + "->containsNoLoops = true;")
  1537. else:
  1538. code.append(self.getCodePrintableID() + "->containsNoLoops = false;")
  1539. code.append(self.getCodePrintableID() + "->eventNotifier = (UA_Byte) " + str(self.eventNotifier()) + ";")
  1540. return code