从节点记录一个袋子 (C++)

目标 将数据从自己的 C++ 节点记录到数据包中。

辅导水平: 高级

时间 20 分钟

背景介绍

rosbag2 不只是提供 玫瑰2 背包 命令行工具。它还提供了一个 C++ 应用程序接口,用于从自己的源代码中读取和写入数据包。这样,您就可以订阅一个主题,并将接收到的数据保存到数据包中,同时对这些数据进行其他处理。

先决条件

您应该有 rosbag2 作为 ROS 2 常规设置的一部分安装的软件包。

如果你在 Linux 上安装了 Debian 软件包,它可能已经默认安装。如果没有,可以使用此命令进行安装。

sudo apt install ros-jazzy-rosbag2

本教程讨论如何使用 ROS 2 工具包,包括从终端使用。您应该已经完成了 ROS 2 工具包基础教程.

任务

1 创建软件包

打开一个新终端,然后 为您的 ROS 2 安装提供源代码 以便 玫瑰2 命令即可运行。

导航进入 ros2_ws 目录中创建的 上一个教程.导航至 ros2_ws/src 目录,并创建一个新软件包:

ros2 pkg create --build-type ament_cmake --license Apache-2.0 bag_recorder_nodes --dependencies example_interfaces rclcpp rosbag2_cpp std_msgs

您的终端将返回一条信息,验证是否创建了软件包 记录袋节点 及其所有必要的文件和文件夹。文件 --依赖 参数会自动在 package.xmlCMakeLists.txt.在这种情况下,软件包将使用 rosbag2_cpp 软件包以及 rclcpp 软件包。对 接口示例 软件包也是本教程后续部分所必需的。

1.1 更新 package.xml

因为您使用了 --依赖 选项,就不必在创建软件包时手动将依赖关系添加到 package.xmlCMakeLists.txt.不过,请务必一如既往地将说明、维护者电子邮件和姓名以及许可证信息添加到 package.xml.

<描述>;C++ 背包 写作 教程</description>;
维护者 电子邮件="[email protected]";>;您的 名称维护人员</maintainer>;
许可证Apache-2.0</license>;

2 编写 C++ 节点

内部 ros2_ws/src/bag_recorder_nodes/src 目录下,新建一个名为 simple_bag_recorder.cpp 并粘贴以下代码。

#include <rclcpp/rclcpp.hpp>;
#include <std_msgs/msg/string.hpp>;

#include <rosbag2_cpp/writer.hppt>;

 简单包记录器 :  rclcpp::节点
{
:
  简单包记录器()
  : 节点("simple_bag_recorder";)
  {
    作家_ = 标准::make_unique<;rosbag2_cpp::作家>;();

    作家_->;("my_bag";);

    汽车 subscription_callback_lambda = [](标准::共享_ptr<;rclcpp::序列化信息>; 信息){
      rclcpp::时间 时间戳 = ->;现在();

      作家_->;写道(信息, 唠叨";, "std_msgs/msg/String";, 时间戳);
    };

    订阅_ = 创建订阅<;std_msgs::信息::字符串>;(
      唠叨";, 10, subscription_callback_lambda);
  }

私人:

  rclcpp::订阅<;std_msgs::信息::字符串>::SharedPtr 订阅_;
  标准::唯一参数<;rosbag2_cpp::作家>; 作家_;
};

int 主要(int 参数, 烧焦 * 参数[])
{
  rclcpp::启动(参数, 参数);
  rclcpp::后旋(标准::共享<;简单包记录器>;());
  rclcpp::关闭();
  返回 0;
}

2.1 检查代码

"(《世界人权宣言》) #include 顶部的语句是软件包的依赖关系。注意包含了 rosbag2_cpp 包中提供了处理袋文件所需的函数和结构。

在类的构造函数中,我们首先创建一个写入器对象,用于向存储袋中写入内容。

作家_ = 标准::make_unique<;rosbag2_cpp::作家>;();

有了写入器对象后,我们就可以用它打开文件袋了。我们只需指定要创建的数据包的 URI,其他选项则保留默认值。我们使用默认的存储选项,这意味着一个 mcap-格式包。也会使用默认的转换选项,即不执行转换,而是以接收到的序列化格式存储信息。

作家_->;("my_bag";);

现在写入器已经设置好记录我们传递给它的数据,我们创建一个订阅并为其指定一个回调。我们将在回调中向袋子写入数据。

汽车 subscription_callback_lambda = [](标准::共享_ptr<;rclcpp::序列化信息>; 信息){
  rclcpp::时间 时间戳 = ->;现在();

  作家_->;写道(信息, 唠叨";, "std_msgs/msg/String";, 时间戳);
};

订阅_ = 创建订阅<;std_msgs::信息::字符串>;(
  唠叨";, 10, subscription_callback_lambda);

回调本身与典型的回调不同。我们接收的不是主题数据类型的实例,而是一个 rclcpp::SerializedMessage.我们这样做有两个原因。

  1. 信息数据需要通过 rosbag2 因此,与其在接收数据时将其取消序列化,然后重新序列化,我们不如要求 ROS 将序列化后的信息原封不动地提供给我们。

  2. 写入器 API 可以接受序列化信息。

汽车 subscription_callback_lambda = [](标准::共享_ptr<;rclcpp::序列化信息>; 信息){

在订阅回调中,要做的第一件事就是确定存储信息要使用的时间戳。时间戳可以是任何与数据相关的时间,但有两个常见的值,一个是数据生成的时间(如果已知),另一个是数据接收的时间。这里使用的是第二个选项,即接收时间。

rclcpp::时间 时间戳 = ->;现在();

然后,我们就可以将信息写入信息袋中。因为我们还没有在信息袋中注册任何主题,所以必须在信息中指定完整的主题信息。这就是我们要输入主题名称和主题类型的原因。

作家_->;写道(信息, 唠叨";, "std_msgs/msg/String";, 时间戳);

该类包含两个成员变量。

  1. 订阅对象。

  2. 用于写入数据包的写入器对象的受管指针。注意这里使用的写入器类型是 rosbag2_cpp::Writer的通用写入器界面。其他写入器可能具有不同的行为。

rclcpp::订阅<;std_msgs::信息::字符串>::SharedPtr 订阅_;
标准::唯一参数<;rosbag2_cpp::作家>; 作家_;

文件以 主要 函数用于创建节点实例并启动 ROS 处理。

int 主要(int 参数, 烧焦 * 参数[])
{
  rclcpp::启动(参数, 参数);
  rclcpp::后旋(标准::共享<;简单包记录器>;());
  rclcpp::关闭();
  返回 0;
}

2.2 添加可执行文件

现在打开 CMakeLists.txt 锉刀

在文件顶部附近,更改 cmake_cxx_standard1417.

# 默认值  C++17
if(NOT CMAKE_CXX_STANDARD)
  set(CMAKE_CXX_STANDARD 17)
endif()

在依赖项块下方,包含 find_package(rosbag2_cpp 必填)添加以下代码

add_executable(simple_bag_recorder src/simple_bag_recorder.cpp)
ament_target_dependencies(simple_bag_recorder rclcpp rosbag2_cpp std_msgs)

install(TARGETS
  简单记录袋
  目的地 lib/${PROJECT_NAME}
)

3 构建和运行

返回工作区的根目录、 ros2_ws然后创建新软件包。

colcon build --packages-select bag_recorder_nodes

打开一个新终端,导航至 ros2_ws并获取设置文件。

source install/setup.bash

现在运行节点:

ros2 run bag_recorder_nodes simple_bag_recorder

打开第二个终端,运行 话匣子 示例节点。

ros2 run demo_nodes_cpp talker

这将开始在 唠叨 主题。写袋节点收到这些数据后,会将其写入 我的包 包。

终止两个节点。然后,在一个终端启动 听众 示例节点。

ros2 运行 demo_nodes_cpp 监听器

在另一个终端,使用 玫瑰2 背包 播放节点录制的音袋。

ros2 背包扮演 my_bag

您将看到来自信息包的信息正在被 听众 节点。

如果您想再次运行写袋节点,首先需要删除 我的包 目录。

4 记录节点的合成数据

任何数据都可以记录到数据包中,而不仅仅是通过主题接收到的数据。从自己的节点写入数据包的一个常见用例是生成和存储合成数据。在本节中,您将学习如何编写一个节点,生成一些数据并将其存储到数据包中。我们将演示两种方法。第一种方法使用带有定时器的节点;如果数据生成是在节点外部进行的,例如直接从硬件(如摄像头)读取数据,则可以使用这种方法。第二种方法不使用节点;这是在不需要使用 ROS 基础架构的任何功能时可以使用的方法。

4.1 编写 C++ 节点

内部 ros2_ws/src/bag_recorder_nodes/src 目录下,新建一个名为 data_generator_node.cpp 并粘贴以下代码。

#include 时间顺序<chrono>;

#include <example_interfaces/msg/int32.hpp>;
#include <rclcpp/rclcpp.hpp>;

#include <rosbag2_cpp/writer.hppt>;

使用 命名空间 标准::计时器;

 数据生成器 :  rclcpp::节点
{
:
  数据生成器()
  : 节点("data_generator";)
  {
    数据_.数据 = 0;
    作家_ = 标准::make_unique<;rosbag2_cpp::作家>;();

    作家_->;("timed_synthetic_bag";);

    作家_->;创建主题(
    {
      0u,
      "合成";,
      "example_interfaces/msg/Int32";,
      rmw_get_serialization_format(),
      {},
      "";,
    });

    汽车 timer_callback_lambda = [](){返回 ->;定时器回调();};
    定时器 = 创建隔离墙计时器(1s, timer_callback_lambda);
  }

私人:
  空白 定时器回调()
  {
    作家_->;写道(数据_, "合成";, 现在());

    ++数据_.数据;
  }

  rclcpp::定时器基数::SharedPtr 定时器;
  标准::唯一参数<;rosbag2_cpp::作家>; 作家_;
  接口示例::信息::Int32 数据_;
};

int 主要(int 参数, 烧焦 * 参数[])
{
  rclcpp::启动(参数, 参数);
  rclcpp::后旋(标准::共享<;数据生成器>;());
  rclcpp::关闭();
  返回 0;
}

4.2 检查代码

该代码的大部分内容与第一个示例相同。重要的不同之处在此说明。

首先,改变袋子的名称。

作家_->;("timed_synthetic_bag";);

在这个示例中,我们提前在信息包中注册了主题。这在大多数情况下是可选的,但在传递没有主题信息的序列化信息时必须这样做。

作家_->;创建主题(
{
  0u,
  "合成";,
  "example_interfaces/msg/Int32";,
  rmw_get_serialization_format(),
  {},
  "";,
});

该节点有一个计时器,而不是对主题的订阅。定时器以一秒为周期触发,并在触发时调用给定的成员函数。

汽车 timer_callback_lambda = [](){返回 ->;定时器回调();};
定时器 = 创建隔离墙计时器(1s, timer_callback_lambda);

在定时器回调中,我们生成(或以其他方式获取,例如从连接到某些硬件的串行端口读取)我们希望存储在数据包中的数据。这与上一个示例的重要区别在于,数据尚未序列化。取而代之的是,我们将 ROS 消息数据类型传递给写入器对象,在本例中是一个 example_interfaces/msg/Int32.在将数据写入数据包之前,写入器会为我们将数据序列化。

作家_->;写道(数据_, "合成";, 现在());

4.3 添加可执行文件

打开 CMakeLists.txt 文件,并在之前添加的行之后添加以下行(特别是在 install(TARGETS ...) 宏调用)。

add_executable(data_generator_node src/data_generator_node.cpp)
ament_target_dependencies(data_generator_node rclcpp rosbag2_cpp example_interfaces)

install(TARGETS
  数据生成器节点
  目的地 lib/${PROJECT_NAME}
)

4.4 构建和运行

返回工作区的根目录、 ros2_ws,并创建您的软件包。

colcon build --packages-select bag_recorder_nodes

打开一个新终端,导航至 ros2_ws并获取设置文件。

source install/setup.bash

如果 定时合成袋 则必须在运行节点前删除该目录)。

现在运行节点:

ros2 run bag_recorder_nodes data_generator_node

等待 30 秒左右,然后使用 按住-c.接着,播放制作好的袋子。

ROS2合成袋定时播放

打开第二个终端,回显 /合成 主题。

ros2 topic echo /synthetic

您将看到以每秒一条信息的速度打印到控制台的已生成并存储在数据包中的数据。

5 记录可执行文件的合成数据

现在,您可以创建一个存储来自主题以外数据源的数据包,您将学习如何从非节点可执行文件生成和记录合成数据。这种方法的优点是代码简单,可以快速创建大量数据。

5.1 编写 C++ 可执行文件

内部 ros2_ws/src/bag_recorder_nodes/src 目录下,新建一个名为 data_generator_executable.cpp 并粘贴以下代码。

#include 时间顺序<chrono>;

#include <rclcpp/rclcpp.hpp>;  // 适用于 rclcpp::时钟、rclcpp::持续时间和 rclcpp::Time
#include <example_interfaces/msg/int32.hpp>;

#include <rosbag2_cpp/writer.hppt>;
#include <rosbag2_cpp/writers/sequential_writer.hpp>;
#include <rosbag2_storage/serialized_bag_message.hpp>;

使用 命名空间 标准::计时器;

int 主要(int, 烧焦**)
{
  接口示例::信息::Int32 数据;
  数据.数据 = 0;
  标准::唯一参数<;rosbag2_cpp::作家>; 作家_ = 标准::make_unique<;rosbag2_cpp::作家>;();

  作家_->;("big_synthetic_bag";);

  作家_->;创建主题(
  {
    0u,
    "合成";,
    "example_interfaces/msg/Int32";,
    rmw_get_serialization_format(),
    {},
    "";,
  });

  rclcpp::时钟 ;
  rclcpp::时间 时间戳 = .现在();
  对于 (int32_t ii = 0; ii <; 100; ++ii) {
    作家_->;写道(数据, "合成";, 时间戳);
    ++数据.数据;
    时间戳 += rclcpp::持续时间(1s);
  }

  返回 0;
}

5.2 检查代码

比较一下这个示例和上一个示例就会发现,它们并没有什么不同。唯一明显的不同是使用 for 循环而不是定时器来驱动数据生成。

请注意,我们现在也在为数据生成时间戳,而不是依赖每个样本的当前系统时间。时间戳可以是你需要的任何值。数据将以这些时间戳给出的速度回放,因此这是控制样本默认回放速度的有效方法。还要注意的是,虽然每个采样之间的时间间隔是一整秒,但该可执行文件不需要在每个采样之间等待一秒钟。这样,我们就能在比回放时间更短的时间内生成大量数据,覆盖更宽的时间跨度。

rclcpp::时钟 ;
rclcpp::时间 时间戳 = .现在();
对于 (int32_t ii = 0; ii <; 100; ++ii) {
  作家_->;写道(数据, "合成";, 时间戳);
  ++数据.数据;
  时间戳 += rclcpp::持续时间(1s);
}

5.3 添加可执行文件

打开 CMakeLists.txt 文件,并在之前添加的行之后添加以下行。

add_executable(data_generator_executable src/data_generator_executable.cpp)
ament_target_dependencies(data_generator_executable rclcpp rosbag2_cpp example_interfaces)

install(TARGETS
  数据生成器可执行
  目的地 lib/${PROJECT_NAME}
)

5.4 构建和运行

返回工作区的根目录、 ros2_ws,并创建您的软件包。

colcon build --packages-select bag_recorder_nodes

打开终端,导航至 ros2_ws并获取设置文件。

source install/setup.bash

如果 大合成包 则必须在运行可执行文件前删除该目录)。

现在运行可执行文件:

ros2 run bag_recorder_nodes data_generator_executable

请注意,可执行文件的运行和结束速度都非常快。

现在回放制作好的袋子。

ROS2 袋装大合成树脂袋

打开第二个终端,回显 /合成 主题。

ros2 topic echo /synthetic

您将看到以每秒一条信息的速度将生成并存储在数据袋中的数据打印到控制台。尽管数据包的生成速度很快,但仍会按照时间戳指示的速度进行回放。

摘要

您创建了一个节点,它可以将收到的主题数据记录到数据包中。您测试了使用该节点记录数据包,并通过回放数据包来验证数据是否被记录。然后,您继续创建了一个节点和一个可执行文件,用于生成合成数据并将其存储到数据包中。