mavgen_swift.py 13 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321
  1. #!/usr/bin/env python
  2. """
  3. Parse a MAVLink protocol XML file and generate Swift implementation
  4. Copyright Max Odnovolyk 2015
  5. Released under GNU GPL version 3 or later
  6. """
  7. from __future__ import print_function
  8. import os
  9. from . import mavparse, mavtemplate
  10. abbreviations = ["MAV", "PX4", "UDB", "PPZ", "PIXHAWK", "SLUGS", "FP", "ASLUAV", "VTOL", "ROI", "UART", "UDP", "IMU", "IMU2", "3D", "RC", "GPS", "GPS1", "GPS2", "NED", "RTK", "ADSB"]
  11. swift_keywords = ["associatedtype", "class", "deinit", "enum", "extension", "fileprivate", "func", "import", "init", "inout", "internal", "let", "open", "operator", "private", "protocol",
  12. "public", "static", "struct", "subscript", "typealias", "var", "break" "case", "continue", "default", "defer", "do", "else", "fallthrough", "for", "guard", "if", "in", "repeat", "return", "switch",
  13. "where", "while", "Any", "catch", "false", "is", "nil", "rethrows", "super", "self", "Self", "throw", "throws", "true", "try"]
  14. swift_types = {'char' : ("String", '"\\0"', "string(at: %u, length: %u)", "set(%s, at: %u, length: %u)"),
  15. 'uint8_t' : ("UInt8", 0, "number(at: %u)", "set(%s, at: %u)"),
  16. 'int8_t' : ("Int8", 0, "number(at: %u)", "set(%s, at: %u)"),
  17. 'uint16_t' : ("UInt16", 0, "number(at: %u)", "set(%s, at: %u)"),
  18. 'int16_t' : ("Int16", 0, "number(at: %u)", "set(%s, at: %u)"),
  19. 'uint32_t' : ("UInt32", 0, "number(at: %u)", "set(%s, at: %u)"),
  20. 'int32_t' : ("Int32", 0, "number(at: %u)", "set(%s, at: %u)"),
  21. 'uint64_t' : ("UInt64", 0, "number(at: %u)", "set(%s, at: %u)"),
  22. 'int64_t' : ("Int64", 0, "number(at: %u)", "set(%s, at: %u)"),
  23. 'float' : ("Float", 0, "number(at: %u)", "set(%s, at: %u)"),
  24. 'double' : ("Double", 0, "number(at: %u)", "set(%s, at: %u)"),
  25. 'uint8_t_mavlink_version' : ("UInt8", 0, "number(at: %u)", "set(%s, at: %u)")}
  26. t = mavtemplate.MAVTemplate()
  27. def generate_mavlink(directory, filelist, xml_list, msgs):
  28. print("Generating MAVLink.swift file")
  29. mavparse.mkdir_p(directory)
  30. filename = 'MAVLink.swift'
  31. filepath = os.path.join(directory, filename)
  32. outf = open(filepath, "w")
  33. generate_header(outf, filelist, xml_list, filename)
  34. append_static_code('MAVLink.swift', outf)
  35. generate_message_mappings_array(outf, msgs)
  36. generate_message_lengths_array(outf, msgs)
  37. generate_message_crc_extra_array(outf, msgs)
  38. outf.close()
  39. def generate_header(outf, filelist, xml_list, filename):
  40. """Generate Swift file header with source files list and creation date"""
  41. t.write(outf, """
  42. //
  43. // ${FILENAME}
  44. // MAVLink Protocol Swift Library
  45. //
  46. // Generated from ${FILELIST} on ${PARSE_TIME} by mavgen_swift.py
  47. // https://github.com/modnovolyk/MAVLinkSwift
  48. //
  49. """, {'FILENAME' : filename,
  50. 'FILELIST' : ", ".join(filelist),
  51. 'PARSE_TIME' : xml_list[0].parse_time})
  52. def generate_enums(directory, filelist, xml_list, enums, msgs):
  53. """Iterate through all enums and create Swift equivalents"""
  54. print("Generating Enumerations")
  55. for enum in enums:
  56. filename = "%s%sEnum.swift" % (enum.swift_name, enum.basename)
  57. filepath = os.path.join(directory, filename)
  58. outf = open(filepath, "w")
  59. generate_header(outf, filelist, xml_list, filename)
  60. t.write(outf, """
  61. ${formatted_description}public enum ${swift_name}: ${raw_value_type} {
  62. ${{entry:${formatted_description}\tcase ${swift_name} = ${value}\n}}
  63. }
  64. extension ${swift_name}: Enumeration {
  65. public static var typeName = "${name}"
  66. public static var typeDescription = "${entity_description}"
  67. public static var allMembers = [${all_entities}]
  68. public static var membersDescriptions = [${entities_info}]
  69. public static var enumEnd = UInt(${enum_end})
  70. }
  71. """, enum)
  72. outf.close()
  73. def get_enum_raw_type(enum, msgs):
  74. """Search appropirate raw type for enums in messages fields"""
  75. for msg in msgs:
  76. for field in msg.fields:
  77. if field.enum == enum.name:
  78. return swift_types[field.type][0]
  79. return "Int"
  80. def generate_messages(directory, filelist, xml_list, msgs):
  81. """Generate Swift structs to represent all MAVLink messages"""
  82. print("Generating Messages")
  83. for msg in msgs:
  84. filename = "%s%sMsg.swift" % (msg.swift_name, msg.basename)
  85. filepath = os.path.join(directory, filename)
  86. outf = open(filepath, "w")
  87. generate_header(outf, filelist, xml_list, filename)
  88. t.write(outf, """
  89. import Foundation
  90. ${formatted_description}public struct ${swift_name} {
  91. ${{fields:${formatted_description}\tpublic let ${swift_name}: ${return_type}\n}}
  92. }
  93. extension ${swift_name}: Message {
  94. public static let id = UInt8(${id})
  95. public static var typeName = "${name}"
  96. public static var typeDescription = "${message_description}"
  97. public static var fieldDefinitions: [FieldDefinition] = [${fields_info}]
  98. public init(data: Data) throws {
  99. ${{ordered_fields:\t\t${init_accessor} = ${initial_value}\n}}
  100. }
  101. public func pack() throws -> Data {
  102. var payload = Data(count: ${wire_length})
  103. ${{ordered_fields:\t\ttry payload.${payload_setter}\n}}
  104. return payload
  105. }
  106. }
  107. """, msg)
  108. outf.close()
  109. def append_static_code(filename, outf):
  110. """Open and copy static code from specified file"""
  111. basepath = os.path.dirname(os.path.realpath(__file__))
  112. filepath = os.path.join(basepath, 'swift/%s' % filename)
  113. print("Appending content of %s" % filename)
  114. with open(filepath) as inf:
  115. for line in inf:
  116. outf.write(line)
  117. def generate_message_mappings_array(outf, msgs):
  118. """Create array for mapping message Ids to proper structs"""
  119. classes = []
  120. for msg in msgs:
  121. classes.append("%u: %s.self" % (msg.id, msg.swift_name))
  122. t.write(outf, """
  123. /// Array for mapping message id to proper struct
  124. private let messageIdToClass: [UInt8: Message.Type] = [${ARRAY_CONTENT}]
  125. """, {'ARRAY_CONTENT' : ", ".join(classes)})
  126. def generate_message_lengths_array(outf, msgs):
  127. """Create array with message lengths to validate known message lengths"""
  128. # form message lengths array
  129. lengths = []
  130. for msg in msgs:
  131. lengths.append("%u: %u" % (msg.id, msg.wire_length))
  132. t.write(outf, """
  133. /// Message lengths array for known messages length validation
  134. private let messageLengths: [UInt8: UInt8] = [${ARRAY_CONTENT}]
  135. """, {'ARRAY_CONTENT' : ", ".join(lengths)})
  136. def generate_message_crc_extra_array(outf, msgs):
  137. """Add array with CRC extra values to detect incompatible XML changes"""
  138. crcs = []
  139. for msg in msgs:
  140. crcs.append("%u: %u" % (msg.id, msg.crc_extra))
  141. t.write(outf, """
  142. /// Message CRSs extra for detection incompatible XML changes
  143. private let messageCRCsExtra: [UInt8: UInt8] = [${ARRAY_CONTENT}]
  144. """, {'ARRAY_CONTENT' : ", ".join(crcs)})
  145. def camel_case_from_underscores(string):
  146. """Generate a CamelCase string from an underscore_string"""
  147. components = string.split('_')
  148. string = ''
  149. for component in components:
  150. if component in abbreviations:
  151. string += component
  152. else:
  153. string += component[0].upper() + component[1:].lower()
  154. return string
  155. def lower_camel_case_from_underscores(string):
  156. """Generate a lower-cased camelCase string from an underscore_string"""
  157. components = string.split('_')
  158. string = components[0].lower()
  159. for component in components[1:]:
  160. string += component[0].upper() + component[1:].lower()
  161. return string
  162. def generate_enums_type_info(enums, msgs):
  163. """Add camel case swift names for enums an entries, descriptions and sort enums alphabetically"""
  164. for enum in enums:
  165. enum.swift_name = camel_case_from_underscores(enum.name)
  166. enum.raw_value_type = get_enum_raw_type(enum, msgs)
  167. enum.formatted_description = ""
  168. if enum.description:
  169. enum.description = " ".join(enum.description.split())
  170. enum.formatted_description = "/// %s\n" % enum.description
  171. for index, entry in enumerate(enum.entry):
  172. if entry.name.endswith("_ENUM_END"):
  173. enum.enum_end = entry.value
  174. del enum.entry[index]
  175. all_entities = []
  176. entities_info = []
  177. for entry in enum.entry:
  178. name = entry.name.replace(enum.name + '_', '')
  179. """Ensure that enums entry name does not start from digit"""
  180. if name[0].isdigit():
  181. name = "MAV_" + name
  182. entry.swift_name = lower_camel_case_from_underscores(name)
  183. """Ensure that enums entry name does not match any swift keyword"""
  184. if entry.swift_name in swift_keywords:
  185. entry.swift_name = lower_camel_case_from_underscores("MAV_" + name)
  186. entry.formatted_description = ""
  187. if entry.description:
  188. entry.description = " ".join(entry.description.split())
  189. entry.formatted_description = "\n\t/// " + entry.description + "\n"
  190. all_entities.append(entry.swift_name)
  191. entities_info.append('("%s", "%s")' % (entry.name, entry.description.replace('"','\\"')))
  192. enum.all_entities = ", ".join(all_entities)
  193. enum.entities_info = ", ".join(entities_info)
  194. enum.entity_description = enum.description.replace('"','\\"')
  195. enums.sort(key = lambda enum : enum.swift_name)
  196. def generate_messages_type_info(msgs):
  197. """Add proper formated variable names, initializers and type names to use in templates"""
  198. for msg in msgs:
  199. msg.swift_name = camel_case_from_underscores(msg.name)
  200. msg.formatted_description = ""
  201. if msg.description:
  202. msg.description = " ".join(msg.description.split())
  203. msg.formatted_description = "/// %s\n" % " ".join(msg.description.split())
  204. msg.message_description = msg.description.replace('"','\\"')
  205. for field in msg.ordered_fields:
  206. field.swift_name = lower_camel_case_from_underscores(field.name)
  207. field.init_accessor = field.swift_name if field.swift_name != "data" else "self.%s" % field.swift_name
  208. field.pack_accessor = field.swift_name if field.swift_name != "payload" else "self.%s" % field.swift_name
  209. field.return_type = swift_types[field.type][0]
  210. # configure fields initializers
  211. if field.enum:
  212. # handle enums
  213. field.return_type = camel_case_from_underscores(field.enum)
  214. field.initial_value = "try data.enumeration(at: %u)" % field.wire_offset
  215. field.payload_setter = "set(%s, at: %u)" % (field.pack_accessor, field.wire_offset)
  216. elif field.array_length > 0:
  217. if field.return_type == "String":
  218. # handle strings
  219. field.initial_value = "try data." + swift_types[field.type][2] % (field.wire_offset, field.array_length)
  220. field.payload_setter = swift_types[field.type][3] % (field.pack_accessor, field.wire_offset, field.array_length)
  221. else:
  222. # other array types
  223. field.return_type = "[%s]" % field.return_type
  224. field.initial_value = "try data.array(at: %u, capacity: %u)" % (field.wire_offset, field.array_length)
  225. field.payload_setter = "set(%s, at: %u, capacity: %u)" % (field.pack_accessor, field.wire_offset, field.array_length)
  226. else:
  227. # simple type field
  228. field.initial_value = "try data." + swift_types[field.type][2] % field.wire_offset
  229. field.payload_setter = swift_types[field.type][3] % (field.pack_accessor, field.wire_offset)
  230. field.formatted_description = ""
  231. if field.description:
  232. field.description = " ".join(field.description.split())
  233. field.formatted_description = "\n\t/// " + field.description + "\n"
  234. fields_info = ['("%s", %u, "%s", %u, "%s")' % (field.swift_name, field.wire_offset, field.return_type, field.array_length, field.description.replace('"','\\"')) for field in msg.fields]
  235. msg.fields_info = ", ".join(fields_info)
  236. msgs.sort(key = lambda msg : msg.id)
  237. def generate(basename, xml_list):
  238. """Generate complete MAVLink Swift implemenation"""
  239. msgs = []
  240. enums = []
  241. filelist = []
  242. for xml in xml_list:
  243. for msg in xml.message: msg.basename = xml.basename.title()
  244. for enum in xml.enum: enum.basename = xml.basename.title()
  245. msgs.extend(xml.message)
  246. enums.extend(xml.enum)
  247. filelist.append(os.path.basename(xml.filename))
  248. generate_enums_type_info(enums, msgs)
  249. generate_messages_type_info(msgs)
  250. generate_mavlink(basename, filelist, xml_list, msgs)
  251. generate_enums(basename, filelist, xml_list, enums, msgs)
  252. generate_messages(basename, filelist, xml_list, msgs)