generate_datatypes.py 14 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379
  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 argparse
  10. fixed_size = {"UA_Boolean": 1, "UA_SByte": 1, "UA_Byte": 1, "UA_Int16": 2, "UA_UInt16": 2,
  11. "UA_Int32": 4, "UA_UInt32": 4, "UA_Int64": 8, "UA_UInt64": 8, "UA_Float": 4,
  12. "UA_Double": 8, "UA_DateTime": 8, "UA_Guid": 16, "UA_StatusCode": 4}
  13. zero_copy = ["UA_Boolean", "UA_Byte", "UA_Int16", "UA_UInt16", "UA_Int32", "UA_UInt32",
  14. "UA_Int64", "UA_UInt64", "UA_Float", "UA_Double", "UA_DateTime", "UA_StatusCode"]
  15. builtin_types = ["UA_Boolean", "UA_Byte", "UA_Int16", "UA_UInt16", "UA_Int32", "UA_UInt32",
  16. "UA_Int64", "UA_UInt64", "UA_Float", "UA_Double", "UA_String", "UA_DateTime",
  17. "UA_Guid", "UA_ByteString", "UA_XmlElement", "UA_NodeId", "UA_ExpandedNodeId",
  18. "UA_StatusCode", "UA_QualifiedName", "UA_LocalizedText", "UA_ExtensionObject",
  19. "UA_Variant", "UA_DataValue", "UA_DiagnosticInfo"]
  20. excluded_types = ["UA_NodeIdType", "UA_InstanceNode", "UA_TypeNode", "UA_Node", "UA_ObjectNode",
  21. "UA_ObjectTypeNode", "UA_VariableNode", "UA_VariableTypeNode", "UA_ReferenceTypeNode",
  22. "UA_MethodNode", "UA_ViewNode", "UA_DataTypeNode", "UA_ServerDiagnosticsSummaryDataType",
  23. "UA_SamplingIntervalDiagnosticsDataType", "UA_SessionSecurityDiagnosticsDataType",
  24. "UA_SubscriptionDiagnosticsDataType", "UA_SessionDiagnosticsDataType"]
  25. class BuiltinType(object):
  26. "Generic type without members. Used for builtin types."
  27. def __init__(self, name, description = ""):
  28. self.name = name
  29. self.description = description
  30. def fixed_size(self):
  31. return self.name in fixed_size
  32. def mem_size(self):
  33. return fixed_size[self.name]
  34. def zero_copy(self):
  35. return self.name in zero_copy
  36. def typedef_c(self):
  37. pass
  38. def typelayout_c(self):
  39. return "{.memSize = sizeof(" + self.name + "), " + \
  40. ".fixedSize = " + ("UA_TRUE" if self.fixed_size() else "UA_FALSE") + \
  41. ", .zeroCopyable = " + ("UA_TRUE" if self.zero_copy() else "UA_FALSE") + \
  42. ", .membersSize = 1,\n\t.members[0] = {.memberTypeIndex = " + self.name.upper() + "," + \
  43. ".nameSpaceZero = UA_TRUE, .padding = 0, .isArray = UA_FALSE }}"
  44. class EnumerationType(object):
  45. def __init__(self, name, description = "", elements = OrderedDict()):
  46. self.name = name
  47. self.description = description
  48. self.elements = elements # maps a name to an integer value
  49. def append_enum(name, value):
  50. self.elements[name] = value
  51. def fixed_size(self):
  52. return True
  53. def mem_size(self):
  54. return 4
  55. def zero_copy(self):
  56. return True
  57. def typedef_c(self):
  58. return "typedef enum { \n " + \
  59. ",\n ".join(map(lambda (key,value) : key.upper() + " = " + value,self.elements.iteritems())) + \
  60. "\n} " + self.name + ";"
  61. def typelayout_c(self):
  62. return "{.memSize = sizeof(" + self.name + "), .fixedSize = UA_TRUE, .zeroCopyable = UA_TRUE, " + \
  63. ".membersSize = 1,\n\t.members[0] = {.memberTypeIndex = UA_INT32," + \
  64. ".nameSpaceZero = UA_TRUE, .padding = 0, .isArray = UA_FALSE } }"
  65. class OpaqueType(object):
  66. def __init__(self, name, description = ""):
  67. self.name = name
  68. self.description = description
  69. def fixed_size(self):
  70. return False
  71. def zero_copy(self):
  72. return False
  73. def typedef_c(self):
  74. return "typedef UA_ByteString " + self.name + ";"
  75. def typelayout_c(self):
  76. return "{.memSize = sizeof(" + self.name + "), .fixedSize = UA_FALSE, .zeroCopyable = UA_FALSE, " + \
  77. ".membersSize = 1,\n\t.members[0] = {.memberTypeIndex = UA_BYTESTRING," + \
  78. ".nameSpaceZero = UA_TRUE, .padding = 0, .isArray = UA_FALSE } }"
  79. class StructMember(object):
  80. def __init__(self, name, memberType, isArray):
  81. self.name = name
  82. self.memberType = memberType
  83. self.isArray = isArray
  84. class StructType(object):
  85. def __init__(self, name, description, members = OrderedDict()):
  86. self.name = name
  87. self.description = description
  88. self.members = members # maps a name to a member definition
  89. def fixed_size(self):
  90. for m in self.members.values():
  91. if m.isArray or not m.memberType.fixed_size():
  92. return False
  93. return True
  94. def mem_size(self):
  95. total = 0
  96. for m in self.members.values():
  97. if m.isArray:
  98. raise Exception("Arrays have no fixed size!")
  99. else:
  100. total += m.memberType.mem_size()
  101. return total
  102. def zero_copy(self):
  103. for m in self.members.values():
  104. if m.isArray or not m.memberType.zero_copy():
  105. return False
  106. return True
  107. def typedef_c(self):
  108. if len(self.members) == 0:
  109. return "typedef void * " + self.name + ";"
  110. returnstr = "typedef struct {\n"
  111. for name, member in self.members.iteritems():
  112. if member.isArray:
  113. returnstr += " UA_Int32 " + name + "Size;\n"
  114. returnstr += " " + member.memberType.name + " *" +name + ";\n"
  115. else:
  116. returnstr += " " + member.memberType.name + " " +name + ";\n"
  117. return returnstr + "} " + self.name + ";"
  118. def typelayout_c(self):
  119. layout = "{.memSize = sizeof(" + self.name + "), "+ \
  120. ".fixedSize = " + ("UA_TRUE" if self.fixed_size() else "UA_FALSE") + \
  121. ", .zeroCopyable = " + ("sizeof(" + self.name + ") == " + str(self.mem_size()) if self.zero_copy() \
  122. else "UA_FALSE") + \
  123. ", .membersSize = " + str(len(self.members)) + ","
  124. for index, member in enumerate(self.members.values()):
  125. layout += "\n\t.members["+ str(index)+ "] = {" + \
  126. ".memberTypeIndex = UA_" + member.memberType.name.upper()[3:] + ", " + \
  127. ".nameSpaceZero = "+ \
  128. ("UA_TRUE, " if args.is_ns0 or member.memberType.name in existing_types else "UA_FALSE, ") + \
  129. ".padding = "
  130. before_endpos = "0"
  131. thispos = "offsetof(%s, %s)" % (self.name, member.name)
  132. if index > 0:
  133. before = self.members.values()[index-1]
  134. before_endpos = "(offsetof(%s, %s)" % (self.name, before.name)
  135. if before.isArray:
  136. before_endpos += " + sizeof(void*))"
  137. else:
  138. before_endpos += " + sizeof(%s))" % before.memberType.name
  139. if member.isArray:
  140. # the first two bytes are padding for the length index, the last three for the pointer
  141. length_pos = "offsetof(%s, %sSize)" % (self.name, member.name)
  142. if index != 0:
  143. layout += "((%s - %s) << 3) + " % (length_pos, before_endpos)
  144. layout += "(%s - sizeof(UA_Int32) - %s)" % (thispos, length_pos)
  145. else:
  146. layout += "%s - %s" % (thispos, before_endpos)
  147. layout += ", .isArray = " + ("UA_TRUE" if member.isArray else "UA_FALSE") + " }, "
  148. return layout + "}"
  149. def parseTypeDefinitions(xmlDescription, existing_types = OrderedDict()):
  150. '''Returns an ordered dict that maps names to types. The order is such that
  151. every type depends only on known types. '''
  152. ns = {"opc": "http://opcfoundation.org/BinarySchema/"}
  153. tree = etree.parse(xmlDescription)
  154. typeSnippets = tree.xpath("/opc:TypeDictionary/*[not(self::opc:Import)]", namespaces=ns)
  155. types = OrderedDict(existing_types.items())
  156. # types we do not want to autogenerate
  157. def skipType(name):
  158. if name in builtin_types:
  159. return True
  160. if name in excluded_types:
  161. return True
  162. if "Test" in name: # skip all test types
  163. return True
  164. if re.search("NodeId$", name) != None:
  165. return True
  166. return False
  167. def stripTypename(tn):
  168. return tn[tn.find(":")+1:]
  169. def camlCase2CCase(item):
  170. "Member names begin with a lower case character"
  171. return item[:1].lower() + item[1:] if item else ''
  172. def typeReady(element):
  173. "Do we have the member types yet?"
  174. for child in element:
  175. if child.tag == "{http://opcfoundation.org/BinarySchema/}Field":
  176. if stripTypename(child.get("TypeName")) not in types:
  177. return False
  178. return True
  179. def parseEnumeration(typeXml):
  180. name = "UA_" + typeXml.get("Name")
  181. description = ""
  182. elements = OrderedDict()
  183. for child in typeXml:
  184. if child.tag == "{http://opcfoundation.org/BinarySchema/}Documentation":
  185. description = child.text
  186. if child.tag == "{http://opcfoundation.org/BinarySchema/}EnumeratedValue":
  187. elements[name + "_" + child.get("Name")] = child.get("Value")
  188. return EnumerationType(name, description, elements)
  189. def parseOpaque(typeXml):
  190. name = "UA_" + typeXml.get("Name")
  191. description = ""
  192. for child in typeXml:
  193. if child.tag == "{http://opcfoundation.org/BinarySchema/}Documentation":
  194. description = child.text
  195. return OpaqueType(name, description)
  196. def parseStructured(typeXml):
  197. "Returns None if we miss member descriptions"
  198. name = "UA_" + typeXml.get("Name")
  199. description = ""
  200. for child in typeXml:
  201. if child.tag == "{http://opcfoundation.org/BinarySchema/}Documentation":
  202. description = child.text
  203. # ignore lengthfields, just tag the array-members as an array
  204. lengthfields = []
  205. for child in typeXml:
  206. if child.get("LengthField"):
  207. lengthfields.append(child.get("LengthField"))
  208. members = OrderedDict()
  209. for child in typeXml:
  210. if not child.tag == "{http://opcfoundation.org/BinarySchema/}Field":
  211. continue
  212. if child.get("Name") in lengthfields:
  213. continue
  214. memberTypeName = "UA_" + stripTypename(child.get("TypeName"))
  215. if not memberTypeName in types:
  216. return None
  217. memberType = types[memberTypeName]
  218. memberName = camlCase2CCase(child.get("Name"))
  219. isArray = True if child.get("LengthField") else False
  220. members[memberName] = StructMember(memberName, memberType, isArray)
  221. return StructType(name, description, members)
  222. finished = False
  223. while(not finished):
  224. finished = True
  225. for typeXml in typeSnippets:
  226. name = "UA_" + typeXml.get("Name")
  227. if name in types or skipType(name):
  228. continue
  229. if typeXml.tag == "{http://opcfoundation.org/BinarySchema/}EnumeratedType":
  230. t = parseEnumeration(typeXml)
  231. types[t.name] = t
  232. elif typeXml.tag == "{http://opcfoundation.org/BinarySchema/}OpaqueType":
  233. t = parseOpaque(typeXml)
  234. types[t.name] = t
  235. elif typeXml.tag == "{http://opcfoundation.org/BinarySchema/}StructuredType":
  236. t = parseStructured(typeXml)
  237. if t == None:
  238. finished = False
  239. else:
  240. types[t.name] = t
  241. return types
  242. parser = argparse.ArgumentParser()
  243. parser.add_argument('--is-ns0', action='store_true', help='type definitions are for namespace zero')
  244. parser.add_argument('--ns0-types-xml', nargs=1, help='xml-definition of the ns0 types that are assumed to already exist')
  245. parser.add_argument('types_xml', help='path/to/Opc.Ua.Types.bsd')
  246. parser.add_argument('outfile', help='outfile w/o extension')
  247. args = parser.parse_args()
  248. outname = args.outfile.split("/")[-1]
  249. inname = args.types_xml.split("/")[-1]
  250. existing_types = OrderedDict()
  251. if args.is_ns0:
  252. existing_types = OrderedDict([(t, BuiltinType(t)) for t in builtin_types])
  253. if args.ns0_types_xml:
  254. existing_types = parseTypeDefinitions(args.ns0_types_xml, existing_types)
  255. types = parseTypeDefinitions(args.types_xml, existing_types)
  256. fh = open("ua_" + args.outfile + "_generated.h",'w')
  257. fc = open("ua_" + args.outfile + "_generated.c",'w')
  258. def printh(string):
  259. print(string, end='\n', file=fh)
  260. def printc(string):
  261. print(string, end='\n', file=fc)
  262. printh('''/**
  263. * @file ''' + outname + '''_generated.h
  264. *
  265. * @brief Autogenerated data types
  266. *
  267. * Generated from ''' + inname + ''' with script ''' + sys.argv[0] + '''
  268. * on host ''' + platform.uname()[1] + ''' by user ''' + getpass.getuser() + ''' at ''' + time.strftime("%Y-%m-%d %I:%M:%S") + '''
  269. */
  270. #ifndef ''' + outname.upper() + '''_GENERATED_H_
  271. #define ''' + outname.upper() + '''_GENERATED_H_
  272. #ifdef __cplusplus
  273. extern "C" {
  274. #endif
  275. #include "ua_types.h"
  276. /**
  277. * @ingroup types
  278. *
  279. * @defgroup ''' + outname + '''_generated Autogenerated ''' + outname + ''' Types
  280. *
  281. * @brief Data structures that are autogenerated from an XML-Schema.
  282. *
  283. * @{
  284. */
  285. extern const UA_DataTypeLayout *UA_''' + outname.upper() + ''';
  286. ''')
  287. i = 0
  288. for t in types.itervalues():
  289. if type(t) != BuiltinType:
  290. printh("")
  291. if t.description != "":
  292. printh("/** @brief " + t.description + " */")
  293. printh(t.typedef_c())
  294. printh("#define UA_" + t.name[3:].upper() + " " + str(i))
  295. i += 1
  296. printh('''
  297. /// @} /* end of group */
  298. #ifdef __cplusplus
  299. } // extern "C"
  300. #endif
  301. #endif''')
  302. printc('''/**
  303. * @file ''' + outname + '''_generated.c
  304. *
  305. * @brief Autogenerated data types
  306. *
  307. * Generated from ''' + inname + ''' with script ''' + sys.argv[0] + '''
  308. * on host ''' + platform.uname()[1] + ''' by user ''' + getpass.getuser() + ''' at ''' + time.strftime("%Y-%m-%d %I:%M:%S") + '''
  309. */
  310. #include "stddef.h"
  311. #include "ua_''' + outname + '''_generated.h"
  312. const UA_DataTypeLayout *UA_TYPES = (const UA_DataTypeLayout[]){''')
  313. for t in types.itervalues():
  314. printc("")
  315. printc("/* " + t.name + " */")
  316. printc(t.typelayout_c() + ",")
  317. printc("};")
  318. fh.close()
  319. fc.close()