backend_open62541_nodes.py 22 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473
  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 nodes import *
  18. from backend_open62541_datatypes import *
  19. import re
  20. import logging
  21. logger = logging.getLogger(__name__)
  22. #################
  23. # Generate Code #
  24. #################
  25. def generateNodeIdPrintable(node):
  26. if isinstance(node.id, NodeId):
  27. CodePrintable = node.__class__.__name__ + "_" + str(node.id)
  28. else:
  29. CodePrintable = node.__class__.__name__ + "_unknown_nid"
  30. return re.sub('[^0-9a-z_]+', '_', CodePrintable.lower())
  31. def generateNodeValueInstanceName(node, parent, arrayIndex):
  32. return generateNodeIdPrintable(parent) + "_" + str(node.alias) + "_" + str(arrayIndex)
  33. def generateReferenceCode(reference):
  34. if reference.isForward:
  35. return "retVal |= UA_Server_addReference(server, %s, %s, %s, true);" % \
  36. (generateNodeIdCode(reference.source),
  37. generateNodeIdCode(reference.referenceType),
  38. generateExpandedNodeIdCode(reference.target))
  39. else:
  40. return "retVal |= UA_Server_addReference(server, %s, %s, %s, false);" % \
  41. (generateNodeIdCode(reference.source),
  42. generateNodeIdCode(reference.referenceType),
  43. generateExpandedNodeIdCode(reference.target))
  44. def generateReferenceTypeNodeCode(node):
  45. code = []
  46. code.append("UA_ReferenceTypeAttributes attr = UA_ReferenceTypeAttributes_default;")
  47. if node.isAbstract:
  48. code.append("attr.isAbstract = true;")
  49. if node.symmetric:
  50. code.append("attr.symmetric = true;")
  51. if node.inverseName != "":
  52. code.append("attr.inverseName = UA_LOCALIZEDTEXT(\"\", \"%s\");" % \
  53. node.inverseName)
  54. return code
  55. def generateObjectNodeCode(node):
  56. code = []
  57. code.append("UA_ObjectAttributes attr = UA_ObjectAttributes_default;")
  58. if node.eventNotifier:
  59. code.append("attr.eventNotifier = true;")
  60. return code
  61. def generateVariableNodeCode(node, nodeset, encode_binary_size):
  62. code = []
  63. codeCleanup = []
  64. codeGlobal = []
  65. code.append("UA_VariableAttributes attr = UA_VariableAttributes_default;")
  66. if node.historizing:
  67. code.append("attr.historizing = true;")
  68. code.append("attr.minimumSamplingInterval = %f;" % node.minimumSamplingInterval)
  69. code.append("attr.userAccessLevel = %d;" % node.userAccessLevel)
  70. code.append("attr.accessLevel = %d;" % node.accessLevel)
  71. # in order to be compatible with mostly OPC UA client
  72. # force valueRank = -1 for scalar VariableNode
  73. if node.valueRank == -2:
  74. node.valueRank = -1
  75. code.append("attr.valueRank = %d;" % node.valueRank)
  76. if node.valueRank > 0:
  77. code.append("attr.arrayDimensionsSize = %d;" % node.valueRank)
  78. code.append("UA_UInt32 arrayDimensions[{}];".format(node.valueRank))
  79. if len(node.arrayDimensions) == node.valueRank:
  80. for idx, v in enumerate(node.arrayDimensions):
  81. code.append("arrayDimensions[{}] = {};".format(idx, int(str(v))))
  82. else:
  83. for dim in range(0, node.valueRank):
  84. code.append("arrayDimensions[{}] = 0;".format(dim))
  85. code.append("attr.arrayDimensions = &arrayDimensions[0];")
  86. if node.dataType is not None:
  87. if isinstance(node.dataType, NodeId) and node.dataType.ns == 0 and node.dataType.i == 0:
  88. #BaseDataType
  89. dataTypeNode = nodeset.nodes[NodeId("i=24")]
  90. dataTypeNodeOpaque = nodeset.nodes[NodeId("i=24")]
  91. else:
  92. dataTypeNodeOpaque = nodeset.getDataTypeNode(node.dataType)
  93. dataTypeNode = nodeset.getBaseDataType(nodeset.getDataTypeNode(node.dataType))
  94. if dataTypeNode is not None:
  95. code.append("attr.dataType = %s;" % generateNodeIdCode(dataTypeNodeOpaque.id))
  96. if dataTypeNode.isEncodable():
  97. if node.value is not None:
  98. [code1, codeCleanup1, codeGlobal1] = generateValueCode(node.value, nodeset.nodes[node.id], nodeset)
  99. code += code1
  100. codeCleanup += codeCleanup1
  101. codeGlobal += codeGlobal1
  102. # #1978 Variant arrayDimensions are only required to properly decode multidimensional arrays
  103. # (valueRank >= 2) from data stored as one-dimensional array of arrayLength elements.
  104. # One-dimensional arrays are already completely defined by arraylength attribute so setting
  105. # also arrayDimensions, even if not explicitly forbidden, can confuse clients
  106. if node.valueRank > 1 and len(node.arrayDimensions) == node.valueRank:
  107. code.append("attr.value.arrayDimensionsSize = attr.arrayDimensionsSize;")
  108. code.append("attr.value.arrayDimensions = attr.arrayDimensions;")
  109. else:
  110. code += generateValueCodeDummy(dataTypeNode, nodeset.nodes[node.id], nodeset)
  111. else:
  112. #TODO: take a look on this
  113. #logger.error("cannot encode: " + node.browseName.name)
  114. pass
  115. return [code, codeCleanup, codeGlobal]
  116. def generateVariableTypeNodeCode(node, nodeset, encode_binary_size):
  117. code = []
  118. codeCleanup = []
  119. codeGlobal = []
  120. code.append("UA_VariableTypeAttributes attr = UA_VariableTypeAttributes_default;")
  121. if node.historizing:
  122. code.append("attr.historizing = true;")
  123. if node.isAbstract:
  124. code.append("attr.isAbstract = true;")
  125. code.append("attr.valueRank = (UA_Int32)%s;" % str(node.valueRank))
  126. if node.dataType is not None:
  127. if isinstance(node.dataType, NodeId) and node.dataType.ns == 0 and node.dataType.i == 0:
  128. #BaseDataType
  129. dataTypeNode = nodeset.nodes[NodeId("i=24")]
  130. else:
  131. dataTypeNode = nodeset.getBaseDataType(nodeset.getDataTypeNode(node.dataType))
  132. if dataTypeNode is not None:
  133. code.append("attr.dataType = %s;" % generateNodeIdCode(dataTypeNode.id))
  134. if dataTypeNode.isEncodable():
  135. if node.value is not None:
  136. [code1, codeCleanup1, codeGlobal1] = generateValueCode(node.value, nodeset.nodes[node.id], nodeset)
  137. code += code1
  138. codeCleanup += codeCleanup1
  139. codeGlobal += codeGlobal1
  140. else:
  141. code += generateValueCodeDummy(dataTypeNode, nodeset.nodes[node.id], nodeset)
  142. return [code, codeCleanup, codeGlobal]
  143. def lowerFirstChar(inputString):
  144. return inputString[0].lower() + inputString[1:]
  145. def generateExtensionObjectSubtypeCode(node, parent, nodeset, global_var_code, instanceName=None, isArrayElement=False):
  146. code = [""]
  147. codeCleanup = [""]
  148. logger.debug("Building extensionObject for " + str(parent.id))
  149. logger.debug("Value " + str(node.value))
  150. logger.debug("Encoding " + str(node.encodingRule))
  151. typeBrowseNode = makeCIdentifier(nodeset.getDataTypeNode(parent.dataType).browseName.name)
  152. #TODO: review this
  153. if typeBrowseNode == "NumericRange":
  154. # in the stack we define a separate structure for the numeric range, but
  155. # the value itself is just a string
  156. typeBrowseNode = "String"
  157. typeString = "UA_" + typeBrowseNode
  158. if instanceName is None:
  159. instanceName = generateNodeValueInstanceName(node, parent, 0)
  160. code.append("UA_STACKARRAY(" + typeString + ", " + instanceName + ", 1);")
  161. typeArr = nodeset.getDataTypeNode(parent.dataType).typesArray
  162. typeString = nodeset.getDataTypeNode(parent.dataType).browseName.name.upper()
  163. typeArrayString = typeArr + "[" + typeArr + "_" + typeString + "]"
  164. code.append("UA_init({ref}{instanceName}, &{typeArrayString});".format(ref="&" if isArrayElement else "",
  165. instanceName=instanceName,
  166. typeArrayString=typeArrayString))
  167. # Assign data to the struct contents
  168. # Track the encoding rule definition to detect arrays and/or ExtensionObjects
  169. encFieldIdx = 0
  170. for subv in node.value:
  171. encField = node.encodingRule[encFieldIdx]
  172. encFieldIdx = encFieldIdx + 1
  173. memberName = lowerFirstChar(encField[0])
  174. if isinstance(subv, list):
  175. if len(subv) == 0:
  176. continue
  177. logger.info("ExtensionObject contains array")
  178. memberName = lowerFirstChar(encField[0])
  179. encTypeString = "UA_" + subv[0].__class__.__name__
  180. code.append("UA_STACKARRAY(" + encTypeString + ", " + instanceName + "_" + memberName+", {0});".format(len(subv)))
  181. encTypeArr = nodeset.getDataTypeNode(subv[0].__class__.__name__).typesArray
  182. encTypeArrayString = encTypeArr + "[" + encTypeArr + "_" + subv[0].__class__.__name__.upper() + "]"
  183. code.append("UA_init({ref}{instanceName}, &{typeArrayString});".format(ref="&" if isArrayElement else "",
  184. instanceName=instanceName + "_" + memberName,
  185. typeArrayString=encTypeArrayString))
  186. subArrayIdx = 0
  187. for val in subv:
  188. code.append(generateNodeValueCode(instanceName + "_" + memberName + "[" + str(subArrayIdx) + "]" +" = ", val, instanceName,instanceName + "_gehtNed_member", global_var_code, asIndirect=False))
  189. subArrayIdx = subArrayIdx + 1
  190. code.append(instanceName + "->" + memberName + " = " + instanceName+"_"+ memberName+";")
  191. continue
  192. else:
  193. logger.debug(
  194. "Encoding of field " + memberName + " is " + str(subv.encodingRule) + "defined by " + str(encField))
  195. # Check if this is an array
  196. accessor = "." if isArrayElement else "->"
  197. if subv.valueRank is None or subv.valueRank == 0:
  198. valueName = instanceName + accessor + memberName
  199. code.append(generateNodeValueCode(valueName + " = " ,
  200. subv, instanceName,valueName, global_var_code, asIndirect=False))
  201. else:
  202. memberName = lowerFirstChar(encField[0])
  203. code.append(generateNodeValueCode(instanceName + accessor + memberName + "Size = ", subv, instanceName,valueName, global_var_code, asIndirect=False))
  204. if not isArrayElement:
  205. code.append("UA_Variant_setScalar(&attr.value, " + instanceName + ", &" + typeArrayString + ");")
  206. return [code, codeCleanup]
  207. def getTypeBrowseName(dataTypeNode):
  208. typeBrowseName = makeCIdentifier(dataTypeNode.browseName.name)
  209. #TODO: review this
  210. if typeBrowseName == "NumericRange":
  211. # in the stack we define a separate structure for the numeric range, but
  212. # the value itself is just a string
  213. typeBrowseName = "String"
  214. return typeBrowseName
  215. def generateValueCodeDummy(dataTypeNode, parentNode, nodeset):
  216. code = []
  217. valueName = generateNodeIdPrintable(parentNode) + "_variant_DataContents"
  218. typeBrowseName = getTypeBrowseName(dataTypeNode)
  219. typeArr = dataTypeNode.typesArray + "[" + dataTypeNode.typesArray + "_" + typeBrowseName.upper() + "]"
  220. typeStr = "UA_" + typeBrowseName
  221. if parentNode.valueRank > 0:
  222. for i in range(0, parentNode.valueRank):
  223. code.append("UA_Variant_setArray(&attr.value, NULL, (UA_Int32) " + "0, &" + typeArr + ");")
  224. elif not dataTypeNode.isAbstract:
  225. code.append("UA_STACKARRAY(" + typeStr + ", " + valueName + ", 1);")
  226. code.append("UA_init(" + valueName + ", &" + typeArr + ");")
  227. code.append("UA_Variant_setScalar(&attr.value, " + valueName + ", &" + typeArr + ");")
  228. return code
  229. def getTypesArrayForValue(nodeset, value):
  230. typeNode = nodeset.getNodeByBrowseName(value.__class__.__name__)
  231. if typeNode is None or value.isInternal:
  232. typesArray = "UA_TYPES"
  233. else:
  234. typesArray = typeNode.typesArray
  235. typeName = makeCIdentifier(value.__class__.__name__.upper())
  236. return "&" + typesArray + "[" + typesArray + "_" + typeName + "]"
  237. def isArrayVariableNode(node, parentNode):
  238. return parentNode.valueRank != -1 and (parentNode.valueRank >= 0
  239. or (len(node.value) > 1
  240. and (parentNode.valueRank != -2 or parentNode.valueRank != -3)))
  241. def generateValueCode(node, parentNode, nodeset, bootstrapping=True):
  242. code = []
  243. codeCleanup = []
  244. codeGlobal = []
  245. valueName = generateNodeIdPrintable(parentNode) + "_variant_DataContents"
  246. # node.value either contains a list of multiple identical BUILTINTYPES, or it
  247. # contains a single builtintype (which may be a container); choose if we need
  248. # to create an array or a single variable.
  249. # Note that some genious defined that there are arrays of size 1, which are
  250. # distinctly different then a single value, so we need to check that as well
  251. # Semantics:
  252. # -3: Scalar or 1-dim
  253. # -2: Scalar or x-dim | x>0
  254. # -1: Scalar
  255. # 0: x-dim | x>0
  256. # n: n-dim | n>0
  257. if (len(node.value) == 0):
  258. return ["", "", ""]
  259. if not isinstance(node.value[0], Value):
  260. return ["", "", ""]
  261. dataTypeNode = nodeset.getDataTypeNode(parentNode.dataType)
  262. if isArrayVariableNode(node, parentNode):
  263. # User the following strategy for all directly mappable values a la 'UA_Type MyInt = (UA_Type) 23;'
  264. if isinstance(node.value[0], Guid):
  265. logger.warn("Don't know how to print array of GUID in node " + str(parentNode.id))
  266. elif isinstance(node.value[0], DiagnosticInfo):
  267. logger.warn("Don't know how to print array of DiagnosticInfo in node " + str(parentNode.id))
  268. elif isinstance(node.value[0], StatusCode):
  269. logger.warn("Don't know how to print array of StatusCode in node " + str(parentNode.id))
  270. else:
  271. if isinstance(node.value[0], ExtensionObject):
  272. code.append("UA_" + getTypeBrowseName(dataTypeNode) + " " + valueName + "[" + str(len(node.value)) + "];")
  273. for idx, v in enumerate(node.value):
  274. logger.debug("Building extObj array index " + str(idx))
  275. instanceName = valueName + "[" + str(idx) + "]"
  276. [code1, codeCleanup1] = generateExtensionObjectSubtypeCode(v, parent=parentNode, nodeset=nodeset,
  277. global_var_code=codeGlobal, instanceName=instanceName,
  278. isArrayElement=True)
  279. code = code + code1
  280. codeCleanup = codeCleanup + codeCleanup1
  281. else:
  282. code.append("UA_" + node.value[0].__class__.__name__ + " " + valueName + "[" + str(len(node.value)) + "];")
  283. for idx, v in enumerate(node.value):
  284. instanceName = generateNodeValueInstanceName(v, parentNode, idx)
  285. code.append(generateNodeValueCode(
  286. valueName + "[" + str(idx) + "] = " , v, instanceName, valueName, codeGlobal))
  287. code.append("UA_Variant_setArray(&attr.value, &" + valueName +
  288. ", (UA_Int32) " + str(len(node.value)) + ", " + "&" +
  289. dataTypeNode.typesArray + "["+dataTypeNode.typesArray + "_" + getTypeBrowseName(dataTypeNode).upper() +"]);")
  290. #scalar value
  291. else:
  292. # User the following strategy for all directly mappable values a la 'UA_Type MyInt = (UA_Type) 23;'
  293. if isinstance(node.value[0], Guid):
  294. logger.warn("Don't know how to print scalar GUID in node " + str(parentNode.id))
  295. elif isinstance(node.value[0], DiagnosticInfo):
  296. logger.warn("Don't know how to print scalar DiagnosticInfo in node " + str(parentNode.id))
  297. elif isinstance(node.value[0], StatusCode):
  298. logger.warn("Don't know how to print scalar StatusCode in node " + str(parentNode.id))
  299. else:
  300. # The following strategy applies to all other types, in particular strings and numerics.
  301. if isinstance(node.value[0], ExtensionObject):
  302. [code1, codeCleanup1] = generateExtensionObjectSubtypeCode(node.value[0], parent=parentNode, nodeset=nodeset, global_var_code=codeGlobal, isArrayElement=False)
  303. code = code + code1
  304. codeCleanup = codeCleanup + codeCleanup1
  305. instanceName = generateNodeValueInstanceName(node.value[0], parentNode, 0)
  306. if not(isinstance(node.value[0], ExtensionObject)):
  307. code.append("UA_" + node.value[0].__class__.__name__ + " *" + valueName + " = UA_" + node.value[
  308. 0].__class__.__name__ + "_new();")
  309. code.append("if (!" + valueName + ") return UA_STATUSCODE_BADOUTOFMEMORY;")
  310. code.append(generateNodeValueCode("*" + valueName + " = " , node.value[0], instanceName, valueName, codeGlobal, asIndirect=True))
  311. code.append(
  312. "UA_Variant_setScalar(&attr.value, " + valueName + ", " +
  313. getTypesArrayForValue(nodeset, node.value[0]) + ");")
  314. if node.value[0].__class__.__name__ == "ByteString":
  315. # The data is on the stack, not heap, so we can not delete the ByteString
  316. codeCleanup.append("{}->data = NULL;".format(valueName))
  317. codeCleanup.append("{}->length = 0;".format(valueName))
  318. codeCleanup.append("UA_{0}_delete({1});".format(
  319. node.value[0].__class__.__name__, valueName))
  320. return [code, codeCleanup, codeGlobal]
  321. def generateMethodNodeCode(node):
  322. code = []
  323. code.append("UA_MethodAttributes attr = UA_MethodAttributes_default;")
  324. if node.executable:
  325. code.append("attr.executable = true;")
  326. if node.userExecutable:
  327. code.append("attr.userExecutable = true;")
  328. return code
  329. def generateObjectTypeNodeCode(node):
  330. code = []
  331. code.append("UA_ObjectTypeAttributes attr = UA_ObjectTypeAttributes_default;")
  332. if node.isAbstract:
  333. code.append("attr.isAbstract = true;")
  334. return code
  335. def generateDataTypeNodeCode(node):
  336. code = []
  337. code.append("UA_DataTypeAttributes attr = UA_DataTypeAttributes_default;")
  338. if node.isAbstract:
  339. code.append("attr.isAbstract = true;")
  340. return code
  341. def generateViewNodeCode(node):
  342. code = []
  343. code.append("UA_ViewAttributes attr = UA_ViewAttributes_default;")
  344. if node.containsNoLoops:
  345. code.append("attr.containsNoLoops = true;")
  346. code.append("attr.eventNotifier = (UA_Byte)%s;" % str(node.eventNotifier))
  347. return code
  348. def getNodeTypeDefinition(node):
  349. for ref in node.references:
  350. # 40 = HasTypeDefinition
  351. if ref.referenceType.i == 40:
  352. return ref.target
  353. return None
  354. def generateSubtypeOfDefinitionCode(node):
  355. for ref in node.inverseReferences:
  356. # 45 = HasSubtype
  357. if ref.referenceType.i == 45:
  358. return generateNodeIdCode(ref.target)
  359. return "UA_NODEID_NULL"
  360. def generateNodeCode_begin(node, nodeset, generate_ns0, parentref, encode_binary_size, code_global):
  361. code = []
  362. codeCleanup = []
  363. code.append("UA_StatusCode retVal = UA_STATUSCODE_GOOD;")
  364. # Attributes
  365. if isinstance(node, ReferenceTypeNode):
  366. code.extend(generateReferenceTypeNodeCode(node))
  367. elif isinstance(node, ObjectNode):
  368. code.extend(generateObjectNodeCode(node))
  369. elif isinstance(node, VariableNode) and not isinstance(node, VariableTypeNode):
  370. [code1, codeCleanup1, codeGlobal1] = generateVariableNodeCode(node, nodeset, encode_binary_size)
  371. code.extend(code1)
  372. codeCleanup.extend(codeCleanup1)
  373. code_global.extend(codeGlobal1)
  374. elif isinstance(node, VariableTypeNode):
  375. [code1, codeCleanup1, codeGlobal1] = generateVariableTypeNodeCode(node, nodeset, encode_binary_size)
  376. code.extend(code1)
  377. codeCleanup.extend(codeCleanup1)
  378. code_global.extend(codeGlobal1)
  379. elif isinstance(node, MethodNode):
  380. code.extend(generateMethodNodeCode(node))
  381. elif isinstance(node, ObjectTypeNode):
  382. code.extend(generateObjectTypeNodeCode(node))
  383. elif isinstance(node, DataTypeNode):
  384. code.extend(generateDataTypeNodeCode(node))
  385. elif isinstance(node, ViewNode):
  386. code.extend(generateViewNodeCode(node))
  387. code.append("attr.displayName = " + generateLocalizedTextCode(node.displayName, alloc=False) + ";")
  388. code.append("#ifdef UA_ENABLE_NODESET_COMPILER_DESCRIPTIONS")
  389. code.append("attr.description = " + generateLocalizedTextCode(node.description, alloc=False) + ";")
  390. code.append("#endif")
  391. code.append("attr.writeMask = %d;" % node.writeMask)
  392. code.append("attr.userWriteMask = %d;" % node.userWriteMask)
  393. # AddNodes call
  394. code.append("retVal |= UA_Server_addNode_begin(server, UA_NODECLASS_{},".
  395. format(makeCIdentifier(node.__class__.__name__.upper().replace("NODE" ,""))))
  396. code.append(generateNodeIdCode(node.id) + ",")
  397. code.append(generateNodeIdCode(parentref.target) + ",")
  398. code.append(generateNodeIdCode(parentref.referenceType) + ",")
  399. code.append(generateQualifiedNameCode(node.browseName) + ",")
  400. if isinstance(node, VariableNode) or isinstance(node, ObjectNode):
  401. typeDefRef = node.popTypeDef()
  402. code.append(generateNodeIdCode(typeDefRef.target) + ",")
  403. else:
  404. code.append(" UA_NODEID_NULL,")
  405. code.append("(const UA_NodeAttributes*)&attr, &UA_TYPES[UA_TYPES_{}ATTRIBUTES],NULL, NULL);".
  406. format(makeCIdentifier(node.__class__.__name__.upper().replace("NODE" ,""))))
  407. code.extend(codeCleanup)
  408. return "\n".join(code)
  409. def generateNodeCode_finish(node):
  410. code = []
  411. if isinstance(node, MethodNode):
  412. code.append("UA_Server_addMethodNode_finish(server, ")
  413. else:
  414. code.append("UA_Server_addNode_finish(server, ")
  415. code.append(generateNodeIdCode(node.id))
  416. if isinstance(node, MethodNode):
  417. code.append(", NULL, 0, NULL, 0, NULL);")
  418. else:
  419. code.append(");")
  420. return "\n".join(code)