博客 / 詳情

返回

qt5.15.2編譯安卓版qgc

最近需要編譯安卓版本的 QGC,雖然最終成功編譯出來了,但是過程確實曲折離奇,更是被編譯環境反覆鞭打,直至絕望。最終也不知道是哪位菩薩保佑,總算是編譯成功了,小子在此跪謝了。

跪謝.png

失敗的經歷

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 盤空間,建議在登錄之前先點擊左下角設置按鈕,修改緩存路徑。緩存目錄要預先創建。在線安裝程序每次啓動都要修改這個地方。

修改緩存路徑.png

組件選擇

在選擇組件步驟,默認只有最新的幾個版本可供選擇,我們把右邊的 Archive 勾選上,然後點擊篩選,就能看到全部版本了。

選擇組件.png

找到 Qt5.15.2,安卓是必選的,其他平台按需選擇,按照官方説明,Qt Charts 也是必選項。然後在下面的 Build Tools 和 Qt Creator 中選擇下面幾個組件。

選擇組件2.png

這裏不用擔心少選或多選了組件,多選了比少選了好,少選了會錯,多選了不會錯。而且安裝完成後我們還是能通過 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"裏,要不是我瞎點了幾下,差點就又夭折在這裏了[捂臉]。

配置JDK.png

下一步是最鬧心的一步,配置安卓編譯環境。回想起曾經兩度在此折戟沉沙,不得不説,配置開發環境真是編程路上最大的絆腳石[捂臉]。

配置安卓 SDK

緊接上一步配置完 JDK 路徑之後,我們找個地方創建一個空目錄作為安卓 SDK 的存放路徑,然後將找個路徑配置到"Android SDK的路徑"中,為了演示,我這裏重新新建了一個叫 AndroidSDK 的目錄。

配置安卓SDK路徑.png

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

報錯1.png

注意上圖中的報錯並不是安卓 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,完成以後會彈出如下提示框。

設置SDK.png

我們點擊確定,隨後會彈出許可協議窗口,我們一直點擊"是"即可。

接受許可.png

接受完所有協議之後還會下載一些包,隨後會彈出下面的提示。

無法下載重要包.png

點擊確定可以看到下面有些項已經打上綠勾了,之後這些包就需要我們手動下載了,不過先不急,讓我們先去看一眼 AndroidSDK 目錄下有什麼了。

安卓SDK1.png

Qt Creator 幫我們下載了 build-toolscmdline-toolsbuild-tools 的版本是 31.0.0cmdline-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,就會發現安卓環境已經設置好了,所有選項都打上了綠色的勾。

設置完成.png

在 SDKs 頁面最下面,還需要配置一個 android_openssl 的地址,這個包不屬於標準安卓庫,所以不能通過 sdkmanager 下載,只能自己去網上下載,然後配置到這裏,好在這個包並不難找,相信一定難不倒在座的各位。

設置android_openssl.png

這個庫不是強制的,所以應該不配置也行。至此,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

找不到命令.png

這兩個命令是 Git 帶的程序,在 Git 安裝目錄下的 usr/bin 目錄下,一般我們安裝 Git 的時候都只配了 bin 目錄,把 usr/bin 也加到 Path 環境變量就可以了,添加完以後重啓下 Qt Creator。還有一種方式是直接在 Qt Creator 中修改環境變量,它並不影響系統環境變量。還是在"配置"頁,找到下面的"Build Environment",點擊右邊的"詳情"展開環境變量,找到 Path ,點擊中間的"Edit"按鈕。

添加Path1.png

然後點擊右上角的添加,找到 Git 安裝目錄的 usr/bin 目錄,添加進來,點擊確定即可,不用重啓 Qt Creator。

添加Path2.png

ndk 路徑問題

繼續錘,還會遇到第二個問題。

Make_Error.png

這個問題是 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 的路徑,並且把反斜槓全部換成正斜槓,這樣就可以了。

修改NDK環境變量.png

找不到 json 庫

繼續錘,繼續報錯,已經麻了。

找不到nlohmann_json.png

這次是缺少 nlohmann_json 庫,它是 QGC 依賴的一個第三方庫,應該在 qgroundcontrol\libs\libevents\libevents\libs\cpp\parse 目錄下,但是卻沒有。它的依賴關係是 qgroundcontrol -> libevents -> nlohmann_json ,我們可以在 Github 頁面點擊對應的目錄直達 nlohmann_json 的下載頁面。

點擊libevents.png

點擊nlohmann_json.png

理論上來説 git submodule update --recursive 命令應該會下載它的,但不知道什麼原因,我試了好幾次都沒下載下來,不過這個問題好解決,手動下載 nlohmann_json 源碼,放到對應的目錄下就可以了。

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

這個問題大家不一定會遇上,如果遇上,或者缺少了其他庫,可以用同樣的方式處理。

API 過時問題

再錘,還有報錯。我他*的********。

使用了過時的API.png

這次報錯是 Java 的錯,因為使用了已過時的 API。不管,忽略風險,繼續編譯。打開 qgroundcontrol.pro 文件,在最後添加一行配置:

QMAKE_CXXFLAGS += -Wno-deprecated-declarations

這次終於可以錘出 APK 安裝包了,編譯好的安裝包在構建目錄下的 android-build\build\outputs\apk\debug 目錄下。不過你以為這就結束了嗎?是嗎?是真的嗎?

nonononono.gif

很遺憾這並不是最後的問題。

連接模擬器

沒有測試的手機(不想用自己手機),搞個安卓模擬器試試。一開始我選的是 MuMu 模擬器,但是它和我的 Easytier 有衝突,一開模擬器遠程連接就頻繁斷線,不得已換成了 Nox 模擬器,也就是夜神模擬器。把錘出來的 APK 拖進模擬器,安裝成功,沒有問題。

不僅沒有測試的手機 ,連無人機也沒有。為了測試安卓 QGC 的功能,只能上 APM 模擬器了,這個之前就裝過了,感興趣的朋友可以去看我之前的文章。

  • 在 Windows 環境下搭建無人機模擬器
  • Ardupilot 模擬器配置與使用基礎
  • APM 仿真遙控指南

但是,網絡怎麼聯通呢?模擬器裏的 QGC 壓根收不到 APM 模擬器的消息啊。。。

這和模擬器的網絡設置有關係,Nox 模擬器默認的網絡模式是 NAT 模式,也就是由宿主機代理模擬器的網絡流量,此時模擬器相當於是一個內網,它和宿主機之間網絡是不通的,我們需要將模擬器網絡改為橋接模式,這樣模擬器就會和宿主機處於同一個網絡內,他們之間就能通信了。

打開模擬器之後點擊左上角的齒輪按鈕,進入設置頁面。點擊左側的手機,然後在網絡設置下面勾選開啓網絡橋接模式,點擊保存設置,首次開啓會下載相應的驅動,成功後模擬器會自動重啓。

點擊模擬器設置.png

開啓網絡橋接.png

這裏要注意 Nox 模擬有兩個程序,一個是 32 位,一個 64 位,默認打開的是 32 位,但是因為我們錘出來的 QGC 是 64 位的,所以當我們打開 QGC 時,會提示在 64 位模擬器中打開,點擊確定,就會啓動 64 位模擬器。這兩個模擬器的設置也是獨立的,所以我們要在 64 位模擬器中設置網絡模式,千萬別搞錯了。

開啓網絡橋接模式後,記錄下面出現的"IP 地址",這就是我們與模擬器中的 QGC 通信的地址。我們也可以點擊模擬器桌面上的工具,打開設置,點擊網絡和互聯網 > WLAN > VirtWifi,點擊高級,也能看到模擬器的 IP 地址,只有在網絡橋接模式下才能看到。可以看到是一個和宿主機在同一網段的內網地址。

查看IP地址.png

然後啓動 APM 模擬器,在 Mavproxy 的控制枱輸入下面的命令,將 mavlink 消息轉發給模擬器中的 QGC。

STABILIZE> output add 192.168.2.111:14550

添加output.png

回到 Nox 模擬器,打開 QGC,見證奇蹟的時刻終於來了,可以看到 QGC 已經連上 APM 模擬器,並收到了來自模擬器的消息。OJBK,我已經迫不及待地要起飛了,啊,起飛,,,,失敗??在點擊起飛按鈕等待一段時間之後,QGC 會彈出提示:無人機無法進入引導模式!

無法進入引導模式.png

What can I say.

what_can_i_say.jpeg

解決無法進入引導模式

都到這一步了,這個問題我吃定了,耶穌也留不住,我説的!

之前我也裝了 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:" << vehicle->flightMode() << " , flight_mode:" << flightMode;

然後在 QGC 中點擊左上角的 QGC 圖標,然後點擊 Application Settings,然後選擇控制枱就能看到日誌了。當然要先點擊起飛才能看到這個日誌。

查看日誌.png

當然這並不是唯一的問題,在這個問題之前還有一個問題,如果我們觀察日誌的話,會發現有這麼一個警告:"FirmwarePlugin::setFlightMode failed, flightMode: Guided"。

![]設置模式失敗日誌.png

它是來自 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@0
pause

啓動 FlightGear 時執行 fg_quad_view_local.bat 就行了,固定翼飛機時 fg_plane_view.bat 也是一樣的修改。其他步驟都按照官網描述進行就可以了。

user avatar
0 位用戶收藏了這個故事!

發佈 評論

Some HTML is okay.