generate_datatypes.py 19 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466
  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 itertools
  10. import argparse
  11. fixed_size = {"UA_Boolean": 1, "UA_SByte": 1, "UA_Byte": 1, "UA_Int16": 2, "UA_UInt16": 2,
  12. "UA_Int32": 4, "UA_UInt32": 4, "UA_Int64": 8, "UA_UInt64": 8, "UA_Float": 4,
  13. "UA_Double": 8, "UA_DateTime": 8, "UA_Guid": 16, "UA_StatusCode": 4}
  14. zero_copy = ["UA_Boolean", "UA_SByte", "UA_Byte", "UA_Int16", "UA_UInt16", "UA_Int32", "UA_UInt32",
  15. "UA_Int64", "UA_UInt64", "UA_Float", "UA_Double", "UA_DateTime", "UA_StatusCode"]
  16. # The order of the builtin-types is not as in the standard. We put all the
  17. # fixed_size types in the front, so they can be distinguished by a simple geq
  18. # comparison. That's ok, since we use the type-index only internally!!
  19. builtin_types = ["UA_Boolean", "UA_SByte", "UA_Byte", # 1 byte
  20. "UA_Int16", "UA_UInt16", # 2 bytes
  21. "UA_Int32", "UA_UInt32", "UA_StatusCode", "UA_Float", # 4 byte
  22. "UA_Int64", "UA_UInt64", "UA_Double", "UA_DateTime", # 8 byte
  23. "UA_Guid", "UA_NodeId", "UA_ExpandedNodeId", "UA_QualifiedName", "UA_LocalizedText", "UA_ExtensionObject", "UA_DataValue", "UA_Variant", "UA_DiagnosticInfo", # fancy types
  24. "UA_String", "UA_ByteString", "UA_XmlElement" # strings (handled as structured types with a single array entry)
  25. ]
  26. excluded_types = ["UA_NodeIdType", "UA_InstanceNode", "UA_TypeNode", "UA_Node", "UA_ObjectNode",
  27. "UA_ObjectTypeNode", "UA_VariableNode", "UA_VariableTypeNode", "UA_ReferenceTypeNode",
  28. "UA_MethodNode", "UA_ViewNode", "UA_DataTypeNode", "UA_ServerDiagnosticsSummaryDataType",
  29. "UA_SamplingIntervalDiagnosticsDataType", "UA_SessionSecurityDiagnosticsDataType",
  30. "UA_SubscriptionDiagnosticsDataType", "UA_SessionDiagnosticsDataType"]
  31. class TypeDescription(object):
  32. def __init__(self, name, nodeid, namespaceid):
  33. self.name = name # without the UA_ prefix
  34. self.nodeid = nodeid
  35. self.namespaceid = namespaceid
  36. def parseTypeDescriptions(filename, namespaceid):
  37. print(filename)
  38. definitions = {}
  39. f = open(filename[0])
  40. input_str = f.read()
  41. f.close()
  42. input_str = input_str.replace('\r','')
  43. rows = map(lambda x:tuple(x.split(',')), input_str.split('\n'))
  44. for index, row in enumerate(rows):
  45. if len(row) < 3:
  46. continue
  47. if row[2] != "DataType":
  48. continue
  49. if row[0] == "BaseDataType":
  50. definitions["UA_Variant"] = TypeDescription(row[0], row[1], namespaceid)
  51. elif row[0] == "Structure":
  52. definitions["UA_ExtensionObject"] = TypeDescription(row[0], row[1], namespaceid)
  53. else:
  54. definitions["UA_" + row[0]] = TypeDescription(row[0], row[1], namespaceid)
  55. return definitions
  56. class BuiltinType(object):
  57. "Generic type without members. Used for builtin types."
  58. def __init__(self, name, description = ""):
  59. self.name = name
  60. self.description = description
  61. def fixed_size(self):
  62. return self.name in fixed_size
  63. def mem_size(self):
  64. return fixed_size[self.name]
  65. def zero_copy(self):
  66. return self.name in zero_copy
  67. def typedef_c(self):
  68. pass
  69. def typelayout_c(self, namespace_0, outname):
  70. return "{.memSize = sizeof(" + self.name + "), " + \
  71. ".namespaceZero = UA_TRUE, " + \
  72. ".fixedSize = " + ("UA_TRUE" if self.fixed_size() else "UA_FALSE") + \
  73. ", .zeroCopyable = " + ("UA_TRUE" if self.zero_copy() else "UA_FALSE") + \
  74. ", .membersSize = 1,\n\t.members = {{.memberTypeIndex = UA_TYPES_" + self.name[3:].upper() + "," + \
  75. ".namespaceZero = UA_TRUE, .padding = 0, .isArray = UA_FALSE }}, " + \
  76. ".typeIndex = %s }" % (outname.upper() + "_" + self.name[3:].upper())
  77. class EnumerationType(object):
  78. def __init__(self, name, description = "", elements = OrderedDict()):
  79. self.name = name
  80. self.description = description
  81. self.elements = elements # maps a name to an integer value
  82. def append_enum(name, value):
  83. self.elements[name] = value
  84. def fixed_size(self):
  85. return True
  86. def mem_size(self):
  87. return 4
  88. def zero_copy(self):
  89. return True
  90. def typedef_c(self):
  91. return "typedef enum { \n " + \
  92. ",\n ".join(map(lambda (key,value) : key.upper() + " = " + value,self.elements.iteritems())) + \
  93. "\n} " + self.name + ";"
  94. def typelayout_c(self, namespace_0, outname):
  95. return "{.memSize = sizeof(" + self.name + "), " +\
  96. ".namespaceZero = " + ("UA_TRUE" if namespace_0 else "UA_FALSE") + \
  97. ", .fixedSize = UA_TRUE, .zeroCopyable = UA_TRUE, " + \
  98. ".membersSize = 1,\n\t.members = {{.memberTypeIndex = UA_TYPES_INT32," + \
  99. ".namespaceZero = UA_TRUE, .padding = 0, .isArray = UA_FALSE }}, .typeIndex = %s }" % (outname.upper() + "_" + self.name[3:].upper())
  100. def functions_c(self, typeTableName):
  101. return '''#define %s_new (UA_Int32*)UA_Int32_new
  102. #define %s_init(p) UA_Int32_init((UA_Int32*)p)
  103. #define %s_delete(p) UA_Int32_delete((UA_Int32*)p)
  104. #define %s_deleteMembers(p) UA_Int32_deleteMembers((UA_Int32*)p)
  105. #define %s_copy(src, dst) UA_Int32_copy((UA_Int32*)src, (UA_Int32*)dst)
  106. #define %s_calcSizeBinary(p) UA_Int32_calcSizeBinary((UA_Int32*)p)
  107. #define %s_encodeBinary(src, dst, offset) UA_Int32_encodeBinary((UA_Int32*)src, dst, offset)
  108. #define %s_decodeBinary(src, offset, dst) UA_Int32_decodeBinary(src, offset, (UA_Int32*)dst)''' % tuple(itertools.repeat(self.name, 8))
  109. class OpaqueType(object):
  110. def __init__(self, name, description = ""):
  111. self.name = name
  112. self.description = description
  113. def fixed_size(self):
  114. return False
  115. def zero_copy(self):
  116. return False
  117. def typedef_c(self):
  118. return "typedef UA_ByteString " + self.name + ";"
  119. def typelayout_c(self, namespace_0, outname):
  120. return "{.memSize = sizeof(" + self.name + "), .fixedSize = UA_FALSE, .zeroCopyable = UA_FALSE, " + \
  121. ".namespaceZero = " + ("UA_TRUE" if namespace_0 else "UA_FALSE") + \
  122. ", .membersSize = 1,\n\t.members = {{.memberTypeIndex = UA_TYPES_BYTESTRING," + \
  123. ".namespaceZero = UA_TRUE, .padding = 0, .isArray = UA_FALSE }}, .typeIndex = %s }" % (outname.upper() + "_" + self.name[3:].upper())
  124. def functions_c(self, typeTableName):
  125. return '''#define %s_new UA_ByteString_new
  126. #define %s_init UA_ByteString_init
  127. #define %s_delete UA_ByteString_delete
  128. #define %s_deleteMembers UA_ByteString_deleteMembers
  129. #define %s_copy UA_ByteString_copy
  130. #define %s_calcSizeBinary UA_ByteString_calcSizeBinary
  131. #define %s_encodeBinary UA_ByteString_encodeBinary
  132. #define %s_decodeBinary UA_ByteString_decodeBinary''' % tuple(itertools.repeat(self.name, 8))
  133. class StructMember(object):
  134. def __init__(self, name, memberType, isArray):
  135. self.name = name
  136. self.memberType = memberType
  137. self.isArray = isArray
  138. class StructType(object):
  139. def __init__(self, name, description, members = OrderedDict()):
  140. self.name = name
  141. self.description = description
  142. self.members = members # maps a name to a member definition
  143. def fixed_size(self):
  144. for m in self.members.values():
  145. if m.isArray or not m.memberType.fixed_size():
  146. return False
  147. return True
  148. def mem_size(self):
  149. total = 0
  150. for m in self.members.values():
  151. if m.isArray:
  152. raise Exception("Arrays have no fixed size!")
  153. else:
  154. total += m.memberType.mem_size()
  155. return total
  156. def zero_copy(self):
  157. for m in self.members.values():
  158. if m.isArray or not m.memberType.zero_copy():
  159. return False
  160. return True
  161. def typedef_c(self):
  162. if len(self.members) == 0:
  163. return "typedef void * " + self.name + ";"
  164. returnstr = "typedef struct {\n"
  165. for name, member in self.members.iteritems():
  166. if member.isArray:
  167. returnstr += " UA_Int32 " + name + "Size;\n"
  168. returnstr += " " + member.memberType.name + " *" +name + ";\n"
  169. else:
  170. returnstr += " " + member.memberType.name + " " +name + ";\n"
  171. return returnstr + "} " + self.name + ";"
  172. def typelayout_c(self, namespace_0, outname):
  173. layout = "{.memSize = sizeof(" + self.name + "), "+ \
  174. ".namespaceZero = " + ("UA_TRUE" if namespace_0 else "UA_FALSE") + \
  175. ", .fixedSize = " + ("UA_TRUE" if self.fixed_size() else "UA_FALSE") + \
  176. ", .zeroCopyable = " + ("sizeof(" + self.name + ") == " + str(self.mem_size()) if self.zero_copy() \
  177. else "UA_FALSE") + \
  178. ", .typeIndex = " + outname.upper() + "_" + self.name[3:].upper() + \
  179. ", .membersSize = " + str(len(self.members)) + "," + \
  180. "\n\t.members={"
  181. for index, member in enumerate(self.members.values()):
  182. layout += "\n\t{" + \
  183. ".memberTypeIndex = " + ("UA_TYPES_" + member.memberType.name[3:].upper() if args.namespace_id == 0 or member.memberType.name in existing_types else \
  184. outname.upper() + "_" + member.memberType.name[3:].upper()) + ", " + \
  185. ".namespaceZero = "+ \
  186. ("UA_TRUE, " if args.namespace_id == 0 or member.memberType.name in existing_types else "UA_FALSE, ") + \
  187. ".padding = "
  188. before_endpos = "0"
  189. thispos = "offsetof(%s, %s)" % (self.name, member.name)
  190. if index > 0:
  191. before = self.members.values()[index-1]
  192. before_endpos = "(offsetof(%s, %s)" % (self.name, before.name)
  193. if before.isArray:
  194. before_endpos += " + sizeof(void*))"
  195. else:
  196. before_endpos += " + sizeof(%s))" % before.memberType.name
  197. if member.isArray:
  198. # the first two bytes are padding for the length index, the last three for the pointer
  199. length_pos = "offsetof(%s, %sSize)" % (self.name, member.name)
  200. if index != 0:
  201. layout += "((%s - %s) << 3) + " % (length_pos, before_endpos)
  202. layout += "(%s - sizeof(UA_Int32) - %s)" % (thispos, length_pos)
  203. else:
  204. layout += "%s - %s" % (thispos, before_endpos)
  205. layout += ", .isArray = " + ("UA_TRUE" if member.isArray else "UA_FALSE") + " }, "
  206. return layout + "}}"
  207. def functions_c(self, typeTableName):
  208. return '''#define %s_new UA_new(%s)
  209. #define %s_init(p) UA_init(p, %s)
  210. #define %s_delete(p) UA_delete(p, %s)
  211. #define %s_deleteMembers(p) UA_deleteMembers(p, %s)
  212. #define %s_copy(src, dst) UA_copy(src, dst, %s)
  213. #define %s_calcSizeBinary(p) UA_calcSizeBinary(p, %s)
  214. #define %s_encodeBinary(src, dst, offset) UA_encodeBinary(src, %s, dst, offset)
  215. #define %s_decodeBinary(src, offset, dst) UA_decodeBinary(src, offset, dst, %s)''' % \
  216. tuple(itertools.chain(*itertools.repeat([self.name, "&"+typeTableName+"[" + typeTableName + "_" + self.name[3:].upper()+"]"], 8)))
  217. def parseTypeDefinitions(xmlDescription, existing_types = OrderedDict()):
  218. '''Returns an ordered dict that maps names to types. The order is such that
  219. every type depends only on known types. '''
  220. ns = {"opc": "http://opcfoundation.org/BinarySchema/"}
  221. tree = etree.parse(xmlDescription)
  222. typeSnippets = tree.xpath("/opc:TypeDictionary/*[not(self::opc:Import)]", namespaces=ns)
  223. types = OrderedDict(existing_types.items())
  224. # types we do not want to autogenerate
  225. def skipType(name):
  226. if name in builtin_types:
  227. return True
  228. if name in excluded_types:
  229. return True
  230. if "Test" in name: # skip all test types
  231. return True
  232. if re.search("NodeId$", name) != None:
  233. return True
  234. return False
  235. def stripTypename(tn):
  236. return tn[tn.find(":")+1:]
  237. def camlCase2CCase(item):
  238. "Member names begin with a lower case character"
  239. return item[:1].lower() + item[1:] if item else ''
  240. def typeReady(element):
  241. "Do we have the member types yet?"
  242. for child in element:
  243. if child.tag == "{http://opcfoundation.org/BinarySchema/}Field":
  244. if stripTypename(child.get("TypeName")) not in types:
  245. return False
  246. return True
  247. def parseEnumeration(typeXml):
  248. name = "UA_" + typeXml.get("Name")
  249. description = ""
  250. elements = OrderedDict()
  251. for child in typeXml:
  252. if child.tag == "{http://opcfoundation.org/BinarySchema/}Documentation":
  253. description = child.text
  254. if child.tag == "{http://opcfoundation.org/BinarySchema/}EnumeratedValue":
  255. elements[name + "_" + child.get("Name")] = child.get("Value")
  256. return EnumerationType(name, description, elements)
  257. def parseOpaque(typeXml):
  258. name = "UA_" + typeXml.get("Name")
  259. description = ""
  260. for child in typeXml:
  261. if child.tag == "{http://opcfoundation.org/BinarySchema/}Documentation":
  262. description = child.text
  263. return OpaqueType(name, description)
  264. def parseStructured(typeXml):
  265. "Returns None if we miss member descriptions"
  266. name = "UA_" + typeXml.get("Name")
  267. description = ""
  268. for child in typeXml:
  269. if child.tag == "{http://opcfoundation.org/BinarySchema/}Documentation":
  270. description = child.text
  271. # ignore lengthfields, just tag the array-members as an array
  272. lengthfields = []
  273. for child in typeXml:
  274. if child.get("LengthField"):
  275. lengthfields.append(child.get("LengthField"))
  276. members = OrderedDict()
  277. for child in typeXml:
  278. if not child.tag == "{http://opcfoundation.org/BinarySchema/}Field":
  279. continue
  280. if child.get("Name") in lengthfields:
  281. continue
  282. memberTypeName = "UA_" + stripTypename(child.get("TypeName"))
  283. if not memberTypeName in types:
  284. return None
  285. memberType = types[memberTypeName]
  286. memberName = camlCase2CCase(child.get("Name"))
  287. isArray = True if child.get("LengthField") else False
  288. members[memberName] = StructMember(memberName, memberType, isArray)
  289. return StructType(name, description, members)
  290. finished = False
  291. while(not finished):
  292. finished = True
  293. for typeXml in typeSnippets:
  294. name = "UA_" + typeXml.get("Name")
  295. if name in types or skipType(name):
  296. continue
  297. if typeXml.tag == "{http://opcfoundation.org/BinarySchema/}EnumeratedType":
  298. t = parseEnumeration(typeXml)
  299. types[t.name] = t
  300. elif typeXml.tag == "{http://opcfoundation.org/BinarySchema/}OpaqueType":
  301. t = parseOpaque(typeXml)
  302. types[t.name] = t
  303. elif typeXml.tag == "{http://opcfoundation.org/BinarySchema/}StructuredType":
  304. t = parseStructured(typeXml)
  305. if t == None:
  306. finished = False
  307. else:
  308. types[t.name] = t
  309. # remove the existing types
  310. for k in existing_types.keys():
  311. types.pop(k)
  312. return types
  313. parser = argparse.ArgumentParser()
  314. parser.add_argument('--ns0-types-xml', nargs=1, help='xml-definition of the ns0 types that are assumed to already exist')
  315. parser.add_argument('--typedescriptions', nargs=1, help='csv file with type descriptions')
  316. parser.add_argument('namespace_id', type=int, help='the id of the target namespace')
  317. parser.add_argument('types_xml', help='path/to/Opc.Ua.Types.bsd')
  318. parser.add_argument('outfile', help='output file w/o extension')
  319. args = parser.parse_args()
  320. outname = args.outfile.split("/")[-1]
  321. inname = args.types_xml.split("/")[-1]
  322. existing_types = OrderedDict()
  323. if args.namespace_id == 0 or args.ns0_types_xml:
  324. existing_types = OrderedDict([(t, BuiltinType(t)) for t in builtin_types])
  325. if args.ns0_types_xml:
  326. OrderedDict(existing_types.items() + parseTypeDefinitions(args.ns0_types_xml[0], existing_types).items())
  327. types = parseTypeDefinitions(args.types_xml, existing_types)
  328. if args.namespace_id == 0:
  329. types = OrderedDict(existing_types.items() + types.items())
  330. typedescriptions = {}
  331. if args.typedescriptions:
  332. typedescriptions = parseTypeDescriptions(args.typedescriptions, args.namespace_id)
  333. fh = open(args.outfile + "_generated.h",'w')
  334. fc = open(args.outfile + "_generated.c",'w')
  335. def printh(string):
  336. print(string, end='\n', file=fh)
  337. def printc(string):
  338. print(string, end='\n', file=fc)
  339. printh('''/**
  340. * @file ''' + outname + '''_generated.h
  341. *
  342. * @brief Autogenerated data types
  343. *
  344. * Generated from ''' + inname + ''' with script ''' + sys.argv[0] + '''
  345. * on host ''' + platform.uname()[1] + ''' by user ''' + getpass.getuser() + ''' at ''' + time.strftime("%Y-%m-%d %I:%M:%S") + '''
  346. */
  347. #ifndef ''' + outname.upper() + '''_GENERATED_H_
  348. #define ''' + outname.upper() + '''_GENERATED_H_
  349. #ifdef __cplusplus
  350. extern "C" {
  351. #endif
  352. #include "ua_types.h" '''
  353. + ('\n#include "ua_types_generated.h"\n' if args.namespace_id != 0 else '') + '''
  354. /**
  355. * @ingroup types
  356. *
  357. * @defgroup ''' + outname + '''_generated Autogenerated ''' + outname + ''' Types
  358. *
  359. * @brief Data structures that are autogenerated from an XML-Schema.
  360. *
  361. * @{
  362. */
  363. ''')
  364. printh("#define " + outname.upper() + "_COUNT %s\n" % (str(len(types))))
  365. printh("extern const UA_DataType *" + outname.upper() + ";\n")
  366. printh("extern const UA_UInt32 *" + outname.upper() + "_IDS;\n")
  367. i = 0
  368. for t in types.itervalues():
  369. if type(t) != BuiltinType:
  370. printh("")
  371. if t.description != "":
  372. printh("/** @brief " + t.description + " */")
  373. printh(t.typedef_c())
  374. printh("#define " + outname.upper() + "_" + t.name[3:].upper() + " " + str(i))
  375. if type(t) != BuiltinType:
  376. printh(t.functions_c(outname.upper()))
  377. i += 1
  378. printh('''
  379. /// @} /* end of group */\n
  380. #ifdef __cplusplus
  381. } // extern "C"
  382. #endif\n
  383. #endif''')
  384. printc('''/**
  385. * @file ''' + outname + '''_generated.c
  386. *
  387. * @brief Autogenerated data types
  388. *
  389. * Generated from ''' + inname + ''' with script ''' + sys.argv[0] + '''
  390. * on host ''' + platform.uname()[1] + ''' by user ''' + getpass.getuser() + ''' at ''' + time.strftime("%Y-%m-%d %I:%M:%S") + '''
  391. */\n
  392. #include "stddef.h"
  393. #include "ua_types.h"
  394. #include "''' + outname + '''_generated.h"\n
  395. const UA_DataType *''' + outname.upper() + ''' = (UA_DataType[]){''')
  396. for t in types.itervalues():
  397. printc("")
  398. printc("/* " + t.name + " */")
  399. printc(t.typelayout_c(args.namespace_id == 0, outname) + ",")
  400. printc("};\n")
  401. if args.typedescriptions:
  402. printc('const UA_UInt32 *' + outname.upper() + '_IDS = (UA_UInt32[]){')
  403. for t in types.itervalues():
  404. print(str(typedescriptions[t.name].nodeid) + ", ", end='', file=fc)
  405. printc("};")
  406. fh.close()
  407. fc.close()