Mavlink.cs 15 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437
  1. using System;
  2. using MavLink;
  3. namespace MavLink
  4. {
  5. /// <summary>
  6. /// Mavlink communication class.
  7. /// </summary>
  8. /// <remarks>
  9. /// Keeps track of state across send and receive of packets.
  10. /// User of this class can just send Mavlink Messsages, and
  11. /// receive them by feeding this class bytes off the wire as
  12. /// they arrive
  13. /// </remarks>
  14. public class Mavlink
  15. {
  16. private byte[] leftovers;
  17. /// <summary>
  18. /// Event raised when a message is decoded successfully
  19. /// </summary>
  20. public event PacketReceivedEventHandler PacketReceived;
  21. /// <summary>
  22. /// Total number of packets successfully received so far
  23. /// </summary>
  24. public UInt32 PacketsReceived { get; private set; }
  25. /// <summary>
  26. /// Total number of packets which have been rejected due to a failed crc
  27. /// </summary>
  28. public UInt32 BadCrcPacketsReceived { get; private set; }
  29. /// <summary>
  30. /// Raised when a packet does not pass CRC
  31. /// </summary>
  32. public event PacketCRCFailEventHandler PacketFailedCRC;
  33. /// <summary>
  34. /// Raised when a number of bytes are passed over and cannot
  35. /// be used to decode a packet
  36. /// </summary>
  37. public event PacketCRCFailEventHandler BytesUnused;
  38. // The current packet sequence number for transmission
  39. // public so it can be manipulated for testing
  40. // Normal usage would only read this
  41. public byte txPacketSequence;
  42. /// <summary>
  43. /// Create a new MavlinkLink Object
  44. /// </summary>
  45. public Mavlink()
  46. {
  47. MavLinkSerializer.SetDataIsLittleEndian(MavlinkSettings.IsLittleEndian);
  48. leftovers = new byte[] {};
  49. }
  50. /// <summary>
  51. /// Process latest bytes from the stream. Received packets will be raised in the event
  52. /// </summary>
  53. public void ParseBytes(byte[] newlyReceived)
  54. {
  55. uint i = 0;
  56. // copy the old and new into a contiguous array
  57. // This is pretty inefficient...
  58. var bytesToProcess = new byte[newlyReceived.Length + leftovers.Length];
  59. int j = 0;
  60. for (i = 0; i < leftovers.Length; i++)
  61. bytesToProcess[j++] = leftovers[i];
  62. for (i = 0; i < newlyReceived.Length; i++)
  63. bytesToProcess[j++] = newlyReceived[i];
  64. i = 0;
  65. // we are going to loop and decode packets until we use up the data
  66. // at which point we will return. Hence one call to this method could
  67. // result in multiple packet decode events
  68. while (true)
  69. {
  70. // Hunt for the start char
  71. int huntStartPos = (int)i;
  72. while (i < bytesToProcess.Length && bytesToProcess[i] != MavlinkSettings.ProtocolMarker)
  73. i++;
  74. if (i == bytesToProcess.Length)
  75. {
  76. // No start byte found in all our bytes. Dump them, Exit.
  77. leftovers = new byte[] { };
  78. return;
  79. }
  80. if (i > huntStartPos)
  81. {
  82. // if we get here then are some bytes which this code thinks are
  83. // not interesting and would be dumped. For diagnostics purposes,
  84. // lets pop these bytes up in an event.
  85. if (BytesUnused != null)
  86. {
  87. var badBytes = new byte[i - huntStartPos];
  88. Array.Copy(bytesToProcess, huntStartPos, badBytes, 0, (int)(i - huntStartPos));
  89. BytesUnused(this, new PacketCRCFailEventArgs(badBytes, bytesToProcess.Length - huntStartPos));
  90. }
  91. }
  92. // We need at least the minimum length of a packet to process it.
  93. // The minimum packet length is 8 bytes for acknowledgement packets without payload
  94. // if we don't have the minimum now, go round again
  95. if (bytesToProcess.Length - i < 8)
  96. {
  97. leftovers = new byte[bytesToProcess.Length - i];
  98. j = 0;
  99. while (i < bytesToProcess.Length)
  100. leftovers[j++] = bytesToProcess[i++];
  101. return;
  102. }
  103. /*
  104. * Byte order:
  105. *
  106. * 0 Packet start sign
  107. * 1 Payload length 0 - 255
  108. * 2 Packet sequence 0 - 255
  109. * 3 System ID 1 - 255
  110. * 4 Component ID 0 - 255
  111. * 5 Message ID 0 - 255
  112. * 6 to (n+6) Data (0 - 255) bytes
  113. * (n+7) to (n+8) Checksum (high byte, low byte) for v0.9, lowbyte, highbyte for 1.0
  114. *
  115. */
  116. UInt16 payLoadLength = bytesToProcess[i + 1];
  117. // Now we know the packet length,
  118. // If we don't have enough bytes in this packet to satisfy that packet lenghth,
  119. // then dump the whole lot in the leftovers and do nothing else - go round again
  120. if (payLoadLength > (bytesToProcess.Length - i - 8)) // payload + 'overhead' bytes (crc, system etc)
  121. {
  122. // back up to the start char for next cycle
  123. j = 0;
  124. leftovers = new byte[bytesToProcess.Length - i];
  125. for (; i < bytesToProcess.Length; i++)
  126. {
  127. leftovers[j++] = bytesToProcess[i];
  128. }
  129. return;
  130. }
  131. i++;
  132. // Check the CRC. Does not include the starting 'U' byte but does include the length
  133. var crc1 = Mavlink_Crc.Calculate(bytesToProcess, (UInt16)(i), (UInt16)(payLoadLength + 5));
  134. if (MavlinkSettings.CrcExtra)
  135. {
  136. var possibleMsgId = bytesToProcess[i + 4];
  137. if (!MavLinkSerializer.Lookup.ContainsKey(possibleMsgId))
  138. {
  139. // we have received an unknown message. In this case we don't know the special
  140. // CRC extra, so we have no choice but to fail.
  141. // The way we do this is to just let the procedure continue
  142. // There will be a natural failure of the main packet CRC
  143. }
  144. else
  145. {
  146. var extra = MavLinkSerializer.Lookup[possibleMsgId];
  147. crc1 = Mavlink_Crc.CrcAccumulate(extra.CrcExtra, crc1);
  148. }
  149. }
  150. byte crcHigh = (byte)(crc1 & 0xFF);
  151. byte crcLow = (byte)(crc1 >> 8);
  152. byte messageCrcHigh = bytesToProcess[i + 5 + payLoadLength];
  153. byte messageCrcLow = bytesToProcess[i + 6 + payLoadLength];
  154. if (messageCrcHigh == crcHigh && messageCrcLow == crcLow)
  155. {
  156. // This is used for data drop outs metrics, not packet windows
  157. // so we should consider this here.
  158. // We pass up to subscribers only as an advisory thing
  159. var rxPacketSequence = bytesToProcess[++i];
  160. i++;
  161. var packet = new byte[payLoadLength + 3]; // +3 because we are going to send up the sys and comp id and msg type with the data
  162. for (j = 0; j < packet.Length; j++)
  163. packet[j] = bytesToProcess[i + j];
  164. var debugArray = new byte[payLoadLength + 7];
  165. Array.Copy(bytesToProcess, (int)(i - 3), debugArray, 0, debugArray.Length);
  166. //OnPacketDecoded(packet, rxPacketSequence, debugArray);
  167. ProcessPacketBytes(packet, rxPacketSequence);
  168. PacketsReceived++;
  169. // clear leftovers, just incase this is the last packet
  170. leftovers = new byte[] { };
  171. // advance i here by j to avoid unecessary hunting
  172. // todo: could advance by j + 2 I think?
  173. i = i + (uint)(j + 2);
  174. }
  175. else
  176. {
  177. var badBytes = new byte[i + 7 + payLoadLength];
  178. Array.Copy(bytesToProcess, (int)(i - 1), badBytes, 0, payLoadLength + 7);
  179. if (PacketFailedCRC != null)
  180. {
  181. PacketFailedCRC(this, new PacketCRCFailEventArgs(badBytes, (int)(bytesToProcess.Length - i - 1)));
  182. }
  183. BadCrcPacketsReceived++;
  184. }
  185. }
  186. }
  187. public byte[] Send(MavlinkPacket mavlinkPacket)
  188. {
  189. var bytes = this.Serialize(mavlinkPacket.Message, mavlinkPacket.SystemId, mavlinkPacket.ComponentId);
  190. return SendPacketLinkLayer(bytes);
  191. }
  192. // Send a raw message over the link -
  193. // this will add start byte, lenghth, crc and other link layer stuff
  194. private byte[] SendPacketLinkLayer(byte[] packetData)
  195. {
  196. /*
  197. * Byte order:
  198. *
  199. * 0 Packet start sign
  200. * 1 Payload length 0 - 255
  201. * 2 Packet sequence 0 - 255
  202. * 3 System ID 1 - 255
  203. * 4 Component ID 0 - 255
  204. * 5 Message ID 0 - 255
  205. * 6 to (n+6) Data (0 - 255) bytes
  206. * (n+7) to (n+8) Checksum (high byte, low byte)
  207. *
  208. */
  209. var outBytes = new byte[packetData.Length + 5];
  210. outBytes[0] = MavlinkSettings.ProtocolMarker;
  211. outBytes[1] = (byte)(packetData.Length-3); // 3 bytes for sequence, id, msg type which this
  212. // layer does not concern itself with
  213. outBytes[2] = unchecked(txPacketSequence++);
  214. int i;
  215. for ( i = 0; i < packetData.Length; i++)
  216. {
  217. outBytes[i + 3] = packetData[i];
  218. }
  219. // Check the CRC. Does not include the starting byte but does include the length
  220. var crc1 = Mavlink_Crc.Calculate(outBytes, 1, (UInt16)(packetData.Length + 2));
  221. if (MavlinkSettings.CrcExtra)
  222. {
  223. var possibleMsgId = outBytes[5];
  224. var extra = MavLinkSerializer.Lookup[possibleMsgId];
  225. crc1 = Mavlink_Crc.CrcAccumulate(extra.CrcExtra, crc1);
  226. }
  227. byte crc_high = (byte)(crc1 & 0xFF);
  228. byte crc_low = (byte)(crc1 >> 8);
  229. outBytes[i + 3] = crc_high;
  230. outBytes[i + 4] = crc_low;
  231. return outBytes;
  232. }
  233. // Process a raw packet in it's entirety in the given byte array
  234. // if deserialization is successful, then the packetdecoded event will be raised
  235. private void ProcessPacketBytes(byte[] packetBytes, byte rxPacketSequence)
  236. {
  237. // System ID 1 - 255
  238. // Component ID 0 - 255
  239. // Message ID 0 - 255
  240. // 6 to (n+6) Data (0 - 255) bytes
  241. var packet = new MavlinkPacket
  242. {
  243. SystemId = packetBytes[0],
  244. ComponentId = packetBytes[1],
  245. SequenceNumber = rxPacketSequence,
  246. Message = this.Deserialize(packetBytes, 2)
  247. };
  248. if (PacketReceived != null)
  249. {
  250. PacketReceived(this, packet);
  251. }
  252. // else do what?
  253. }
  254. public MavlinkMessage Deserialize(byte[] bytes, int offset)
  255. {
  256. // first byte is the mavlink
  257. var packetNum = (int)bytes[offset + 0];
  258. var packetGen = MavLinkSerializer.Lookup[packetNum].Deserializer;
  259. return packetGen.Invoke(bytes, offset + 1);
  260. }
  261. public byte[] Serialize(MavlinkMessage message, int systemId, int componentId)
  262. {
  263. var buff = new byte[256];
  264. buff[0] = (byte)systemId;
  265. buff[1] = (byte)componentId;
  266. var endPos = 3;
  267. var msgId = message.Serialize(buff, ref endPos);
  268. buff[2] = (byte)msgId;
  269. var resultBytes = new byte[endPos];
  270. Array.Copy(buff, resultBytes, endPos);
  271. return resultBytes;
  272. }
  273. }
  274. ///<summary>
  275. /// Describes an occurance when a packet fails CRC
  276. ///</summary>
  277. public class PacketCRCFailEventArgs : EventArgs
  278. {
  279. ///<summary>
  280. ///</summary>
  281. public PacketCRCFailEventArgs(byte[] badPacket, int offset)
  282. {
  283. BadPacket = badPacket;
  284. Offset = offset;
  285. }
  286. /// <summary>
  287. /// The bytes that filed the CRC, including the starting character
  288. /// </summary>
  289. public byte[] BadPacket;
  290. /// <summary>
  291. /// The offset in bytes where the start of the block begins, e.g
  292. /// 50 would mean the block of badbytes would start 50 bytes ago
  293. /// in the stread. No negative sign is necessary
  294. /// </summary>
  295. public int Offset;
  296. }
  297. ///<summary>
  298. /// Handler for an PacketFailedCRC Event
  299. ///</summary>
  300. public delegate void PacketCRCFailEventHandler(object sender, PacketCRCFailEventArgs e);
  301. public delegate void PacketReceivedEventHandler(object sender, MavlinkPacket e);
  302. ///<summary>
  303. /// Represents a Mavlink message - both the message object itself
  304. /// and the identified sending party
  305. ///</summary>
  306. public class MavlinkPacket
  307. {
  308. /// <summary>
  309. /// The sender's system ID
  310. /// </summary>
  311. public int SystemId;
  312. /// <summary>
  313. /// The sender's component ID
  314. /// </summary>
  315. public int ComponentId;
  316. /// <summary>
  317. /// The sequence number received for this packet
  318. /// </summary>
  319. public byte SequenceNumber;
  320. /// <summary>
  321. /// Time of receipt
  322. /// </summary>
  323. public DateTime TimeStamp;
  324. /// <summary>
  325. /// Object which is the mavlink message
  326. /// </summary>
  327. public MavlinkMessage Message;
  328. }
  329. /// <summary>
  330. /// Crc code copied/adapted from ardumega planner code
  331. /// </summary>
  332. internal static class Mavlink_Crc
  333. {
  334. const UInt16 X25_INIT_CRC = 0xffff;
  335. public static UInt16 CrcAccumulate(byte b, UInt16 crc)
  336. {
  337. unchecked
  338. {
  339. byte ch = (byte)(b ^ (byte)(crc & 0x00ff));
  340. ch = (byte)(ch ^ (ch << 4));
  341. return (UInt16)((crc >> 8) ^ (ch << 8) ^ (ch << 3) ^ (ch >> 4));
  342. }
  343. }
  344. // For a "message" of length bytes contained in the byte array
  345. // pointed to by buffer, calculate the CRC
  346. public static UInt16 Calculate(byte[] buffer, UInt16 start, UInt16 length)
  347. {
  348. UInt16 crcTmp = X25_INIT_CRC;
  349. for (int i = start; i < start + length; i++)
  350. crcTmp = CrcAccumulate(buffer[i], crcTmp);
  351. return crcTmp;
  352. }
  353. }
  354. }