為什麼 QGC 會把 DO_CHANGE_SPEED 藏起來?從 MAVLink 任務結構看速度設定的真相

前言:一個看起來很怪、其實很合理的現象

在開發自家地面站的過程中,我遇到一個看起來「很怪」的現象:

  • 自己的程式裡,當我在某個航點指定速度時,程式會插入一個 MAV_CMD_DO_CHANGE_SPEED 的任務項目。
  • 把這份任務上傳到飛控,再用 QGroundControl(以下簡稱 QGC) 打開來看:
    • QGC 會把 DO_CHANGE_SPEED 顯示成一條獨立的「特殊航點」。
    • 原本那個 NAV_WAYPOINT 上的「Speed」欄位反而是灰色、不可編輯。

但反過來:

  • 如果我在 QGC 裡面替某個航點設定 Flight Speed,
  • 然後再從飛控把任務下載回我的程式,
  • 我就會看到:
    • QGC 確實有產生 MAV_CMD_DO_CHANGE_SPEED,
    • 只是 在 QGC 的 UI 裡,這條指令是被「藏」起來的,速度顯示在上一個航點的欄位中

同樣都是 DO_CHANGE_SPEED,

為什麼「QGC 自己產生的」跟「我程式產生的」在 QGC 裡面呈現方式完全不同?

這篇文章就是要把這件事的來龍去脈講清楚:

這不是誰錯,而是 MAVLink 規格+QGC 設計哲學加起來的結果。

基礎概念:MAVLink 任務裡,其實沒有「航點自己的速度欄位」

先把底層規格講白:

在 MAVLink 的任務模型裡,大致可以分兩種指令:

  1. NAV 系列航點指令
    • MAV_CMD_NAV_WAYPOINT
    • MAV_CMD_NAV_TAKEOFF
    • MAV_CMD_NAV_LAND
    • ……這些是「飛到某個位置」的指令。
  2. DO / 其他動作指令
    • MAV_CMD_DO_CHANGE_SPEED
    • MAV_CMD_DO_SET_MODE
    • MAV_CMD_DO_SET_SERVO
    • ……這些是「在某個時間點做某件事」的指令。

MAV_CMD_DO_CHANGE_SPEED 的語意其實很單純:

當飛機執行到這個 mission item 的時候,

從這一刻開始,把巡航速度改成 param2 指定的值,

之後的任務都用這個速度,直到下一次 DO_CHANGE_SPEED。

也就是說:

  • 在純 MAVLink 任務列表裡,「速度」其實是獨立的一條指令
  • 並不是「某一個 waypoint 項目自帶的欄位」。

你在 QGC 任務畫面上看到的「這個航點有 Speed 欄位」,

其實只是 QGC 幫你做了一層 UI 抽象,底下還是 DO_CHANGE_SPEED


QGC 的兩個世界:高階任務模型 vs. 低階 MAVLink 任務

要理解這個差異,要先知道 QGC 其實同時活在兩個世界:

  1. 高階世界:QGC 自己的任務模型
    • 例如 SimpleMissionItem + SpeedSection 等結構。
    • 在這個世界裡,一個「航點」可以掛很多「附加設定」,像是速度、相機觸發、ROI 等。
    • 使用者在畫面上看到的,是這個「高階模型」。
  2. 低階世界:飛控真正看的 MAVLink 任務列表
    • 就是一串 MissionItem / MissionItemInt。
    • 每一筆只有:command、frame、x/y/z、param1~4 這些欄位。
    • DO_CHANGE_SPEED 就是其中一個 command。

QGC 的作法是這樣:

  • 當你在 Plan 介面設定某個航點的 Flight Speed 時:
    1. 在高階模型裡,QGC 幫這個航點加上一個 SpeedSection。
    2. 當你按「上傳」時,它才把整個高階模型展開成一串 MAVLink mission items,並在適當的位置插入 DO_CHANGE_SPEED。
    3. 飛控只會看到一條條 mission item,完全不知道你原本 UI 長什麼樣子。
  • 當 QGC 再從飛控「下載任務」時:
    • 如果這份任務是它自己產的,而且結構「長得像它熟悉的 pattern」,它就會試著把這些 DO_CHANGE_SPEED 還原回高階的 SpeedSection,然後在 UI 上以「航點的速度欄位」來呈現。

關鍵在這裡——

QGC 懂得「還原自己生出來的 DO_CHANGE_SPEED」,

但它並沒有義務、也不一定有能力去還原「別人生的 mission」。


情境一:在 QGC 設速度 → 下載到自家程式看到 DO_CHANGE_SPEED

先看第一種情境:

  1. 你在 QGC 裡替航點 A 設定 Flight Speed。
  2. QGC 在高階模型裡,對航點 A 加了一個 SpeedSection。
  3. 當你按「上傳」到飛控時,QGC 實際發送的是:
    • NAV_WAYPOINT(航點 A)
    • 接著一條 DO_CHANGE_SPEED
  4. 你的程式呼叫 MISSION_REQUEST_LIST / MISSION_REQUEST_INT,把任務下載回來,看到的就是那條 MAV_CMD_DO_CHANGE_SPEED。

這是完全正常的——

因為飛控世界只認得低階的 MAVLink 任務,

高階的「航點有速度欄位」只是 QGC 的 UI 抽象。

同一份任務:

  • 在 QGC UI 裡:你看到「航點 + speed 欄位」,
  • 在你的程式裡:你看到「NAV_WAYPOINT + DO_CHANGE_SPEED 兩條任務項目」。

兩者沒有矛盾,是同一件事的兩種呈現方式


情境二:在自家程式產生 DO_CHANGE_SPEED → QGC 顯示成「獨立特殊航點」

第二種情境就反過來:

  1. 你在自己的地面站程式裡,比方說:只要某個航點的 param2 > 0,就自動插入一條 DO_CHANGE_SPEED。
  2. 把這份任務上傳到飛控。
  3. 開 QGC,按「從飛控下載任務」。

此時對 QGC 來說,它拿到的只是「一串陌生的 MAVLink mission items」:

  • 它只知道:
    • 這裡有一個 NAV_WAYPOINT,
    • 旁邊有一個 DO_CHANGE_SPEED,
    • 但這份任務不是它自己產的,
    • 它不知道你原本 UI 是怎麼設計的,也不知道這個 DO_CHANGE_SPEED 是不是你想掛在上一個點、下一個點,還是中途某段。

為了避免「亂猜」造成更大的問題(例如誤把 DO_CHANGE_SPEED 合併錯航點),

QGC 選擇一個保守作法:

✔️ 忠實反映低階資料:

DO_CHANGE_SPEED 就是一條獨立任務項目,照原樣顯示。

於是你看到:

  • QGC 任務清單裡多了一條「DO_CHANGE_SPEED」特殊航點。
  • 該航點前後的 NAV_WAYPOINT,Speed 欄位變成灰色、不可編輯。因為 QGC 已經不再使用自己的 SpeedSection 模型,而是尊重你「明確指定的一條 DO_CHANGE_SPEED 指令」。

為什麼 QGC 不「聰明一點」幫我把外來的 DO_CHANGE_SPEED 合併進上一個航點?

這個問題直覺上很合理,但實務上風險很大。

幾個簡單的例子:

  1. 你可能希望在兩個航點中間才改速,而不是掛在任何一個航點上。
  2. 有人可能寫了更複雜的任務,在一串 DO_* 指令之間穿插 DO_CHANGE_SPEED,這時候 QGC 根本猜不出來哪一條是要當作「航點的屬性」。
  3. 不同飛控、不同 GCS 對 mission 排列的習慣都不一定一樣。一旦 QGC 自己亂「合併」外來任務,有可能直接破壞任務邏輯。

所以 QGC 寧願:

  • 對於「自己產的」任務,它有足夠上下文,可以安全地還原成高階模型;
  • 對於「別人產的」任務,它就忠實地顯示底層 MAVLink 資料,把 DO_CHANGE_SPEED 當成獨立指令來看待。

用一句話概括:

QGC 信任自己產生的東西,但對外來 mission 選擇保守處理。


對 GCS / 地面站開發者的啟示

如果你像我一樣,也在做自己的地面站(Android、桌機、Web 都一樣),

這件事有幾個實務上的啟示:

  1. 在 UI 上,你可以學 QGC 的做法,對使用者更友善
    • 你可以自己定義一個規則,例如:
      • 若發現某個 WAYPOINT 後面緊接著一條 DO_CHANGE_SPEED,
      • 就在 UI 上把這個速度顯示在該航點的「Speed 欄位」,
      • 同時在列表中是否要顯示這條 DO_CHANGE_SPEED,由你自己決定。
    • 這樣你的 UI 一樣直覺,但底層仍然是標準的 MAVLink 任務。
  2. 在資料模型上,要把 DO_CHANGE_SPEED 視為「一級公民」
    • 不要期待所有 GCS 都會自動把它掛在某個 waypoint 身上。
    • 要習慣:「速度更改」就是一條獨立的 mission item,而不是某個欄位。
  3. 不要期望「任務可逆」:UI → MAVLink → 其他 GCS → 回來還原成一模一樣的 UI
    • 尤其是不同 GCS 之間來回時,高階模型的資訊很容易遺失。
    • 你能保證的是:飛控看得懂、飛得正確,但不能保證每一個 GCS 都會用完全一樣的 UI 概念呈現。

結語:怪現象,其實是設計上的取捨

故事到這裡,整件事情就變得不神秘了:

  • 在 MAVLink 規格裡,速度是用 MAV_CMD_DO_CHANGE_SPEED 這種獨立指令來表達。
  • 在 QGC 的 UI 裡,它幫你做了一層包裝,把 DO_CHANGE_SPEED 抽象成「航點的速度設定」。
  • QGC 自己產的 DO_CHANGE_SPEED
    • 它有完整上下文,可以在下載時還原成 SpeedSection,
    • 所以你在 QGC 裡看不到那條指令,只看到航點 Speed。
  • 你程式產的 DO_CHANGE_SPEED
    • 對 QGC 來說是「外來任務」,
    • 它無法安全判斷這條指令要掛在哪個航點,
    • 於是選擇忠實顯示成「特殊航點」,並停用航點上的 Speed 欄位。

從開發者的角度來看,

這其實是一個很好的提醒:

當我們在做自己的地面站時,

一定要同時理解「協議層(MAVLink)」跟「工具層(QGC 等 GCS 的設計)」的差異,

才不會被 UI 表象誤導,以為那就是「真實結構」。

如果你也遇到類似的疑惑,希望這篇文章有幫助,

也歡迎你在自己的專案裡,實作一套適合你任務規劃邏輯的 speed 顯示方式,

讓使用者用起來直覺、飛控看起來正確,兩邊都兼顧。

You may also like...

發表迴響