最近需要编译安卓版本的 QGC,虽然最终成功编译出来了,但是过程确实曲折离奇,更是被编译环境反复鞭打,直至绝望。最终也不知道是哪位菩萨保佑,总算是编译成功了,小子在此跪谢了。
失败的经历
Qt 6.8.2
第一次尝试是在 Qt 6.8.2 上编译,因为之前用这个版本编译过 Windows 版的 QGC,用的是 master 分支。按照官方文档说明需要 Qt 6.8.2(only),不过截止到写稿,官网上支持的 Qt 版本已经是 Qt 6.8.3(only)了。
因为已经有了 Qt 6.8.2 的环境,所以我决定直接用它来编译安卓版本。虽然配置好了安卓编译环境,但是在编译时遇到了问题,cmake 报错:命令行太长。当时没能解决这个问题,现在回过头来想可能跟我自己安装的 cmake 有关,应该在安装 Qt 的时候把 cmake 组件勾选上,但也有可能是 NDK 的 cmake 报错了,原谅我已经不想去验证了。
Qt 5.12.9
在 Qt 6 编译失败之后,我又转向了之前安装的 Qt 5.12.9,因为 master 分支中去掉了 qgroundcontrol.pro
文件而是转投 cmake 管理工程,我不知道在 Qt 6 中的报错是否和这个有关系,所以只能找个旧版本试试看。
在经过了几天的斗智斗勇之后,终于还是倒在了配置安卓编译环境上,是的,这次连环境都没配出来。按照指定版本装好安卓的各个库以后,死活就是不行,各个库之间的版本兼容性就像一团乱码。我甚至把每个安卓版本都装了一遍,结果还是报错,问题是连原因都不知道,看网友的文章吧,不能说没用,那是一点儿用也没有。
重开
在经历了两轮失败以后,我一怒之下卸载了所有 QT 版本,准备重新开始。这一次我选择了 Qt 5.15.2,因为除了 master
版本,就只有 4.4
版本有官方文档了,而它要求的 Qt 版本是 Qt 5.15.2(only)。
随着新版本的发布,这个页面可能也会被官方删除掉,但是就目前而言,它最大的参考意义就是告我需要的库的版本。
Android: Android 5.0 and later.
- Standard QGC is built against ndk version 19.
- Java JDK 11 is required.
- Qt version: 5.15.2 (only)
安装 Qt 5.15.2
遗憾的是自 Qt 5.15 开始就没有独立发行的安装包了,因此 Qt 5.15.2 也只能通过在线方式安装。Qt 的安装我就不废话了,这里只提几个需要注意的地方。
设置国内镜像
在线安装时默认使用的是国外的镜像仓库,速度非常感人,所以我们需要换成国内镜像源,推荐使用阿里云镜像:
要注意的是不要选清华镜像站,它上面的资料不全,到后面安装的时候会找不到想要安装的版本。阿里云这个镜像站是我亲测好用的,推荐直接用这个。再此感谢博友十年之少的使用国内镜像网站在线下载安装 Qt(解决官网慢的问题)。
找到下载的在线安装程序,目前最新的是 qt-online-installer-windows-x64-4.8.1.exe
,不要直接双击打开,先在该文件所在目录下打开命令行(或者打开命令行后切换到此目录下),然后运行下面的命令:
> qt-online-installer-windows-x64-4.8.1.exe --mirror https://mirrors.aliyun.com/qt/
--mirror
参数可以指定程序使用的镜像源,不仅在线安装程序支持,MaintenanceTool.exe
也支持这个参数。Qt 安装完以后,如果我们想添加或移除组件,就可以使用下面的命令:
> MaintenanceTool.exe --mirror https://mirrors.aliyun.com/qt/
MaintenanceTool.exe
就在 Qt 安装目录的根目录下。
更改缓存目录
Qt 安装程序默认的缓存目录是 C:\Users\用户名\AppData\Local\cache\qt-unified-windows-online
,如果你和我一样,C 盘空间十分紧张,到后面真正安装的时候就会提示你磁盘空间不足。
为了节约 C 盘空间,建议在登录之前先点击左下角设置按钮,修改缓存路径。缓存目录要预先创建。在线安装程序每次启动都要修改这个地方。
组件选择
在选择组件步骤,默认只有最新的几个版本可供选择,我们把右边的 Archive 勾选上,然后点击筛选,就能看到全部版本了。
找到 Qt5.15.2,安卓是必选的,其他平台按需选择,按照官方说明,Qt Charts
也是必选项。然后在下面的 Build Tools 和 Qt Creator 中选择下面几个组件。
这里不用担心少选或多选了组件,多选了比少选了好,少选了会错,多选了不会错。而且安装完成后我们还是能通过 Maintenance.exe
工具来添加组件,它就在 Qt 的安装目录下。打开以后也是先点击左下角的设置按钮,修改缓存目录,不过它只用改一次就可以了,因为它有配置文件记录用户的设置。
安装 Java
安卓环境需要用到 Java SDK,因此首先我们需要安装 JDK。根据官方文档的描述,QGC 4.4 版本要求 JDK 11,所以我们直接安装 JDK 11 即可,这一步不会有什么问题。只是 JDK 11 的下载需要强制登录,但是我相信这难不倒各位聪明的小伙伴,找个其他镜像源下载即可,zip 格式就挺方便的,解压就能用,最后配置下 JAVA_HOME
环境变量。这个环境变量也不是给 Qt 用的,是后面给安卓库用的。
打开 Qt Creator,点击菜单 工具 > 外部 > 配置
,打开首选项窗口,点击左侧 SDKs
,在 Android 选项卡中配置 JDK 位置。之前用到的 Qt Creator 都是在"设备"页配置安卓环境,这个版本的 Qt Creator 是在"SDKs"里,要不是我瞎点了几下,差点就又夭折在这里了[捂脸]。
下一步是最闹心的一步,配置安卓编译环境。回想起曾经两度在此折戟沉沙,不得不说,配置开发环境真是编程路上最大的绊脚石[捂脸]。
配置安卓 SDK
紧接上一步配置完 JDK 路径之后,我们找个地方创建一个空目录作为安卓 SDK 的存放路径,然后将找个路径配置到"Android SDK的路径"中,为了演示,我这里重新新建了一个叫 AndroidSDK
的目录。
先不要着急,网上又很多教程在用图形界面的 SDK Manager 来安装安卓包,但是这种方式已经过时了,并且官方也不在提供该程序下载了。找个功能被合并到了 Android Studio 中,官方推荐通过 Android Studio 来管理安卓库。此外还提供了一个命令行版本的 cmdline-tools 来管理安卓库,我们不做安卓开发,所以使用这个命令行工具就行。
但是先别急着去下载!我们首先去到 Qt 安装目录下的 Tools\QtCreator\share\qtcreator\android\
目录下找到 sdk_definitions.json
文件,打开这个文件,先仔细看看它的内容。
{
"common": {
"sdk_tools_url": {
"linux": "https://dl.google.com/android/repository/commandlinetools-linux-11076708_latest.zip",
"linux_sha256": "2d2d50857e4eb553af5a6dc3ad507a17adf43d115264b1afc116f95c92e5e258",
"windows": "https://dl.google.com/android/repository/commandlinetools-win-11076708_latest.zip",
"windows_sha256": "4d6931209eebb1bfb7c7e8b240a6a3cb3ab24479ea294f3539429574b1eec862",
"mac": "https://dl.google.com/android/repository/commandlinetools-mac-11076708_latest.zip",
"mac_sha256": "7bc5c72ba0275c80a8f19684fb92793b83a6b5c94d4d179fc5988930282d7e64"
},
"sdk_essential_packages": {
"default": ["platform-tools", "cmdline-tools;latest", "emulator"],
"linux": [],
"mac": [],
"windows": ["extras;google;usb_driver"]
}
},
"specific_qt_versions": [
{
"versions": ["6.4"],
"sdk_essential_packages": [
"build-tools;31.0.0",
"ndk;23.1.7779620",
"platforms;android-31"
]
},
{
"versions": ["6.3", "6.2", "5.15.[9-20]"],
"sdk_essential_packages": [
"build-tools;31.0.0",
"ndk;22.1.7171670",
"platforms;android-31"
]
},
{
"versions": ["5.15.[0-8]", "5.14.[0-2]", "5.13.2", "6.0", "6.1"],
"sdk_essential_packages": [
"build-tools;31.0.0",
"ndk;21.3.6528147",
"platforms;android-31"
]
},
{
"versions": ["5.12.[0-5]", "5.13.[0-1]"],
"sdk_essential_packages": [
"build-tools;28.0.2",
"ndk;19.2.5345600",
"platforms;android-28"
]
}
]
}
这个文件中记录了前面提到的命令行工具 cmdline-tools 的下载地址,以及一些安卓库和 Qt 之间的版本对应关系,我们只要比照这个文件来配置安卓库,大概率就没什么毛病,,,吗?
然而真相却是,最大的坑就出在这里!因为 sdk_definitions.json
是跟随 Qt Creator 发布的,Qt Creator 和 Qt 是独立的,他们之间没什么直接关系。特别是现在这种在线安装的方式,Qt Creator 不再随着 Qt 一起发布,这就导致我们即使装了一个旧版本的 Qt,但还是会装一个较新版本的 Qt Creator,导致随着 Qt Creator 发布 的 sdk_definitions.json
文件中的信息和所安装的 Qt 版本不那么匹配。
比如这里,"sdk_tools_url"
字段中记录的 commandlinetools
的下载地址对应的版本其实就和 Qt 5.15.2 不兼容。导致我把其他包版本都一一对上后,依然提示我 Android Platform SDK 版本不对。
注意上图中的报错并不是安卓 SDK 版本的问题,而是 cmdline-tools 版本不对。之前我一直以为是安卓 SDK 版本不对,导致我一怒之下把所有版本的安卓 SDK 都下载了下来,可结果依然是报这个错,那才叫一个崩溃,差点梅开三度了。现在我明白了,这个错说的其实不是安卓 SDK,而是 commandlinetools
!
Qt 5.15.2 支持的 cmdline-tools 的版本是 8.0,而上面连接中的版本是 12.0。我们可以把上面 json 文件中的 "windows"
字段后的地址替换成下面这个:
然后将 "windows_sha256"
替换成下面这个:
8a90e6a3deb2fa13229b2e335efd07687dcc8a55a3c544da9f40b41404993e7d
"windows": "https://dl.google.com/android/repository/commandlinetools-win-9123335_latest.zip",
"windows_sha256": "8a90e6a3deb2fa13229b2e335efd07687dcc8a55a3c544da9f40b41404993e7d",
哈希会用来校验下载的文件,所以当我们替换掉下载地址后,也要把文件哈希替换掉。修改完这个文件后,重启 Qt Creator,回到 SDKs 设置页面,点击"设置 SDK"按钮,然后 Qt Creator 会自动去下载 cmdline-tools,完成以后会弹出如下提示框。
我们点击确定,随后会弹出许可协议窗口,我们一直点击"是"即可。
接受完所有协议之后还会下载一些包,随后会弹出下面的提示。
点击确定可以看到下面有些项已经打上绿勾了,之后这些包就需要我们手动下载了,不过先不急,让我们先去看一眼 AndroidSDK
目录下有什么了。
Qt Creator 帮我们下载了 build-tools
和 cmdline-tools
,build-tools
的版本是 31.0.0
,cmdline-tools
放在 latest
目录下,进入 cmdline-tools\latest
打开 source.properties
文件:
Pkg.Revision=19.0
Pkg.Path=cmdline-tools;19.0
Pkg.Desc=Android SDK Command-line Tools
神奇的事情,Qt Creator 居然还是下载了最新版的 cmdline-tools,也就是 19.0
版本。前面我们修改了 cmdline-tools 的下载地址只是为了"设置 SDK"按钮能起作用,否则点击"设置 SDK"不会下载任何东西。回到这里我们将 latest
重命名为 19.0
(或者直接删掉也行),然后新建一个空的 latest
目录,从前面的 cmdline-tools 下载地址手动下载 cmdline-tools 包,解压到 latest
目录中。
然后根据前面 json 文件的指引,"sdk_essential_packages"
字段告诉我们需要以下包:
platform-tools
cmdline-tools
(已经装过了)emulator
extras/google/usb_driver
因为我们是在 Windows 平台上编译,所以还需要谷歌的 usb_driver
库。紧接下面的 "specific_qt_versions"
字段中,找到 5.15.2 所在版本,我们还需要下面的包:
build-tools
31.0.0 版本(已经装过了)ndk
21.3.6528147 版本android-31
虽然需要的包比较多,但是我们可以一次搞定,千万不要照着某些教程自己傻乎乎的去网上一个一个下载,很多库都是不提供历史版本下载的,即使找到了,也不一定有你需要的版本,别提有多折磨人了。
接下来进入 cmdline-tools\latest\bin
目录中,一定要确定这里的 cmdline-tools 是 8.0 的版本,在这里打开命令行,输入下面的命令:
sdkmanager "ndk;21.3.6528147" "platform-tools" "platforms;android-31" "extras;google;usb_driver" "emulator" --sdk_root=E:\progrom\Android\AndroidSDK
用这一个命令就能把所需的包都安装好了,不必去网上自己下,也不用装 Android Studio。这里稍微解释下,sdkmanager
后面每个双引号内都是一个要下载的包,;
表示的是路径分隔符,有些路径中也包含了版本信息,下载完后,每个库的目录结构和这里都是对应的。因为我们没有配置安卓环境变量,所以这里通过 --sdk_root
指定了安卓库的根目录。
如果这里报错了,就要去检查下你的 cmdline-tools 的版本了,特别是提示 Java 版本过低的错误。因为最新版的 cmdline-tools 要求 Java 版本在 16 以上。
下载完成后回到 Qt Creator,点击 Android SDK 路径后面的"浏览",切换到别的目录,然后再点"浏览"切回来,或者点击"设置 SDK",或者重启下 Qt Creator,就会发现安卓环境已经设置好了,所有选项都打上了绿色的勾。
在 SDKs 页面最下面,还需要配置一个 android_openssl
的地址,这个包不属于标准安卓库,所以不能通过 sdkmanager
下载,只能自己去网上下载,然后配置到这里,好在这个包并不难找,相信一定难不倒在座的各位。
这个库不是强制的,所以应该不配置也行。至此,Qt 下的安卓构建环境就配置好了。虽然现在回过头来看整个过程非常简单,但在当时确实让我几近崩溃。明明很简单的过程,却被坑的**,后面的话不能讲出来,希望这篇文章能帮到迷途的人们吧。
终极方法:一键配置!
前面的方法已经能够让你很轻松的配置好安卓环境了,但是,还有更简单的方式,真正的一键配置!
在前面我们看到,点击"设置SDK"后,自动下载了 build-tools 和 cmdline-tools 这两个库之后就报错了,提示之后的库无法下载,需要手动配置。那么这是什么原因呢?
其实前面我们也看到了,Qt Creator 自动下载的 cmdline-tools 实际上是 19.0 版本,问题就出在这里。这个版本的 cmdline-tools 太新了,需要 JDK 16 及以上,不能兼容我们 JDK 11,所以后面的流程就报错了。
那么为什么 Qt Creator 下载的是 19.0 版本呢?其实他也不是非要下载 19.0 版本,它只是下载了最新版本,而当前最新版本是 19.0 而已。那么它为什么会下载最新版呢,我们不是修改了下载地址了吗?还是回到前面的 json 文件中,我们注意到 "sdk_essential_packages"
字段下的 "default"
数组:
"default": ["platform-tools", "cmdline-tools;latest", "emulator"],
这里指定了 "cmdline-tools;latest"
,这就是 Qt Creator 去下载最新版 cmdline-tools 的原因,还真是所有的问题都出在这个 json 文件上,我们将 latest
改成 8.0
后保存即可。
"default": ["platform-tools", "cmdline-tools;8.0", "emulator"],
然后重启 Qt Creator,设置一个空的文件夹作为安卓SDK的目录,然后直接点击"设置SDK",接受协议时一路点击"是",最后静静的等待 Qt Creator 下载完所有需要的包,安卓环境就配置完成了。真踏马的就是一键配置成功,害我之前折腾那么久。从一闪而过的下载日志中,我们也能看到这次下载的是 cmdline-tools;8.0,而不是 cmdline-tools;latest 了。
编译 QGC
配置安卓环境已是千难万险,编译 QGC 也不会一帆风顺。哎,时也,运也,命也!
下载 QGC 源码这一步按照官方文档的指示进行就行了,但是要注意下载完源码后要切换到 Stable_V4.4
分支,可以用 Git 命令行,或者任何你喜欢的 Git 工具。
git clone --recursive -j8 https://github.com/mavlink/qgroundcontrol.git
git checkout Stable_V4.4
git submodule update --recursive
然后正常打开 Qt Creator,点击打开项目,找到 QGC 源码目录,选择 qgroundcontrol.pro
打开。首次打开会先进入配置项目页面,也就是最左边"项目"栏。默认选择的构建套件是 MSVC,我们需要编译的是安卓版本,所以取消勾选 MSVC,勾选下面的安卓。在安卓构建套件上可能有个黄色的警告标志 ⚠,这是因为没有可用安卓设备,不影响编译。最后点击右下角的"配置工程",等待工程加载完毕。
这时候如果直接点锤子是锤不出来的,因为在编译的时候还会遇到几个问题,我们来一一解决。
找不到命令
首先遇到的问题就是找不到命令,一个是 which
,另一个是 sed
。
这两个命令是 Git 带的程序,在 Git 安装目录下的 usr/bin
目录下,一般我们安装 Git 的时候都只配了 bin
目录,把 usr/bin
也加到 Path
环境变量就可以了,添加完以后重启下 Qt Creator。还有一种方式是直接在 Qt Creator 中修改环境变量,它并不影响系统环境变量。还是在"配置"页,找到下面的"Build Environment",点击右边的"详情"展开环境变量,找到 Path
,点击中间的"Edit"按钮。
然后点击右上角的添加,找到 Git 安装目录的 usr/bin
目录,添加进来,点击确定即可,不用重启 Qt Creator。
ndk 路径问题
继续锤,还会遇到第二个问题。
这个问题是 ndk 的 make
报错了,错误的原因是找不到 clang++
,这是 ndk 的 c++ 编译器。根据上图中的错误提示,这个路径明显是错误的,前面缺少了路径分隔符。这个路径出现在 qgroundcontrol\build\Qt_5_15_2_Clang_Multi_Abi-Debug\Makefile
文件中,打开这个文件,找到 2501 行,可以看到如下的路径:
E:\progrom\Android\AndroidSDK\ndk\21.3.6528147/toolchains/llvm/prebuilt/windows-x86_64/bin/clang++
这一行很长,要往后翻一翻才能看到。乍看起来视乎没什么问题,但仔细看就会发现前面的路径分隔符是反斜杠,后面的路径分隔符是正斜杠。这个文件的执行者是 ndk 的 cmake
,而这个cmake
认为反斜杠是换行连接符,一行写不下了,就换行写,用反斜杠连接,正斜杠才是路径分隔符。其实我们在上面的 Makefile 文件中也能看到大量的反斜杠出现在行尾。Linux 和 Windows 路径分隔符风格问题也是老生常谈了。
这里教大家怎么区分正反斜杠,不斜的杠叫竖杠|
,竖杠往左斜\
就是反斜杠,往右斜/
就是正斜杠。我们常说,反正,左右,所以他们是对应的,左对应反,右对应正。
由于反斜杠被识别为了连接符,所以最终我们看到了 E:progromAndroidAndroidSDKndk21.3.6528147/toolchains/llvm/prebuilt/windows-x86_64/bin/clang++
这么个奇怪的路径,这能找到文件才怪了。但是这个 Makefile 是编译过程中由 qmake 生成的,所以直接改这个文件肯定是不行的,那么这个路径是从哪里来的呢?
它其实是根据环境变量生成的,从头到尾我们一直没有配过任何安卓库的环境变量,实际上也没必要,Qt 会自己帮我们配置安卓库的环境变量。点击 Qt Creator 左侧的"项目",找到下面的"Build Environment",点击"详情",展开环境变量列表。可以看到左边已经有了安卓库的环境变量,我们点击 ANDROID_NDK_ROOT
,然后点击中间的"Edit"按钮,在右边等号后面输入 NDK 的路径,并且把反斜杠全部换成正斜杠,这样就可以了。
找不到 json 库
继续锤,继续报错,已经麻了。
这次是缺少 nlohmann_json
库,它是 QGC 依赖的一个第三方库,应该在 qgroundcontrol\libs\libevents\libevents\libs\cpp\parse
目录下,但是却没有。它的依赖关系是 qgroundcontrol -> libevents -> nlohmann_json
,我们可以在 Github 页面点击对应的目录直达 nlohmann_json
的下载页面。
理论上来说 git submodule update --recursive
命令应该会下载它的,但不知道什么原因,我试了好几次都没下载下来,不过这个问题好解决,手动下载 nlohmann_json
源码,放到对应的目录下就可以了。
这个问题大家不一定会遇上,如果遇上,或者缺少了其他库,可以用同样的方式处理。
API 过时问题
再锤,还有报错。我他*的********。
这次报错是 Java 的错,因为使用了已过时的 API。不管,忽略风险,继续编译。打开 qgroundcontrol.pro
文件,在最后添加一行配置:
QMAKE_CXXFLAGS += -Wno-deprecated-declarations
这次终于可以锤出 APK 安装包了,编译好的安装包在构建目录下的 android-build\build\outputs\apk\debug
目录下。不过你以为这就结束了吗?是吗?是真的吗?
很遗憾这并不是最后的问题。
连接模拟器
没有测试的手机(不想用自己手机),搞个安卓模拟器试试。一开始我选的是 MuMu 模拟器,但是它和我的 Easytier 有冲突,一开模拟器远程连接就频繁断线,不得已换成了 Nox 模拟器,也就是夜神模拟器。把锤出来的 APK 拖进模拟器,安装成功,没有问题。
不仅没有测试的手机 ,连无人机也没有。为了测试安卓 QGC 的功能,只能上 APM 模拟器了,这个之前就装过了,感兴趣的朋友可以去看我之前的文章。
但是,网络怎么联通呢?模拟器里的 QGC 压根收不到 APM 模拟器的消息啊。。。
这和模拟器的网络设置有关系,Nox 模拟器默认的网络模式是 NAT 模式,也就是由宿主机代理模拟器的网络流量,此时模拟器相当于是一个内网,它和宿主机之间网络是不通的,我们需要将模拟器网络改为桥接模式,这样模拟器就会和宿主机处于同一个网络内,他们之间就能通信了。
打开模拟器之后点击左上角的齿轮按钮,进入设置页面。点击左侧的手机,然后在网络设置下面勾选开启网络桥接模式,点击保存设置,首次开启会下载相应的驱动,成功后模拟器会自动重启。
这里要注意 Nox 模拟有两个程序,一个是 32 位,一个 64 位,默认打开的是 32 位,但是因为我们锤出来的 QGC 是 64 位的,所以当我们打开 QGC 时,会提示在 64 位模拟器中打开,点击确定,就会启动 64 位模拟器。这两个模拟器的设置也是独立的,所以我们要在 64 位模拟器中设置网络模式,千万别搞错了。
开启网络桥接模式后,记录下面出现的"IP 地址",这就是我们与模拟器中的 QGC 通信的地址。我们也可以点击模拟器桌面上的工具,打开设置,点击网络和互联网 > WLAN > VirtWifi,点击高级,也能看到模拟器的 IP 地址,只有在网络桥接模式下才能看到。可以看到是一个和宿主机在同一网段的内网地址。
然后启动 APM 模拟器,在 Mavproxy 的控制台输入下面的命令,将 mavlink 消息转发给模拟器中的 QGC。
STABILIZE> output add 192.168.2.111:14550
回到 Nox 模拟器,打开 QGC,见证奇迹的时刻终于来了,可以看到 QGC 已经连上 APM 模拟器,并收到了来自模拟器的消息。OJBK,我已经迫不及待地要起飞了,啊,起飞,,,,失败??在点击起飞按钮等待一段时间之后,QGC 会弹出提示:无人机无法进入引导模式!
What can I say.
解决无法进入引导模式
都到这一步了,这个问题我吃定了,耶稣也留不住,我说的!
之前我也装了 Windows 版的 QGC,但是 Windows 版就没有这个问题,只有安卓版有,查看日志,他们都是 4.4
版本,这就离了谱了。
查看 Mavlink 消息的接收也是正常的,而且用 Windows 版的 QGC 把无人机先飞起来后,再回到安卓版 QGC 进行其他操作也都是正常的。没办法,源码,启动!
找到 src/FirmwarePlugin/APM/APMFirmwarePlugin.cc
,接着找到 _guidedModeTakeoff
(926 行) 函数,它就是在 QGC 中点击起飞时调用的函数,报错来自该函数中的下面这段代码:
if (!_setFlightModeAndValidate(vehicle, "Guided")) {
qgcApp()->showAppMessage(tr("Unable to takeoff: Vehicle failed to change to Guided mode."));
return false;
}
_setFlightModeAndValidate
函数在 src/FirmwarePlugin.cc
(965 行) 中,源码如下:
bool FirmwarePlugin::_setFlightModeAndValidate(Vehicle* vehicle, const QString& flightMode)
{
if (vehicle->flightMode() == flightMode) {
return true;
}
bool flightModeChanged = false;
// We try 3 times
for (int retries=0; retries<3; retries++) {
vehicle->setFlightMode(flightMode);
// Wait for vehicle to return flight mode
for (int i=0; i<13; i++) {
if (vehicle->flightMode() == flightMode) {
flightModeChanged = true;
break;
}
QGC::SLEEP::msleep(100);
qgcApp()->processEvents(QEventLoop::ExcludeUserInputEvents);
}
if (flightModeChanged) {
break;
}
}
return flightModeChanged;
}
它的逻辑比较简单,调用 vehicle
的 setFlightMode
方法,然后以轮询的方式判断 vehicle
的飞行模式是否与目标模式相等。问题就出在这个相等判断if (vehicle->flightMode() == flightMode)
上,它是直接用的 ==
判定。通过打印日志我们会发现 vehicle->flightMode()
返回的其实是 GUIDED
,而传入的 flightMode
是 Guided
。嗯~确实是一样的,但又不完全一样,程序就是这么的一丝不苟,都不知道通融一下。知道了问题,修改起来就很简单了,只需要把相等判断换成忽略大小写的相等判断就可以了。
QString::compare(vehicle->flightMode(), flightMode, Qt::CaseInsensitive) == 0
关于打印日志,我们可以在
if
判断之前加上下面这行代码:qCWarning(FirmwarePluginLog) << "vehicle_mode:" << vehicle->flightMode() << " , flight_mode:" << flightMode;
然后在 QGC 中点击左上角的 QGC 图标,然后点击 Application Settings,然后选择控制台就能看到日志了。当然要先点击起飞才能看到这个日志。
当然这并不是唯一的问题,在这个问题之前还有一个问题,如果我们观察日志的话,会发现有这么一个警告:"FirmwarePlugin::setFlightMode failed, flightMode: Guided"。
![]
它是来自 vehicle
的 setFlightMode
函数,让我继续追源码,找到 src/Vehicle/Vehicle.cc
(2281 行)的 setFlightMode
函数,源码如下:
void Vehicle::setFlightMode(const QString& flightMode)
{
uint8_t base_mode;
uint32_t custom_mode;
if (setFlightModeCustom(flightMode, &base_mode, &custom_mode)) {
SharedLinkInterfacePtr sharedLink = vehicleLinkManager()->primaryLink().lock();
if (!sharedLink) {
qCDebug(VehicleLog) << "setFlightMode: primary link gone!";
return;
}
uint8_t newBaseMode = _base_mode & ~MAV_MODE_FLAG_DECODE_POSITION_CUSTOM_MODE;
// setFlightMode will only set MAV_MODE_FLAG_CUSTOM_MODE_ENABLED in base_mode, we need to move back in the existing
// states.
newBaseMode |= base_mode;
if (_firmwarePlugin->MAV_CMD_DO_SET_MODE_is_supported()) {
sendMavCommand(defaultComponentId(),
MAV_CMD_DO_SET_MODE,
true, // show error if fails
MAV_MODE_FLAG_CUSTOM_MODE_ENABLED,
custom_mode);
} else {
mavlink_message_t msg;
mavlink_msg_set_mode_pack_chan(_mavlink->getSystemId(),
_mavlink->getComponentId(),
sharedLink->mavlinkChannel(),
&msg,
id(),
newBaseMode,
custom_mode);
sendMessageOnLinkThreadSafe(sharedLink.get(), msg);
}
} else {
qCWarning(VehicleLog) << "FirmwarePlugin::setFlightMode failed, flightMode:" << flightMode;
}
}
它的功能也很简单,就是给无人机发送设置模式的消息。但是,既然我们看到了 else
分支的警告日志,这就说明 if
条件就失败了,消息根本没发出去,难怪无人机无法进入飞行模式。setFlightModeCustom
(2270 行)就在 setFlightMode
函数的上面,源码如下:
bool Vehicle::setFlightModeCustom(const QString& flightMode, uint8_t* base_mode, uint32_t* custom_mode)
{
if (_standardModes->supported()) {
*base_mode = MAV_MODE_FLAG_CUSTOM_MODE_ENABLED;
qCWarning(VehicleLog) << "setFlightModeCustom::_standardModes, flightMode:" << flightMode << ", base_mode:" << *base_mode << ", custom_mode:" << *custom_mode;
return _standardModes->setFlightMode(flightMode, custom_mode);
}
qCWarning(VehicleLog) << "setFlightModeCustom::_firmwarePlugin, flightMode:" << flightMode << ", base_mode:" << *base_mode << ", custom_mode:" << *custom_mode;
return _firmwarePlugin->setFlightMode(flightMode, base_mode, custom_mode);
}
这里我加了日志,知道了这个函数走的是 if
分支。继续找到 src/Vehicle/StandardModes.cc
(174 行) 的 setFlightMode
函数,源码如下:
bool StandardModes::setFlightMode(const QString &flightMode, uint32_t *custom_mode)
{
for (auto iter = _modes.constBegin(); iter != _modes.constEnd(); ++iter) {
if (iter->name == flightMode) {
*custom_mode = iter.key();
return true;
}
}
return false;
}
这里 _modes
是一个集合,记录的是无人机支持的模式,它是无人机通过 MAVLink 消息广播给地面站的,flightMode
是我们的目标模式,所以这个函数实际上就是在判断无人机是否支持我们的目标模式。问题还是那个问题,两个模式名称一个是全大写,一个是首字母大写,我们需要将它换成忽略大小写的判断:
if (QString::compare(iter->name, flightMode, Qt::CaseInsensitive) == 0)
这里我们也可以在 if
前面加上一行日志,问题就十分明了了。
qCWarning(StandardModesLog) << "_modes[" << _modes.size() << "]:" << iter->name << " " << iter->standardMode;
既然我们知道了问题的原因,那有没有更简单的解决办法呢?有的,兄弟,有的。
回到我们最开始的地方,src/FirmwarePlugin/APM/APMFirmwarePlugin.cc
(926 行) 的 _guidedModeTakeoff
函数:
bool APMFirmwarePlugin::_guidedModeTakeoff(Vehicle* vehicle, double altitudeRel)
{
... ...
if (!_setFlightModeAndValidate(vehicle, "Guided")) {
qgcApp()->showAppMessage(tr("Unable to takeoff: Vehicle failed to change to Guided mode."));
return false;
}
... ...
}
其实我们直接把 _setFlightModeAndValidate
函数的参数改成 GUIDED
就可以了。这样能解决起飞的问题,但是!这个函数不只有这一个地方调用,其他地方的参数依然是首字母大写的形式。如果采用这种方式的话,就要把所有出现这个函数调用的地方都改掉,否者在进入其他逻辑分支时,依然会报错。
改掉这些问题之后,重新编译 QGC,这时起飞功能就正常了。啊,世界终于清静了!
3D 模拟器
不管是 Mavproxy 还是 QGC,都是二维的,不便于观察无人机的状态,ArduPilot 支持 FlightGear 3D 无人机模拟器,设置起来也非常容易,官方文档如下:
但是有几个需要注意的地方,首先是不要去 FlightGear 官网下载最新版本,找一个旧版本下载,比如 2020 版本。因为最新版程序和数据分离了,虽然安装包体积变小了,但是打开后需要先下载数据,这一步会失败,直接闪退,我下载的是 2020.3.9 版本,安装包 1.77G,如果你下载的安装包只有几百兆,那估计就是不行的。不过要是你能解决网络问题,成功下载数据,那也是可以的。
其次是 ArduPilot 提供的 FilghtGear 启动脚本会默认 FlightGear 是安装在 C 盘的,如果你不是装在 C 盘的话,就需要修改下启动脚本,或者将原本的启动脚本复制一份再修改,都可以。比如我是装在 E 盘的,我将 fg_quad_view.bat
拷贝了一份重命名为 fg_quad_view_local.bat
,然后修改如下:
set AUTOTESTDIR="%~dp0\aircraft"
:: c:
:: FOR /F "delims=" %%D in ('dir /b "\Program Files"\FlightGear*') DO set FGDIR=%%D
:: echo "Using FlightGear %FGDIR%"
:: cd "\Program Files\%FGDIR%\bin"
e:
cd "E:\progrom\FlightGear 2020.3\bin"
fgfs ^
--native-fdm=socket,in,10,,5503,udp ^
--fdm=external ^
--aircraft=arducopter ^
--fg-aircraft=%AUTOTESTDIR% ^
--airport=KSFO ^
--geometry=650x550 ^
--bpp=32 ^
--disable-hud-3d ^
--disable-horizon-effect ^
--timeofday=noon ^
--disable-sound ^
--disable-fullscreen ^
--disable-random-objects ^
--disable-ai-models ^
--fog-disable ^
--disable-specular-highlight ^
--disable-anti-alias-hud ^
--wind=0@0
pause
启动 FlightGear 时执行 fg_quad_view_local.bat
就行了,固定翼飞机时 fg_plane_view.bat
也是一样的修改。其他步骤都按照官网描述进行就可以了。
**粗体** _斜体_ [链接](http://example.com) `代码` - 列表 > 引用
。你还可以使用@
来通知其他用户。