為什麼 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 的任務模型裡,大致可以分兩種指令:
- NAV 系列航點指令
- MAV_CMD_NAV_WAYPOINT
- MAV_CMD_NAV_TAKEOFF
- MAV_CMD_NAV_LAND
- ……這些是「飛到某個位置」的指令。
- 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 其實同時活在兩個世界:
- 高階世界:QGC 自己的任務模型
- 例如 SimpleMissionItem + SpeedSection 等結構。
- 在這個世界裡,一個「航點」可以掛很多「附加設定」,像是速度、相機觸發、ROI 等。
- 使用者在畫面上看到的,是這個「高階模型」。
- 低階世界:飛控真正看的 MAVLink 任務列表
- 就是一串 MissionItem / MissionItemInt。
- 每一筆只有:command、frame、x/y/z、param1~4 這些欄位。
- DO_CHANGE_SPEED 就是其中一個 command。
QGC 的作法是這樣:
- 當你在 Plan 介面設定某個航點的 Flight Speed 時:
- 在高階模型裡,QGC 幫這個航點加上一個 SpeedSection。
- 當你按「上傳」時,它才把整個高階模型展開成一串 MAVLink mission items,並在適當的位置插入 DO_CHANGE_SPEED。
- 飛控只會看到一條條 mission item,完全不知道你原本 UI 長什麼樣子。
- 當 QGC 再從飛控「下載任務」時:
- 如果這份任務是它自己產的,而且結構「長得像它熟悉的 pattern」,它就會試著把這些 DO_CHANGE_SPEED 還原回高階的 SpeedSection,然後在 UI 上以「航點的速度欄位」來呈現。
關鍵在這裡——
QGC 懂得「還原自己生出來的 DO_CHANGE_SPEED」,
但它並沒有義務、也不一定有能力去還原「別人生的 mission」。
情境一:在 QGC 設速度 → 下載到自家程式看到 DO_CHANGE_SPEED
先看第一種情境:
- 你在 QGC 裡替航點 A 設定 Flight Speed。
- QGC 在高階模型裡,對航點 A 加了一個 SpeedSection。
- 當你按「上傳」到飛控時,QGC 實際發送的是:
- NAV_WAYPOINT(航點 A)
- 接著一條 DO_CHANGE_SPEED
- 你的程式呼叫 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 顯示成「獨立特殊航點」
第二種情境就反過來:
- 你在自己的地面站程式裡,比方說:只要某個航點的 param2 > 0,就自動插入一條 DO_CHANGE_SPEED。
- 把這份任務上傳到飛控。
- 開 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 合併進上一個航點?
這個問題直覺上很合理,但實務上風險很大。
幾個簡單的例子:
- 你可能希望在兩個航點中間才改速,而不是掛在任何一個航點上。
- 有人可能寫了更複雜的任務,在一串 DO_* 指令之間穿插 DO_CHANGE_SPEED,這時候 QGC 根本猜不出來哪一條是要當作「航點的屬性」。
- 不同飛控、不同 GCS 對 mission 排列的習慣都不一定一樣。一旦 QGC 自己亂「合併」外來任務,有可能直接破壞任務邏輯。
所以 QGC 寧願:
- 對於「自己產的」任務,它有足夠上下文,可以安全地還原成高階模型;
- 對於「別人產的」任務,它就忠實地顯示底層 MAVLink 資料,把 DO_CHANGE_SPEED 當成獨立指令來看待。
用一句話概括:
QGC 信任自己產生的東西,但對外來 mission 選擇保守處理。
對 GCS / 地面站開發者的啟示
如果你像我一樣,也在做自己的地面站(Android、桌機、Web 都一樣),
這件事有幾個實務上的啟示:
- 在 UI 上,你可以學 QGC 的做法,對使用者更友善
- 你可以自己定義一個規則,例如:
- 若發現某個 WAYPOINT 後面緊接著一條 DO_CHANGE_SPEED,
- 就在 UI 上把這個速度顯示在該航點的「Speed 欄位」,
- 同時在列表中是否要顯示這條 DO_CHANGE_SPEED,由你自己決定。
- 這樣你的 UI 一樣直覺,但底層仍然是標準的 MAVLink 任務。
- 你可以自己定義一個規則,例如:
- 在資料模型上,要把 DO_CHANGE_SPEED 視為「一級公民」
- 不要期待所有 GCS 都會自動把它掛在某個 waypoint 身上。
- 要習慣:「速度更改」就是一條獨立的 mission item,而不是某個欄位。
- 不要期望「任務可逆」: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 顯示方式,
讓使用者用起來直覺、飛控看起來正確,兩邊都兼顧。