generate_builtin.py 14 KB

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