mavgen_cs.py 12 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349
  1. #!/usr/bin/env python
  2. '''
  3. parse a MAVLink protocol XML file and generate a CSharp implementation
  4. '''
  5. from __future__ import print_function
  6. from builtins import range
  7. import os
  8. import platform
  9. from . import mavtemplate
  10. t = mavtemplate.MAVTemplate()
  11. # todo - refactor this in to the other array
  12. map = {
  13. 'float' : 'float',
  14. 'double' : 'double',
  15. 'char' : 'byte',
  16. 'int8_t' : 'sbyte',
  17. 'uint8_t' : 'byte',
  18. 'uint8_t_mavlink_version' : 'B',
  19. 'int16_t' : 'Int16',
  20. 'uint16_t' : 'UInt16',
  21. 'int32_t' : 'Int32',
  22. 'uint32_t' : 'UInt32',
  23. 'int64_t' : 'Int64',
  24. 'uint64_t' : 'UInt64',
  25. }
  26. # Map of field type to bitconverter bytedecoding function, and number of bytes used for the encoding
  27. mapType = {
  28. 'float' : ('ToSingle', 4),
  29. 'double' : ('ToDouble', 8),
  30. 'int8_t' : ('ToInt8', 1),
  31. 'uint8_t' : ('ToUInt8', 1),
  32. 'char' : ('ToChar', 1),
  33. 'int16_t' : ('ToInt16', 2),
  34. 'uint16_t' : ('ToUInt16', 2),
  35. 'int32_t' : ('ToInt32', 4),
  36. 'uint32_t' : ('ToUInt32', 4),
  37. 'int64_t' : ('ToInt64', 8),
  38. 'uint64_t' : ('ToUInt64', 8),
  39. }
  40. # Map of field names to names that are C# compatible and not illegal class field names
  41. mapFieldName = {
  42. 'fixed' : '@fixed'
  43. }
  44. def generate_preamble(outf, msgs, args, xml):
  45. print("Generating preamble")
  46. t.write(outf, """
  47. /*
  48. MAVLink protocol implementation (auto-generated by mavgen.py)
  49. Generated from: ${FILELIST}
  50. Note: this file has been auto-generated. DO NOT EDIT
  51. */
  52. using System;
  53. """, {'FILELIST' : ",".join(args)})
  54. def generate_xmlDocSummary(outf, summaryText, tabDepth):
  55. indent = '\t' * tabDepth
  56. escapedText = summaryText.replace("\n","\n%s///" % indent)
  57. outf.write("\n%s/// <summary>\n" % indent)
  58. outf.write("%s/// %s\n" % (indent, escapedText))
  59. outf.write("%s/// </summary>\n" % indent)
  60. def generate_enums(outf, enums):
  61. print("Generating enums")
  62. outf.write("namespace MavLink\n{\n")
  63. for e in enums:
  64. #if len(e.description) > 0:
  65. generate_xmlDocSummary(outf, e.description, 1)
  66. outf.write("\tpublic enum %s : uint\n\t{\n" % e.name)
  67. for entry in e.entry:
  68. if len(entry.description) > 0:
  69. generate_xmlDocSummary(outf, entry.description, 2)
  70. outf.write("\t\t%s = %u,\n" % (entry.name, entry.value))
  71. outf.write("\n\t}\n\n")
  72. outf.write("\n}\n")
  73. def generate_classes(outf, msgs):
  74. print("Generating class definitions")
  75. outf.write("""
  76. namespace MavLink\n{
  77. public abstract class MavlinkMessage
  78. {
  79. public abstract int Serialize(byte[] bytes, ref int offset);
  80. }
  81. """)
  82. for m in msgs:
  83. if (len(m.description) >0):
  84. generate_xmlDocSummary(outf, m.description, 1)
  85. outf.write("""\tpublic class Msg_%s : MavlinkMessage
  86. {
  87. """ % m.name.lower())
  88. for f in m.fields:
  89. if (f.description.upper() != f.name.upper()):
  90. generate_xmlDocSummary(outf, f.description, 2)
  91. if (f.array_length):
  92. outf.write("\t\tpublic %s[] %s; // Array size %s\n" % (map[f.type], mapFieldName.get(f.name, f.name), f.array_length))
  93. else:
  94. outf.write("\t\tpublic %s %s;\n" % (map[f.type], mapFieldName.get(f.name, f.name)))
  95. outf.write("""
  96. public override int Serialize(byte[] bytes, ref int offset)
  97. {
  98. return MavLinkSerializer.Serialize_%s(this, bytes, ref offset);
  99. }
  100. """ % m.name.upper())
  101. outf.write("\t}\n\n")
  102. outf.write("}\n\n")
  103. def generate_Deserialization(outf, messages):
  104. # Create the deserialization funcs
  105. for m in messages:
  106. classname="Msg_%s" % m.name.lower()
  107. outf.write("\n\t\tinternal static MavlinkMessage Deserialize_%s(byte[] bytes, int offset)\n\t\t{\n" % (m.name))
  108. offset = 0
  109. outf.write("\t\t\treturn new %s\n" % classname)
  110. outf.write("\t\t\t{\n")
  111. for f in m.ordered_fields:
  112. if (f.array_length):
  113. 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))
  114. offset += (f.array_length * mapType[f.type][1])
  115. continue
  116. # mapping 'char' to byte here since there is no real equivalent in the CLR
  117. if (f.type == 'uint8_t' or f.type == 'char' ):
  118. outf.write("\t\t\t\t%s = bytes[offset + %s],\n" % (mapFieldName.get(f.name, f.name),offset))
  119. offset+=1
  120. else:
  121. outf.write("\t\t\t\t%s = bitconverter.%s(bytes, offset + %s),\n" % (mapFieldName.get(f.name, f.name), mapType[f.type][0] , offset))
  122. offset += mapType[f.type][1]
  123. outf.write("\t\t\t};\n")
  124. outf.write("\t\t}\n")
  125. def generate_Serialization(outf, messages):
  126. # Create the table of serialization delegates
  127. for m in messages:
  128. classname="Msg_%s" % m.name.lower()
  129. outf.write("\n\t\tinternal static int Serialize_%s(this %s msg, byte[] bytes, ref int offset)\n\t\t{\n" % (m.name, classname))
  130. offset=0
  131. # Now (since Mavlink 1.0) we need to deal with ordering of fields
  132. for f in m.ordered_fields:
  133. if (f.array_length):
  134. outf.write("\t\t\tByteArrayUtil.ToByteArray(msg.%s, bytes, offset + %s, %s);\n" % (f.name, offset, f.array_length))
  135. offset += f.array_length * mapType[f.type][1]
  136. continue
  137. if (f.type == 'uint8_t'):
  138. outf.write("\t\t\tbytes[offset + %s] = msg.%s;\n" % (offset,mapFieldName.get(f.name, f.name)))
  139. offset+=1
  140. elif (f.type == 'int8_t'):
  141. outf.write("\t\t\tbytes[offset + %s] = unchecked((byte)msg.%s);\n" % (offset,mapFieldName.get(f.name, f.name)))
  142. offset+=1
  143. elif (f.type == 'char'):
  144. 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)))
  145. offset+=1
  146. else:
  147. outf.write("\t\t\tbitconverter.GetBytes(msg.%s, bytes, offset + %s);\n" % (mapFieldName.get(f.name, f.name),offset))
  148. offset += mapType[f.type][1]
  149. outf.write("\t\t\toffset += %s;\n" % offset)
  150. outf.write("\t\t\treturn %s;\n" % m.id)
  151. outf.write("\t\t}\n")
  152. def generate_CodecIndex(outf, messages, xml):
  153. outf.write("""
  154. /*
  155. MAVLink protocol implementation (auto-generated by mavgen.py)
  156. Note: this file has been auto-generated. DO NOT EDIT
  157. */
  158. using System;
  159. using System.Collections;
  160. using System.Collections.Generic;
  161. namespace MavLink
  162. {
  163. public static class MavlinkSettings
  164. {
  165. """)
  166. outf.write('\t\tpublic const string WireProtocolVersion = "%s";' % xml[0].wire_protocol_version)
  167. outf.write('\n\t\tpublic const byte ProtocolMarker = 0x%x;' % xml[0].protocol_marker)
  168. outf.write('\n\t\tpublic const bool CrcExtra = %s;' % str(xml[0].crc_extra).lower())
  169. outf.write('\n\t\tpublic const bool IsLittleEndian = %s;' % str(xml[0].little_endian).lower())
  170. outf.write("""
  171. }
  172. public delegate MavlinkMessage MavlinkPacketDeserializeFunc(byte[] bytes, int offset);
  173. //returns the message ID, offset is advanced by the number of bytes used to serialize
  174. public delegate int MavlinkPacketSerializeFunc(byte[] bytes, ref int offset, object mavlinkPacket);
  175. public class MavPacketInfo
  176. {
  177. public MavlinkPacketDeserializeFunc Deserializer;
  178. public int [] OrderMap;
  179. public byte CrcExtra;
  180. public MavPacketInfo(MavlinkPacketDeserializeFunc deserializer, byte crcExtra)
  181. {
  182. this.Deserializer = deserializer;
  183. this.CrcExtra = crcExtra;
  184. }
  185. }
  186. public static class MavLinkSerializer
  187. {
  188. public static void SetDataIsLittleEndian(bool isLittle)
  189. {
  190. bitconverter.SetDataIsLittleEndian(isLittle);
  191. }
  192. private static readonly FrameworkBitConverter bitconverter = new FrameworkBitConverter();
  193. public static Dictionary<int, MavPacketInfo> Lookup = new Dictionary<int, MavPacketInfo>
  194. {""")
  195. for m in messages:
  196. classname="Msg_%s" % m.name.lower()
  197. outf.write("\n\t\t\t{%s, new MavPacketInfo(Deserialize_%s, %s)}," % (m.id, m.name, m.crc_extra))
  198. outf.write("\n\t\t};\n")
  199. def generate(basename, xml):
  200. '''generate complete MAVLink CSharp implemenation'''
  201. structsfilename = basename + '.generated.cs'
  202. msgs = []
  203. enums = []
  204. filelist = []
  205. for x in xml:
  206. msgs.extend(x.message)
  207. enums.extend(x.enum)
  208. filelist.append(os.path.basename(x.filename))
  209. for m in msgs:
  210. m.order_map = [ 0 ] * len(m.fieldnames)
  211. for i in range(0, len(m.fieldnames)):
  212. m.order_map[i] = m.ordered_fieldnames.index(m.fieldnames[i])
  213. m.fields_in_order = []
  214. for i in range(0, len(m.fieldnames)):
  215. m.order_map[i] = m.ordered_fieldnames.index(m.fieldnames[i])
  216. print("Generating messages file: %s" % structsfilename)
  217. dir = os.path.dirname(structsfilename)
  218. if not os.path.exists(dir):
  219. os.makedirs(dir)
  220. outf = open(structsfilename, "w")
  221. generate_preamble(outf, msgs, filelist, xml[0])
  222. outf.write("""
  223. using System.Reflection;
  224. [assembly: AssemblyTitle("Mavlink Classes")]
  225. [assembly: AssemblyDescription("Generated Message Classes for Mavlink. See http://qgroundcontrol.org/mavlink/start")]
  226. [assembly: AssemblyProduct("Mavlink")]
  227. [assembly: AssemblyVersion("1.0.0.0")]
  228. [assembly: AssemblyFileVersion("1.0.0.0")]
  229. """)
  230. generate_enums(outf, enums)
  231. generate_classes(outf, msgs)
  232. outf.close()
  233. print("Generating the (De)Serializer classes")
  234. serfilename = basename + '_codec.generated.cs'
  235. outf = open(serfilename, "w")
  236. generate_CodecIndex(outf, msgs, xml)
  237. generate_Deserialization(outf, msgs)
  238. generate_Serialization(outf, msgs)
  239. outf.write("\t}\n\n")
  240. outf.write("}\n\n")
  241. outf.close()
  242. # Some build commands depend on the platform - eg MS .NET Windows Vs Mono on Linux
  243. if platform.system() == "Windows":
  244. winpath=os.environ['WinDir']
  245. cscCommand = winpath + "\\Microsoft.NET\\Framework\\v4.0.30319\\csc.exe"
  246. if not os.path.exists(cscCommand):
  247. print("\nError: CS compiler not found. .Net Assembly generation skipped")
  248. return
  249. else:
  250. print("Error:.Net Assembly generation not yet supported on non Windows platforms")
  251. return
  252. cscCommand = "csc"
  253. print("Compiling Assembly for .Net Framework 4.0")
  254. generatedCsFiles = [ serfilename, structsfilename]
  255. includedCsFiles = [ 'CS/common/ByteArrayUtil.cs', 'CS/common/FrameworkBitConverter.cs', 'CS/common/Mavlink.cs' ]
  256. outputLibraryPath = os.path.normpath(dir + "/mavlink.dll")
  257. compileCommand = "%s %s" % (cscCommand, "/target:library /debug /out:" + outputLibraryPath)
  258. compileCommand = compileCommand + " /doc:" + os.path.normpath(dir + "/mavlink.xml")
  259. for csFile in generatedCsFiles + includedCsFiles:
  260. compileCommand = compileCommand + " " + os.path.normpath(csFile)
  261. #print("Cmd:" + compileCommand)
  262. res = os.system (compileCommand)
  263. if res == 0:
  264. print("Generated %s OK" % outputLibraryPath)
  265. else:
  266. print("Error")
  267. print("Error: Compilation failed. (" + str(res) + ")")
  268. raise SystemError("Compilation failed. (" + str(res) + ")")