backend_open62541.py 11 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320
  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. ### Copyright 2014-2015 (c) TU-Dresden (Author: Chris Iatrou)
  7. ### Copyright 2014-2017 (c) Fraunhofer IOSB (Author: Julius Pfrommer)
  8. ### Copyright 2016-2017 (c) Stefan Profanter, fortiss GmbH
  9. from __future__ import print_function
  10. from os.path import basename
  11. import logging
  12. import codecs
  13. import os
  14. try:
  15. from StringIO import StringIO
  16. except ImportError:
  17. from io import StringIO
  18. import sys
  19. if sys.version_info[0] >= 3:
  20. # strings are already parsed to unicode
  21. def unicode(s):
  22. return s
  23. logger = logging.getLogger(__name__)
  24. from datatypes import NodeId
  25. from nodes import *
  26. from nodeset import *
  27. from backend_open62541_nodes import generateNodeCode_begin, generateNodeCode_finish, generateReferenceCode
  28. # Kahn's algorithm: https://algocoding.wordpress.com/2015/04/05/topological-sorting-python/
  29. def sortNodes(nodeset):
  30. # reverse hastypedefinition references to treat only forward references
  31. hasTypeDef = NodeId("ns=0;i=40")
  32. for u in nodeset.nodes.values():
  33. for ref in u.references:
  34. if ref.referenceType == hasTypeDef:
  35. ref.isForward = not ref.isForward
  36. # Only hierarchical types...
  37. relevant_refs = nodeset.getRelevantOrderingReferences()
  38. # determine in-degree of unfulfilled references
  39. L = [node for node in nodeset.nodes.values() if node.hidden] # ordered list of nodes
  40. R = {node.id: node for node in nodeset.nodes.values() if not node.hidden} # remaining nodes
  41. in_degree = {id: 0 for id in R.keys()}
  42. for u in R.values(): # for each node
  43. for ref in u.references:
  44. if not ref.referenceType in relevant_refs:
  45. continue
  46. if nodeset.nodes[ref.target].hidden:
  47. continue
  48. if ref.isForward:
  49. continue
  50. in_degree[u.id] += 1
  51. # Print ReferenceType and DataType nodes first. They may be required even
  52. # though there is no reference to them. For example if the referencetype is
  53. # used in a reference, it must exist. A Variable node may point to a
  54. # DataTypeNode in the datatype attribute and not via an explicit reference.
  55. Q = [node for node in R.values() if in_degree[node.id] == 0 and
  56. (isinstance(node, ReferenceTypeNode) or isinstance(node, DataTypeNode))]
  57. while Q:
  58. u = Q.pop() # choose node of zero in-degree and 'remove' it from graph
  59. L.append(u)
  60. del R[u.id]
  61. for ref in u.references:
  62. if not ref.referenceType in relevant_refs:
  63. continue
  64. if nodeset.nodes[ref.target].hidden:
  65. continue
  66. if not ref.isForward:
  67. continue
  68. in_degree[ref.target] -= 1
  69. if in_degree[ref.target] == 0:
  70. Q.append(R[ref.target])
  71. # Order the remaining nodes
  72. Q = [node for node in R.values() if in_degree[node.id] == 0]
  73. while Q:
  74. u = Q.pop() # choose node of zero in-degree and 'remove' it from graph
  75. L.append(u)
  76. del R[u.id]
  77. for ref in u.references:
  78. if not ref.referenceType in relevant_refs:
  79. continue
  80. if nodeset.nodes[ref.target].hidden:
  81. continue
  82. if not ref.isForward:
  83. continue
  84. in_degree[ref.target] -= 1
  85. if in_degree[ref.target] == 0:
  86. Q.append(R[ref.target])
  87. # reverse hastype references
  88. for u in nodeset.nodes.values():
  89. for ref in u.references:
  90. if ref.referenceType == hasTypeDef:
  91. ref.isForward = not ref.isForward
  92. if len(L) != len(nodeset.nodes.values()):
  93. print(len(L))
  94. stillOpen = ""
  95. for id in in_degree:
  96. if in_degree[id] == 0:
  97. continue
  98. node = nodeset.nodes[id]
  99. stillOpen += node.browseName.name + "/" + str(node.id) + " = " + str(in_degree[id]) + \
  100. " " + str(node.references) + "\r\n"
  101. raise Exception("Node graph is circular on the specified references. Still open nodes:\r\n" + stillOpen)
  102. return L
  103. ###################
  104. # Generate C Code #
  105. ###################
  106. def generateOpen62541Code(nodeset, outfilename, internal_headers=False, typesArray=[]):
  107. outfilebase = basename(outfilename)
  108. # Printing functions
  109. outfileh = codecs.open(outfilename + ".h", r"w+", encoding='utf-8')
  110. outfilec = StringIO()
  111. def writeh(line):
  112. print(unicode(line), end='\n', file=outfileh)
  113. def writec(line):
  114. print(unicode(line), end='\n', file=outfilec)
  115. additionalHeaders = ""
  116. if len(typesArray) > 0:
  117. for arr in set(typesArray):
  118. if arr == "UA_TYPES":
  119. continue
  120. # remove ua_ prefix if exists
  121. typeFile = arr.lower()
  122. typeFile = typeFile[typeFile.startswith("ua_") and len("ua_"):]
  123. additionalHeaders += """#include "%s_generated.h"\n""" % typeFile
  124. # Print the preamble of the generated code
  125. writeh("""/* WARNING: This is a generated file.
  126. * Any manual changes will be overwritten. */
  127. #ifndef %s_H_
  128. #define %s_H_
  129. """ % (outfilebase.upper(), outfilebase.upper()))
  130. if internal_headers:
  131. writeh("""
  132. #ifdef UA_ENABLE_AMALGAMATION
  133. # include "open62541.h"
  134. /* The following declarations are in the open62541.c file so here's needed when compiling nodesets externally */
  135. # ifndef UA_INTERNAL //this definition is needed to hide this code in the amalgamated .c file
  136. typedef UA_StatusCode (*UA_exchangeEncodeBuffer)(void *handle, UA_Byte **bufPos,
  137. const UA_Byte **bufEnd);
  138. UA_StatusCode
  139. UA_encodeBinary(const void *src, const UA_DataType *type,
  140. UA_Byte **bufPos, const UA_Byte **bufEnd,
  141. UA_exchangeEncodeBuffer exchangeCallback,
  142. void *exchangeHandle) UA_FUNC_ATTR_WARN_UNUSED_RESULT;
  143. UA_StatusCode
  144. UA_decodeBinary(const UA_ByteString *src, size_t *offset, void *dst,
  145. const UA_DataType *type, size_t customTypesSize,
  146. const UA_DataType *customTypes) UA_FUNC_ATTR_WARN_UNUSED_RESULT;
  147. size_t
  148. UA_calcSizeBinary(void *p, const UA_DataType *type);
  149. const UA_DataType *
  150. UA_findDataTypeByBinary(const UA_NodeId *typeId);
  151. # endif // UA_INTERNAL
  152. #else // UA_ENABLE_AMALGAMATION
  153. # include <open62541/server.h>
  154. #endif
  155. %s
  156. """ % (additionalHeaders))
  157. else:
  158. writeh("""
  159. #ifdef UA_ENABLE_AMALGAMATION
  160. # include "open62541.h"
  161. #else
  162. # include <open62541/server.h>
  163. #endif
  164. %s
  165. """ % (additionalHeaders))
  166. writeh("""
  167. _UA_BEGIN_DECLS
  168. extern UA_StatusCode %s(UA_Server *server);
  169. _UA_END_DECLS
  170. #endif /* %s_H_ */""" % \
  171. (outfilebase, outfilebase.upper()))
  172. writec("""/* WARNING: This is a generated file.
  173. * Any manual changes will be overwritten. */
  174. #include "%s.h"
  175. """ % (outfilebase))
  176. # Loop over the sorted nodes
  177. logger.info("Reordering nodes for minimal dependencies during printing")
  178. sorted_nodes = sortNodes(nodeset)
  179. logger.info("Writing code for nodes and references")
  180. functionNumber = 0
  181. printed_ids = set()
  182. for node in sorted_nodes:
  183. printed_ids.add(node.id)
  184. if not node.hidden:
  185. writec("\n/* " + str(node.displayName) + " - " + str(node.id) + " */")
  186. code_global = []
  187. code = generateNodeCode_begin(node, nodeset, code_global)
  188. if code is None:
  189. writec("/* Ignored. No parent */")
  190. nodeset.hide_node(node.id)
  191. continue
  192. else:
  193. if len(code_global) > 0:
  194. writec("\n".join(code_global))
  195. writec("\n")
  196. writec("\nstatic UA_StatusCode function_" + outfilebase + "_" + str(functionNumber) + "_begin(UA_Server *server, UA_UInt16* ns) {")
  197. if isinstance(node, MethodNode):
  198. writec("#ifdef UA_ENABLE_METHODCALLS")
  199. writec(code)
  200. # Print inverse references leading to this node
  201. for ref in node.references:
  202. if ref.target not in printed_ids:
  203. continue
  204. if node.hidden and nodeset.nodes[ref.target].hidden:
  205. continue
  206. if node.parent is not None and ref.target == node.parent.id \
  207. and ref.referenceType == node.parentReference.id:
  208. # Skip parent reference
  209. continue
  210. writec(generateReferenceCode(ref))
  211. if node.hidden:
  212. continue
  213. writec("return retVal;")
  214. if isinstance(node, MethodNode):
  215. writec("#else")
  216. writec("return UA_STATUSCODE_GOOD;")
  217. writec("#endif /* UA_ENABLE_METHODCALLS */")
  218. writec("}");
  219. writec("\nstatic UA_StatusCode function_" + outfilebase + "_" + str(functionNumber) + "_finish(UA_Server *server, UA_UInt16* ns) {")
  220. if isinstance(node, MethodNode):
  221. writec("#ifdef UA_ENABLE_METHODCALLS")
  222. writec("return " + generateNodeCode_finish(node))
  223. if isinstance(node, MethodNode):
  224. writec("#else")
  225. writec("return UA_STATUSCODE_GOOD;")
  226. writec("#endif /* UA_ENABLE_METHODCALLS */")
  227. writec("}");
  228. functionNumber = functionNumber + 1
  229. writec("""
  230. UA_StatusCode %s(UA_Server *server) {
  231. UA_StatusCode retVal = UA_STATUSCODE_GOOD;""" % (outfilebase))
  232. # Generate namespaces (don't worry about duplicates)
  233. writec("/* Use namespace ids generated by the server */")
  234. writec("UA_UInt16 ns[" + str(len(nodeset.namespaces)) + "];")
  235. for i, nsid in enumerate(nodeset.namespaces):
  236. nsid = nsid.replace("\"", "\\\"")
  237. writec("ns[" + str(i) + "] = UA_Server_addNamespace(server, \"" + nsid + "\");")
  238. if functionNumber > 0:
  239. # concatenate method calls with "&&" operator.
  240. # The first method which does not return UA_STATUSCODE_GOOD (=0) will cause aborting
  241. # the remaining calls and retVal will be set to that error code.
  242. writec("bool dummy = (")
  243. for i in range(0, functionNumber):
  244. writec("!(retVal = function_{outfilebase}_{idx}_begin(server, ns)) &&".format(
  245. outfilebase=outfilebase, idx=str(i)))
  246. for i in reversed(range(0, functionNumber)):
  247. writec("!(retVal = function_{outfilebase}_{idx}_finish(server, ns)) {concat}".format(
  248. outfilebase=outfilebase, idx=str(i), concat= "&&" if i>0 else ""))
  249. # use (void)(dummy) to avoid unused variable error.
  250. writec("); (void)(dummy);")
  251. writec("return retVal;\n}")
  252. outfileh.flush()
  253. os.fsync(outfileh)
  254. outfileh.close()
  255. fullCode = outfilec.getvalue()
  256. outfilec.close()
  257. outfilec = codecs.open(outfilename + ".c", r"w+", encoding='utf-8')
  258. outfilec.write(fullCode)
  259. outfilec.flush()
  260. os.fsync(outfilec)
  261. outfilec.close()