generate_datatypes.py 12 KB

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