Learning Objectives
- 讀懂 Twist、LaserScan、Odometry 這些常用訊息的欄位
- 說出發布者與訂閱者型別不合、QoS 不相容時會發生什麼
- 依官方相容規則判斷一組 QoS 能不能連上
- 說明晚加入的訂閱者為什麼收不到舊訊息,以及 TRANSIENT_LOCAL 的用途
- 說明感測器資料為什麼寧可丟掉也不要舊的
節點都起來了,topic 名稱也對,還是收不到
第 3 課看過名稱打錯會收不到。這一課是另外兩個更難發現的原因:訊息型別不合,以及 QoS 不相容。兩者的共同點是程式不會當掉,機器人只是安靜地不動。
先看 ROS 2 的訊息長什麼樣子。訊息的型別寫在定義檔裡,欄位可以再包別的訊息。官方的 common_interfaces 定義了最常用的幾個:
| 訊息 | 重要欄位 | 用途 |
|---|---|---|
geometry_msgs/msg/Twist |
linear、angular(各是 x、y、z) |
速度指令。差速機器人只用 linear.x 和 angular.z |
sensor_msgs/msg/LaserScan |
header、angle_min、angle_increment、range_min、range_max、ranges、intensities |
光達一圈的距離 |
nav_msgs/msg/Odometry |
header、child_frame_id、pose、twist |
里程計:位置、朝向與速度 |
header 裡有時間戳記(stamp)和座標系名稱(frame_id)。感測器資料都帶著它,這樣才知道資料是什麼時候、相對於哪個座標系量到的。
真實環境可以用 ros2 interface show geometry_msgs/msg/Twist 看定義。
訊息是有型別的物件
看怎麼建立訊息、填巢狀欄位,以及填錯時會怎樣。注意:這裡的錯誤訊息來自模擬環境。真實的 ROS 2 也會拒絕型別不對的訊息,但錯誤出現的時機與文字以實際執行為準。
QoS:對傳輸品質的要求
QoS(Quality of Service)是每個發布者與訂閱者各自設定的傳輸規則。最常用的三個:
| 政策 | 選項 | 意思 |
|---|---|---|
| Reliability(可靠性) | RELIABLE |
保證送達,可能重送 |
BEST_EFFORT |
盡量送,掉了就算了 | |
| Durability(持久性) | VOLATILE |
不保留舊訊息 |
TRANSIENT_LOCAL |
發布者保留訊息,給晚加入的訂閱者 | |
| History(歷史) | KEEP_LAST 加 depth |
只留最新的 depth 筆 |
KEEP_ALL |
全部留著(受資源限制) |
create_publisher(String, 'topic', 10) 裡的 10 就是 KEEP_LAST、depth=10,其他維持預設:RELIABLE、VOLATILE。
相容規則:發布者「提供」、訂閱者「要求」,要求的品質不能比提供的高,否則不連線。
| 發布者 | 訂閱者 | 結果 |
|---|---|---|
| BEST_EFFORT | BEST_EFFORT | 可以 |
| BEST_EFFORT | RELIABLE | 不連線 |
| RELIABLE | BEST_EFFORT | 可以 |
| RELIABLE | RELIABLE | 可以 |
| 發布者 | 訂閱者 | 結果 |
|---|---|---|
| VOLATILE | VOLATILE | 可以,只收新訊息 |
| VOLATILE | TRANSIENT_LOCAL | 不連線 |
| TRANSIENT_LOCAL | VOLATILE | 可以,只收新訊息 |
| TRANSIENT_LOCAL | TRANSIENT_LOCAL | 可以,新舊訊息都收得到 |
(兩張表依 ROS 2 官方文件 About Quality of Service settings,CC BY 4.0,翻譯並有修改。)
真實環境可以用 ros2 topic info /scan --verbose 看每個發布者與訂閱者的 QoS。不確定對方怎麼發布時,訂閱者用 BEST_EFFORT 幾乎都連得上,所以訂閱感測器資料常用 qos_profile_sensor_data。
感測器資料寧可丟掉也不要舊的
官方的感測器資料 QoS(qos_profile_sensor_data)用 BEST_EFFORT 加較小的佇列,理由是:感測器資料要的是及時,比每一筆都送到重要。
想想機器人一邊走一邊避障:0.2 秒前的距離還有用,3 秒前的距離是危險的,因為程式會以為牆還很遠。如果每一筆都要保留、都要處理,一旦處理速度跟不上,佇列就越積越長,手上的資料也越來越舊。
depth 決定佇列滿了之前能積多少。大佇列不會讓你漏資料,但可能讓你越來越落後;小佇列會丟資料,但手上的永遠是比較新的。沒有哪一個永遠對:
| 資料 | 比較在意什麼 | 常見設定 |
|---|---|---|
| 光達、相機、IMU | 要新 | BEST_EFFORT、小 depth |
| 速度指令 | 要新,而且不能掉太多 | RELIABLE、小 depth |
| 地圖、感測器安裝位置 | 晚到也要拿得到 | RELIABLE、TRANSIENT_LOCAL |
| 記錄研究或除錯資料 | 每筆都要 | RELIABLE、大 depth,並確認存得夠快 |
下面用訊息上的時間戳記,實際量資料有多舊。
確認一下
發布者 RELIABLE+VOLATILE,訂閱者 BEST_EFFORT+TRANSIENT_LOCAL。連得上嗎?
你的節點訂閱 /scan 時直接寫數字 10,光達用 BEST_EFFORT 發布。會發生什麼?
節點 A 發布 Float64 到 /speed,節點 B 用 Float32 訂閱 /speed。下面哪個最接近實際情況?
避障節點處理得比光達慢。把訂閱的 depth 從 5 改成 500,機器人會變得比較安全嗎?
重點整理
- 訊息有固定的欄位與型別;
header帶著時間戳記與座標系。 - 訂閱了收不到,除了名稱,還要查型別與 QoS。這兩種情況程式都不會當掉。
- QoS 相容規則:訂閱者要求的不能高於發布者提供的。
RELIABLE訂閱BEST_EFFORT、TRANSIENT_LOCAL訂閱VOLATILE都連不上。 - 晚加入的訂閱者收不到舊訊息,除非兩邊都用
TRANSIENT_LOCAL;/tf_static就是這樣。 - 處理跟不上時,大佇列讓資料越來越舊,小佇列讓資料被丟掉。感測器資料通常選新,紀錄資料通常選完整。
下一課看另一種溝通方式:問一句答一句的 service,以及不改程式就能調整的參數。