generate_datatypes.py 19 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465
  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. import xml.etree.ElementTree as etree
  9. import itertools
  10. import argparse
  11. types = OrderedDict() # contains types that were already parsed
  12. typedescriptions = {} # contains type nodeids
  13. excluded_types = ["NodeIdType", "InstanceNode", "TypeNode", "Node", "ObjectNode",
  14. "ObjectTypeNode", "VariableNode", "VariableTypeNode", "ReferenceTypeNode",
  15. "MethodNode", "ViewNode", "DataTypeNode",
  16. "UA_ServerDiagnosticsSummaryDataType", "UA_SamplingIntervalDiagnosticsDataType",
  17. "UA_SessionSecurityDiagnosticsDataType", "UA_SubscriptionDiagnosticsDataType",
  18. "UA_SessionDiagnosticsDataType"]
  19. builtin_types = ["Boolean", "SByte", "Byte", "Int16", "UInt16", "Int32", "UInt32",
  20. "Int64", "UInt64", "Float", "Double", "String", "DateTime", "Guid",
  21. "ByteString", "XmlElement", "NodeId", "ExpandedNodeId", "StatusCode",
  22. "QualifiedName", "LocalizedText", "ExtensionObject", "DataValue",
  23. "Variant", "DiagnosticInfo"]
  24. # If the type does not contain pointers, it can be copied with memcpy
  25. # (internally, not into the protocol message). This dict contains the sizes of
  26. # fixed-size types. Parsed types are added if they apply.
  27. builtin_fixed_size = ["Boolean", "SByte", "Byte", "Int16", "UInt16", "Int32", "UInt32",
  28. "Int64", "UInt64", "Float", "Double", "DateTime", "Guid", "StatusCode"]
  29. # Some types can be memcpy'd off the binary stream. That's especially important
  30. # for arrays. But we need to check if they contain padding and whether the
  31. # endianness is correct. This dict gives the C-statement that must be true for the
  32. # type to be overlayable. Parsed types are added if they apply.
  33. builtin_overlayable = {"Boolean": "true", "SByte": "true", "Byte": "true",
  34. "Int16": "UA_BINARY_OVERLAYABLE_INTEGER", "UInt16": "UA_BINARY_OVERLAYABLE_INTEGER",
  35. "Int32": "UA_BINARY_OVERLAYABLE_INTEGER", "UInt32": "UA_BINARY_OVERLAYABLE_INTEGER",
  36. "Int64": "UA_BINARY_OVERLAYABLE_INTEGER", "UInt64": "UA_BINARY_OVERLAYABLE_INTEGER",
  37. "Float": "UA_BINARY_OVERLAYABLE_FLOAT", "Double": "UA_BINARY_OVERLAYABLE_FLOAT",
  38. "DateTime": "UA_BINARY_OVERLAYABLE_INTEGER", "StatusCode": "UA_BINARY_OVERLAYABLE_INTEGER",
  39. "Guid": "(UA_BINARY_OVERLAYABLE_INTEGER && offsetof(UA_Guid, data2) == sizeof(UA_UInt32) && " + \
  40. "offsetof(UA_Guid, data3) == (sizeof(UA_UInt16) + sizeof(UA_UInt32)) && " + \
  41. "offsetof(UA_Guid, data4) == (2*sizeof(UA_UInt32)))"}
  42. ################
  43. # Type Classes #
  44. ################
  45. class StructMember(object):
  46. def __init__(self, name, memberType, isArray):
  47. self.name = name
  48. self.memberType = memberType
  49. self.isArray = isArray
  50. class Type(object):
  51. def __init__(self, outname, xml):
  52. self.name = xml.get("Name")
  53. self.ns0 = ("true" if outname == "ua_types" else "false")
  54. self.typeIndex = outname.upper() + "_" + self.name.upper()
  55. self.outname = outname
  56. self.description = ""
  57. self.fixed_size = "false"
  58. self.overlayable = "false"
  59. if self.name in builtin_types:
  60. self.builtin = "true"
  61. else:
  62. self.builtin = "false"
  63. self.members = [StructMember("", self, False)] # Returns one member: itself. Overwritten by some types.
  64. for child in xml:
  65. if child.tag == "{http://opcfoundation.org/BinarySchema/}Documentation":
  66. self.description = child.text
  67. break
  68. def datatype_c(self):
  69. if self.name in typedescriptions:
  70. description = typedescriptions[self.name]
  71. typeid = "{.namespaceIndex = %s, .identifierType = UA_NODEIDTYPE_NUMERIC, .identifier.numeric = %s}" % (description.namespaceid, description.nodeid)
  72. else:
  73. typeid = "{.namespaceIndex = 0, .identifierType = UA_NODEIDTYPE_NUMERIC, .identifier.numeric = 0}"
  74. return "{ .typeId = " + typeid + \
  75. ",\n .typeIndex = " + self.typeIndex + \
  76. ",\n#ifdef UA_ENABLE_TYPENAMES\n .typeName = \"%s\",\n#endif\n" % self.name + \
  77. " .memSize = sizeof(UA_" + self.name + ")" + \
  78. ",\n .builtin = " + self.builtin + \
  79. ",\n .fixedSize = " + self.fixed_size + \
  80. ",\n .overlayable = " + self.overlayable + \
  81. ",\n .membersSize = " + str(len(self.members)) + \
  82. ",\n .members = %s_members" % self.name + " }"
  83. def members_c(self):
  84. members = "static UA_DataTypeMember %s_members[%s] = {" % (self.name, len(self.members))
  85. before = None
  86. for index, member in enumerate(self.members):
  87. m = "\n { .memberTypeIndex = %s_%s,\n" % (member.memberType.outname.upper(), member.memberType.name.upper())
  88. m += "#ifdef UA_ENABLE_TYPENAMES\n .memberName = \"%s\",\n#endif\n" % member.name
  89. m += " .namespaceZero = %s,\n" % member.memberType.ns0
  90. m += " .padding = "
  91. if not before:
  92. m += "0,\n"
  93. else:
  94. if member.isArray:
  95. m += "offsetof(UA_%s, %sSize)" % (self.name, member.name)
  96. else:
  97. m += "offsetof(UA_%s, %s)" % (self.name, member.name)
  98. m += " - offsetof(UA_%s, %s)" % (self.name, before.name)
  99. if before.isArray:
  100. m += " - sizeof(void*),\n"
  101. else:
  102. m += " - sizeof(UA_%s),\n" % before.memberType.name
  103. m += " .isArray = " + ("true" if member.isArray else "false")
  104. members += m + "\n },"
  105. before = member
  106. return members + "};"
  107. def datatype_ptr(self):
  108. return "&" + self.outname.upper() + "[" + self.outname.upper() + "_" + self.name.upper() + "]"
  109. def functions_c(self):
  110. funcs = "static UA_INLINE void UA_%s_init(UA_%s *p) { memset(p, 0, sizeof(UA_%s)); }\n" % (self.name, self.name, self.name)
  111. funcs += "static UA_INLINE UA_%s * UA_%s_new(void) { return (UA_%s*) UA_new(%s); }\n" % (self.name, self.name, self.name, self.datatype_ptr())
  112. if self.fixed_size == "true":
  113. funcs += "static UA_INLINE UA_StatusCode UA_%s_copy(const UA_%s *src, UA_%s *dst) { *dst = *src; return UA_STATUSCODE_GOOD; }\n" % (self.name, self.name, self.name)
  114. funcs += "static UA_INLINE void UA_%s_deleteMembers(UA_%s *p) { }\n" % (self.name, self.name)
  115. else:
  116. funcs += "static UA_INLINE UA_StatusCode UA_%s_copy(const UA_%s *src, UA_%s *dst) { return UA_copy(src, dst, %s); }\n" % (self.name, self.name, self.name, self.datatype_ptr())
  117. funcs += "static UA_INLINE void UA_%s_deleteMembers(UA_%s *p) { UA_deleteMembers(p, %s); }\n" % (self.name, self.name, self.datatype_ptr())
  118. funcs += "static UA_INLINE void UA_%s_delete(UA_%s *p) { UA_delete(p, %s); }" % (self.name, self.name, self.datatype_ptr())
  119. return funcs
  120. def encoding_h(self):
  121. enc = "static UA_INLINE UA_StatusCode UA_%s_encodeBinary(const UA_%s *src, UA_ByteString *dst, size_t *offset) { return UA_encodeBinary(src, %s, NULL, NULL, dst, offset); }\n"
  122. enc += "static UA_INLINE UA_StatusCode UA_%s_decodeBinary(const UA_ByteString *src, size_t *offset, UA_%s *dst) { return UA_decodeBinary(src, offset, dst, %s); }"
  123. return enc % tuple(list(itertools.chain(*itertools.repeat([self.name, self.name, self.datatype_ptr()], 2))))
  124. class BuiltinType(Type):
  125. def __init__(self, name):
  126. self.name = name
  127. self.ns0 = "true"
  128. self.typeIndex = "UA_TYPES_" + self.name.upper()
  129. self.outname = "ua_types"
  130. self.description = ""
  131. self.fixed_size = "false"
  132. if self.name in builtin_fixed_size:
  133. self.fixed_size = "true"
  134. self.overlayable = "false"
  135. if name in builtin_overlayable:
  136. self.overlayable = builtin_overlayable[name]
  137. self.builtin = "true"
  138. if self.name == "QualifiedName":
  139. self.members = [StructMember("namespaceIndex", types["Int16"], False), StructMember("name", types["String"], False)]
  140. elif self.name in ["String", "ByteString", "XmlElement"]:
  141. self.members = [StructMember("", types["Byte"], True)]
  142. else:
  143. self.members = [StructMember("", self, False)]
  144. class EnumerationType(Type):
  145. def __init__(self, outname, xml):
  146. Type.__init__(self, outname, xml)
  147. self.fixed_size = "true"
  148. self.overlayable = "UA_BINARY_OVERLAYABLE_INTEGER"
  149. self.members = [StructMember("", types["Int32"], False)] # encoded as uint32
  150. self.builtin = "true"
  151. self.typeIndex = "UA_TYPES_INT32"
  152. self.elements = OrderedDict()
  153. for child in xml:
  154. if child.tag == "{http://opcfoundation.org/BinarySchema/}EnumeratedValue":
  155. self.elements[child.get("Name")] = child.get("Value")
  156. def typedef_h(self):
  157. if sys.version_info[0] < 3:
  158. values = self.elements.iteritems()
  159. else:
  160. values = self.elements.items()
  161. return "typedef enum { \n " + ",\n ".join(map(lambda kv : "UA_" + self.name.upper() + "_" + kv[0].upper() + \
  162. " = " + kv[1], values)) + "\n} UA_%s;" % self.name
  163. class OpaqueType(Type):
  164. def __init__(self, outname, xml):
  165. Type.__init__(self, outname, xml)
  166. self.members = [StructMember("", types["ByteString"], False)] # encoded as string
  167. def typedef_h(self):
  168. return "typedef UA_ByteString UA_%s;" % self.name
  169. class StructType(Type):
  170. def __init__(self, outname, xml):
  171. Type.__init__(self, outname, xml)
  172. self.members = []
  173. lengthfields = [] # lengthfields of arrays are not included as members
  174. for child in xml:
  175. if child.get("LengthField"):
  176. lengthfields.append(child.get("LengthField"))
  177. for child in xml:
  178. if not child.tag == "{http://opcfoundation.org/BinarySchema/}Field":
  179. continue
  180. if child.get("Name") in lengthfields:
  181. continue
  182. memberName = child.get("Name")
  183. memberName = memberName[:1].lower() + memberName[1:]
  184. memberTypeName = child.get("TypeName")
  185. memberType = types[memberTypeName[memberTypeName.find(":")+1:]]
  186. isArray = True if child.get("LengthField") else False
  187. self.members.append(StructMember(memberName, memberType, isArray))
  188. self.fixed_size = "true"
  189. self.overlayable = "true"
  190. before = None
  191. for m in self.members:
  192. if m.isArray or m.memberType.fixed_size != "true":
  193. self.fixed_size = "false"
  194. self.overlayable = "false"
  195. else:
  196. self.overlayable += " && " + m.memberType.overlayable
  197. if before:
  198. self.overlayable += " && offsetof(UA_%s, %s) == (offsetof(UA_%s, %s) + sizeof(UA_%s))" % \
  199. (self.name, m.name, self.name, before.name, before.memberType.name)
  200. if "false" in self.overlayable:
  201. self.overlayable = "false"
  202. before = m
  203. def typedef_h(self):
  204. if len(self.members) == 0:
  205. return "typedef void * UA_%s;" % self.name
  206. returnstr = "typedef struct {\n"
  207. for member in self.members:
  208. if member.isArray:
  209. returnstr += " size_t %sSize;\n" % member.name
  210. returnstr += " UA_%s *%s;\n" % (member.memberType.name, member.name)
  211. else:
  212. returnstr += " UA_%s %s;\n" % (member.memberType.name, member.name)
  213. return returnstr + "} UA_%s;" % self.name
  214. #########################
  215. # Parse Typedefinitions #
  216. #########################
  217. def parseTypeDefinitions(outname, xmlDescription):
  218. def typeReady(element):
  219. "Are all member types defined?"
  220. for child in element:
  221. if child.tag == "{http://opcfoundation.org/BinarySchema/}Field":
  222. childname = child.get("TypeName")
  223. if childname[childname.find(":")+1:] not in types:
  224. return False
  225. return True
  226. def skipType(name):
  227. if name in excluded_types:
  228. return True
  229. if "Test" in name: # skip all test types
  230. return True
  231. if re.search("NodeId$", name) != None:
  232. return True
  233. return False
  234. snippets = {}
  235. for typeXml in etree.parse(xmlDescription).getroot():
  236. if not typeXml.get("Name"):
  237. continue
  238. name = typeXml.get("Name")
  239. snippets[name] = typeXml
  240. while(len(snippets) > 0):
  241. for name, typeXml in snippets.items():
  242. if name in types or skipType(name):
  243. del snippets[name]
  244. continue
  245. if not typeReady(typeXml):
  246. continue
  247. if name in builtin_types:
  248. types[name] = BuiltinType(name)
  249. elif typeXml.tag == "{http://opcfoundation.org/BinarySchema/}EnumeratedType":
  250. types[name] = EnumerationType(outname, typeXml)
  251. elif typeXml.tag == "{http://opcfoundation.org/BinarySchema/}OpaqueType":
  252. types[name] = OpaqueType(outname, typeXml)
  253. elif typeXml.tag == "{http://opcfoundation.org/BinarySchema/}StructuredType":
  254. types[name] = StructType(outname, typeXml)
  255. else:
  256. raise Exception("Type not known")
  257. del snippets[name]
  258. ##########################
  259. # Parse TypeDescriptions #
  260. ##########################
  261. class TypeDescription(object):
  262. def __init__(self, name, nodeid, namespaceid):
  263. self.name = name
  264. self.nodeid = nodeid
  265. self.namespaceid = namespaceid
  266. def parseTypeDescriptions(filename, namespaceid):
  267. definitions = {}
  268. with open(filename) as f:
  269. input_str = f.read()
  270. input_str = input_str.replace('\r','')
  271. rows = map(lambda x:tuple(x.split(',')), input_str.split('\n'))
  272. for index, row in enumerate(rows):
  273. if len(row) < 3:
  274. continue
  275. if row[2] != "DataType":
  276. continue
  277. if row[0] == "BaseDataType":
  278. definitions["Variant"] = TypeDescription(row[0], row[1], namespaceid)
  279. elif row[0] == "Structure":
  280. definitions["ExtensionObject"] = TypeDescription(row[0], row[1], namespaceid)
  281. elif row[0] not in types:
  282. continue
  283. elif type(types[row[0]]) == EnumerationType:
  284. definitions[row[0]] = TypeDescription(row[0], "6", namespaceid) # enumerations look like int32 on the wire
  285. else:
  286. definitions[row[0]] = TypeDescription(row[0], row[1], namespaceid)
  287. return definitions
  288. ###############################
  289. # Parse the Command Line Input#
  290. ###############################
  291. parser = argparse.ArgumentParser()
  292. parser.add_argument('--typedescriptions', help='csv file with type descriptions')
  293. parser.add_argument('--namespace', type=int, default=0, help='namespace id of the generated type nodeids (defaults to 0)')
  294. parser.add_argument('--selected_types', help='file with list of types (among those parsed) to be generated')
  295. parser.add_argument('typexml_ns0', help='path/to/Opc.Ua.Types.bsd ...')
  296. parser.add_argument('typexml_additional', nargs='*', help='path/to/Opc.Ua.Types.bsd ...')
  297. parser.add_argument('outfile', help='output file w/o extension')
  298. args = parser.parse_args()
  299. outname = args.outfile.split("/")[-1]
  300. inname = ', '.join([args.typexml_ns0.split("/")[-1]] + map(lambda x:x.split("/")[-1], args.typexml_additional))
  301. ################
  302. # Create Types #
  303. ################
  304. for builtin in builtin_types:
  305. types[builtin] = BuiltinType(builtin)
  306. with open(args.typexml_ns0) as f:
  307. parseTypeDefinitions("ua_types", f)
  308. for typexml in args.typexml_additional:
  309. with open(typexml) as f:
  310. parseTypeDefinitions(outname, f)
  311. typedescriptions = {}
  312. if args.typedescriptions:
  313. typedescriptions = parseTypeDescriptions(args.typedescriptions, args.namespace)
  314. selected_types = types.keys()
  315. if args.selected_types:
  316. with open(args.selected_types) as f:
  317. selected_types = filter(len, [line.strip() for line in f])
  318. #############################
  319. # Write out the Definitions #
  320. #############################
  321. fh = open(args.outfile + "_generated.h",'w')
  322. fe = open(args.outfile + "_generated_encoding_binary.h",'w')
  323. fc = open(args.outfile + "_generated.c",'w')
  324. def printh(string):
  325. print(string, end='\n', file=fh)
  326. def printe(string):
  327. print(string, end='\n', file=fe)
  328. def printc(string):
  329. print(string, end='\n', file=fc)
  330. printh('''/* Generated from ''' + inname + ''' with script ''' + sys.argv[0] + '''
  331. * on host ''' + platform.uname()[1] + ''' by user ''' + getpass.getuser() + \
  332. ''' at ''' + time.strftime("%Y-%m-%d %I:%M:%S") + ''' */
  333. #ifndef ''' + outname.upper() + '''_GENERATED_H_
  334. #define ''' + outname.upper() + '''_GENERATED_H_
  335. #ifdef __cplusplus
  336. extern "C" {
  337. #endif
  338. #include "ua_types.h"
  339. #ifdef UA_INTERNAL
  340. #include "ua_types_encoding_binary.h"
  341. #endif''' + ('\n#include "ua_types_generated.h"\n' if outname != "ua_types" else '') + '''
  342. /**
  343. * Additional Data Type Definitions
  344. * ================================
  345. */
  346. ''')
  347. printh("#define " + outname.upper() + "_COUNT %s" % (str(len(selected_types))))
  348. printh("extern UA_EXPORT const UA_DataType " + outname.upper() + "[" + outname.upper() + "_COUNT];")
  349. printc('''/* Generated from ''' + inname + ''' with script ''' + sys.argv[0] + '''
  350. * on host ''' + platform.uname()[1] + ''' by user ''' + getpass.getuser() + \
  351. ''' at ''' + time.strftime("%Y-%m-%d %I:%M:%S") + ''' */
  352. #include "stddef.h"
  353. #include "ua_types.h"
  354. #include "''' + outname + '''_generated.h"''')
  355. printe('''/* Generated from ''' + inname + ''' with script ''' + sys.argv[0] + '''
  356. * on host ''' + platform.uname()[1] + ''' by user ''' + getpass.getuser() + \
  357. ''' at ''' + time.strftime("%Y-%m-%d %I:%M:%S") + ''' */
  358. #include "ua_types_encoding_binary.h"
  359. #include "''' + outname + '''_generated.h"''')
  360. if sys.version_info[0] < 3:
  361. values = types.itervalues()
  362. else:
  363. values = types.values()
  364. # Datatype members
  365. for t in values:
  366. if not t.name in selected_types:
  367. continue
  368. printc("")
  369. printc("/* " + t.name + " */")
  370. printc(t.members_c())
  371. printc("const UA_DataType %s[%s_COUNT] = {" % (outname.upper(), outname.upper()))
  372. if sys.version_info[0] < 3:
  373. values = types.itervalues()
  374. else:
  375. values = types.values()
  376. i = 0
  377. for t in values:
  378. if not t.name in selected_types:
  379. continue
  380. # Header
  381. printh("\n/**\n * " + t.name)
  382. printh(" * " + "-" * len(t.name))
  383. if t.description == "":
  384. printh(" */")
  385. else:
  386. printh(" * " + t.description + " */")
  387. if type(t) != BuiltinType:
  388. printh(t.typedef_h() + "\n")
  389. printh("#define " + outname.upper() + "_" + t.name.upper() + " " + str(i))
  390. printh(t.functions_c())
  391. i += 1
  392. # Datatype
  393. printc("")
  394. printc("/* " + t.name + " */")
  395. printc(t.datatype_c() + ",")
  396. # Encoding
  397. printe("")
  398. printe("/* " + t.name + " */")
  399. printe(t.encoding_h())
  400. printh('''
  401. #ifdef __cplusplus
  402. } // extern "C"
  403. #endif\n
  404. #endif /* %s_GENERATED_H_ */''' % outname.upper())
  405. printc("};\n")
  406. fh.close()
  407. fc.close()
  408. fe.close()