backend_open62541.py 11 KB

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