ua_node_types.py 56 KB

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