MissionPlanner-开发历程-1

1 Mission Planner 简介

Mission Planner是使用C#开发的开源飞控地面站软件,使用MavLink通信协议,浏览官方网站

Mission Planner是ArduPilot开源自动驾驶项目的全功能地面站应用程序。Mission Planner项目代码已在github上进行开源,前往github克隆项目代码。

2 Mission Planner开发

进行Mission Planner定制开发前的准备:

3 Mission Planner 编译运行

注:笔者进行修改的Mission Planner 版本代码已经克隆到个人的github仓库,后续开源作者更新时,本项目不会进行更新,保持代码版本不变,前往下载MMission Plannr代码

从github下载Mission Planner代码到本地,使用visual studio 2017打开目录下的MissionPlanner.sln项目文件,运行项目解决方案。在进行编译项目前需要还原nuget程序包,也可以直接进行生成解决方案,会进行自动还原nuget程序包。如图,直接进行生成解决方案时,项目进行报错:

MissionPlanner-开发历程-1

为了可以进行编译,下面开始逐步进行修复错误。请耐心往下看。错误信息下拉到最底部,发现是nuget程序没有下载成功,因此当发现这么多错误的时候,先不要急修改前面的错误,有很多错误是因为缺少对应的程序包才会导致出错,Mission Planner项目包含了很多的项目依赖,各个项目之间依赖关系比较复杂,一般情况下不要随便去更改程序集依赖来修改错误,这有可能会导致更多的不可预知的错误。下载nuget程序包错误时,请先检查网络是否正常。还原nuget程序包时,发现项目需要的程序包版本比nuget服务器上的版本要更新,所以导致还原失败,可以使用文件搜索整个项目,把无法还原的nuget程序的版本号改成nuget服务器中的最新版本,再次还原即可(如果读者在还原时发现nuget服务器没有“4.5.0-preview2-26225-02”该版本时,可以全局搜索“4.5.0-preview2-26225-02”,将其替换为“4.5.0-preview1-26216-02”),如图,笔者更改无法还原的nuget程序包版本后,再次编译时出现了一下错误,没有了还原nuget程序失败的错误了:

MissionPlanner-开发历程-1

距离成功编译生成项目又更近了一步( ̄▽ ̄)~*

然而,接着继续折腾,发现各种错误,各种捣鼓都不行,接近崩溃了。。。。。。。

冷静下来仔细想了下,考虑到开源作者的开发环境与笔者的不同,有很大可能是主干分支中的代码缺少了部分必须的文件还没进行提交,所以笔者编译主干代码时候就出现了各种无法理解的错误,嗯,暂时先这样认为吧。

下面开始使用稳定版本的代码进行开发测试,在克隆下来的项目代码根目录,使用tortoiseGit进行分支导出,选择标签为1.3.52的版本代码进行检出,如图:

MissionPlanner-开发历程-1

检出成功后,再次进行编译,运行,终于可以执行成功了,来个图吧:

MissionPlanner-开发历程-1

好了,终于成功了,可以开始下一步的个性定制了,后面会进行功能性的定制以及自定义MavLink消息。

4 Mission Planner 项目代码结构

如下图,Mission Planner的工程结构:

MissionPlanner-开发历程-1

该项目包含了大量的外部库以及自定义插件等,我们可以先从主项目Mission Planner开始进行学习,Mission Planner是基于C#的Winform进行开发的,但需要注意的是,Mission Planner的界面是手工进行编码的,不是基于VS的可拖动界面进行所见即所得的界面开发,因此要改变MP(Mission Planner)的界面时最好不要进行拖放控件的形式进行改动,很容易出现问题,最好是进行手工代码编写。

项目GCSViews下面的是程序界面,FlightData.cs是飞行数据界面,可以在vs里面右键使用“查看设计器”来查看界面,通过界面上的控件名来进行搜索对应的代码,初学者可以这样进行一点点的查看,Program.cs是程序入口,在这里可以看到程序是如何一点一点的运行起来的,另外MP中的地图控件是使用开源的GMap.net,有兴趣的读者需要开发地图程序时可以考虑使用该插件。

5 Mission Planner 的MavLink消息生成器

在Mission Planner工程项目目录:ExtLibs\Mavlink下,可以直接使用脚本:regenerate.bat生成MavLink消息,执行脚本后,会生成C#文件:Mavlink.cs,该代码内是各种MavLink消息的结构体定义以及消息类型。(运行消息生成器脚本,需要安装python2.7

自定义消息可以在消息定义xml文件中自行添加,文件目录为:ExtLibs\Mavlink\message_definitions,把自定义消息写在common.xml文件中,具体参照原有的消息格式进行自行添加,添加完毕后,重新运行消息生成脚本即可。

MissionPlanner-开发历程-1

MavLink消息通信协议开源项目地址为:https://github.com/mavlink/mavlink

6 Mission Planner MavLink消息解析

在工程目录MavLink目录下,有MavLink消息解码方式,如图:
MissionPlanner-开发历程-1

类MAVLinkInterface包含了MavLink消息的解包和封包,可以在这个类中详细了解MavLink的消息通信包的解析原理。

public MAVLinkMessage getHeartBeat() { giveComport = true; DateTime start = DateTime.Now; int readcount = 0; while (true) { MAVLinkMessage buffer = readPacket(); readcount++; if (buffer.Length > 5) { //log.Info("getHB packet received: " + buffer.Length + " btr " + BaseStream.BytesToRead + " type " + buffer.msgid ); if (buffer.msgid == (byte) MAVLINK_MSG_ID.HEARTBEAT) { mavlink_heartbeat_t hb = buffer.ToStructure<mavlink_heartbeat_t>(); if (hb.type != (byte) MAV_TYPE.GCS) { SetupMavConnect(buffer, hb); giveComport = false; return buffer; } } } if (DateTime.Now > start.AddMilliseconds(2200) || readcount > 200) // was 1200 , now 2.2 sec { giveComport = false; return MAVLinkMessage.Invalid; } } } public void sendPacket(object indata, int sysid, int compid) { bool validPacket = false; foreach (var ty in MAVLINK_MESSAGE_INFOS) { if (ty.type == indata.GetType()) { validPacket = true; generatePacket((int)ty.msgid, indata, sysid, compid); return; } } if (!validPacket) { log.Info("Mavlink : NOT VALID PACKET sendPacket() " + indata.GetType().ToString()); } } private void generatePacket(MAVLINK_MSG_ID messageType, object indata) { generatePacket((int)messageType, indata); } void generatePacket(int messageType, object indata) { //uses currently targeted mavs sysid and compid generatePacket(messageType, indata, MAV.sysid, MAV.compid); }
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
public MAVLinkMessage getHeartBeat()
{
giveComport = true;
DateTime start = DateTime.Now;
int readcount = 0;
while (true)
{
MAVLinkMessage buffer = readPacket();
readcount++;
if (buffer.Length > 5)
{
//log.Info("getHB packet received: " + buffer.Length + " btr " + BaseStream.BytesToRead + " type " + buffer.msgid );
if (buffer.msgid == (byte) MAVLINK_MSG_ID.HEARTBEAT)
{
mavlink_heartbeat_t hb = buffer.ToStructure<mavlink_heartbeat_t>();
 
if (hb.type != (byte) MAV_TYPE.GCS)
{
SetupMavConnect(buffer, hb);
 
giveComport = false;
return buffer;
}
}
}
if (DateTime.Now > start.AddMilliseconds(2200) || readcount > 200) // was 1200 , now 2.2 sec
{
giveComport = false;
return MAVLinkMessage.Invalid;
}
}
}
 
public void sendPacket(object indata, int sysid, int compid)
{
bool validPacket = false;
foreach (var ty in MAVLINK_MESSAGE_INFOS)
{
if (ty.type == indata.GetType())
{
validPacket = true;
generatePacket((int)ty.msgid, indata, sysid, compid);
return;
}
}
if (!validPacket)
{
log.Info("Mavlink : NOT VALID PACKET sendPacket() " + indata.GetType().ToString());
}
}
 
private void generatePacket(MAVLINK_MSG_ID messageType, object indata)
{
generatePacket((int)messageType, indata);
}
 
void generatePacket(int messageType, object indata)
{
//uses currently targeted mavs sysid and compid
generatePacket(messageType, indata, MAV.sysid, MAV.compid);
}

getHeartBeat()是获取消息心跳包的操作,sendPacket和generatePacket分别是发包和打包的操作函数,其中generatePacket做个多个函数重载。

项目根目录下的CurrentState.cs是进行接收到的MavLink消息的解码,可以在里面获取到需要的MavLink消息,以及可以自行添加自定义消息的解码和发送。