跳到正文

Wiki

回溯rosbag2

約 24 分鐘閱讀

本文由簡體中文內容確定性轉換,並受版本化術語表保護。

1.概述

方式1一邊採集數據,一邊生成地圖信息。

方式2是將採集數據和生成地圖信息分割開來了,方式2做到了解耦合,所以更靈活一些。用同一套數據,可能用不同的算法處理,總之,非常靈活。

留存的過程咱們也可以叫做序列化。(轉化為磁盤文件)

留存一般叫錄製(序列化)。

讀取一般叫回放(反序列化)。

留存時也可以將文件進行分卷,也就是每個文件最大能佔多大的大小,如果超過該大小,就新建一個文件繼續留存。(類似於壓縮文件的分卷)

這樣的話,存的數據太大,我們一次性打開太慢,就可以分段打開。

需要依賴於rosbag2_cpp或者rosbag2_py

然後還要依賴於geometry_msgs,這個是因為我們要序列化的數據是這個包下的速度指令。

ros2 pkg create cpp02_rosbag --build-type ament_cmake --dependencies rclcpp rosbag2_cpp geometry_msgs --node-name cpp01_writer

2.rosbag2的命令工具

一般ROSBAG2的使用有命令行工具和編碼兩種使用方式,命令行工具功能比較齊全,夠用。

查看幫助文檔ros2 bag -h

主要有6個指令。

可以看record的詳細用法。

record是用來將消息序列化的,但實際上比這個複雜。(錄製)

play是用來反序列化消息的,但實際上比這個複雜。(回放)

ROS 2 Message
    ↓ serialization
serialized bytes
    ↓ rosbag2 + timestamp + topic metadata + storage plugin
bag

序列化只是錄製過程中的一個環節。

record 實際上包含:

发现 Topic
    ↓
建立 Subscription
    ↓
接收序列化消息
    ↓
记录时间戳
    ↓
交给 Storage Plugin
    ↓
写入磁盘
读取 bag
    ↓
按照记录时间调度消息
    ↓
创建 Publisher
    ↓
按照 QoS 发布
    ↓
ROS 2 其他节点重新收到消息

這一點非常重要,因為後面所有 QoS 問題,本質上就是 Recorder 是訂閲者、Player 是發佈者。

info是用來輸出bag文件的相關信息的。比如有多少條消息,錄製起始時間和終止時間以及持續時間。

reindex是重建bag文件,可以根據現有 bag 數據重新構建 metadata。 例如異常退出導致 metadata.yaml 缺失或損壞時,它很有價值。

list是輸出rosbag2中可用的插件(高階應用)。

convert我們可以用這個給bag重新讀取再寫出一個新的 bag,也可以把多個bag文件合併成一個文件。 可以用來:

  • 合併 bag
  • 拆分 bag
  • 重新選擇話題
  • 改 storage
  • 壓縮/解壓
  • 修改序列化格式

打開小烏龜節點與鍵盤控制小烏龜節點。

我們用鍵盤控制小烏龜,然後把速度指令通過rosbag2給序列化。

然後我們關掉兩個節點,再重啓小烏龜節點,然後這次不通過鍵盤控制,而是通過play bag文件讓小烏龜運動。

record指令後面要跟一個話題組成的列表,但是在咱們下面的操作中,只用到了一個話題。

然後再用output,把序列化後的文件寫出到一個磁盤目錄中去。

cd進想保存bag的目錄,然輸入record指令,後面跟話題名稱,然後-o +bag文件名,這裏也可以不重新命名bag文件,這樣會用默認的名字,默認的名稱是年月日命名的。

這樣就已經開始錄製了。

按Ctrl + C進行結束,結束有個提示,説正在將消息寫入bag,需要一段時間。

my_bag/
├── metadata.yaml
└── my_bag_0.db3

其中:

metadata.yaml
    → bag 的描述信息
    → 录了哪些 topic
    → topic 类型
    → 消息数量
    → duration
    → storage id
    → bag 分片信息
    ...

*.db3
    → SQLite3 storage plugin 真正保存的消息数据

db3是SQLite數據庫,這個是移動端(比如手機)常用的數據庫,但其他平台其實也可以用。 所以 .db3 才是主要的數據載體。

這個數據庫就存儲了錄製的數據。

Humble 默認用 SQLite3 storage plugin 保存 bag。

而且存儲插件是可替換的。你本機究竟有哪些插件,不應該靠猜,應該直接:

ros2 bag list storage

Humble 的 CLI 會根據系統實際註冊的 writer 插件決定 -s/--storage 可以選擇什麼。ROS 2 Humble rosbag2 README

3.rosbag2中的QoS

前面已經知道,rosbag2 在錄製和回放時並不是單純地“讀寫文件”:

录制:Publisher → rosbag2 Recorder(Subscriber)

回放:rosbag2 Player(Publisher)→ Subscriber

因此,只要 rosbag2 參與 ROS 2 通信,就同樣要遵守 ROS 2 的 QoS(服務質量,Quality of Service)規則。

ROS 2 的 QoS 策略很多,例如 History(歷史策略)、Depth(隊列深度)、Reliability(可靠性)、Durability(持久性)、Deadline(截止期限)、Liveliness(活躍性) 等。

對於 rosbag2 的錄製和回放,首先最需要理解的是:

  • Reliability(可靠性):Reliable(可靠) 和 Best Effort(盡力而為)
  • Durability(持久性):Transient Local(本地瞬態) 和 Volatile(易失性)

官方文檔:

3.1.Request / Offered 模型

ROS 2 中,Publisher 和 Subscriber 的 QoS 並不是“必須完全相同”才能通信。

它採用的是 Request / Offered 模型:

Publisher
    ↓
Offered QoS(提供的服务质量)

Subscriber
    ↓
Requested QoS(请求的服务质量)

只有 Publisher 提供的 QoS 能滿足 Subscriber 請求的 QoS,兩者才能正常通信。

所以不能簡單理解成:

Publisher QoS == Subscriber QoS

而應該理解成:

Publisher 提供的能力
        ↓
能不能满足 Subscriber 的要求?

這個規則對普通 ROS 2 節點成立,對 rosbag2 也一樣成立。

錄製時:

原节点 Publisher
        ↓
rosbag2 Recorder Subscriber

回放時:

rosbag2 Player Publisher
        ↓
算法节点 Subscriber

3.2.Reliability(可靠性)

Reliability 決定消息傳輸過程中,對“消息是否必須儘量送達”的要求。

Reliable(可靠) 可以理解為:

尽量保证消息送达
如果底层发现消息丢失,可以进行重传等处理

Best Effort(盡力而為) 可以理解為:

尽最大努力发送
如果某条消息丢失,不要求一定重传
更加关注最新的数据

很多高頻傳感器數據使用的 Sensor Data QoS Profile 會採用 Best Effort。對於這類數據,相比於等待舊數據重傳,系統通常更關心最新的傳感器數據。

Reliability 的兼容關係如下:

Publisher Offered Subscriber Requested 是否兼容
Best Effort Best Effort ✅
Best Effort Reliable ❌
Reliable Best Effort ✅
Reliable Reliable ✅

最容易混淆的是下面兩種情況:

Best Effort Publisher
        ↓
Reliable Subscriber

❌ 不兼容

原因是 Subscriber 要求“可靠傳輸”,但 Publisher 只提供 Best Effort,無法滿足 Subscriber 的要求。

反過來:

Reliable Publisher
        ↓
Best Effort Subscriber

✅ 兼容

因為 Publisher 提供的能力已經能夠滿足 Subscriber 較低的要求。

因此,Reliable 並不是簡單意義上的“比 Best Effort 更高級,所以任何時候都應該選 Reliable”。

QoS 的核心是:雙方是否兼容,以及這種通信策略是否適合當前數據。

3.3.Durability(持久性)

Durability 主要決定:一個 Subscriber 後加入通信時,能不能收到 Publisher 之前已經發布過的數據。

Volatile(易失性) 可以理解為:

只接收建立通信之后的新消息
不保留之前已经发布的历史消息

例如:

Publisher:A  B  C

                Subscriber 在这里启动

Publisher:          D  E

如果 Subscriber 使用 Volatile,它通常只關心:

D  E

而不會要求得到之前的:

A  B  C

Transient Local(本地瞬態) 則允許 Publisher 為後加入的 Subscriber 保留一定的歷史樣本。

可以把它理解成:

Publisher
   │
   ├── 发布消息
   │
   └── 在本地保留历史样本
             ↓
       Late Joiner Subscriber

它的行為和 ROS 1 中 latched publisher 的概念有一定相似之處。

Durability 的兼容關係如下:

Publisher Offered Subscriber Requested 是否兼容
Volatile Volatile ✅
Volatile Transient Local ❌
Transient Local Volatile ✅
Transient Local Transient Local ✅

其中:

Volatile Publisher
        ↓
Transient Local Subscriber

❌ 不兼容

因為 Subscriber 要求能夠獲得 Transient Local 的歷史數據能力,但 Publisher 並沒有提供這種能力。

而:

Transient Local Publisher
        ↓
Volatile Subscriber

✅ 可以通信

但 Volatile Subscriber 並不會因為 Publisher 是 Transient Local,就自動要求得到建立通信之前的歷史數據。

3.4./tf_static為什麼特殊

/tf_static 是理解 Durability 很典型的例子。

靜態 TF 的特點是:

变换关系基本不会变化
        ↓
没有必要像动态 /tf 一样持续高频发布

例如機械人中的某個固定傳感器變換:

base_link
    ↓
laser_link

這個靜態關係可能在系統啓動時就已經發布。

假設:

10:00:00
Static TF Publisher 发布 /tf_static

10:00:20
rosbag2 Recorder 才启动

如果 Recorder 只能接收啓動之後的新數據,而 /tf_static 後面又沒有重新發布,那麼這個 bag 就可能缺少關鍵的靜態座標變換。

因此 /tf_static 使用 Transient Local Durability,使後加入的 Subscriber 也有機會獲得之前保留的靜態 TF 數據。

這也是為什麼錄製 SLAM、定位和導航數據時,不能只確認“bag 裏面有沒有 /tf”,還需要關注 /tf_static 是否被正確錄製。

3.5.rosbag2會自動適配QoS

理解 QoS 以後,也不要走到另一個極端:

是不是录每一个 Topic 都必须手动写 QoS?

通常不是。

ROS 2 Humble 的 rosbag2 在錄製時會根據發現到的 Publisher 嘗試調整 Recorder 的 Requested QoS;在回放時,也會嘗試保留原 Topic 原先 Offered 的 QoS。

因此大多數普通 Topic 不需要手動指定 QoS。

但是在一些需要明確控制行為的場景中,例如:

  • 關鍵傳感器 Topic
  • /tf_static
  • 同一個 Topic 存在多個 Publisher,並且 QoS 不完全一致
  • 默認自動適配無法滿足實際錄製或回放需求
  • 希望實驗中的 QoS 行為固定、可復現

就可以使用 rosbag2 的 QoS Override。

3.6.查看Topic實際使用的QoS

不要只根據源碼、Topic 名字或者經驗猜測實際 QoS。

系統真正運行起來後,可以使用:

ros2 topic info -v /topic_name

查看該 Topic 當前 Publisher 和 Subscriber 實際使用的 QoS。

例如重點觀察:

Reliability
Durability
History
Depth

尤其在實車中,最終的 rosbag2 配置應該以實際運行時的 Topic 和 QoS為準。

3.7.rosbag2 QoS Override

rosbag2 可以通過 YAML 文件為特定 Topic 覆蓋 QoS。

例如:

/tf_static:
  reliability: reliable
  durability: transient_local

如果某個傳感器 Topic 實際使用 Best Effort,也可以按實際情況配置:

/sensor_points:
  reliability: best_effort
  durability: volatile

這裏的 /sensor_points 只是示例。實際工程中應當先運行系統,再通過 ros2 topic info -v 確認真實 Topic 和 QoS。

錄製時使用:

ros2 bag record \
  --qos-profile-overrides-path qos_overrides.yaml \
  /topic1 /topic2

此時 QoS Override 影響的是:

rosbag2 Recorder
        ↓
Subscription QoS

回放時使用:

ros2 bag play \
  --qos-profile-overrides-path qos_overrides.yaml \
  my_bag

此時 QoS Override 影響的是:

rosbag2 Player
        ↓
Publisher QoS

所以同一個參數在 record 和 play 中的作用對象並不相同。

最終可以把這一節的關係整理成:

录制

原 Publisher
    │ Offered QoS
    ↓
rosbag2 Recorder
    Requested QoS


回放

rosbag2 Player
    │ Offered QoS
    ↓
算法 Subscriber
    Requested QoS

理解這個模型以後,後面處理傳感器數據、/tf_static 以及 rosbag2 的 QoS Override,就不再只是記配置參數,而是在判斷:

Publisher 提供的 QoS,能不能滿足 Subscriber 請求的 QoS。

4.rosbag2的話題選擇

實際使用 rosbag2 時,首先要決定的就是:到底錄哪些 Topic。

最直接的方式是顯式指定 Topic:

ros2 bag record /topic1 /topic2 /topic3

這種方式最明確,也最適合已經知道自己需要哪些數據的場景。

如果希望錄製當前系統中的全部 Topic,可以使用:

ros2 bag record -a

其中:

-a
--all

表示錄製所有能夠發現的 Topic。

默認情況下,rosbag2 在錄製過程中還會繼續進行 Topic Discovery。也就是説,即使某個 Topic 在啓動 rosbag2 時還不存在,只要後面出現,Recorder 仍然可以發現它並開始錄製。

如果不希望錄製過程中繼續發現新 Topic,可以使用:

ros2 bag record -a --no-discovery

此時只會考慮 Recorder 啓動時已經存在的 Topic。

需要注意,Humble 中 --use-sim-time 和 --no-discovery 不能同時使用,因為使用仿真時間時 Recorder 需要發現並訂閲 /clock。

4.1.使用正則表達式選擇Topic

除了顯式列出 Topic 和使用 -a 以外,還可以使用:

-e
--regex

例如:

ros2 bag record -e ".*camera.*"

它適合 Topic 數量比較多,並且命名有明顯規律的系統。

4.2.排除不需要的Topic

如果希望先選擇一大批 Topic,再排除其中一部分,可以使用:

-x
--exclude

例如:

ros2 bag record -a -x ".*debug.*"

可以理解成:

先选择一批 Topic
        ↓
再排除匹配 exclude 的 Topic

Humble 中,--exclude 需要和 --all 或 --regex 一起使用。顯式列出 Topic 時不能同時使用 --exclude。

4.3.Hidden Topic和Unpublished Topic

--include-hidden-topics 用於把隱藏 Topic 也納入發現和錄製範圍。隱藏 Topic 通常用於 ROS 2 內部或框架功能,一般不需要默認錄製。

--include-unpublished-topics 則允許發現併為當前沒有 Publisher 的 Topic 建立 Subscription。對於這種 Topic,如果沒有額外提供 QoS Override,rosbag2 會使用默認 QoS 建立 Subscription。

4.4.Topic選擇的基本原則

可以簡單理解為:

临时调试 / 初步摸清系统
        ↓
-a 比较方便

长期录制 / 固定实验
        ↓
显式 Topic 或正则筛选更可控

-a 並不一定是錯誤的,但它可能把大量無關、高頻或者調試 Topic 一起錄下來。

Topic 越多,通常意味着:

DDS 接收量增加
        ↓
内存缓存压力增加
        ↓
磁盘写入量增加
        ↓
bag 体积增加

因此在正式實驗中,更重要的不是“錄得越多越好”,而是保證真正需要的數據被完整記錄。


5.rosbag2中的時間

rosbag2 不只是保存消息內容,還必須為消息記錄時間。時間決定了回放時消息之間的先後關係和間隔。

在普通系統中,Recorder 通常使用正常的 ROS/System 時間。

但是在 Gazebo 等仿真環境裏,經常會使用 ROS Time,並由 /clock 提供仿真時間。

5.1.錄製時使用仿真時間

Humble 中可以使用:

ros2 bag record --use-sim-time -a

開啓以後,Recorder 會訂閲 /clock,並使用最新收到的仿真時間作為寫入 bag 的時間戳。

這裏有一個非常重要的行為:

在收到第一條 /clock 之前,rosbag2 不會把消息寫入 bag。

這是為了避免前面的消息以時間 0 寫入,隨後時間突然跳到正常仿真時間,導致 bag 的時間軸出現很大的跳變。

可以理解成:

启动 Recorder
      ↓
等待 /clock
      ↓
收到第一个有效仿真时间
      ↓
开始正常写 bag

所以:

实机正常系统
    ↓
通常不需要 --use-sim-time

使用 /clock 的仿真系统
    ↓
需要考虑 --use-sim-time

5.2.回放時發佈/clock

回放 bag 時,可以讓 Player 發佈 /clock:

ros2 bag play my_bag --clock

Humble 中,--clock 可以指定發佈頻率:

ros2 bag play my_bag --clock 40

如果只寫 --clock,Humble 默認使用 40 Hz。

這對於設置了:

use_sim_time = true

的節點很重要,因為這些節點需要從 /clock 獲取 ROS Time。

5.3.bag時間和消息Header不是一回事

很多 ROS 2 消息本身帶有:

header.stamp

但 rosbag2 自己也會記錄用於保存和調度消息的 bag 時間。

所以不能簡單把:

消息内部的 header.stamp

和:

rosbag2 的记录/回放时间

認為是同一個概念。


6.rosbag2的分卷

如果長時間錄製數據,一個 bag 內部的數據文件可能越來越大。

rosbag2 支持按照文件大小或者錄製時間自動分卷。

6.1.按大小分卷

Humble 使用:

-b
--max-bag-size

單位是 byte。

例如:

ros2 bag record -a -b 1000000000

噹噹前 bag 數據文件達到設定大小後,會切換到新的分片繼續寫入。參數為 0 時關閉按大小分卷。

6.2.按時間分卷

Humble 使用:

-d
--max-bag-duration

單位是秒。

例如:

ros2 bag record -a -d 900

表示大約每錄製 900 秒切換到新的分片。參數為 0 時關閉按時間分卷。

6.3.同時設置大小和時間

兩個條件可以同時啓用:

ros2 bag record -a \
  --max-bag-size 1000000000 \
  --max-bag-duration 900

此時:

达到大小上限
      或
达到时间上限
      ↓
哪个先发生,就先触发分卷

一個 bag 目錄中可能出現:

my_bag/
├── metadata.yaml
├── my_bag_0.db3
├── my_bag_1.db3
├── my_bag_2.db3
└── ...

這些文件仍然屬於同一個邏輯上的 bag。

分卷除了控制單文件大小,也方便長時間錄製、文件傳輸、數據歸檔和異常情況下的恢復。官方 rosbag2 文檔也建議在使用壓縮時考慮配合分卷。


7.rosbag2的壓縮

傳感器數據、圖像、點雲等 Topic 可能很快產生大量數據。

rosbag2 可以在錄製過程中進行壓縮,主要通過:

--compression-mode
--compression-format

配置。

7.1.Compression Mode

Humble 支持:

none
file
message

其中:

  • none:不啓用壓縮;
  • file:對 bag 數據文件進行壓縮;
  • message:按消息進行壓縮。

7.2.Compression Format

Humble 官方 rosbag2 提供的壓縮格式是:

zstd

例如:

ros2 bag record -a \
  --compression-mode file \
  --compression-format zstd

如果指定了 --compression-format,卻沒有設置有效的 --compression-mode,Humble 會認為參數組合無效。

7.3.壓縮不是沒有代價

壓縮本質上是在:

磁盘占用
    ↕
CPU计算量

之間進行權衡。

大致可以理解成:

不压缩
  ↓
CPU 开销较低
磁盘写入量和文件体积较大

启用压缩
  ↓
文件体积可能下降
但需要额外 CPU 进行压缩

因此不能只因為“文件更小”就默認打開壓縮,還應該觀察 Recorder 是否掉消息、CPU 是否過載、磁盤是否成為瓶頸,以及最終 bag 是否能夠穩定回放。

Humble 還提供:

--compression-queue-size
--compression-threads

用於控制壓縮隊列和並行線程。這類參數通常應該在確實遇到性能問題以後再調整。


8.rosbag2的存儲配置

前面已經知道,rosbag2 的底層數據保存由 Storage Plugin 完成。

可以查看當前系統安裝的 Storage Plugin:

ros2 bag list storage

錄製時可以通過:

-s
--storage

選擇 storage id,例如:

ros2 bag record -a -s sqlite3

如果不指定,rosbag2 會使用當前系統的默認 writer。

8.1.storage配置文件

Humble 支持:

--storage-config-file

傳入 Storage Plugin 自己的配置。

對於 SQLite3 Storage Plugin,配置結構可以包含:

read:
  pragmas: []

write:
  pragmas: []

這裏的 pragma 是 SQLite 自己的數據庫配置,例如可能涉及 journal mode、synchronous、cache size、page size 等。

這些參數會影響:

写入性能
内存占用
崩溃时的数据安全性

因此不應該在不瞭解 SQLite 行為時隨意修改。

8.2.性能和數據安全之間的權衡

rosbag2 默認 SQLite 設置偏向錄製性能。

這樣有利於高吞吐寫入,但如果 Recorder 或整個系統異常崩潰,當前正在寫入的數據文件可能存在損壞風險。

如果已經啓用分卷,風險通常主要集中在當前正在寫入的分片,而不是已經正常關閉的舊分片。

Humble 還提供:

--storage-preset-profile

官方文檔提到可以使用:

resilient

提高對異常崩潰造成損壞的抵抗能力。

但更高的數據安全性也可能帶來一定的性能代價,所以 Storage 配置同樣是在平衡:

写入性能
    ↕
异常情况下的数据完整性

9.rosbag2的回放控制

最基本的回放命令是:

ros2 bag play my_bag

除此以外,Humble 的 Player 還提供很多控制選項。

9.1.調整回放速度

使用:

-r
--rate

例如:

ros2 bag play my_bag -r 0.5

表示按照原始時間關係的一半速度回放。

ros2 bag play my_bag -r 2.0

表示按照兩倍速度回放。rate 必須大於 0。

9.2.只回放部分Topic

使用:

--topics

例如:

ros2 bag play my_bag \
  --topics /topic1 /topic2

如果不指定 --topics,默認回放 bag 中的全部 Topic。

9.3.Topic Remap

Player 支持:

-m
--remap

例如:

ros2 bag play my_bag \
  --remap /old_topic:=/new_topic

這樣可以在不修改 bag 本身的情況下,把回放出來的 Topic 映射到新的名稱。

9.4.延遲、起始位置與暫停

--delay 可以讓 Player 啓動後等待一段時間再開始回放:

ros2 bag play my_bag --delay 5

--start-offset 可以從 bag 中間某個時間位置開始:

ros2 bag play my_bag --start-offset 30

-p / --start-paused 可以讓 Player 啓動後先處於暫停狀態。

9.5.循環播放

使用:

-l
--loop

例如:

ros2 bag play my_bag --loop

到達 bag 結尾後,會重新從頭開始播放。

9.6.回放緩存

Humble 提供:

--read-ahead-queue-size

控制 Player 預讀到內存中的消息隊列大小。

隊列更大可能減少因為磁盤讀取不足造成的播放延遲,但也會增加內存佔用。默認情況下通常不需要修改。

9.7.等待Reliable消息確認

Humble 還提供:

--wait-for-all-acked

用於在回放結束前等待 Reliable Publisher 發出的消息被 Subscriber 確認。

這個參數只對 Reliable QoS 有意義,適合某些短時間內快速發送大消息的場景。

9.8.通過Service控制Player

rosbag2 Player 還提供一組 ROS 2 Service,可以在運行期間控制播放狀態,例如:

pause
resume
toggle_paused
play_next
burst
seek
get_rate
set_rate
is_paused
play

因此 Player 並不只是一個“從頭到尾自動播放完”的命令,它也可以被其他程序動態控制。

總體上可以把回放過程理解成:

bag
 ↓
rosbag2 Player
 ├── 选择 Topic
 ├── 控制播放速度
 ├── 控制起始位置
 ├── Pause / Resume
 ├── Remap Topic
 ├── 发布 /clock
 └── 按 QoS 发布消息
           ↓
      ROS 2 节点

到這裏,rosbag2 在實際使用中最常見的錄製、回放和配置能力已經基本覆蓋。

對於一般工程,優先複用官方提供的 ros2 bag record、ros2 bag play 等工具,再通過參數、配置文件和 Launch 進行組織即可,沒有必要為了錄包功能重新實現一套 Recorder 或 Player。

下面的 C++ Writer / Reader 示例主要用於理解 rosbag2_cpp 的程序接口和基本工作原理。

10.rosbag2 C++案例分析及框架搭建

add_executable(demo01_writer src/demo01_writer.cpp)
ament_target_dependencies(
  demo01_writer
  "rclcpp"
  "rosbag2_cpp"
  "geometry_msgs"
)

add_executable(demo02_reader src/demo02_reader.cpp)
ament_target_dependencies(
  demo02_reader
  "rclcpp"
  "rosbag2_cpp"
  "geometry_msgs"
)

install(TARGETS 
  demo01_writer
  demo02_reader
  DESTINATION lib/${PROJECT_NAME})

11.rosbag2 C++ 錄製數據

/* 
  需求:录制 turtle_teleop_key 节点发布的速度指令。
  步骤:
    1.包含头文件;
    2.初始化 ROS 客户端;
    3.定义节点类;
      3-1.创建写出对象指针;
      3-2.设置写出的目标文件;
      3-3.写出消息。
    4.调用 spin 函数,并传入对象指针;
    5.释放资源。

 */
// 1.包含头文件;
#include "rclcpp/rclcpp.hpp"
#include "rosbag2_cpp/writer.hpp"
#include "geometry_msgs/msg/twist.hpp"

using std::placeholders::_1;

// 3.定义节点类;
class SimpleBagRecorder : public rclcpp::Node
{
public:
  SimpleBagRecorder()
  : Node("simple_bag_recorder")
  {
    // 3-1.创建写出对象指针;
    writer_ = std::make_unique<rosbag2_cpp::Writer>();
    // 3-2.设置写出的目标文件;(目录为ws目录)
    writer_->open("src/cpp02_rosbag/my_bag");
    subscription_ = create_subscription<geometry_msgs::msg::Twist>(
      "/turtle1/cmd_vel", 10, std::bind(&SimpleBagRecorder::topic_callback, this, _1));
  }

private:
  void topic_callback(std::shared_ptr<rclcpp::SerializedMessage> msg) const
  {
    rclcpp::Time time_stamp = this->now();
    // 3-3.写出消息。
    RCLCPP_INFO(this->get_logger(),"数据写出... ...");
    writer_->write(msg, "/turtle1/cmd_vel", "geometry_msgs/msg/Twist", time_stamp);
  }

  rclcpp::Subscription<geometry_msgs::msg::Twist>::SharedPtr subscription_;
  std::unique_ptr<rosbag2_cpp::Writer> writer_;
};

int main(int argc, char * argv[])
{
  // 2.初始化 ROS 客户端;
  rclcpp::init(argc, argv);
  // 4.调用 spin 函数,并传入对象指针;
  rclcpp::spin(std::make_shared<SimpleBagRecorder>());
  // 5.释放资源。
  rclcpp::shutdown();
  return 0;
}

這是一個相對目錄,目錄位置是工作空間目錄。

寫數據之前,要先創建一個訂閲方,訂閲方要建立一個回調函數。

回調函數入口參數,消息類型用write函數的入口參數。

這裏要用斜槓代替冒號,因為入口是string類型。

創建烏龜節點。

啓動writer節點

運行小烏龜

運行一會兒後,關掉所有節點。

成功生成了文件

驗證錄製文件是否成功。

在驗證前,先創建一個烏龜節點,不用創建控制節點。

ros2 bag play src/cpp02_rosbag/my_bag

可以看到烏龜正常走了。回放成功!

如果已經生成了一遍my_bag,再想生成新的會顯示不能覆蓋。

解決方案,可以把my_bag設置為動態的,加個時間戳或者直接按功能命名。

12.rosbag2 C++ 讀取數據

/* 
  需求:读取 bag 文件数据。
  步骤:
    1.包含头文件;
    2.初始化 ROS 客户端;
    3.定义节点类;
      3-1.创建读取对象指针;
      3-2.设置读取的目标文件;
      3-3.读消息;
      3-4.关闭文件。
    4.调用 spin 函数,并传入对象指针;
    5.释放资源。

 */
 // 1.包含头文件;
 #include "rclcpp/rclcpp.hpp"
 #include "rosbag2_cpp/reader.hpp"
 #include "geometry_msgs/msg/twist.hpp"
 // 3.定义节点类;
class SimpleBagPlayer: public rclcpp::Node {
public:
    SimpleBagPlayer():Node("simple_bag_player"){
        // 3-1.创建读取对象指针;
        reader_ = std::make_unique<rosbag2_cpp::Reader>();
        // 3-2.设置读取的目标文件;
        reader_->open("src/cpp02_rosbag/my_bag");
        // 3-3.读消息;
        while (reader_->has_next())
        {
            auto twist = reader_->read_next<geometry_msgs::msg::Twist>();
            RCLCPP_INFO(this->get_logger(),"线速度:%.2f, 角速度: %.2f",twist.linear.x, twist.angular.z);
        }
        // 3-4.关闭文件。
        reader_->close();
    }
private:
    std::unique_ptr<rosbag2_cpp::Reader> reader_;

};

int main(int argc, char const *argv[]){
    // 2.初始化 ROS 客户端;
    rclcpp::init(argc,argv);
    // 4.调用 spin 函数,并传入对象指针;
    rclcpp::spin(std::make_shared<SimpleBagPlayer>());
    // 5.释放资源。
    rclcpp::shutdown();
    return 0;
}

編譯

這顯示能讀出來幾條信息。能讀出來8條。

正好8條