当前位置:   article > 正文

【ROS2】高级:从包文件读取 (C++)

【ROS2】高级:从包文件读取 (C++)

目标:在不使用 CLI 的情况下从包中读取数据

 教程级别:高级

 时间:10 分钟

 目录

  •  背景

  •  先决条件

  •  任务

    • 1 创建一个包裹

    • 2 编写 C++ 读取器

    • 3 构建并运行

  •  摘要

 背景

rosbag2 不仅提供 ros2 bag 命令行工具。它还提供了一个 C++ API,用于从您自己的源代码中读取和写入包。这使您可以在不播放包的情况下读取包中的内容,这有时会很有用

 先决条件

您应该在常规的 ROS 2 设置中安装 rosbag2 软件包。

如果您需要安装 ROS 2,请查看安装说明。

你应该已经完成了基本的 ROS 2 包教程https://docs.ros.org/en/jazzy/Tutorials/Beginner-CLI-Tools/Recording-And-Playing-Back-Data/Recording-And-Playing-Back-Data.html ,我们将使用你在那里创建的 subset 包。

 任务

1 创建一个包裹

打开一个新的终端并且初始化您的 ROS 2 安装,这样 ros2 命令就会生效。

在新的或现有的工作区中,导航到 src 目录并创建一个新包:

ros2 pkg create --build-type ament_cmake --license Apache-2.0 bag_reading_cpp --dependencies rclcpp rosbag2_transport turtlesim

您的终端将返回一条消息,验证您的包 bag_reading_cpp 及其所有必要的文件和文件夹的创建。 --dependencies 参数将自动添加必要的依赖行到 package.xml 和 CMakeLists.txt 。在这种情况下,该包将使用 rosbag2_transport 包以及 rclcpp 包。要处理自定义的 turtlesim 消息,还需要依赖 turtlesim 包。

 1.1 更新 package.xml 

因为您在创建包时使用了 --dependencies 选项,您无需手动将依赖项添加到 package.xml 或 CMakeLists.txt 。但是,一如既往,请确保将描述、维护者电子邮件和姓名以及许可信息添加到 package.xml 。

  1. <description>C++ bag reading tutorial</description>
  2. <maintainer email="cxy@126.com">cxy</maintainer>
  3. <license>Apache-2.0</license>

2 编写 C++ 读取器

在你的包的 src 目录中,创建一个名为 simple_bag_reader.cpp 的新文件,并将以下代码粘贴到其中。

  1. #include <chrono> // 用于时间处理的库
  2. #include <functional> // 用于函数对象的库
  3. #include <iostream> // 用于输入输出流的库
  4. #include <memory> // 用于智能指针的库
  5. #include <string> // 用于字符串处理的库
  6. #include "rclcpp/rclcpp.hpp" // ROS 2 的 C++ 客户端库
  7. #include "rclcpp/serialization.hpp" // ROS 2 的序列化库
  8. #include "rosbag2_transport/reader_writer_factory.hpp" // 用于读写 rosbag2 的工厂
  9. #include "turtlesim/msg/pose.hpp" // turtlesim 包中的 Pose 消息类型
  10. using namespace std::chrono_literals; // 使用 chrono 的字面量
  11. class PlaybackNode : public rclcpp::Node // 定义 PlaybackNode 类,继承自 rclcpp::Node
  12. {
  13. public:
  14. PlaybackNode(const std::string & bag_filename) // 构造函数,接受一个 bag 文件名作为参数
  15. : Node("playback_node") // 调用基类构造函数,设置节点名称为 "playback_node"
  16. {
  17. publisher_ = this->create_publisher<turtlesim::msg::Pose>("/turtle1/pose", 10); // 创建一个发布者,发布到 "/turtle1/pose" 话题,队列大小为 10
  18. timer_ = this->create_wall_timer(100ms, // 创建一个定时器,每 100 毫秒触发一次
  19. [this](){return this->timer_callback();} // 定时器的回调函数
  20. );
  21. rosbag2_storage::StorageOptions storage_options; // 创建存储选项实例
  22. storage_options.uri = bag_filename; // 设置存储选项的 URI 为 bag 文件名
  23. reader_ = rosbag2_transport::ReaderWriterFactory::make_reader(storage_options); // 通过工厂创建一个 rosbag2 读取器
  24. reader_->open(storage_options); // 打开读取器
  25. }
  26. private:
  27. void timer_callback() // 定时器的回调函数
  28. {
  29. while (reader_->has_next()) { // 如果读取器有下一条消息
  30. rosbag2_storage::SerializedBagMessageSharedPtr msg = reader_->read_next(); // 读取下一条消息
  31. if (msg->topic_name != "/turtle1/pose") { // 如果消息的主题不是 "/turtle1/pose"
  32. continue; // 跳过本次循环
  33. }
  34. rclcpp::SerializedMessage serialized_msg(*msg->serialized_data); // 创建一个序列化消息
  35. turtlesim::msg::Pose::SharedPtr ros_msg = std::make_shared<turtlesim::msg::Pose>(); // 创建一个 Pose 消息的共享指针
  36. serialization_.deserialize_message(&serialized_msg, ros_msg.get()); // 反序列化消息
  37. publisher_->publish(*ros_msg); // 发布消息
  38. std::cout << '(' << ros_msg->x << ", " << ros_msg->y << ")\n"; // 输出消息的位置信息
  39. break; // 退出循环
  40. }
  41. }
  42. rclcpp::TimerBase::SharedPtr timer_; // 定时器的共享指针
  43. rclcpp::Publisher<turtlesim::msg::Pose>::SharedPtr publisher_; // 发布者的共享指针
  44. rclcpp::Serialization<turtlesim::msg::Pose> serialization_; // 序列化实例
  45. std::unique_ptr<rosbag2_cpp::Reader> reader_; // 读取器的唯一指针
  46. };
  47. int main(int argc, char ** argv) // 主函数
  48. {
  49. if (argc != 2) { // 如果参数数量不等于 2
  50. std::cerr << "Usage: " << argv[0] << " <bag>" << std::endl; // 输出用法提示
  51. return 1; // 返回错误码 1
  52. }
  53. rclcpp::init(argc, argv); // 初始化 ROS 2
  54. rclcpp::spin(std::make_shared<PlaybackNode>(argv[1])); // 创建 PlaybackNode 实例并进入事件循环
  55. rclcpp::shutdown(); // 关闭 ROS 2
  56. return 0; // 返回 0
  57. }
2.1 检查代码 

顶部的 #include 语句是包依赖项。请注意, rosbag2_transport 包中的头文件包含了处理包文件所需的函数和结构。

下一行创建将从包文件读取并回放数据的节点。

class PlaybackNode : public rclcpp::Node

现在,我们可以创建一个以 10 赫兹运行的定时器回调。我们的目标是在每次运行回调时向 /turtle1/pose 主题重放一条消息。请注意,构造函数将路径作为参数传递给包文件。

  1. public:
  2. PlaybackNode(const std::string & bag_filename)
  3. : Node("playback_node")
  4. {
  5. publisher_ = this->create_publisher<turtlesim::msg::Pose>("/turtle1/pose", 10);
  6. timer_ = this->create_wall_timer(100ms,
  7. [this](){return this->timer_callback();}
  8. );

我们还在构造函数中打开bag。 rosbag2_transport::ReaderWriterFactory 是一个类,可以根据存储选项构造压缩或未压缩的读取器或写入器

  1. rosbag2_storage::StorageOptions storage_options;
  2. storage_options.uri = bag_filename;
  3. reader_ = rosbag2_transport::ReaderWriterFactory::make_reader(storage_options);
  4. reader_->open(storage_options);

现在,在我们的计时器回调中,我们循环遍历包中的消息,直到读取到从我们所需主题记录的消息。请注意,序列化消息除了主题名称外还有时间戳元数据。

  1. void timer_callback()
  2. {
  3. while (reader_->has_next()) {
  4. rosbag2_storage::SerializedBagMessageSharedPtr msg = reader_->read_next();
  5. if (msg->topic_name != "/turtle1/pose") {
  6. continue;
  7. }

然后,我们从刚刚读取的序列化数据构建一个 rclcpp::SerializedMessage 对象。此外,我们需要创建一个 ROS 2 反序列化消息,该消息将保存我们的反序列化结果。然后,我们可以将这两个对象传递给 rclcpp::Serialization::deserialize_message 方法。

  1. rclcpp::SerializedMessage serialized_msg(*msg->serialized_data);
  2. turtlesim::msg::Pose::SharedPtr ros_msg = std::make_shared<turtlesim::msg::Pose>();
  3. serialization_.deserialize_message(&serialized_msg, ros_msg.get());

最后,我们发布反序列化的消息并将 xy 坐标打印到终端。我们还会跳出循环,以便在下一个计时器回调期间发布下一条消息。

  1. publisher_->publish(*ros_msg);
  2. std::cout << '(' << ros_msg->x << ", " << ros_msg->y << ")\n";
  3. break;
  4. }

我们还必须声明在整个节点中使用的私有变量。

  1. rclcpp::TimerBase::SharedPtr timer_;
  2. rclcpp::Publisher<turtlesim::msg::Pose>::SharedPtr publisher_;
  3. rclcpp::Serialization<turtlesim::msg::Pose> serialization_;
  4. std::unique_ptr<rosbag2_cpp::Reader> reader_;
  5. };

最后,我们创建主函数,该函数将检查用户是否传递了包文件路径的参数并启动我们的节点。

  1. int main(int argc, char ** argv)
  2. {
  3. if (argc != 2) {
  4. std::cerr << "Usage: " << argv[0] << " <bag>" << std::endl;
  5. return 1;
  6. }
  7. rclcpp::init(argc, argv);
  8. rclcpp::spin(std::make_shared<PlaybackNode>(argv[1]));
  9. rclcpp::shutdown();
  10. return 0;
  11. }
 2.2 添加可执行文件

现在打开 CMakeLists.txt 文件。

在包含 find_package(rosbag2_transport REQUIRED) 的依赖块下面,添加以下代码行。

  1. add_executable(simple_bag_reader src/simple_bag_reader.cpp)
  2. ament_target_dependencies(simple_bag_reader rclcpp rosbag2_transport turtlesim)
  3. install(TARGETS
  4. simple_bag_reader
  5. DESTINATION lib/${PROJECT_NAME}
  6. )

3. 构建并运行

导航回到工作区的根目录并构建您的新包。

colcon build --packages-select bag_reading_cpp

接下来,获取安装文件

source install/setup.bash

现在运行脚本。确保将 /path/to/subset 替换为 subset 包的路径。

ros2 run bag_reading_cpp simple_bag_reader ~/ros2_ws/subset

你应该看到乌龟的 (x, y) 坐标打印到控制台。

bd7154d8099209f8be0899bd06e798c5.png

摘要

你创建了一个读取包中数据的 C++可执行文件。然后你编译并运行了该可执行文件,它将包中的一些信息打印到控制台。

附录:

bbfe199a26286b5703defc2e18d379c6.png

记录的包内容:  ~/ros2_ws/subset/metadata.yaml

8a07cd020897b5c8f6c77d4a2ff4a23e.png

  1. rosbag2_bagfile_information:
  2. version: 9
  3. storage_identifier: mcap
  4. duration:
  5. nanoseconds: 8911648921
  6. starting_time:
  7. nanoseconds_since_epoch: 1721184333150957762
  8. message_count: 640
  9. topics_with_message_count:
  10. - topic_metadata:
  11. name: /turtle1/cmd_vel
  12. type: geometry_msgs/msg/Twist
  13. serialization_format: cdr
  14. offered_qos_profiles:
  15. - history: unknown
  16. depth: 0
  17. reliability: reliable
  18. durability: volatile
  19. deadline:
  20. sec: 9223372036
  21. nsec: 854775807
  22. lifespan:
  23. sec: 9223372036
  24. nsec: 854775807
  25. liveliness: automatic
  26. liveliness_lease_duration:
  27. sec: 9223372036
  28. nsec: 854775807
  29. avoid_ros_namespace_conventions: false
  30. type_description_hash: RIHS01_9c45bf16fe0983d80e3cfe750d6835843d265a9a6c46bd2e609fcddde6fb8d2a
  31. message_count: 82
  32. - topic_metadata:
  33. name: /turtle1/pose
  34. type: turtlesim/msg/Pose
  35. serialization_format: cdr
  36. offered_qos_profiles:
  37. - history: unknown
  38. depth: 0
  39. reliability: reliable
  40. durability: volatile
  41. deadline:
  42. sec: 9223372036
  43. nsec: 854775807
  44. lifespan:
  45. sec: 9223372036
  46. nsec: 854775807
  47. liveliness: automatic
  48. liveliness_lease_duration:
  49. sec: 9223372036
  50. nsec: 854775807
  51. avoid_ros_namespace_conventions: false
  52. type_description_hash: RIHS01_739beba26bcba6920404ba722b7b8321348512f92ea5be235c47251940dd8aa9
  53. message_count: 558
  54. compression_format: ""
  55. compression_mode: ""
  56. relative_file_paths:
  57. - subset_0.mcap
  58. files:
  59. - path: subset_0.mcap
  60. starting_time:
  61. nanoseconds_since_epoch: 1721184333150957762
  62. duration:
  63. nanoseconds: 8911648921
  64. message_count: 640
  65. custom_data: ~
  66. ros_distro: jazzy

记录多个主题:

ros2 bag record -o subset /turtle1/cmd_vel /turtle1/pose
声明:本文内容由网友自发贡献,不代表【wpsshop博客】立场,版权归原作者所有,本站不承担相应法律责任。如您发现有侵权的内容,请联系我们。转载请注明出处:https://www.wpsshop.cn/w/从前慢现在也慢/article/detail/862511
推荐阅读
相关标签
  

闽ICP备14008679号