generate_builtin.py 15 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416
  1. from __future__ import print_function
  2. import sys
  3. import time
  4. import platform
  5. import getpass
  6. from collections import OrderedDict
  7. import re
  8. from lxml import etree
  9. import inspect
  10. import argparse
  11. #####################
  12. # Utility functions #
  13. #####################
  14. print(sys.argv)
  15. parser = argparse.ArgumentParser()
  16. parser.add_argument('--export-prototypes', action='store_true', help='make the prototypes (init, delete, copy, ..) of generated types visible for users of the library')
  17. parser.add_argument('--with-xml', action='store_true', help='generate xml encoding')
  18. parser.add_argument('--with-json', action='store_true', help='generate json encoding')
  19. parser.add_argument('--only-nano', action='store_true', help='generate only the types for the nano profile')
  20. parser.add_argument('--only-needed', action='store_true', help='generate only types needed for compile')
  21. parser.add_argument('--additional-includes', action='store', help='include additional header files (separated by comma)')
  22. parser.add_argument('xml', help='path/to/Opc.Ua.Types.bsd')
  23. parser.add_argument('outfile', help='outfile w/o extension')
  24. args = parser.parse_args()
  25. outname = args.outfile.split("/")[-1]
  26. inname = args.xml.split("/")[-1]
  27. ns = {"opc": "http://opcfoundation.org/BinarySchema/"}
  28. tree = etree.parse(args.xml)
  29. types = tree.xpath("/opc:TypeDictionary/*[not(self::opc:Import)]", namespaces=ns)
  30. fh = open(args.outfile + "_generated.h",'w')
  31. fc = open(args.outfile + "_generated.c",'w')
  32. # dirty hack. we go up the call frames to access local variables of the calling
  33. # function. this allows to shorten code and get %()s replaces with less clutter.
  34. def printh(string):
  35. print(string % inspect.currentframe().f_back.f_locals, end='\n', file=fh)
  36. def printc(string):
  37. print(string % inspect.currentframe().f_back.f_locals, end='\n', file=fc)
  38. # types that are coded manually
  39. from type_lists import existing_types
  40. # whitelist for "only needed" profile
  41. from type_lists import only_needed_types
  42. # some types are omitted (pretend they exist already)
  43. existing_types.add("NodeIdType")
  44. fixed_size = {"UA_Boolean": 1, "UA_SByte": 1, "UA_Byte": 1, "UA_Int16": 2, "UA_UInt16": 2,
  45. "UA_Int32": 4, "UA_UInt32": 4, "UA_Int64": 8, "UA_UInt64": 8, "UA_Float": 4,
  46. "UA_Double": 8, "UA_DateTime": 8, "UA_Guid": 16, "UA_StatusCode": 4}
  47. # types we do not want to autogenerate
  48. def skipType(name):
  49. if name in existing_types:
  50. return True
  51. if "Test" in name: #skip all Test types
  52. return True
  53. if re.search("NodeId$", name) != None:
  54. return True
  55. if args.only_needed and not(name in only_needed_types):
  56. return True
  57. return False
  58. def stripTypename(tn):
  59. return tn[tn.find(":")+1:]
  60. def camlCase2CCase(item):
  61. if item in ["Float","Double"]:
  62. return "my" + item
  63. return item[:1].lower() + item[1:] if item else ''
  64. ################################
  65. # Type Definition and Encoding #
  66. ################################
  67. # are the types we need already in place? if not, postpone.
  68. def printableStructuredType(element):
  69. for child in element:
  70. if child.tag == "{http://opcfoundation.org/BinarySchema/}Field":
  71. typename = stripTypename(child.get("TypeName"))
  72. if typename not in existing_types:
  73. return False
  74. return True
  75. def printPrototypes(name):
  76. if args.export_prototypes:
  77. printh("UA_TYPE_PROTOTYPES(%(name)s)")
  78. else:
  79. printh("UA_TYPE_PROTOTYPES_NOEXPORT(%(name)s)")
  80. printh("UA_TYPE_BINARY_ENCODING(%(name)s)")
  81. if args.with_xml:
  82. printh("UA_TYPE_XML_ENCODING(%(name)s)")
  83. def createEnumerated(element):
  84. valuemap = OrderedDict()
  85. name = "UA_" + element.get("Name")
  86. fixed_size[name] = 4
  87. printh("") # newline
  88. for child in element:
  89. if child.tag == "{http://opcfoundation.org/BinarySchema/}Documentation":
  90. printh("/** @brief " + child.text + " */")
  91. if child.tag == "{http://opcfoundation.org/BinarySchema/}EnumeratedValue":
  92. valuemap[name + "_" + child.get("Name")] = child.get("Value")
  93. valuemap = OrderedDict(sorted(valuemap.iteritems(), key=lambda (k,v): int(v)))
  94. # printh("typedef UA_Int32 " + name + ";")
  95. printh("typedef enum { \n\t" +
  96. ",\n\t".join(map(lambda (key, value) : key.upper() + " = " + value, valuemap.iteritems())) +
  97. "\n} " + name + ";")
  98. printPrototypes(name)
  99. printc("UA_TYPE_AS(" + name + ", UA_Int32)")
  100. printc("UA_TYPE_BINARY_ENCODING_AS(" + name + ", UA_Int32)")
  101. if args.with_xml:
  102. printh("UA_TYPE_XML_ENCODING(" + name + ")\n")
  103. printc('''UA_TYPE_METHOD_CALCSIZEXML_NOTIMPL(%(name)s)
  104. UA_TYPE_METHOD_ENCODEXML_NOTIMPL(%(name)s)
  105. UA_TYPE_METHOD_DECODEXML_NOTIMPL(%(name)s\n)''')
  106. def createOpaque(element):
  107. name = "UA_" + element.get("Name")
  108. printh("") # newline
  109. for child in element:
  110. if child.tag == "{http://opcfoundation.org/BinarySchema/}Documentation":
  111. printh("/** @brief " + child.text + " */")
  112. printh("typedef UA_ByteString %(name)s;")
  113. printPrototypes(name)
  114. printc("UA_TYPE_AS(%(name)s, UA_ByteString)")
  115. printc("UA_TYPE_BINARY_ENCODING_AS(%(name)s, UA_ByteString)")
  116. if args.with_xml:
  117. printc('''UA_TYPE_METHOD_CALCSIZEXML_NOTIMPL(%(name)s)
  118. UA_TYPE_METHOD_ENCODEXML_NOTIMPL(%(name)s)
  119. UA_TYPE_METHOD_DECODEXML_NOTIMPL(%(name)s)\n''')
  120. def createStructured(element):
  121. name = "UA_" + element.get("Name")
  122. # 1) Are there arrays in the type?
  123. lengthfields = set()
  124. for child in element:
  125. if child.get("LengthField"):
  126. lengthfields.add(child.get("LengthField"))
  127. # 2) Store members in membermap (name->type).
  128. membermap = OrderedDict()
  129. printh("") # newline
  130. for child in element:
  131. if child.tag == "{http://opcfoundation.org/BinarySchema/}Documentation":
  132. printh("/** @brief " + child.text + " */")
  133. elif child.tag == "{http://opcfoundation.org/BinarySchema/}Field":
  134. if child.get("Name") in lengthfields:
  135. continue
  136. childname = camlCase2CCase(child.get("Name"))
  137. typename = stripTypename(child.get("TypeName"))
  138. if child.get("LengthField"):
  139. membermap[childname] = "UA_" + typename + "*"
  140. else:
  141. membermap[childname] = "UA_" + typename
  142. # fixed size?
  143. is_fixed_size = True
  144. this_fixed_size = 0
  145. for n,t in membermap.iteritems():
  146. if t not in fixed_size.keys():
  147. is_fixed_size = False
  148. else:
  149. this_fixed_size += fixed_size[t]
  150. if is_fixed_size:
  151. fixed_size[name] = this_fixed_size
  152. # 3) Print structure
  153. if len(membermap) > 0:
  154. printh("typedef struct {")
  155. for n,t in membermap.iteritems():
  156. if t.find("*") != -1:
  157. printh("\t" + "UA_Int32 " + n + "Size;")
  158. printh("\t%(t)s %(n)s;")
  159. printh("} %(name)s;")
  160. else:
  161. printh("typedef void* %(name)s;")
  162. # 3) function prototypes
  163. printPrototypes(name)
  164. # 4) CalcSizeBinary
  165. printc('''UA_UInt32 %(name)s_calcSizeBinary(%(name)s const * ptr) {
  166. return 0''')
  167. if is_fixed_size:
  168. printc('''+ %(this_fixed_size)s''')
  169. else:
  170. for n,t in membermap.iteritems():
  171. if t in fixed_size:
  172. printc('\t + ' + str(fixed_size[t]) + ' // %(n)s')
  173. elif t.find("*") != -1:
  174. printc('\t + UA_Array_calcSizeBinary(ptr->%(n)sSize,&UA_TYPES[' +
  175. t[0:t.find("*")].upper() + "],ptr->%(n)s)")
  176. else:
  177. printc('\t + %(t)s_calcSizeBinary(&ptr->%(n)s)')
  178. printc("\t;\n}\n")
  179. # 5) EncodeBinary
  180. printc('''UA_StatusCode %(name)s_encodeBinary(%(name)s const * src, UA_ByteString* dst, UA_UInt32 *offset) {
  181. UA_StatusCode retval = UA_STATUSCODE_GOOD;''')
  182. for n,t in membermap.iteritems():
  183. if t.find("*") != -1:
  184. printc("\tretval |= UA_Array_encodeBinary(src->%(n)s,src->%(n)sSize,&UA_TYPES[" + t[0:t.find("*")].upper() + "],dst,offset);")
  185. else:
  186. printc('\tretval |= %(t)s_encodeBinary(&src->%(n)s,dst,offset);')
  187. printc("\treturn retval;\n}\n")
  188. # 6) DecodeBinary
  189. printc('''UA_StatusCode %(name)s_decodeBinary(UA_ByteString const * src, UA_UInt32 *offset, %(name)s * dst) {
  190. UA_StatusCode retval = UA_STATUSCODE_GOOD;
  191. %(name)s_init(dst);''')
  192. printc('\t'+name+'_init(dst);')
  193. for n,t in membermap.iteritems():
  194. if t.find("*") != -1:
  195. printc('\tretval |= UA_Int32_decodeBinary(src,offset,&dst->%(n)sSize);')
  196. printc('\tif(!retval) { retval |= UA_Array_decodeBinary(src,offset,dst->%(n)sSize,&UA_TYPES[' + t[0:t.find("*")].upper() + '],(void**)&dst->%(n)s); }')
  197. printc('\tif(retval) { dst->%(n)sSize = -1; }') # arrays clean up internally. But the size needs to be set here for the eventual deleteMembers.
  198. else:
  199. printc('\tretval |= %(t)s_decodeBinary(src,offset,&dst->%(n)s);')
  200. printc("\tif(retval) %(name)s_deleteMembers(dst);")
  201. printc("\treturn retval;\n}\n")
  202. # 7) Xml
  203. if args.with_xml:
  204. printc('''UA_TYPE_METHOD_CALCSIZEXML_NOTIMPL(%(name)s)
  205. UA_TYPE_METHOD_ENCODEXML_NOTIMPL(%(name)s)
  206. UA_TYPE_METHOD_DECODEXML_NOTIMPL(%(name)s)''')
  207. # 8) Delete
  208. printc('''void %(name)s_delete(%(name)s *p) {
  209. %(name)s_deleteMembers(p);
  210. UA_free(p);\n}\n''')
  211. # 9) DeleteMembers
  212. printc('''void %(name)s_deleteMembers(%(name)s *p) {''')
  213. for n,t in membermap.iteritems():
  214. if not t in fixed_size: # dynamic size on the wire
  215. if t.find("*") != -1:
  216. printc("\tUA_Array_delete((void*)p->%(n)s,p->%(n)sSize,&UA_TYPES["+t[0:t.find("*")].upper()+"]);")
  217. else:
  218. printc('\t%(t)s_deleteMembers(&p->%(n)s);')
  219. printc("}\n")
  220. # 10) Init
  221. printc('''void %(name)s_init(%(name)s *p) {
  222. if(!p) return;''')
  223. for n,t in membermap.iteritems():
  224. if t.find("*") != -1:
  225. printc('\tp->%(n)sSize = -1;')
  226. printc('\tp->%(n)s = UA_NULL;')
  227. else:
  228. printc('\t%(t)s_init(&p->%(n)s);')
  229. printc("}\n")
  230. # 11) New
  231. printc("UA_TYPE_NEW_DEFAULT(%(name)s)")
  232. # 12) Copy
  233. printc('''UA_StatusCode %(name)s_copy(const %(name)s *src,%(name)s *dst) {
  234. UA_StatusCode retval = UA_STATUSCODE_GOOD;''')
  235. printc("\t%(name)s_init(dst);")
  236. for n,t in membermap.iteritems():
  237. if t.find("*") != -1:
  238. printc('\tdst->%(n)sSize = src->%(n)sSize;')
  239. printc("\tretval |= UA_Array_copy(src->%(n)s, src->%(n)sSize,&UA_TYPES[" + t[0:t.find("*")].upper() + "],(void**)&dst->%(n)s);")
  240. continue
  241. if not t in fixed_size: # there are members of variable size
  242. printc('\tretval |= %(t)s_copy(&src->%(n)s,&dst->%(n)s);')
  243. continue
  244. printc("\tdst->%(n)s = src->%(n)s;")
  245. printc('''\tif(retval)
  246. \t%(name)s_deleteMembers(dst);''')
  247. printc("\treturn retval;\n}\n")
  248. # 13) Print
  249. printc('''#ifdef UA_DEBUG''')
  250. printc('''void %(name)s_print(const %(name)s *p, FILE *stream) {
  251. fprintf(stream, "(%(name)s){");''')
  252. for i,(n,t) in enumerate(membermap.iteritems()):
  253. if t.find("*") != -1:
  254. printc('\tUA_Int32_print(&p->%(n)sSize, stream);')
  255. printc("\tUA_Array_print(p->%(n)s, p->%(n)sSize, &UA_TYPES[" + t[0:t.find("*")].upper()+"], stream);")
  256. else:
  257. printc('\t%(t)s_print(&p->%(n)s,stream);')
  258. if i == len(membermap)-1:
  259. continue
  260. printc('\tfprintf(stream, ",");')
  261. printc('''\tfprintf(stream, "}");\n}''')
  262. printc('#endif');
  263. printc('''\n''')
  264. ##########################
  265. # Header and Boilerplate #
  266. ##########################
  267. printh('''/**
  268. * @file %(outname)s_generated.h
  269. *
  270. * @brief Autogenerated data types defined in the UA standard
  271. *
  272. * Generated from %(inname)s with script ''' + sys.argv[0] + '''
  273. * on host ''' + platform.uname()[1] + ''' by user ''' + getpass.getuser() + ''' at ''' + time.strftime("%Y-%m-%d %I:%M:%S") + '''
  274. */
  275. #ifndef ''' + outname.upper() + '''_GENERATED_H_
  276. #define ''' + outname.upper() + '''_GENERATED_H_
  277. #ifdef __cplusplus
  278. extern "C" {
  279. #endif
  280. #include "ua_types.h"
  281. #include "ua_types_encoding_binary.h"
  282. /**
  283. * @ingroup types
  284. *
  285. * @defgroup ''' + outname + '''_generated Autogenerated ''' + outname + ''' Types
  286. *
  287. * @brief Data structures that are autogenerated from an XML-Schema.
  288. * @{
  289. */''')
  290. if args.with_xml:
  291. printh('#include "ua_types_encoding_xml.h"')
  292. if args.additional_includes:
  293. for incl in args.additional_includes.split(","):
  294. printh("#include \"" + incl + "\"")
  295. printc('''/**
  296. * @file ''' + outname + '''_generated.c
  297. *
  298. * @brief Autogenerated function implementations to manage the data types defined in the UA standard
  299. *
  300. * Generated from %(inname)s with script '''+sys.argv[0]+'''
  301. * on host '''+platform.uname()[1]+''' by user '''+getpass.getuser()+''' at '''+ time.strftime("%Y-%m-%d %I:%M:%S")+'''
  302. */
  303. #include "%(outname)s_generated.h"
  304. #include "ua_types_macros.h"
  305. #include "ua_namespace_0.h"
  306. #include "ua_util.h"\n''')
  307. ##############################
  308. # Loop over types in the XML #
  309. ##############################
  310. # # plugin handling
  311. # import os
  312. # files = [f for f in os.listdir('.') if os.path.isfile(f) and f[-3:] == ".py" and f[:7] == "plugin_"]
  313. # plugin_types = []
  314. # packageForType = OrderedDict()
  315. #
  316. # for f in files:
  317. # package = f[:-3]
  318. # exec "import " + package
  319. # exec "pluginSetup = " + package + ".setup()"
  320. # if pluginSetup["pluginType"] == "structuredObject":
  321. # plugin_types.append(pluginSetup["tagName"])
  322. # packageForType[pluginSetup["tagName"]] = [package,pluginSetup]
  323. # print("Custom object creation for tag " + pluginSetup["tagName"] + " imported from package " + package)
  324. deferred_types = OrderedDict()
  325. for element in types:
  326. name = element.get("Name")
  327. if skipType(name):
  328. continue
  329. if element.tag == "{http://opcfoundation.org/BinarySchema/}EnumeratedType":
  330. createEnumerated(element)
  331. existing_types.add(name)
  332. elif element.tag == "{http://opcfoundation.org/BinarySchema/}StructuredType":
  333. if printableStructuredType(element):
  334. createStructured(element)
  335. existing_types.add(name)
  336. else: # the record contains types that were not yet detailed
  337. deferred_types[name] = element
  338. continue
  339. elif element.tag == "{http://opcfoundation.org/BinarySchema/}OpaqueType":
  340. createOpaque(element)
  341. existing_types.add(name)
  342. for name, element in deferred_types.iteritems():
  343. # if name in plugin_types:
  344. # #execute plugin if registered
  345. # exec "ret = " + packageForType[name][0]+"."+packageForType[name][1]["functionCall"]
  346. # if ret == "default":
  347. # createStructured(element)
  348. # existing_types.add(name)
  349. # else:
  350. createStructured(element)
  351. existing_types.add(name)
  352. #############
  353. # Finish Up #
  354. #############
  355. printh('''
  356. #ifdef __cplusplus
  357. } // extern "C"
  358. #endif
  359. /// @} /* end of group */''')
  360. printh('#endif')
  361. fh.close()
  362. fc.close()