generate_datatypes.py 19 KB

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