最近需要編譯安卓版本的 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 盤空間,建議在登錄之前先點擊左下角設置按鈕,修改緩存路徑。緩存目錄要預先創建。在線安裝程序每次啓動都要修改這個地方。
組件選擇
在選擇組件步驟,默認只有最新的幾個版本可供選擇,我們把右邊的 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" 字段後的地址替換成下面這個:
- https://dl.google.com/android/repository/commandlinetools-win...
然後將 "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-toolscmdline-tools(已經裝過了)emulatorextras/google/usb_driver
因為我們是在 Windows 平台上編譯,所以還需要谷歌的 usb_driver 庫。緊接下面的 "specific_qt_versions" 字段中,找到 5.15.2 所在版本,我們還需要下面的包:
build-tools31.0.0 版本(已經裝過了)ndk21.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 源碼,放到對應的目錄下就可以了。
- https://github.com/nlohmann/json/tree/bc889afb4c5bf1c0d8ee29ef35eaaf4c8bef8a5d
這個問題大家不一定會遇上,如果遇上,或者缺少了其他庫,可以用同樣的方式處理。
API 過時問題
再錘,還有報錯。我他*的********。
這次報錯是 Java 的錯,因為使用了已過時的 API。不管,忽略風險,繼續編譯。打開 qgroundcontrol.pro 文件,在最後添加一行配置:
QMAKE_CXXFLAGS += -Wno-deprecated-declarations
這次終於可以錘出 APK 安裝包了,編譯好的安裝包在構建目錄下的 android-build\build\outputs\apk\debug 目錄下。不過你以為這就結束了嗎?是嗎?是真的嗎?
很遺憾這並不是最後的問題。
連接模擬器
沒有測試的手機(不想用自己手機),搞個安卓模擬器試試。一開始我選的是 MuMu 模擬器,但是它和我的 Easytier 有衝突,一開模擬器遠程連接就頻繁斷線,不得已換成了 Nox 模擬器,也就是夜神模擬器。把錘出來的 APK 拖進模擬器,安裝成功,沒有問題。
不僅沒有測試的手機 ,連無人機也沒有。為了測試安卓 QGC 的功能,只能上 APM 模擬器了,這個之前就裝過了,感興趣的朋友可以去看我之前的文章。
- 在 Windows 環境下搭建無人機模擬器
- Ardupilot 模擬器配置與使用基礎
- 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 無人機模擬器,設置起來也非常容易,官方文檔如下:
- 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@0
pause
啓動 FlightGear 時執行 fg_quad_view_local.bat 就行了,固定翼飛機時 fg_plane_view.bat 也是一樣的修改。其他步驟都按照官網描述進行就可以了。