123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321 |
- #!/usr/bin/env python
- """
- Parse a MAVLink protocol XML file and generate Swift implementation
- Copyright Max Odnovolyk 2015
- Released under GNU GPL version 3 or later
- """
- from __future__ import print_function
- import os
- from . import mavparse, mavtemplate
- abbreviations = ["MAV", "PX4", "UDB", "PPZ", "PIXHAWK", "SLUGS", "FP", "ASLUAV", "VTOL", "ROI", "UART", "UDP", "IMU", "IMU2", "3D", "RC", "GPS", "GPS1", "GPS2", "NED", "RTK", "ADSB"]
- swift_keywords = ["associatedtype", "class", "deinit", "enum", "extension", "fileprivate", "func", "import", "init", "inout", "internal", "let", "open", "operator", "private", "protocol",
- "public", "static", "struct", "subscript", "typealias", "var", "break" "case", "continue", "default", "defer", "do", "else", "fallthrough", "for", "guard", "if", "in", "repeat", "return", "switch",
- "where", "while", "Any", "catch", "false", "is", "nil", "rethrows", "super", "self", "Self", "throw", "throws", "true", "try"]
- swift_types = {'char' : ("String", '"\\0"', "string(at: %u, length: %u)", "set(%s, at: %u, length: %u)"),
- 'uint8_t' : ("UInt8", 0, "number(at: %u)", "set(%s, at: %u)"),
- 'int8_t' : ("Int8", 0, "number(at: %u)", "set(%s, at: %u)"),
- 'uint16_t' : ("UInt16", 0, "number(at: %u)", "set(%s, at: %u)"),
- 'int16_t' : ("Int16", 0, "number(at: %u)", "set(%s, at: %u)"),
- 'uint32_t' : ("UInt32", 0, "number(at: %u)", "set(%s, at: %u)"),
- 'int32_t' : ("Int32", 0, "number(at: %u)", "set(%s, at: %u)"),
- 'uint64_t' : ("UInt64", 0, "number(at: %u)", "set(%s, at: %u)"),
- 'int64_t' : ("Int64", 0, "number(at: %u)", "set(%s, at: %u)"),
- 'float' : ("Float", 0, "number(at: %u)", "set(%s, at: %u)"),
- 'double' : ("Double", 0, "number(at: %u)", "set(%s, at: %u)"),
- 'uint8_t_mavlink_version' : ("UInt8", 0, "number(at: %u)", "set(%s, at: %u)")}
- t = mavtemplate.MAVTemplate()
- def generate_mavlink(directory, filelist, xml_list, msgs):
- print("Generating MAVLink.swift file")
- mavparse.mkdir_p(directory)
- filename = 'MAVLink.swift'
- filepath = os.path.join(directory, filename)
- outf = open(filepath, "w")
- generate_header(outf, filelist, xml_list, filename)
- append_static_code('MAVLink.swift', outf)
- generate_message_mappings_array(outf, msgs)
- generate_message_lengths_array(outf, msgs)
- generate_message_crc_extra_array(outf, msgs)
- outf.close()
- def generate_header(outf, filelist, xml_list, filename):
- """Generate Swift file header with source files list and creation date"""
- t.write(outf, """
- //
- // ${FILENAME}
- // MAVLink Protocol Swift Library
- //
- // Generated from ${FILELIST} on ${PARSE_TIME} by mavgen_swift.py
- // https://github.com/modnovolyk/MAVLinkSwift
- //
- """, {'FILENAME' : filename,
- 'FILELIST' : ", ".join(filelist),
- 'PARSE_TIME' : xml_list[0].parse_time})
- def generate_enums(directory, filelist, xml_list, enums, msgs):
- """Iterate through all enums and create Swift equivalents"""
- print("Generating Enumerations")
- for enum in enums:
- filename = "%s%sEnum.swift" % (enum.swift_name, enum.basename)
- filepath = os.path.join(directory, filename)
- outf = open(filepath, "w")
- generate_header(outf, filelist, xml_list, filename)
- t.write(outf, """
- ${formatted_description}public enum ${swift_name}: ${raw_value_type} {
- ${{entry:${formatted_description}\tcase ${swift_name} = ${value}\n}}
- }
- extension ${swift_name}: Enumeration {
- public static var typeName = "${name}"
- public static var typeDescription = "${entity_description}"
- public static var allMembers = [${all_entities}]
- public static var membersDescriptions = [${entities_info}]
- public static var enumEnd = UInt(${enum_end})
- }
- """, enum)
- outf.close()
- def get_enum_raw_type(enum, msgs):
- """Search appropirate raw type for enums in messages fields"""
- for msg in msgs:
- for field in msg.fields:
- if field.enum == enum.name:
- return swift_types[field.type][0]
- return "Int"
- def generate_messages(directory, filelist, xml_list, msgs):
- """Generate Swift structs to represent all MAVLink messages"""
- print("Generating Messages")
- for msg in msgs:
- filename = "%s%sMsg.swift" % (msg.swift_name, msg.basename)
- filepath = os.path.join(directory, filename)
- outf = open(filepath, "w")
- generate_header(outf, filelist, xml_list, filename)
- t.write(outf, """
- import Foundation
- ${formatted_description}public struct ${swift_name} {
- ${{fields:${formatted_description}\tpublic let ${swift_name}: ${return_type}\n}}
- }
- extension ${swift_name}: Message {
- public static let id = UInt8(${id})
- public static var typeName = "${name}"
- public static var typeDescription = "${message_description}"
- public static var fieldDefinitions: [FieldDefinition] = [${fields_info}]
- public init(data: Data) throws {
- ${{ordered_fields:\t\t${init_accessor} = ${initial_value}\n}}
- }
- public func pack() throws -> Data {
- var payload = Data(count: ${wire_length})
- ${{ordered_fields:\t\ttry payload.${payload_setter}\n}}
- return payload
- }
- }
- """, msg)
- outf.close()
- def append_static_code(filename, outf):
- """Open and copy static code from specified file"""
- basepath = os.path.dirname(os.path.realpath(__file__))
- filepath = os.path.join(basepath, 'swift/%s' % filename)
-
- print("Appending content of %s" % filename)
-
- with open(filepath) as inf:
- for line in inf:
- outf.write(line)
- def generate_message_mappings_array(outf, msgs):
- """Create array for mapping message Ids to proper structs"""
- classes = []
- for msg in msgs:
- classes.append("%u: %s.self" % (msg.id, msg.swift_name))
- t.write(outf, """
- /// Array for mapping message id to proper struct
- private let messageIdToClass: [UInt8: Message.Type] = [${ARRAY_CONTENT}]
- """, {'ARRAY_CONTENT' : ", ".join(classes)})
- def generate_message_lengths_array(outf, msgs):
- """Create array with message lengths to validate known message lengths"""
- # form message lengths array
- lengths = []
- for msg in msgs:
- lengths.append("%u: %u" % (msg.id, msg.wire_length))
- t.write(outf, """
- /// Message lengths array for known messages length validation
- private let messageLengths: [UInt8: UInt8] = [${ARRAY_CONTENT}]
- """, {'ARRAY_CONTENT' : ", ".join(lengths)})
- def generate_message_crc_extra_array(outf, msgs):
- """Add array with CRC extra values to detect incompatible XML changes"""
- crcs = []
- for msg in msgs:
- crcs.append("%u: %u" % (msg.id, msg.crc_extra))
- t.write(outf, """
- /// Message CRSs extra for detection incompatible XML changes
- private let messageCRCsExtra: [UInt8: UInt8] = [${ARRAY_CONTENT}]
- """, {'ARRAY_CONTENT' : ", ".join(crcs)})
- def camel_case_from_underscores(string):
- """Generate a CamelCase string from an underscore_string"""
- components = string.split('_')
- string = ''
- for component in components:
- if component in abbreviations:
- string += component
- else:
- string += component[0].upper() + component[1:].lower()
- return string
- def lower_camel_case_from_underscores(string):
- """Generate a lower-cased camelCase string from an underscore_string"""
- components = string.split('_')
- string = components[0].lower()
- for component in components[1:]:
- string += component[0].upper() + component[1:].lower()
- return string
- def generate_enums_type_info(enums, msgs):
- """Add camel case swift names for enums an entries, descriptions and sort enums alphabetically"""
- for enum in enums:
- enum.swift_name = camel_case_from_underscores(enum.name)
- enum.raw_value_type = get_enum_raw_type(enum, msgs)
- enum.formatted_description = ""
- if enum.description:
- enum.description = " ".join(enum.description.split())
- enum.formatted_description = "/// %s\n" % enum.description
- for index, entry in enumerate(enum.entry):
- if entry.name.endswith("_ENUM_END"):
- enum.enum_end = entry.value
- del enum.entry[index]
- all_entities = []
- entities_info = []
- for entry in enum.entry:
- name = entry.name.replace(enum.name + '_', '')
- """Ensure that enums entry name does not start from digit"""
- if name[0].isdigit():
- name = "MAV_" + name
- entry.swift_name = lower_camel_case_from_underscores(name)
- """Ensure that enums entry name does not match any swift keyword"""
- if entry.swift_name in swift_keywords:
- entry.swift_name = lower_camel_case_from_underscores("MAV_" + name)
- entry.formatted_description = ""
- if entry.description:
- entry.description = " ".join(entry.description.split())
- entry.formatted_description = "\n\t/// " + entry.description + "\n"
- all_entities.append(entry.swift_name)
- entities_info.append('("%s", "%s")' % (entry.name, entry.description.replace('"','\\"')))
- enum.all_entities = ", ".join(all_entities)
- enum.entities_info = ", ".join(entities_info)
- enum.entity_description = enum.description.replace('"','\\"')
- enums.sort(key = lambda enum : enum.swift_name)
- def generate_messages_type_info(msgs):
- """Add proper formated variable names, initializers and type names to use in templates"""
- for msg in msgs:
- msg.swift_name = camel_case_from_underscores(msg.name)
- msg.formatted_description = ""
- if msg.description:
- msg.description = " ".join(msg.description.split())
- msg.formatted_description = "/// %s\n" % " ".join(msg.description.split())
- msg.message_description = msg.description.replace('"','\\"')
- for field in msg.ordered_fields:
- field.swift_name = lower_camel_case_from_underscores(field.name)
- field.init_accessor = field.swift_name if field.swift_name != "data" else "self.%s" % field.swift_name
- field.pack_accessor = field.swift_name if field.swift_name != "payload" else "self.%s" % field.swift_name
- field.return_type = swift_types[field.type][0]
-
- # configure fields initializers
- if field.enum:
- # handle enums
- field.return_type = camel_case_from_underscores(field.enum)
- field.initial_value = "try data.enumeration(at: %u)" % field.wire_offset
- field.payload_setter = "set(%s, at: %u)" % (field.pack_accessor, field.wire_offset)
- elif field.array_length > 0:
- if field.return_type == "String":
- # handle strings
- field.initial_value = "try data." + swift_types[field.type][2] % (field.wire_offset, field.array_length)
- field.payload_setter = swift_types[field.type][3] % (field.pack_accessor, field.wire_offset, field.array_length)
- else:
- # other array types
- field.return_type = "[%s]" % field.return_type
- field.initial_value = "try data.array(at: %u, capacity: %u)" % (field.wire_offset, field.array_length)
- field.payload_setter = "set(%s, at: %u, capacity: %u)" % (field.pack_accessor, field.wire_offset, field.array_length)
- else:
- # simple type field
- field.initial_value = "try data." + swift_types[field.type][2] % field.wire_offset
- field.payload_setter = swift_types[field.type][3] % (field.pack_accessor, field.wire_offset)
- field.formatted_description = ""
- if field.description:
- field.description = " ".join(field.description.split())
- field.formatted_description = "\n\t/// " + field.description + "\n"
-
- 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]
- msg.fields_info = ", ".join(fields_info)
- msgs.sort(key = lambda msg : msg.id)
- def generate(basename, xml_list):
- """Generate complete MAVLink Swift implemenation"""
- msgs = []
- enums = []
- filelist = []
- for xml in xml_list:
- for msg in xml.message: msg.basename = xml.basename.title()
- for enum in xml.enum: enum.basename = xml.basename.title()
- msgs.extend(xml.message)
- enums.extend(xml.enum)
- filelist.append(os.path.basename(xml.filename))
-
- generate_enums_type_info(enums, msgs)
- generate_messages_type_info(msgs)
- generate_mavlink(basename, filelist, xml_list, msgs)
- generate_enums(basename, filelist, xml_list, enums, msgs)
- generate_messages(basename, filelist, xml_list, msgs)
|