#!/usr/bin/env python ''' parse a MAVLink protocol XML file and generate a CSharp implementation ''' from __future__ import print_function from builtins import range import os import platform from . import mavtemplate t = mavtemplate.MAVTemplate() # todo - refactor this in to the other array map = { 'float' : 'float', 'double' : 'double', 'char' : 'byte', 'int8_t' : 'sbyte', 'uint8_t' : 'byte', 'uint8_t_mavlink_version' : 'B', 'int16_t' : 'Int16', 'uint16_t' : 'UInt16', 'int32_t' : 'Int32', 'uint32_t' : 'UInt32', 'int64_t' : 'Int64', 'uint64_t' : 'UInt64', } # Map of field type to bitconverter bytedecoding function, and number of bytes used for the encoding mapType = { 'float' : ('ToSingle', 4), 'double' : ('ToDouble', 8), 'int8_t' : ('ToInt8', 1), 'uint8_t' : ('ToUInt8', 1), 'char' : ('ToChar', 1), 'int16_t' : ('ToInt16', 2), 'uint16_t' : ('ToUInt16', 2), 'int32_t' : ('ToInt32', 4), 'uint32_t' : ('ToUInt32', 4), 'int64_t' : ('ToInt64', 8), 'uint64_t' : ('ToUInt64', 8), } # Map of field names to names that are C# compatible and not illegal class field names mapFieldName = { 'fixed' : '@fixed' } def generate_preamble(outf, msgs, args, xml): print("Generating preamble") t.write(outf, """ /* MAVLink protocol implementation (auto-generated by mavgen.py) Generated from: ${FILELIST} Note: this file has been auto-generated. DO NOT EDIT */ using System; """, {'FILELIST' : ",".join(args)}) def generate_xmlDocSummary(outf, summaryText, tabDepth): indent = '\t' * tabDepth escapedText = summaryText.replace("\n","\n%s///" % indent) outf.write("\n%s/// \n" % indent) outf.write("%s/// %s\n" % (indent, escapedText)) outf.write("%s/// \n" % indent) def generate_enums(outf, enums): print("Generating enums") outf.write("namespace MavLink\n{\n") for e in enums: #if len(e.description) > 0: generate_xmlDocSummary(outf, e.description, 1) outf.write("\tpublic enum %s : uint\n\t{\n" % e.name) for entry in e.entry: if len(entry.description) > 0: generate_xmlDocSummary(outf, entry.description, 2) outf.write("\t\t%s = %u,\n" % (entry.name, entry.value)) outf.write("\n\t}\n\n") outf.write("\n}\n") def generate_classes(outf, msgs): print("Generating class definitions") outf.write(""" namespace MavLink\n{ public abstract class MavlinkMessage { public abstract int Serialize(byte[] bytes, ref int offset); } """) for m in msgs: if (len(m.description) >0): generate_xmlDocSummary(outf, m.description, 1) outf.write("""\tpublic class Msg_%s : MavlinkMessage { """ % m.name.lower()) for f in m.fields: if (f.description.upper() != f.name.upper()): generate_xmlDocSummary(outf, f.description, 2) if (f.array_length): outf.write("\t\tpublic %s[] %s; // Array size %s\n" % (map[f.type], mapFieldName.get(f.name, f.name), f.array_length)) else: outf.write("\t\tpublic %s %s;\n" % (map[f.type], mapFieldName.get(f.name, f.name))) outf.write(""" public override int Serialize(byte[] bytes, ref int offset) { return MavLinkSerializer.Serialize_%s(this, bytes, ref offset); } """ % m.name.upper()) outf.write("\t}\n\n") outf.write("}\n\n") def generate_Deserialization(outf, messages): # Create the deserialization funcs for m in messages: classname="Msg_%s" % m.name.lower() outf.write("\n\t\tinternal static MavlinkMessage Deserialize_%s(byte[] bytes, int offset)\n\t\t{\n" % (m.name)) offset = 0 outf.write("\t\t\treturn new %s\n" % classname) outf.write("\t\t\t{\n") for f in m.ordered_fields: if (f.array_length): outf.write("\t\t\t\t%s = ByteArrayUtil.%s(bytes, offset + %s, %s),\n" % (mapFieldName.get(f.name, f.name), mapType[f.type][0], offset, f.array_length)) offset += (f.array_length * mapType[f.type][1]) continue # mapping 'char' to byte here since there is no real equivalent in the CLR if (f.type == 'uint8_t' or f.type == 'char' ): outf.write("\t\t\t\t%s = bytes[offset + %s],\n" % (mapFieldName.get(f.name, f.name),offset)) offset+=1 else: outf.write("\t\t\t\t%s = bitconverter.%s(bytes, offset + %s),\n" % (mapFieldName.get(f.name, f.name), mapType[f.type][0] , offset)) offset += mapType[f.type][1] outf.write("\t\t\t};\n") outf.write("\t\t}\n") def generate_Serialization(outf, messages): # Create the table of serialization delegates for m in messages: classname="Msg_%s" % m.name.lower() outf.write("\n\t\tinternal static int Serialize_%s(this %s msg, byte[] bytes, ref int offset)\n\t\t{\n" % (m.name, classname)) offset=0 # Now (since Mavlink 1.0) we need to deal with ordering of fields for f in m.ordered_fields: if (f.array_length): outf.write("\t\t\tByteArrayUtil.ToByteArray(msg.%s, bytes, offset + %s, %s);\n" % (f.name, offset, f.array_length)) offset += f.array_length * mapType[f.type][1] continue if (f.type == 'uint8_t'): outf.write("\t\t\tbytes[offset + %s] = msg.%s;\n" % (offset,mapFieldName.get(f.name, f.name))) offset+=1 elif (f.type == 'int8_t'): outf.write("\t\t\tbytes[offset + %s] = unchecked((byte)msg.%s);\n" % (offset,mapFieldName.get(f.name, f.name))) offset+=1 elif (f.type == 'char'): outf.write("\t\t\tbytes[offset + %s] = msg.%s; // todo: check int8_t and char are compatible\n" % (offset,mapFieldName.get(f.name, f.name))) offset+=1 else: outf.write("\t\t\tbitconverter.GetBytes(msg.%s, bytes, offset + %s);\n" % (mapFieldName.get(f.name, f.name),offset)) offset += mapType[f.type][1] outf.write("\t\t\toffset += %s;\n" % offset) outf.write("\t\t\treturn %s;\n" % m.id) outf.write("\t\t}\n") def generate_CodecIndex(outf, messages, xml): outf.write(""" /* MAVLink protocol implementation (auto-generated by mavgen.py) Note: this file has been auto-generated. DO NOT EDIT */ using System; using System.Collections; using System.Collections.Generic; namespace MavLink { public static class MavlinkSettings { """) outf.write('\t\tpublic const string WireProtocolVersion = "%s";' % xml[0].wire_protocol_version) outf.write('\n\t\tpublic const byte ProtocolMarker = 0x%x;' % xml[0].protocol_marker) outf.write('\n\t\tpublic const bool CrcExtra = %s;' % str(xml[0].crc_extra).lower()) outf.write('\n\t\tpublic const bool IsLittleEndian = %s;' % str(xml[0].little_endian).lower()) outf.write(""" } public delegate MavlinkMessage MavlinkPacketDeserializeFunc(byte[] bytes, int offset); //returns the message ID, offset is advanced by the number of bytes used to serialize public delegate int MavlinkPacketSerializeFunc(byte[] bytes, ref int offset, object mavlinkPacket); public class MavPacketInfo { public MavlinkPacketDeserializeFunc Deserializer; public int [] OrderMap; public byte CrcExtra; public MavPacketInfo(MavlinkPacketDeserializeFunc deserializer, byte crcExtra) { this.Deserializer = deserializer; this.CrcExtra = crcExtra; } } public static class MavLinkSerializer { public static void SetDataIsLittleEndian(bool isLittle) { bitconverter.SetDataIsLittleEndian(isLittle); } private static readonly FrameworkBitConverter bitconverter = new FrameworkBitConverter(); public static Dictionary Lookup = new Dictionary {""") for m in messages: classname="Msg_%s" % m.name.lower() outf.write("\n\t\t\t{%s, new MavPacketInfo(Deserialize_%s, %s)}," % (m.id, m.name, m.crc_extra)) outf.write("\n\t\t};\n") def generate(basename, xml): '''generate complete MAVLink CSharp implemenation''' structsfilename = basename + '.generated.cs' msgs = [] enums = [] filelist = [] for x in xml: msgs.extend(x.message) enums.extend(x.enum) filelist.append(os.path.basename(x.filename)) for m in msgs: m.order_map = [ 0 ] * len(m.fieldnames) for i in range(0, len(m.fieldnames)): m.order_map[i] = m.ordered_fieldnames.index(m.fieldnames[i]) m.fields_in_order = [] for i in range(0, len(m.fieldnames)): m.order_map[i] = m.ordered_fieldnames.index(m.fieldnames[i]) print("Generating messages file: %s" % structsfilename) dir = os.path.dirname(structsfilename) if not os.path.exists(dir): os.makedirs(dir) outf = open(structsfilename, "w") generate_preamble(outf, msgs, filelist, xml[0]) outf.write(""" using System.Reflection; [assembly: AssemblyTitle("Mavlink Classes")] [assembly: AssemblyDescription("Generated Message Classes for Mavlink. See http://qgroundcontrol.org/mavlink/start")] [assembly: AssemblyProduct("Mavlink")] [assembly: AssemblyVersion("1.0.0.0")] [assembly: AssemblyFileVersion("1.0.0.0")] """) generate_enums(outf, enums) generate_classes(outf, msgs) outf.close() print("Generating the (De)Serializer classes") serfilename = basename + '_codec.generated.cs' outf = open(serfilename, "w") generate_CodecIndex(outf, msgs, xml) generate_Deserialization(outf, msgs) generate_Serialization(outf, msgs) outf.write("\t}\n\n") outf.write("}\n\n") outf.close() # Some build commands depend on the platform - eg MS .NET Windows Vs Mono on Linux if platform.system() == "Windows": winpath=os.environ['WinDir'] cscCommand = winpath + "\\Microsoft.NET\\Framework\\v4.0.30319\\csc.exe" if not os.path.exists(cscCommand): print("\nError: CS compiler not found. .Net Assembly generation skipped") return else: print("Error:.Net Assembly generation not yet supported on non Windows platforms") return cscCommand = "csc" print("Compiling Assembly for .Net Framework 4.0") generatedCsFiles = [ serfilename, structsfilename] includedCsFiles = [ 'CS/common/ByteArrayUtil.cs', 'CS/common/FrameworkBitConverter.cs', 'CS/common/Mavlink.cs' ] outputLibraryPath = os.path.normpath(dir + "/mavlink.dll") compileCommand = "%s %s" % (cscCommand, "/target:library /debug /out:" + outputLibraryPath) compileCommand = compileCommand + " /doc:" + os.path.normpath(dir + "/mavlink.xml") for csFile in generatedCsFiles + includedCsFiles: compileCommand = compileCommand + " " + os.path.normpath(csFile) #print("Cmd:" + compileCommand) res = os.system (compileCommand) if res == 0: print("Generated %s OK" % outputLibraryPath) else: print("Error") print("Error: Compilation failed. (" + str(res) + ")") raise SystemError("Compilation failed. (" + str(res) + ")")