using System;
using MavLink;
namespace MavLink
{
///
/// Mavlink communication class.
///
///
/// Keeps track of state across send and receive of packets.
/// User of this class can just send Mavlink Messsages, and
/// receive them by feeding this class bytes off the wire as
/// they arrive
///
public class Mavlink
{
private byte[] leftovers;
///
/// Event raised when a message is decoded successfully
///
public event PacketReceivedEventHandler PacketReceived;
///
/// Total number of packets successfully received so far
///
public UInt32 PacketsReceived { get; private set; }
///
/// Total number of packets which have been rejected due to a failed crc
///
public UInt32 BadCrcPacketsReceived { get; private set; }
///
/// Raised when a packet does not pass CRC
///
public event PacketCRCFailEventHandler PacketFailedCRC;
///
/// Raised when a number of bytes are passed over and cannot
/// be used to decode a packet
///
public event PacketCRCFailEventHandler BytesUnused;
// The current packet sequence number for transmission
// public so it can be manipulated for testing
// Normal usage would only read this
public byte txPacketSequence;
///
/// Create a new MavlinkLink Object
///
public Mavlink()
{
MavLinkSerializer.SetDataIsLittleEndian(MavlinkSettings.IsLittleEndian);
leftovers = new byte[] {};
}
///
/// Process latest bytes from the stream. Received packets will be raised in the event
///
public void ParseBytes(byte[] newlyReceived)
{
uint i = 0;
// copy the old and new into a contiguous array
// This is pretty inefficient...
var bytesToProcess = new byte[newlyReceived.Length + leftovers.Length];
int j = 0;
for (i = 0; i < leftovers.Length; i++)
bytesToProcess[j++] = leftovers[i];
for (i = 0; i < newlyReceived.Length; i++)
bytesToProcess[j++] = newlyReceived[i];
i = 0;
// we are going to loop and decode packets until we use up the data
// at which point we will return. Hence one call to this method could
// result in multiple packet decode events
while (true)
{
// Hunt for the start char
int huntStartPos = (int)i;
while (i < bytesToProcess.Length && bytesToProcess[i] != MavlinkSettings.ProtocolMarker)
i++;
if (i == bytesToProcess.Length)
{
// No start byte found in all our bytes. Dump them, Exit.
leftovers = new byte[] { };
return;
}
if (i > huntStartPos)
{
// if we get here then are some bytes which this code thinks are
// not interesting and would be dumped. For diagnostics purposes,
// lets pop these bytes up in an event.
if (BytesUnused != null)
{
var badBytes = new byte[i - huntStartPos];
Array.Copy(bytesToProcess, huntStartPos, badBytes, 0, (int)(i - huntStartPos));
BytesUnused(this, new PacketCRCFailEventArgs(badBytes, bytesToProcess.Length - huntStartPos));
}
}
// We need at least the minimum length of a packet to process it.
// The minimum packet length is 8 bytes for acknowledgement packets without payload
// if we don't have the minimum now, go round again
if (bytesToProcess.Length - i < 8)
{
leftovers = new byte[bytesToProcess.Length - i];
j = 0;
while (i < bytesToProcess.Length)
leftovers[j++] = bytesToProcess[i++];
return;
}
/*
* Byte order:
*
* 0 Packet start sign
* 1 Payload length 0 - 255
* 2 Packet sequence 0 - 255
* 3 System ID 1 - 255
* 4 Component ID 0 - 255
* 5 Message ID 0 - 255
* 6 to (n+6) Data (0 - 255) bytes
* (n+7) to (n+8) Checksum (high byte, low byte) for v0.9, lowbyte, highbyte for 1.0
*
*/
UInt16 payLoadLength = bytesToProcess[i + 1];
// Now we know the packet length,
// If we don't have enough bytes in this packet to satisfy that packet lenghth,
// then dump the whole lot in the leftovers and do nothing else - go round again
if (payLoadLength > (bytesToProcess.Length - i - 8)) // payload + 'overhead' bytes (crc, system etc)
{
// back up to the start char for next cycle
j = 0;
leftovers = new byte[bytesToProcess.Length - i];
for (; i < bytesToProcess.Length; i++)
{
leftovers[j++] = bytesToProcess[i];
}
return;
}
i++;
// Check the CRC. Does not include the starting 'U' byte but does include the length
var crc1 = Mavlink_Crc.Calculate(bytesToProcess, (UInt16)(i), (UInt16)(payLoadLength + 5));
if (MavlinkSettings.CrcExtra)
{
var possibleMsgId = bytesToProcess[i + 4];
if (!MavLinkSerializer.Lookup.ContainsKey(possibleMsgId))
{
// we have received an unknown message. In this case we don't know the special
// CRC extra, so we have no choice but to fail.
// The way we do this is to just let the procedure continue
// There will be a natural failure of the main packet CRC
}
else
{
var extra = MavLinkSerializer.Lookup[possibleMsgId];
crc1 = Mavlink_Crc.CrcAccumulate(extra.CrcExtra, crc1);
}
}
byte crcHigh = (byte)(crc1 & 0xFF);
byte crcLow = (byte)(crc1 >> 8);
byte messageCrcHigh = bytesToProcess[i + 5 + payLoadLength];
byte messageCrcLow = bytesToProcess[i + 6 + payLoadLength];
if (messageCrcHigh == crcHigh && messageCrcLow == crcLow)
{
// This is used for data drop outs metrics, not packet windows
// so we should consider this here.
// We pass up to subscribers only as an advisory thing
var rxPacketSequence = bytesToProcess[++i];
i++;
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
for (j = 0; j < packet.Length; j++)
packet[j] = bytesToProcess[i + j];
var debugArray = new byte[payLoadLength + 7];
Array.Copy(bytesToProcess, (int)(i - 3), debugArray, 0, debugArray.Length);
//OnPacketDecoded(packet, rxPacketSequence, debugArray);
ProcessPacketBytes(packet, rxPacketSequence);
PacketsReceived++;
// clear leftovers, just incase this is the last packet
leftovers = new byte[] { };
// advance i here by j to avoid unecessary hunting
// todo: could advance by j + 2 I think?
i = i + (uint)(j + 2);
}
else
{
var badBytes = new byte[i + 7 + payLoadLength];
Array.Copy(bytesToProcess, (int)(i - 1), badBytes, 0, payLoadLength + 7);
if (PacketFailedCRC != null)
{
PacketFailedCRC(this, new PacketCRCFailEventArgs(badBytes, (int)(bytesToProcess.Length - i - 1)));
}
BadCrcPacketsReceived++;
}
}
}
public byte[] Send(MavlinkPacket mavlinkPacket)
{
var bytes = this.Serialize(mavlinkPacket.Message, mavlinkPacket.SystemId, mavlinkPacket.ComponentId);
return SendPacketLinkLayer(bytes);
}
// Send a raw message over the link -
// this will add start byte, lenghth, crc and other link layer stuff
private byte[] SendPacketLinkLayer(byte[] packetData)
{
/*
* Byte order:
*
* 0 Packet start sign
* 1 Payload length 0 - 255
* 2 Packet sequence 0 - 255
* 3 System ID 1 - 255
* 4 Component ID 0 - 255
* 5 Message ID 0 - 255
* 6 to (n+6) Data (0 - 255) bytes
* (n+7) to (n+8) Checksum (high byte, low byte)
*
*/
var outBytes = new byte[packetData.Length + 5];
outBytes[0] = MavlinkSettings.ProtocolMarker;
outBytes[1] = (byte)(packetData.Length-3); // 3 bytes for sequence, id, msg type which this
// layer does not concern itself with
outBytes[2] = unchecked(txPacketSequence++);
int i;
for ( i = 0; i < packetData.Length; i++)
{
outBytes[i + 3] = packetData[i];
}
// Check the CRC. Does not include the starting byte but does include the length
var crc1 = Mavlink_Crc.Calculate(outBytes, 1, (UInt16)(packetData.Length + 2));
if (MavlinkSettings.CrcExtra)
{
var possibleMsgId = outBytes[5];
var extra = MavLinkSerializer.Lookup[possibleMsgId];
crc1 = Mavlink_Crc.CrcAccumulate(extra.CrcExtra, crc1);
}
byte crc_high = (byte)(crc1 & 0xFF);
byte crc_low = (byte)(crc1 >> 8);
outBytes[i + 3] = crc_high;
outBytes[i + 4] = crc_low;
return outBytes;
}
// Process a raw packet in it's entirety in the given byte array
// if deserialization is successful, then the packetdecoded event will be raised
private void ProcessPacketBytes(byte[] packetBytes, byte rxPacketSequence)
{
// System ID 1 - 255
// Component ID 0 - 255
// Message ID 0 - 255
// 6 to (n+6) Data (0 - 255) bytes
var packet = new MavlinkPacket
{
SystemId = packetBytes[0],
ComponentId = packetBytes[1],
SequenceNumber = rxPacketSequence,
Message = this.Deserialize(packetBytes, 2)
};
if (PacketReceived != null)
{
PacketReceived(this, packet);
}
// else do what?
}
public MavlinkMessage Deserialize(byte[] bytes, int offset)
{
// first byte is the mavlink
var packetNum = (int)bytes[offset + 0];
var packetGen = MavLinkSerializer.Lookup[packetNum].Deserializer;
return packetGen.Invoke(bytes, offset + 1);
}
public byte[] Serialize(MavlinkMessage message, int systemId, int componentId)
{
var buff = new byte[256];
buff[0] = (byte)systemId;
buff[1] = (byte)componentId;
var endPos = 3;
var msgId = message.Serialize(buff, ref endPos);
buff[2] = (byte)msgId;
var resultBytes = new byte[endPos];
Array.Copy(buff, resultBytes, endPos);
return resultBytes;
}
}
///
/// Describes an occurance when a packet fails CRC
///
public class PacketCRCFailEventArgs : EventArgs
{
///
///
public PacketCRCFailEventArgs(byte[] badPacket, int offset)
{
BadPacket = badPacket;
Offset = offset;
}
///
/// The bytes that filed the CRC, including the starting character
///
public byte[] BadPacket;
///
/// The offset in bytes where the start of the block begins, e.g
/// 50 would mean the block of badbytes would start 50 bytes ago
/// in the stread. No negative sign is necessary
///
public int Offset;
}
///
/// Handler for an PacketFailedCRC Event
///
public delegate void PacketCRCFailEventHandler(object sender, PacketCRCFailEventArgs e);
public delegate void PacketReceivedEventHandler(object sender, MavlinkPacket e);
///
/// Represents a Mavlink message - both the message object itself
/// and the identified sending party
///
public class MavlinkPacket
{
///
/// The sender's system ID
///
public int SystemId;
///
/// The sender's component ID
///
public int ComponentId;
///
/// The sequence number received for this packet
///
public byte SequenceNumber;
///
/// Time of receipt
///
public DateTime TimeStamp;
///
/// Object which is the mavlink message
///
public MavlinkMessage Message;
}
///
/// Crc code copied/adapted from ardumega planner code
///
internal static class Mavlink_Crc
{
const UInt16 X25_INIT_CRC = 0xffff;
public static UInt16 CrcAccumulate(byte b, UInt16 crc)
{
unchecked
{
byte ch = (byte)(b ^ (byte)(crc & 0x00ff));
ch = (byte)(ch ^ (ch << 4));
return (UInt16)((crc >> 8) ^ (ch << 8) ^ (ch << 3) ^ (ch >> 4));
}
}
// For a "message" of length bytes contained in the byte array
// pointed to by buffer, calculate the CRC
public static UInt16 Calculate(byte[] buffer, UInt16 start, UInt16 length)
{
UInt16 crcTmp = X25_INIT_CRC;
for (int i = start; i < start + length; i++)
crcTmp = CrcAccumulate(buffer[i], crcTmp);
return crcTmp;
}
}
}