generate_builtin.py 14 KB

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