generate_builtin.py 14 KB

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