> 技术文档 > Qt 5.15.2编译安卓QGC指南_qgc编译

Qt 5.15.2编译安卓QGC指南_qgc编译

最近需要编译安卓版本的 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)。

  • Getting Started with Source and Builds | QGC Guide (4.4)

随着新版本的发布,这个页面可能也会被官方删除掉,但是就目前而言,它最大的参考意义就是告我需要的库的版本。

  • 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 的安装我就不废话了,这里只提几个需要注意的地方。

设置国内镜像

在线安装时默认使用的是国外的镜像仓库,速度非常感人,所以我们需要换成国内镜像源,推荐使用阿里云镜像:

  • https://mirrors.aliyun.com/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 盘空间,建议在登录之前先点击左下角设置按钮,修改缓存路径。缓存目录要预先创建。在线安装程序每次启动都要修改这个地方。

Qt 5.15.2编译安卓QGC指南_qgc编译

组件选择

在选择组件步骤,默认只有最新的几个版本可供选择,我们把右边的 Archive 勾选上,然后点击筛选,就能看到全部版本了。

Qt 5.15.2编译安卓QGC指南_qgc编译

找到 Qt5.15.2,安卓是必选的,其他平台按需选择,按照官方说明,Qt Charts 也是必选项。然后在下面的 Build Tools 和 Qt Creator 中选择下面几个组件。

Qt 5.15.2编译安卓QGC指南_qgc编译

这里不用担心少选或多选了组件,多选了比少选了好,少选了会错,多选了不会错。而且安装完成后我们还是能通过 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\"里,要不是我瞎点了几下,差点就又夭折在这里了[捂脸]。

Qt 5.15.2编译安卓QGC指南_qgc编译

下一步是最闹心的一步,配置安卓编译环境。回想起曾经两度在此折戟沉沙,不得不说,配置开发环境真是编程路上最大的绊脚石[捂脸]。

配置安卓 SDK

紧接上一步配置完 JDK 路径之后,我们找个地方创建一个空目录作为安卓 SDK 的存放路径,然后将找个路径配置到\"Android SDK的路径\"中,为了演示,我这里重新新建了一个叫 AndroidSDK 的目录。

Qt 5.15.2编译安卓QGC指南_qgc编译

先不要着急,网上又很多教程在用图形界面的 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 版本不对。

Qt 5.15.2编译安卓QGC指南_qgc编译

注意上图中的报错并不是安卓 SDK 版本的问题,而是 cmdline-tools 版本不对。之前我一直以为是安卓 SDK 版本不对,导致我一怒之下把所有版本的安卓 SDK 都下载了下来,可结果依然是报这个错,那才叫一个崩溃,差点梅开三度了。现在我明白了,这个错说的其实不是安卓 SDK,而是 commandlinetools

Qt 5.15.2 支持的 cmdline-tools 的版本是 8.0,而上面连接中的版本是 12.0。我们可以把上面 json 文件中的 \"windows\" 字段后的地址替换成下面这个:

  • https://dl.google.com/android/repository/commandlinetools-win-9123335_latest.zip

然后将 \"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,完成以后会弹出如下提示框。

Qt 5.15.2编译安卓QGC指南_qgc编译

我们点击确定,随后会弹出许可协议窗口,我们一直点击\"是\"即可。

Qt 5.15.2编译安卓QGC指南_qgc编译

接受完所有协议之后还会下载一些包,随后会弹出下面的提示。

Qt 5.15.2编译安卓QGC指南_qgc编译

点击确定可以看到下面有些项已经打上绿勾了,之后这些包就需要我们手动下载了,不过先不急,让我们先去看一眼 AndroidSDK 目录下有什么了。

Qt 5.15.2编译安卓QGC指南_qgc编译

Qt Creator 帮我们下载了 build-toolscmdline-toolsbuild-tools 的版本是 31.0.0cmdline-tools 放在 latest 目录下,进入 cmdline-tools\\latest 打开 source.properties 文件:

Pkg.Revision=19.0Pkg.Path=cmdline-tools;19.0Pkg.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,就会发现安卓环境已经设置好了,所有选项都打上了绿色的勾。

Qt 5.15.2编译安卓QGC指南_qgc编译

在 SDKs 页面最下面,还需要配置一个 android_openssl 的地址,这个包不属于标准安卓库,所以不能通过 sdkmanager 下载,只能自己去网上下载,然后配置到这里,好在这个包并不难找,相信一定难不倒在座的各位。

Qt 5.15.2编译安卓QGC指南_qgc编译

这个库不是强制的,所以应该不配置也行。至此,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.gitgit checkout Stable_V4.4git submodule update --recursive

然后正常打开 Qt Creator,点击打开项目,找到 QGC 源码目录,选择 qgroundcontrol.pro 打开。首次打开会先进入配置项目页面,也就是最左边\"项目\"栏。默认选择的构建套件是 MSVC,我们需要编译的是安卓版本,所以取消勾选 MSVC,勾选下面的安卓。在安卓构建套件上可能有个黄色的警告标志 ⚠,这是因为没有可用安卓设备,不影响编译。最后点击右下角的\"配置工程\",等待工程加载完毕。

这时候如果直接点锤子是锤不出来的,因为在编译的时候还会遇到几个问题,我们来一一解决。

找不到命令

首先遇到的问题就是找不到命令,一个是 which ,另一个是 sed

Qt 5.15.2编译安卓QGC指南_qgc编译

这两个命令是 Git 带的程序,在 Git 安装目录下的 usr/bin 目录下,一般我们安装 Git 的时候都只配了 bin 目录,把 usr/bin 也加到 Path 环境变量就可以了,添加完以后重启下 Qt Creator。还有一种方式是直接在 Qt Creator 中修改环境变量,它并不影响系统环境变量。还是在\"配置\"页,找到下面的\"Build Environment\",点击右边的\"详情\"展开环境变量,找到 Path ,点击中间的\"Edit\"按钮。

Qt 5.15.2编译安卓QGC指南_qgc编译

然后点击右上角的添加,找到 Git 安装目录的 usr/bin 目录,添加进来,点击确定即可,不用重启 Qt Creator。

Qt 5.15.2编译安卓QGC指南_qgc编译

ndk 路径问题

继续锤,还会遇到第二个问题。

Qt 5.15.2编译安卓QGC指南_qgc编译

这个问题是 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 的路径,并且把反斜杠全部换成正斜杠,这样就可以了。

Qt 5.15.2编译安卓QGC指南_qgc编译

找不到 json 库

继续锤,继续报错,已经麻了。

Qt 5.15.2编译安卓QGC指南_qgc编译

这次是缺少 nlohmann_json 库,它是 QGC 依赖的一个第三方库,应该在 qgroundcontrol\\libs\\libevents\\libevents\\libs\\cpp\\parse 目录下,但是却没有。它的依赖关系是 qgroundcontrol -> libevents -> nlohmann_json ,我们可以在 Github 页面点击对应的目录直达 nlohmann_json 的下载页面。

Qt 5.15.2编译安卓QGC指南_qgc编译

Qt 5.15.2编译安卓QGC指南_qgc编译

理论上来说 git submodule update --recursive 命令应该会下载它的,但不知道什么原因,我试了好几次都没下载下来,不过这个问题好解决,手动下载 nlohmann_json 源码,放到对应的目录下就可以了。

  • https://github.com/nlohmann/json/tree/bc889afb4c5bf1c0d8ee29ef35eaaf4c8bef8a5d

这个问题大家不一定会遇上,如果遇上,或者缺少了其他库,可以用同样的方式处理。

API 过时问题

再锤,还有报错。我他*的********。

Qt 5.15.2编译安卓QGC指南_qgc编译

这次报错是 Java 的错,因为使用了已过时的 API。不管,忽略风险,继续编译。打开 qgroundcontrol.pro 文件,在最后添加一行配置:

QMAKE_CXXFLAGS += -Wno-deprecated-declarations

这次终于可以锤出 APK 安装包了,编译好的安装包在构建目录下的 android-build\\build\\outputs\\apk\\debug 目录下。不过你以为这就结束了吗?是吗?是真的吗?

Qt 5.15.2编译安卓QGC指南_qgc编译

很遗憾这并不是最后的问题。

连接模拟器

没有测试的手机(不想用自己手机),搞个安卓模拟器试试。一开始我选的是 MuMu 模拟器,但是它和我的 Easytier 有冲突,一开模拟器远程连接就频繁断线,不得已换成了 Nox 模拟器,也就是夜神模拟器。把锤出来的 APK 拖进模拟器,安装成功,没有问题。

不仅没有测试的手机 ,连无人机也没有。为了测试安卓 QGC 的功能,只能上 APM 模拟器了,这个之前就装过了,感兴趣的朋友可以去看我之前的文章。

  • 在 Windows 环境下搭建无人机模拟器

  • Ardupilot 模拟器配置与使用基础

  • APM 仿真遥控指南

但是,网络怎么联通呢?模拟器里的 QGC 压根收不到 APM 模拟器的消息啊。。。

这和模拟器的网络设置有关系,Nox 模拟器默认的网络模式是 NAT 模式,也就是由宿主机代理模拟器的网络流量,此时模拟器相当于是一个内网,它和宿主机之间网络是不通的,我们需要将模拟器网络改为桥接模式,这样模拟器就会和宿主机处于同一个网络内,他们之间就能通信了。

打开模拟器之后点击左上角的齿轮按钮,进入设置页面。点击左侧的手机,然后在网络设置下面勾选开启网络桥接模式,点击保存设置,首次开启会下载相应的驱动,成功后模拟器会自动重启。

Qt 5.15.2编译安卓QGC指南_qgc编译

Qt 5.15.2编译安卓QGC指南_qgc编译

这里要注意 Nox 模拟有两个程序,一个是 32 位,一个 64 位,默认打开的是 32 位,但是因为我们锤出来的 QGC 是 64 位的,所以当我们打开 QGC 时,会提示在 64 位模拟器中打开,点击确定,就会启动 64 位模拟器。这两个模拟器的设置也是独立的,所以我们要在 64 位模拟器中设置网络模式,千万别搞错了。

开启网络桥接模式后,记录下面出现的\"IP 地址\",这就是我们与模拟器中的 QGC 通信的地址。我们也可以点击模拟器桌面上的工具,打开设置,点击网络和互联网 > WLAN > VirtWifi,点击高级,也能看到模拟器的 IP 地址,只有在网络桥接模式下才能看到。可以看到是一个和宿主机在同一网段的内网地址。

Qt 5.15.2编译安卓QGC指南_qgc编译

然后启动 APM 模拟器,在 Mavproxy 的控制台输入下面的命令,将 mavlink 消息转发给模拟器中的 QGC。

STABILIZE> output add 192.168.2.111:14550

Qt 5.15.2编译安卓QGC指南_qgc编译

回到 Nox 模拟器,打开 QGC,见证奇迹的时刻终于来了,可以看到 QGC 已经连上 APM 模拟器,并收到了来自模拟器的消息。OJBK,我已经迫不及待地要起飞了,啊,起飞,,,,失败??在点击起飞按钮等待一段时间之后,QGC 会弹出提示:无人机无法进入引导模式!

Qt 5.15.2编译安卓QGC指南_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;}

它的逻辑比较简单,调用 vehiclesetFlightMode 方法,然后以轮询的方式判断 vehicle 的飞行模式是否与目标模式相等。问题就出在这个相等判断if (vehicle->flightMode() == flightMode)上,它是直接用的 == 判定。通过打印日志我们会发现 vehicle->flightMode() 返回的其实是 GUIDED,而传入的 flightModeGuided。嗯~确实是一样的,但又不完全一样,程序就是这么的一丝不苟,都不知道通融一下。知道了问题,修改起来就很简单了,只需要把相等判断换成忽略大小写的相等判断就可以了。

QString::compare(vehicle->flightMode(), flightMode, Qt::CaseInsensitive) == 0

关于打印日志,我们可以在 if 判断之前加上下面这行代码:

qCWarning(FirmwarePluginLog) << \"vehicle_mode:\" <flightMode() << \" , flight_mode:\" << flightMode;

然后在 QGC 中点击左上角的 QGC 图标,然后点击 Application Settings,然后选择控制台就能看到日志了。当然要先点击起飞才能看到这个日志。

Qt 5.15.2编译安卓QGC指南_qgc编译

当然这并不是唯一的问题,在这个问题之前还有一个问题,如果我们观察日志的话,会发现有这么一个警告:“FirmwarePlugin::setFlightMode failed, flightMode: Guided”。

Qt 5.15.2编译安卓QGC指南_qgc编译

它是来自 vehiclesetFlightMode 函数,让我继续追源码,找到 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 无人机模拟器,设置起来也非常容易,官方文档如下:

  • SITL setup on Windows using Cygwin (not recommended) — Dev documentation

但是有几个需要注意的地方,首先是不要去 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@0pause

启动 FlightGear 时执行 fg_quad_view_local.bat 就行了,固定翼飞机时 fg_plane_view.bat 也是一样的修改。其他步骤都按照官网描述进行就可以了。