MAVLink(Micro Air Vehicle Link,微型空中飞行器链路通讯协议)是无人飞行器与地面站(Ground Control Station ,GCS)之间通讯,以及无人飞行器之间通讯最常用的协议。它已经在PX4、APM、PIXHAWK和Parrot AR.Drone飞控平台上进行了大量测试。
MAVLink的最初开发于2009年,由Lorenz Meier完成。Lorenz Meier的LinkedIn主页是:https://www.linkedin.com/in/meierlorenz,个人主页是:https://www.inf.ethz.ch/personal/lomeier/。
根据官网和个人主页,Lorenz Meie的个人经历如下:
2004年~2008年在德国康斯坦茨大学(Universität Konstanz)就读信息工程专业;
2008 年~2011年在苏黎世联邦理工学院(德语:Eidgenössische Technische Hochschule Zürich,简称 ETH Zürich或ETHZ)就读视觉计算方向研究生;
2011~至今在ETHZ攻读博士后,研究方向是:Research on Drones and mobile phones focused on obstacle mapping, path planning and control.
2011年到现在,Lorenz Meier一直是开源无人机项目Autopilot的建立者和维护者。关于Autopilot,我会另辟章节介绍。
从介绍来看,Lorenz Meier的研究方向包括了无人机避障、基于智能手机或无人机的3D重建、无人机通讯协议等有趣又前言的内容。
这个页面是Lorenz Meier发表的几篇文章:https://www.researchgate.net/profile/Lorenz_Meier3
维基百科:https://en.wikipedia.org/wiki/MAVLink
MavLink官方网站:http://qgroundcontrol.org/mavlink/start
Python写的用于生成C、Java等语言的MavLink生成器软件:https://github.com/mavlink/mavlink
下面内容引自官网。
• The checksum is the same as used in ITU X.25 and SAE AS-4 standards (CRC-16-CCITT), documented in SAE AS5669A. Please see the MAVLink source code for a documented C-implementation of it. LINK TO CHECKSUM
• The minimum packet length is 8 bytes for acknowledgement packets without payload
• The maximum packet length is 263 bytes for full payload
MavLink的长度是固定的,即 17byte= 6 bytes header + 9 bytes payload + 2 bytes checksum。
由用户生成的部分包括PlayLoad本身、消息包的STX、COMP、MSG,其他部分自动生成。下图来自于博客http://blog.csdn.net/u013983741/article/details/48053235,侵删。
3DR Service是Autopilot提供的Android端的app服务,用于做SDK,提供与无人机通讯,以AIDL的方式为上层的App提供服务。基于3DRService,开发者可以不用处理复杂的MavLink通讯,只根据AIDL接口调用服务即可。
这里下载了3DRService用于分析,地址为:https://github.com/ne0fhyk/3DRServices。3DRService将MavLink的协议部分作为单独的包,即项目中的dependencyLibs文件夹。
绘制dependencyLibs的UML图。该包主要提供了MavLink的所有类型的封包类和解析类。
例如,对于MAVLinkPacket类,其核心部分为封包过程。
/**
* Encode this packet for transmission.
*
* @return Array with bytes to be transmitted
*/
public byte[] encodePacket() {
byte[] buffer = new byte[6 + len + 2];
int i = 0;
buffer[i++] = (byte) MAVLINK_STX;
buffer[i++] = (byte) len;
buffer[i++] = (byte) seq;
buffer[i++] = (byte) sysid;
buffer[i++] = (byte) compid;
buffer[i++] = (byte) msgid;
final int payloadSize = payload.size();
for (int j = 0; j < payloadSize; j++) {
buffer[i++] = payload.payload.get(j);
}
generateCRC();
buffer[i++] = (byte) (crc.getLSB());
buffer[i++] = (byte) (crc.getMSB());
return buffer;
}
解包的核心部分在Parser类中:
/**
* This is a convenience function which handles the complete MAVLink
* parsing. the function will parse one byte at a time and return the
* complete packet once it could be successfully decoded. Checksum and other
* failures will be silently ignored.
*
* @param c
* The char to parse
*/
public MAVLinkPacket mavlink_parse_char(int c) {
msg_received = false;
switch (state) {
case MAVLINK_PARSE_STATE_UNINIT:
case MAVLINK_PARSE_STATE_IDLE:
if (c == MAVLinkPacket.MAVLINK_STX) {
state = MAV_states.MAVLINK_PARSE_STATE_GOT_STX;
}
break;
case MAVLINK_PARSE_STATE_GOT_STX:
if (msg_received) {
msg_received = false;
state = MAV_states.MAVLINK_PARSE_STATE_IDLE;
} else {
m = new MAVLinkPacket(c);
state = MAV_states.MAVLINK_PARSE_STATE_GOT_LENGTH;
}
break;
case MAVLINK_PARSE_STATE_GOT_LENGTH:
m.seq = c;
state = MAV_states.MAVLINK_PARSE_STATE_GOT_SEQ;
break;
case MAVLINK_PARSE_STATE_GOT_SEQ:
m.sysid = c;
state = MAV_states.MAVLINK_PARSE_STATE_GOT_SYSID;
break;
case MAVLINK_PARSE_STATE_GOT_SYSID:
m.compid = c;
state = MAV_states.MAVLINK_PARSE_STATE_GOT_COMPID;
break;
case MAVLINK_PARSE_STATE_GOT_COMPID:
m.msgid = c;
if (m.len == 0) {
state = MAV_states.MAVLINK_PARSE_STATE_GOT_PAYLOAD;
} else {
state = MAV_states.MAVLINK_PARSE_STATE_GOT_MSGID;
}
break;
case MAVLINK_PARSE_STATE_GOT_MSGID:
m.payload.add((byte) c);
if (m.payloadIsFilled()) {
state = MAV_states.MAVLINK_PARSE_STATE_GOT_PAYLOAD;
}
break;
case MAVLINK_PARSE_STATE_GOT_PAYLOAD:
m.generateCRC();
// Check first checksum byte
if (c != m.crc.getLSB()) {
msg_received = false;
state = MAV_states.MAVLINK_PARSE_STATE_IDLE;
if (c == MAVLinkPacket.MAVLINK_STX) {
state = MAV_states.MAVLINK_PARSE_STATE_GOT_STX;
m.crc.start_checksum();
}
stats.crcError();
} else {
state = MAV_states.MAVLINK_PARSE_STATE_GOT_CRC1;
}
break;
case MAVLINK_PARSE_STATE_GOT_CRC1:
// Check second checksum byte
if (c != m.crc.getMSB()) {
msg_received = false;
state = MAV_states.MAVLINK_PARSE_STATE_IDLE;
if (c == MAVLinkPacket.MAVLINK_STX) {
state = MAV_states.MAVLINK_PARSE_STATE_GOT_STX;
m.crc.start_checksum();
}
stats.crcError();
} else { // Successfully received the message
stats.newPacket(m);
msg_received = true;
state = MAV_states.MAVLINK_PARSE_STATE_IDLE;
}
break;
}
if (msg_received) {
return m;
} else {
return null;
}
}
这个类的使用是逐个直接解析,解析完毕后返回完整的包,例如,对字节数组packet,解析过程如下:
for(int i = 0; i < packet.length - 1; i++){
parser.mavlink_parse_char(packet[i] & 0xFF);//每次解析1位
}
MAVLinkPacket m = parser.mavlink_parse_char(packet[packet.length - 1] & 0xFF);//最后1位即可返回
dependencyLibs提供了测试实例。以msg_altitude为例,判断生成的包和解析的包是否相同,即可判断该类是否正确。
/**
* The current system altitude.
*/
public class msg_altitude_test{
public static final int MAVLINK_MSG_ID_ALTITUDE = 141;
public static final int MAVLINK_MSG_LENGTH = 24;
private static final long serialVersionUID = MAVLINK_MSG_ID_ALTITUDE;
private Parser parser = new Parser();//1位解析类
public CRC generateCRC(byte[] packet){
CRC crc = new CRC();
for (int i = 1; i < packet.length - 2; i++) {
crc.update_checksum(packet[i] & 0xFF);
}
crc.finish_checksum(MAVLINK_MSG_ID_ALTITUDE);
return crc;
}
public byte[] generateTestPacket(){
ByteBuffer payload = ByteBuffer.allocate(6 + MAVLINK_MSG_LENGTH + 2);
payload.put((byte)MAVLinkPacket.MAVLINK_STX); //stx
payload.put((byte)MAVLINK_MSG_LENGTH); //len
payload.put((byte)0); //seq
payload.put((byte)255); //sysid
payload.put((byte)190); //comp id
payload.put((byte)MAVLINK_MSG_ID_ALTITUDE); //msg id
payload.putFloat((float)17.0); //altitude_monotonic
payload.putFloat((float)45.0); //altitude_amsl
payload.putFloat((float)73.0); //altitude_local
payload.putFloat((float)101.0); //altitude_relative
payload.putFloat((float)129.0); //altitude_terrain
payload.putFloat((float)157.0); //bottom_clearance
CRC crc = generateCRC(payload.array());
payload.put((byte)crc.getLSB());
payload.put((byte)crc.getMSB());
return payload.array();
}
@Test
public void test(){
byte[] packet = generateTestPacket();
for(int i = 0; i < packet.length - 1; i++){
parser.mavlink_parse_char(packet[i] & 0xFF);//每次解析1位
}
MAVLinkPacket m = parser.mavlink_parse_char(packet[packet.length - 1] & 0xFF);//最后1位即可返回
byte[] processedPacket = m.encodePacket();//解析
assertArrayEquals("msg_altitude", processedPacket, packet);
}
}
[1]http://mrsxm.mfzgi5lqnfwg65bomnxw2.erenta.ru/wp-content/uploads/sites/6/2015/05/MAVLINK_FOR_DUMMIESPart1_v.1.1.pdf
[2]http://blog.csdn.net/u013983741/article/details/48053235
MavLink是轻量级的通讯协议,主要应用于终端与小型无人载具间的通讯。由于它的通用性,MavLink可以被翻译成各种语言的代码应用于各种不同的环境。具体如何通过工具来生成对应的MavLink代码请访问:
http://www.qgroundcontrol.org/mavlink/create_new_mavlink_message
MavLink协议所定义的消息,大致分为两类,一类是通用消息,另外一种是自定义消息。通用消息和自定义消息的数据结构相同,差异只体现在数据本身。我取MavLink中最常使用的心跳消息作为例子:
这里的心跳和push中的心跳是一个意思。由于网络环境的不确定性,加入心跳来保证长连接的可靠性。MavLink的消息定义以通用的XML格式为基准,并且根节点是
1. type: 代表小型无人交通工具的类型,可能是直升机,汽车,多旋翼等等
2.autopilot: 代表此操作系统平台,平台的类型由MAV_TYPE类:
public class MAV_TYPE {
public static final int MAV_TYPE_GENERIC = 0; /* Generic micro air vehicle. | */
public static final int MAV_TYPE_FIXED_WING = 1; /* Fixed wing aircraft. | */
public static final int MAV_TYPE_QUADROTOR = 2; /* Quadrotor | */
public static final int MAV_TYPE_COAXIAL = 3; /* Coaxial helicopter | */
public static final int MAV_TYPE_HELICOPTER = 4; /* Normal helicopter with tail rotor. | */
public static final int MAV_TYPE_ANTENNA_TRACKER = 5; /* Ground installation | */
public static final int MAV_TYPE_GCS = 6; /* Operator control unit / ground control station | */
public static final int MAV_TYPE_AIRSHIP = 7; /* Airship, controlled | */
public static final int MAV_TYPE_FREE_BALLOON = 8; /* Free balloon, uncontrolled | */
public static final int MAV_TYPE_ROCKET = 9; /* Rocket | */
public static final int MAV_TYPE_GROUND_ROVER = 10; /* Ground rover | */
public static final int MAV_TYPE_SURFACE_BOAT = 11; /* Surface vessel, boat, ship | */
public static final int MAV_TYPE_SUBMARINE = 12; /* Submarine | */
public static final int MAV_TYPE_HEXAROTOR = 13; /* Hexarotor | */
public static final int MAV_TYPE_OCTOROTOR = 14; /* Octorotor | */
public static final int MAV_TYPE_TRICOPTER = 15; /* Octorotor | */
public static final int MAV_TYPE_FLAPPING_WING = 16; /* Flapping wing | */
public static final int MAV_TYPE_KITE = 17; /* Flapping wing | */
public static final int MAV_TYPE_ONBOARD_CONTROLLER = 18; /* Onboard companion controller | */
public static final int MAV_TYPE_VTOL_DUOROTOR = 19; /* Two-rotor VTOL using control surfaces in vertical operation in addition. Tailsitter. | */
public static final int MAV_TYPE_VTOL_QUADROTOR = 20; /* Quad-rotor VTOL using a V-shaped quad config in vertical operation. Tailsitter. | */
public static final int MAV_TYPE_VTOL_TILTROTOR = 21; /* Tiltrotor VTOL | */
public static final int MAV_TYPE_VTOL_RESERVED2 = 22; /* VTOL reserved 2 | */
public static final int MAV_TYPE_VTOL_RESERVED3 = 23; /* VTOL reserved 3 | */
public static final int MAV_TYPE_VTOL_RESERVED4 = 24; /* VTOL reserved 4 | */
public static final int MAV_TYPE_VTOL_RESERVED5 = 25; /* VTOL reserved 5 | */
public static final int MAV_TYPE_GIMBAL = 26; /* Onboard gimbal | */
public static final int MAV_TYPE_ADSB = 27; /* Onboard ADSB peripheral | */
public static final int MAV_TYPE_ENUM_END = 28; /* | */
}
3.base_mode:记录小型交通工具的基本模式
4.custom_mode:记录小型交工具的特征模式
5.mavlink_version:mavlink协议的版本号
大家可能好奇为什么有了个基本模式还有有个特征模式,原因是因为MavLink是要兼顾多种类型的小型交通工具的协议,这样的话,不能保证所有的基本模式覆盖到所有的交通器。
接下来,我们通过网站上的mavlink-generator 去生成一套java代码,用在我们的android程序中。
生成的代码移植性很好,我们可以无缝的直接copy到我们的android工程中。我们来看下生成的代码的分包:
common包: 放一些常用的MavLink消息和CRC校验工具
ardupilotmega包:存放针对mega板子特有的消息
Messages包:提供消息基本类和一些缓存处理类
enums包:存放一些常量
MAVLinkPacket类:用来记录原始报文
Parser类:用于解析信道中传递过来的数据,生成MAVLinkPacket格式的报文。
由于本篇的主题是MavLink消息在Android地面站的解析,因此我们不过分的关注于信道和业务本身。我们看上面的分包我们会发现,其实对于解析来说,最重要的就是Parser类。在我们开始解析前,通过一张图再回忆一下心跳消息的数据结构,因为我们将以它为样本作为例子:
我们收到的心跳完整报文是一个byte数组,因此我们需要对它进行解析,解析出我们自己的对象模型,就需要调用mavlink_parse_char(int c)方法。这就有个问题,我们明明读取到的是byte数组,但是方法中要我们传递一个int。这个原因我们不妨来看一下Parser这个类:
public class Parser {
/**
* States from the parsing state machine
*/
enum MAV_states {
MAVLINK_PARSE_STATE_UNINIT, MAVLINK_PARSE_STATE_IDLE, MAVLINK_PARSE_STATE_GOT_STX, MAVLINK_PARSE_STATE_GOT_LENGTH, MAVLINK_PARSE_STATE_GOT_SEQ, MAVLINK_PARSE_STATE_GOT_SYSID, MAVLINK_PARSE_STATE_GOT_COMPID, MAVLINK_PARSE_STATE_GOT_MSGID, MAVLINK_PARSE_STATE_GOT_CRC1, MAVLINK_PARSE_STATE_GOT_PAYLOAD
}
MAV_states state = MAV_states.MAVLINK_PARSE_STATE_UNINIT;
private boolean msg_received;
public MAVLinkStats stats = new MAVLinkStats();
private MAVLinkPacket m;
/**
* This is a convenience function which handles the complete MAVLink
* parsing. the function will parse one byte at a time and return the
* complete packet once it could be successfully decoded. Checksum and other
* failures will be silently ignored.
*
* @param c
* The char to parse
*/
public MAVLinkPacket mavlink_parse_char(int c) {
msg_received = false;
switch (state) {
case MAVLINK_PARSE_STATE_UNINIT:
case MAVLINK_PARSE_STATE_IDLE:
if (c == MAVLinkPacket.MAVLINK_STX) {
state = MAV_states.MAVLINK_PARSE_STATE_GOT_STX;
}
break;
case MAVLINK_PARSE_STATE_GOT_STX:
if (msg_received) {
msg_received = false;
state = MAV_states.MAVLINK_PARSE_STATE_IDLE;
} else {
m = new MAVLinkPacket(c);
state = MAV_states.MAVLINK_PARSE_STATE_GOT_LENGTH;
}
break;
case MAVLINK_PARSE_STATE_GOT_LENGTH:
m.seq = c;
state = MAV_states.MAVLINK_PARSE_STATE_GOT_SEQ;
break;
case MAVLINK_PARSE_STATE_GOT_SEQ:
m.sysid = c;
state = MAV_states.MAVLINK_PARSE_STATE_GOT_SYSID;
break;
case MAVLINK_PARSE_STATE_GOT_SYSID:
m.compid = c;
state = MAV_states.MAVLINK_PARSE_STATE_GOT_COMPID;
break;
case MAVLINK_PARSE_STATE_GOT_COMPID:
m.msgid = c;
if (m.len == 0) {
state = MAV_states.MAVLINK_PARSE_STATE_GOT_PAYLOAD;
} else {
state = MAV_states.MAVLINK_PARSE_STATE_GOT_MSGID;
}
break;
case MAVLINK_PARSE_STATE_GOT_MSGID:
m.payload.add((byte) c);
if (m.payloadIsFilled()) {
state = MAV_states.MAVLINK_PARSE_STATE_GOT_PAYLOAD;
}
break;
case MAVLINK_PARSE_STATE_GOT_PAYLOAD:
m.generateCRC();
// Check first checksum byte
if (c != m.crc.getLSB()) {
msg_received = false;
state = MAV_states.MAVLINK_PARSE_STATE_IDLE;
if (c == MAVLinkPacket.MAVLINK_STX) {
state = MAV_states.MAVLINK_PARSE_STATE_GOT_STX;
m.crc.start_checksum();
}
stats.crcError();
} else {
state = MAV_states.MAVLINK_PARSE_STATE_GOT_CRC1;
}
break;
case MAVLINK_PARSE_STATE_GOT_CRC1:
// Check second checksum byte
if (c != m.crc.getMSB()) {
msg_received = false;
state = MAV_states.MAVLINK_PARSE_STATE_IDLE;
if (c == MAVLinkPacket.MAVLINK_STX) {
state = MAV_states.MAVLINK_PARSE_STATE_GOT_STX;
m.crc.start_checksum();
}
stats.crcError();
} else { // Successfully received the message
stats.newPacket(m);
msg_received = true;
state = MAV_states.MAVLINK_PARSE_STATE_IDLE;
}
break;
}
if (msg_received) {
return m;
} else {
return null;
}
}
}
我们发现,Parser类必须要线性的解析报文,也就是说,在同一个周期内,只能有一条消息在Parser类中处理。并且Parser类的方法结构本质上是一个状态机。外部代码需要遍历传入byte中的数据用于生成报文:
private void handleData(Parser parser, int bufferSize, byte[] buffer) {
if (bufferSize < 1) {
return;
}
for (int i = 0; i < bufferSize; i++) {
int code = buffer[i] & 0x00ff;
MAVLinkPacket receivedPacket = parser.mavlink_parse_char(code);
if (receivedPacket != null) {
//test(receivedPacket);
}
}
}
Parser类所具有的状态列表:
enum MAV_states {
MAVLINK_PARSE_STATE_UNINIT, MAVLINK_PARSE_STATE_IDLE, MAVLINK_PARSE_STATE_GOT_STX, MAVLINK_PARSE_STATE_GOT_LENGTH, MAVLINK_PARSE_STATE_GOT_SEQ, MAVLINK_PARSE_STATE_GOT_SYSID, MAVLINK_PARSE_STATE_GOT_COMPID, MAVLINK_PARSE_STATE_GOT_MSGID, MAVLINK_PARSE_STATE_GOT_CRC1, MAVLINK_PARSE_STATE_GOT_PAYLOAD
}
前面说了Parser类本质上是一个状态机,初始的状态是:MAV_PARSE_STATE_UNINIT。一旦解析成功或者失败,状态将进入到IDLE。
Parser类的状态机基本可以使用上面的图片表示,基本上没有什么复杂的内容,主要的在与刚开始的数据长度的记录。如果你的数据长度大于零的话,解析器会将你的数据缓存在一个叫做payload的数据结构中。
case MAVLINK_PARSE_STATE_GOT_MSGID:
m.payload.add((byte) c);
if (m.payloadIsFilled()) {
state = MAV_states.MAVLINK_PARSE_STATE_GOT_PAYLOAD;
}
break;
PayLoad对应的类是MAVLinkPayLoad类,他是数据的缓存器和转换器,就是将无意义的byte数组,组织成为有意义的平台数据类型。
public class MAVLinkPayload {
private static final byte UNSIGNED_BYTE_MIN_VALUE = 0;
private static final short UNSIGNED_BYTE_MAX_VALUE = Byte.MAX_VALUE - Byte.MIN_VALUE;
private static final short UNSIGNED_SHORT_MIN_VALUE = 0;
private static final int UNSIGNED_SHORT_MAX_VALUE = Short.MAX_VALUE - Short.MIN_VALUE;
private static final int UNSIGNED_INT_MIN_VALUE = 0;
private static final long UNSIGNED_INT_MAX_VALUE = (long) Integer.MAX_VALUE - Integer.MIN_VALUE;
private static final long UNSIGNED_LONG_MIN_VALUE = 0;
public static final int MAX_PAYLOAD_SIZE = 255;
public final ByteBuffer payload;
public int index;
public MAVLinkPayload(int payloadSize) {
if(payloadSize > MAX_PAYLOAD_SIZE) {
payload = ByteBuffer.allocate(MAX_PAYLOAD_SIZE);
} else {
payload = ByteBuffer.allocate(payloadSize);
}
}
public ByteBuffer getData() {
return payload;
}
public int size() {
return payload.position();
}
public void add(byte c) {
payload.put(c);
}
public void resetIndex() {
index = 0;
}
public byte getByte() {
byte result = 0;
result |= (payload.get(index + 0) & 0xFF);
index += 1;
return result;
}
public short getUnsignedByte(){
short result = 0;
result |= payload.get(index + 0) & 0xFF;
index+= 1;
return result;
}
public short getShort() {
short result = 0;
result |= (payload.get(index + 1) & 0xFF) << 8;
result |= (payload.get(index + 0) & 0xFF);
index += 2;
return result;
}
public int getUnsignedShort(){
int result = 0;
result |= (payload.get(index + 1) & 0xFF) << 8;
result |= (payload.get(index + 0) & 0xFF);
index += 2;
return result;
}
public int getInt() {
int result = 0;
result |= (payload.get(index + 3) & 0xFF) << 24;
result |= (payload.get(index + 2) & 0xFF) << 16;
result |= (payload.get(index + 1) & 0xFF) << 8;
result |= (payload.get(index + 0) & 0xFF);
index += 4;
return result;
}
public long getUnsignedInt(){
long result = 0;
result |= (payload.get(index + 3) & 0xFFFFL) << 24;
result |= (payload.get(index + 2) & 0xFFFFL) << 16;
result |= (payload.get(index + 1) & 0xFFFFL) << 8;
result |= (payload.get(index + 0) & 0xFFFFL);
index += 4;
return result;
}
public long getLong() {
long result = 0;
result |= (payload.get(index + 7) & 0xFFFFL) << 56;
result |= (payload.get(index + 6) & 0xFFFFL) << 48;
result |= (payload.get(index + 5) & 0xFFFFL) << 40;
result |= (payload.get(index + 4) & 0xFFFFL) << 32;
result |= (payload.get(index + 3) & 0xFFFFL) << 24;
result |= (payload.get(index + 2) & 0xFFFFL) << 16;
result |= (payload.get(index + 1) & 0xFFFFL) << 8;
result |= (payload.get(index + 0) & 0xFFFFL);
index += 8;
return result;
}
public long getUnsignedLong(){
return getLong();
}
public long getLongReverse() {
long result = 0;
result |= (payload.get(index + 0) & 0xFFFFL) << 56;
result |= (payload.get(index + 1) & 0xFFFFL) << 48;
result |= (payload.get(index + 2) & 0xFFFFL) << 40;
result |= (payload.get(index + 3) & 0xFFFFL) << 32;
result |= (payload.get(index + 4) & 0xFFFFL) << 24;
result |= (payload.get(index + 5) & 0xFFFFL) << 16;
result |= (payload.get(index + 6) & 0xFFFFL) << 8;
result |= (payload.get(index + 7) & 0xFFFFL);
index += 8;
return result;
}
public float getFloat() {
return Float.intBitsToFloat(getInt());
}
public void putByte(byte data) {
add(data);
}
public void putUnsignedByte(short data){
if(data < UNSIGNED_BYTE_MIN_VALUE || data > UNSIGNED_BYTE_MAX_VALUE){
throw new IllegalArgumentException("Value is outside of the range of an unsigned byte: " + data);
}
putByte((byte) data);
}
public void putShort(short data) {
add((byte) (data >> 0));
add((byte) (data >> 8));
}
public void putUnsignedShort(int data){
if(data < UNSIGNED_SHORT_MIN_VALUE || data > UNSIGNED_SHORT_MAX_VALUE){
throw new IllegalArgumentException("Value is outside of the range of an unsigned short: " + data);
}
putShort((short) data);
}
public void putInt(int data) {
add((byte) (data >> 0));
add((byte) (data >> 8));
add((byte) (data >> 16));
add((byte) (data >> 24));
}
public void putUnsignedInt(long data){
if(data < UNSIGNED_INT_MIN_VALUE || data > UNSIGNED_INT_MAX_VALUE){
throw new IllegalArgumentException("Value is outside of the range of an unsigned int: " + data);
}
putInt((int) data);
}
public void putLong(long data) {
add((byte) (data >> 0));
add((byte) (data >> 8));
add((byte) (data >> 16));
add((byte) (data >> 24));
add((byte) (data >> 32));
add((byte) (data >> 40));
add((byte) (data >> 48));
add((byte) (data >> 56));
}
public void putUnsignedLong(long data){
if(data < UNSIGNED_LONG_MIN_VALUE){
throw new IllegalArgumentException("Value is outside of the range of an unsigned long: " + data);
}
putLong(data);
}
public void putFloat(float data) {
putInt(Float.floatToIntBits(data));
}
}
这个类的复用性很高,我们在很多解析器里面都可以用到它,希望大家以后如果写自己的解析器的话可以想到它。好的,我们现在有了数据Payload我们怎么解析出消息呢?
我们回到我们的Packet类,Packet用了一个很典型的命名unpack来用来解包:
public MAVLinkMessage unpack() {
switch (msgid) {
case msg_sensor_offsets.MAVLINK_MSG_ID_SENSOR_OFFSETS:
return new msg_sensor_offsets(this);
case msg_set_mag_offsets.MAVLINK_MSG_ID_SET_MAG_OFFSETS:
return new msg_set_mag_offsets(this);
case msg_meminfo.MAVLINK_MSG_ID_MEMINFO:
return new msg_meminfo(this);
......
}
如果你自定义了一种MavLink协议类型的话,代码生成器会自动帮你生成一个case和一个消息类,而在这里,我们找到我们所需要的心跳类case:
case msg_heartbeat.MAVLINK_MSG_ID_HEARTBEAT:
return new msg_heartbeat(this);
在心跳消息的构造器里,具体消息类型会对报文的具体内容做真正的解包:
public void unpack(MAVLinkPayload payload) {
payload.resetIndex();
this.custom_mode = payload.getUnsignedInt();
this.type = payload.getUnsignedByte();
this.autopilot = payload.getUnsignedByte();
this.base_mode = payload.getUnsignedByte();
this.system_status = payload.getUnsignedByte();
this.mavlink_version = payload.getUnsignedByte();
}
这样,记录在payload中杂乱的数据就被记录在msg_heartbeat类的变量中啦~
大家如果感兴趣,就自己去生成和阅读它的代码,代码量很少很好读懂,且通用性很好很好调试。
1 简介MAVLink通讯协议是一个为微型飞行器设计的非常轻巧的、只由头文件构成的信息编组库。
它可以通过串口非常高效地封装C结构数据,并将这些数据包发送至地面控制站。该协议被PX4, PIXHAWK, APM和Parrot AR.Drone平台所广泛测试并在以上的项目中作为MCU/IMU间以及Linux进程和地面站链路通信间的主干通信协议。
MAVLink最初由LorenzMeier根据LGPL许可在2009年初发表。
1.1 综合教程(见附件1)1.2 MAVLink消息格式及MAVLink API标准文件(见附件2)1.3 协议内容
1)线上(On-the-wire)格式和CRC冗余校验——
2)MAVLink 1.1版兼容性和特点
3)MAVLink任务内容
(1) 路径点协议
(2) 参数协议
(3) 图像传输协议
(4) 控制接口
4)MAVLink数据类型及相关约定
5)常见MAVLink消息
1.4 MAVLink代码及生成器
“普通信息集”包含了非常广泛的常用信息并以C语言头文件储存。如果你想定义并使用自己的代码,可以使用MAVLink Generator(C/C++,python)或QGroundcontrol。
1.5 MAVLink生态系统
1) 使用MAVLink的自动驾系统
Ÿ ArduPilotMega (mainprotocol)
Ÿ pxIMU Autopilot (mainprotocol)
Ÿ SLUGS Autopilot (mainprotocol)
Ÿ FLEXIPILOT (optionalprotocol)
Ÿ UAVDevBoard/Gentlenav/MatrixPilot (optionalprotocol)
Ÿ SenseSoarAutopilot (main protocol)
Ÿ SmartAPAutopilot (main protocol)
Ÿ AutoQuad 6 AutoPilot (mainprotocol)
2) 使用MAVLink的软件包
Ÿ QGroundControl(Windows/Mac/Linux)
Ÿ HK GroundControl Station (Windows)
Ÿ APMPlanner (Windows/Mac)
Ÿ QGroundControlw/ AutoQuad MainWidget (Windows/Mac/Linux)
Ÿ Copter GCS(Android)
Ÿ AutoQuadGCS (Android)
Ÿ ROS to MAVLink bridge: https://github.com/mavlink/mavlink-ros
Ÿ MAVCONN Lightweight Aerial Middleware http://pixhawk.ethz.ch/software/middleware/start
Ÿ oooArk / MAVSim http://www.youtube.com/watch?v=-wQVrM5SL2o&fe
Ÿ MAVLinkpython bindings
Ÿ MAVProxy (allows toconnect multiple UDP/serial links, including flightgear):
3) 使用MAVLink的项目
Ÿ ArduPilotMega http://code.google.com/p/ardupilot-mega/
Ÿ MatrixPilot UAVDevBoard http://code.google.com/p/gentlenav/
Ÿ PIXHAWK http://pixhawk.ethz.ch/
Ÿ ETH Flying MachineArena http://www.idsc.ethz.ch/Research_DAndrea/FMA
Ÿ ETH SenseSoarSolar Airplane Project http://www.sensesoar.ethz.ch/doku.php?id=news
Ÿ ETH Skye BlimpProject http://www.projectskye.ch/
Ÿ UC Santa CruzSLUGS http://slugsuav.soe.ucsc.edu/index.html
Ÿ ArduCAMOSD http://code.google.com/p/arducam-osd/
Ÿ Sky-Drones - UAVFlight Control Systems http://www.sky-drones.com/
Ÿ AutoQuad -Autonomous Multirotor Vehicle controller http://autoquad.org/
1.6 MAVLink 数据格式分解
以一个MAVLink数据包为例,下图为其数据包具体的结构剖析。该结构是受到CAN和SAE AS-4标准的启发构建的。
字节顺序 |
内容 |
值 |
含义 |
0 |
数据包开始标志 |
v1.0: 0xFE (v0.9: 0x55) |
指示新的数据包即将开始 |
1 |
有效载荷长度 |
0 - 255 |
指示接下来的有效负载长度 |
2 |
数据包 序列号 |
0 - 255 |
每个传输部件都会对发送序列进行计数。该结构能够检测到数据包的丢失 |
3 |
系统ID |
1 – 255 |
发送该包的系统ID。该结构能够区分在同一网络中不同的MAV。 |
4 |
设备ID |
0 – 255 |
发送该数据包的设备ID。该结构能够区分同一系统的不同发送设备。如:IMU和AutoPilot。 |
5 |
消息ID |
0 - 255 |
消息ID。该ID定义了有效负载的“含义”以及负载应被如何正确的解码 |
6至(n+6) |
数据 |
(0 – 255)字节 |
消息数据。具体内容取决于消息ID |
(n+7)至(n+8) |
校验位 (低字节和高字节) |
ITU X.25/ SAE AS-4哈希校验,不包括数据包开始的标志,所以字节1 ..(N+6)注意:校验码同样包括MAVLINK_CRC_EXTRA(数值从消息字段开始计算,避免同一个数据包因版本而变量值不同因其的解码问题。 |
Ÿ 校验值使用的是与 ITU X.25 和SAE AS-4相同的校验标准(CRC-16-CCITT),在文档SAE AS5669A中有叙述。
Ÿ 通信包的最小长度为不带任何有效负载的8字节大小。
Ÿ 通信包的最大长度为满载263字节。
1.7 协议支持的数据结构
MAVLink协议支持固定大小的整形数据类型、IEEE 754协议规定的单精度浮点型数据、以上数据构成的数组(如:char[10]),以及由协议自动添加的特别的MAVLink版本字段类型。具体如下:
§ char -Characters / strings
§ uint8_t - Unsigned8 bit
§ int8_t - Signed 8bit
§ uint16_t - Unsigned16 bit
§ int16_t - Signed16 bit
§ uint32_t - Unsigned32 bit
§ int32_t - Signed32 bit
§ uint64_t - Unsigned64 bit
§ int64_t - Signed64 bit
§ float - IEEE 754single precision floating point number
§ double - IEEE 754double precision floating point number
§ uint8_t_mavlink_version - 无符号8位字段,以当前的MAVLink版本发送时被自动添加,它不能被更改只是在数据包中以通常无符号8位整型字段读取。
1.8 性能
该协议主要面向两方面特性:传输速度和安全性。协议允许检验消息内容,同样允许检测丢失的消息序列但仍然只需要每个包中6个字节的开销来保证。
传输实例:
链路速度/波特 |
硬件 |
更新速率 |
负载 |
浮点值 |
115200 baud |
XBee Pro 2.4 GHz |
50 Hz |
224 bytes |
56 |
115200 baud |
XBee Pro 2.4 GHz |
100 Hz |
109 bytes |
27 |
57600 baud |
XBee Pro 2.4 GHz |
100 Hz |
51 bytes |
12 |
9600 baud |
XBee Pro XSC 900 |
50 Hz |
13 bytes |
3 |
9600 baud |
XBee Pro XSC 900 |
20 Hz |
42 bytes |
10 |
1.9 未来的工作/想法
Ÿ 采用变长数组
Ÿ 支持位域(例如:将8个布尔型的值封装入一个uint8_t结构中,但所有的C结构函数都可调用全部的8个布尔型量。用户就不必在整体移动或隐藏中浪费时间)
Ÿ 可变的多样头部,允许设定目标系统和目标部件(不改变协议本身,只是方便函数调用它们)
2 路径点协议
路径点协议描述了路径点是如何被发送到MAV并被它所读取的。其目标是在发送端和接收端确保建立一种连续稳定的状态。QGroundControl执行了在地面控制端的协议要求,每个在MAV上使用MAVLink实现路径点协议的规划模块能够和QGroundControl通讯并交换和更新它的路径点。
2.1 通信/状态机
协议中包含很多事务,每一个事务的完成情况以成功和失败来描述,在接收端的路径点列表状态未改变则被认为该事务的完成情况是失败的。只有在两个正在通讯的部分之间么有其他的事务在活动中时,新的事务才可以被一个特定的消息字段唤起进行。这意味着,如果想要发起一个新的事务,参与到该事务中的通讯部分必须全部处于IDLE即空状态。如果开始了一个事务,则通讯部分的状态将改变。引入一个以这种方式工作的状态机,则路径点协议可以十分简单的实现。
除了应答消息之外的其他信息在被发送之后,发送端的部件就会启动一个定时器。如果在特定的时间内,没有收到响应则请求的消息将会被重新发送一次。该重传过程会重复数次,如果在最后一次的重传超时后仍然没有收到相应,则认定该项事务失败。该重传机制意味着所有的部件必须能够处理重复的消息。
2.2 读取MAV的路径点列表
为了能够从部件中检索含有所有路径点的列表,将会发送一个WAYPOINT_REQUEST_LIST的消息,而目标部件将会回应一个WAYPOINT_COUNT消息,描述在它的列表中的路径点数量。
之后,正在发送请求的部件会要求没有个路径点从序列号0开始发送WAYPOINT_REQUEST消息,而目标部件会以包含有路径点数据的对应的WAYPOINT消息来回应每一个请求。
当最后一个路径节点成功的接收到消息之后,发送请求的部件会向目的地部件发送一个WAYPOINT_ACK消息。这个消息将结束该项事务。需要注意的是,目的地部件必须监听来自最后一个路径节点的WAYPOINT_REQUEST消息,知道它拿到WAYPOINT_ACK消息或另一个消息命令开启一个不同的事务或者发生了超时。
2.3 书写MAV的路径列表
为了能够发送一个路径点的列表,一个包含有列表中路径点数量的WAYPOINT_COUNT消息会被发送至目的地设备。而该设备将做好接收消息的准备,同时通过发送从序列号0开始的WAYPOINT_REQUEST消息开始检索所有的路径点。而发送路径点列表的设备将会以WAYPOINT消息来回应所有的请求。
当最后一个路径节点被目的地设备成功接收后,该目标设备将向发送设备发送一个WAYPOINT_ACK消息。之后将结束该事务。
如果一个路径点计划器设备在事务之外收到了WAYPOINT消息,它将发送一个WAYPOINT_ACK消息。
2.4 清除MAV的路径点列表
要清除一个设备的路径点列表则可以发送WAYPOINT_CLEAR_ALL消息。在清除了路径点列表之后,目标设备将以WAYPOINT_ACK消息作为回应。
2.5 设置新的当前MAV路径点
要向设备设置新的当前活跃路径点,发送一个WAYPOINT_SET_CURRENT消息即可。在完成了更改当前的路径点后目标设备将通过带有新的当前序列号的WAYPOINT_CURRENT消息作为回应。
2.6 MAV抵达路径点的状态消息
如果MAV上的路径点规划设备抵达一个路径点,它将会广播一个WAYPOINT_REACHED消息。由于是广播消息,因此不需要发送ACK消息,同样也就不能保证消息的接收。
2.7 MAV更改当前路径点的状态消息
如果一个MAV上的路径点规划设备选择了一个新的路径点作为它的新的当前航电,它将会广播一个WAYPOINT_CURRENT消息。由于是广播消息,因此不需要发送ACK消息,同样也就不能保证消息的接收。建议该消息能够在较小的间隔中发送两次,从而保证能够有较大的可能性使得所有接收端收到。
2.8 路径点文件格式
尽管这不是MAVLink协议的一部分,这个是建议的路径点文件格式(QGroundControl和其他设备默认使用)。请注意在参数之间的空格实质上是
3 参数协议
板载参数协议接口允许向内存(RAM)读写参数(例如:PID增益)也可以向参数存储器读写(EEPROM或者硬盘)。
它不仅可以被部署安装在一个微型控制器上(例如:带有ARM7的pxIMU),也可以是标准软件(如:Linux系统中的px_multitracker进程)。
3.1 支持的数据类型
MAVLink v1.0支持如下数据类型:
§ uint32_t - 32bit unsigned integer (use the ENUMvalue MAV_PARAM_TYPE_UINT32)
§ int32_t - 32bit signed integer (use the ENUM value MAV_PARAM_TYPE_INT32)
§ float - IEEE754 single precision floating point number(use the ENUM value MAV_PARAM_TYPE_FLOAT)
注意:所有的参数都以浮点型的mavlink_param_union_t值发送,也就是意味着一个参数应当已这个按字节转换为相应的浮点型数据(没有类型转换)【which means that a parameter should be byte-wise convertedwith this union to a byte-wise float (no type conversion)】。这对于为了不影响和限制扩展后的整型参数的最大精确度的目的是非常必要的。例如:GPS定位的参数以单精度浮点型数据只能精确到几米的级别,而GPS定位参数如果以1E7
3.2 多系统和多部件支持
MAVLink在同一链接上支持并行的多系统/飞行器。不仅如此,它同样还支持在同一飞行器上的多个支持MAVLink协议设备的情况。例如,协议允许自动驾驶设备和负载单元在一个电磁波链接上来沟通。因此,在不同的部件之间参数协议也是不同的。通过发送请求参数消息target_component从0开始进行计数(步进值设置:MAV_COMP_ID_ALL)来从系统获得完整的参数列表。所有的机载部件都应当以带有他们的ID或是MAV_COMP_ID_ALL(0)的ID对参数请求信息进行回应。QGroundControl默认查询系统的所有部件(它只查询当前选定的系统,并不是所有系统),因此发送ID 0/MAV_COMP_ID_ALL。
3.3 QGroundControl里的图形用户界面
基于这个原因,参数接口将会辨别不同系统(一个系统就是一架飞机)和部件(一个部件就是一个实体的架构,例如:IMU或一个Linux进程)。这可以允许透明的访问每一个独立的部件参数而不再需要一个主要的机载单元来负责翻译对于机载部件的读/写参数请求。
如下图所示,在参数树中每个部件被一个顶层节点代表。系统(即无人机)可以从顶层的下拉菜单中选择。GUI将会跟踪参数的改变并且将参数发送至正确的部件当中。
为了方便多个参数的使用,树是根据第一下划线中的参数名称从顶层开始分组和构建的。所以PID_POS_X_P和PID_POS_Y_P将会被划分在在PID节点下。
3.4 通信/状态机
机载参数被定义为一个14字符的字符串并存储了一个浮点值(IEEE 754 单精度,4字节)。此键值对有许多重要的属性:
Ÿ 可读的变量名对于用户来说是非常有帮助的,但是它仍然足够小。
Ÿ GCS不需要提前知道机载部件的参数有哪些。
Ÿ 确保支持未知的autopilots设备,只要他们执行协议内容。
Ÿ 添加一个参数只会改变飞机上的代码。
3.4.1读取参数
发送PARAM_REQUEST_LIST消息将激活读取参数列表的活动。飞机上的部件在收到这条指令后应当开始独立的传输参数。发送过程中在每个参数之后要适当的延迟,以免占用
全部的无线电带宽。
3.4.2读取单个参数
单个参数可以通过PARAM_REQUEST_READ消息来读取。
3.4.3写参数
由于QGroundControl在初始状态时并没有自己的参数列表,在写参数列表之前参数列表首先要被读取一次。在那之后,参数才可以通过向部件发送键值对来被写入进去。由于QGroundControl会追踪参数的改变,电机写入按钮只会传输那些已经被更改的值。MAV必须确认这个写入的操作通过发送一个含有最新被写入进去的参数值的PARAM_VALUE的参数值消息。
3.5 机上永久存储器
参数接口提供了两个按钮来将飞机上的参数从永久的机上存储器装载至RAM和将当前的参数值从RAM保存至永久存储器。若需要从QGroundControl向永久存储器写入数据,首先数据需要被传输然后必须执行写入到ROM的命令。
3.6 QGroundControl的参数文档
QGroundControl允许将当前飞机上的参数值保存至一个文本文档中。之后文档可以被再次的导入和传输到MAV中。这就可以允许如配置几个飞行器都是完全相似的默认安全值。
3.7 MCU
这个单片机实现来自PIXHAWK IMU/Autopilot代码库。它允许贮存机上参数。请注意,这段代码可能没有编译,您首先需要调整你的板载数据结构。
4 图像传输协议
本节讲述图像数据流是如何工作和如何囊括了包括实施细节(在MAV和QGroundControl)和MAV与QGroundControl之间的通讯。
图像传输协议包含两个模块:图像流和视频流:
Ÿ 图像流
部分使用MAVLink作为通讯频道,同时图像流部分能够被用来从MAV向QGroundControl传输任何类型的图像(原始图像、体感数据…)。它主要是获取相机的实时图像病将其分解为小块通过MAVLink来进行发送。该模块主要是用来直接向QGroundControl传输图像(即HUD不见如下所述)。
注意:该模块也可以用来向QGroundControl发送海量数据块而不是图像。
Ÿ 视频流
部分以MPEG2的格式流式发送“真实(real)”的视频。它使用实时的照相机图像并使用FFMpeg来编码视频流。这个模块的主要应用情形是在任意的移动设备(笔记本电脑、只能手机、、、)上观看实时的视频传输而不需要使用QGroundControl和MAVLink的客户端。
图像流部分相较于视频流部分的主要优点是更好的融入了QGroundControl。最主要的缺点是需要MAVLink的支持(因此不像视频流部分由较好的跨平台特性)。
4.1 通讯4.1.1 图像流
图像流部分使用了两个MAVLink消息:一个是握手消息,DATA_TRANSMISSION_HANDSHAKE来初始化、控制和停止图像流;还有一个数据容器消息,ENCAPSULATED_IMAGE,来传输图像数据(见右图)。
(1) 通讯是由QGroundControl发起开始数据流的请求而发起的。因此,若要开始通讯,MAVLink消息的如下字段必须被设置:
Ÿ target:设置为目标MAV的ID;
Ÿ state:设置为0;
Ÿ id:表示图像流的ID;
注意:目前,图像流部分只支持每个图像类型一个数据流,因此需要将id设置为和type字段想吐的整数。
Ÿ type:在mavlink.h头文件中ENUM MAVLINK_DATA_STREAM_TYPES记录的任意类型。
Ÿ freq:对于“frame per seconds”则大于0,“seconds per frame”则小于0。
该协议允许请求一个特定图像的质量。如需要,则必须设置quality字段。在初始请求时所有其他的字段必须为0。
(2) 当目标MAV收到握手请求之后,它会回送一个确认消息并开始在请求的帧频上进行图像流传输。握手消息的ACK包通常情况下包含和由QGroundControl请求的消息相同的值(state设置为1,因为这是ACK消息),并增添有关下一个发送消息的大小的相关数据:
Ÿ 值packets包括MAVLink ENCAPSULATED_DATA包的数量;
Ÿ 值payload指定了每个数据包的有效载荷大小(通常为252字节)。
Ÿ 值size指定了图像的字节大小。
(3) 之后,图像数据会被分割成适合正常的MAVLink消息的数据块。它们会被封装在ENCAPSULATED_DATA数据包中通过MAVLink进行发送。每个数据包包含一个序列号以及图像流对应的ID。然后,图像流就开始定期的发送新的图片,期间不再需要其他的交互。每一个新图像都有一个新的DATA_TRANSMISSION_HANDSHAKE的ACK包并更新图像的size,packets和payload值。在这个ACK包之后,新的图像作为一系列的ENCAPSULATED_DATA包而到达。注意:对于流中的每一个新图像,序列号都从0开始。
(4) 要停止一个图像流,你必须要发送一个新的DATA_TRANSMISSION_HANDSHAKE请求包,并将频率设置为0。
4.1.2 视频流
视频传输协议相较于图像传输协议简单许多:它只包含一个MAVLink消息,VIDEO_STREAM,用来开始和停止视频流(见下图)。
视频流消息有两个值需要设置:
Ÿ target:目标MAV;
Ÿ start_stop:1为开始视频流,0为结束视频流。
视频流由FFMpeg于MAV端创建并开始。一个小的MAVLink包抓取摄像机图像,添加(Y)UV渠道给YUV420 rawimage格式,并将该图片填制FFMpeg。之后,输出将被传输至地面站(注意:目前,这需要地面站有一个固定的IP并且在初始状态时有一个配置MAV的步骤)。收到视频后,QGroundControl打开VLC窗口对视频流进行重新分配:它从MAV得到这个视频流并将它作为RTP流(在一个多播地址)和HTTP流(直接单播流)提供给网络。这个过程并没有对原始流进行代码转换,以保持尽可能低的性能影响。
现在其他的移动设备可以通过多播地址 239.255.12.45 来链接到这个视频流,或通过[url=http://[qgc-host]/MAVLive.mpg%E6%9D%A5%E9%93%BE%E6%8E%A5%E5%88%B0HTTP]http://[QGC-HOST]/MAVLive.mpg来链接到HTTP[/url]流。多播流通过SAP声明为”MAVLive”.
4.2 使用/配置
在MAV上使用这两个模块,你需要做如下几步:
4.2.1 图像流
1、为你的MAV编译mavconn中间件
2、至少开启MAV上的这些设备:
px_mavlink_bridge_udp&
px_system_control--heartbeat &
px_camera -o lcm&
3、编译并启动QGroundControl
4、开启图像流设备(你可以添加-v标志来查看更多输出):
px_imagestreamer
5、初始化图像流: 打开HUD部件,到小部件上单击右键并选择“启用图像直播”。
现在你可以观看每秒一个画面的视频了(默认,此处为硬编码)
4.2.2 视频流
1、执行上文所述的图像流的1至3步;
2、在主目录中创建一个符号链接:
cd~
ln-s mavconn/src/comm/video/px_videostreamer.sh px_videostreamer.sh
(注意:你可以复制这个文件,但不建议这么做);
3、开启MAV上的视频流传输部件:
px_videostreamer
4、初始化视频流:打开HUD插件,单击右键并选择“启用视频直播”。
之后一个VLC窗口将开启。只要你想向其他人传输视频流,就请不要关闭这个窗口!如果你想观看当前的视频流,只需在其他的VLC窗口中打开这个视频流。
4.2.3 Developer
目前,只实现了对当前摄像机的图像以JPEG格式进行图像流传输。要想实现自己的图像流传输,你必须做如下的事情:
Ÿ 编写一个MAVLink处理程序来处理您选定的格式的图片流开始传输的请求。
Ÿ 编写一个数据处理程序来处理你的数据(如,立体相机图像),将其编码为你选择的格式(如,原始图像、JPEG、BMP)然后将其分散并通过MAVLink进行传输。
Ÿ 扩展QGroundControl中UAS部件内的数据/消息处理程序来正确处理你的数据(如,对选定的格式进行拆包)。
Ÿ 编写或对原来的插件进行扩展来根据你的想法显示你的数据。