编写一个简单的发布者和订阅者(​​C++)

目标:使用 C++ 创建并运行发布者和订阅者节点。

教程级别:初学者

时间:20 分钟

背景

节点 是通过 ROS 图进行通信的可执行进程。 在本教程中,节点将通过 主题 以字符串消息的形式相互传递信息。 此处使用的示例是一个简单的 谈话者``和 ``侦听者 系统;一个节点发布数据,另一个节点订阅该主题,以便可以接收该数据。

这些示例中使用的代码可以在 此处 找到。

先决条件

在之前的教程中,您学习了如何 create a workspacecreate a package.

任务

1 创建包

打开一个新终端并 source your ROS 2 installation,以便 ros2 命令可以正常工作。

导航到在 previous tutorial. 中创建的 ros2_ws 目录。

回想一下,应该在 src 目录中创建包,而不是在工作区的根目录中创建包。 因此,导航到 ros2_ws/src,并运行包创建命令:

ros2 pkg create --build-type ament_cmake --license Apache-2.0 cpp_pubsub

您的终端将返回一条消息,验证您的包 cpp_pubsub 及其所有必要的文件和文件夹的创建。

导航到 ros2_ws/src/cpp_pubsub/src。 回想一下,这是任何 CMake 包中包含可执行文件的源文件所属的目录。

2 编写发布者节点

通过输入以下命令下载示例 talker 代码:

wget -O publisher_lambda_function.cpp https://raw.githubusercontent.com/ros2/examples/rolling/rclcpp/topics/minimal_publisher/lambda.cpp

现在将有一个名为 ``publisher_lambda_function.cpp``的新文件。 使用您喜欢的文本编辑器打开该文件。

#include <chrono>
#include <memory>
#include <string>

#include "rclcpp/rclcpp.hpp"
#include "std_msgs/msg/string.hpp"

using namespace std::chrono_literals;

/* This example creates a subclass of Node and uses a fancy C++11 lambda
* function to shorten the callback syntax, at the expense of making the
* code somewhat more difficult to understand at first glance. */

class MinimalPublisher : public rclcpp::Node
{
public:
  MinimalPublisher()
  : Node("minimal_publisher"), count_(0)
  {
    publisher_ = this->create_publisher<std_msgs::msg::String>("topic", 10);
    auto timer_callback =
      [this]() -> void {
        auto message = std_msgs::msg::String();
        message.data = "Hello, world! " + std::to_string(this->count_++);
        RCLCPP_INFO(this->get_logger(), "Publishing: '%s'", message.data.c_str());
        this->publisher_->publish(message);
      };
    timer_ = this->create_wall_timer(500ms, timer_callback);
  }

private:
  rclcpp::TimerBase::SharedPtr timer_;
  rclcpp::Publisher<std_msgs::msg::String>::SharedPtr publisher_;
  size_t count_;
};

int main(int argc, char * argv[])
{
  rclcpp::init(argc, argv);
  rclcpp::spin(std::make_shared<MinimalPublisher>());
  rclcpp::shutdown();
  return 0;
}

2.1 检查代码

代码顶部包含您将使用的标准 C++ 标头。

标准 C++ 标头之后是 rclcpp/rclcpp.hpp 包含,它允许您使用 ROS 2 系统中最常见的部分。

最后是 std_msgs/msg/string.hpp,它包含您将用于发布数据的内置消息类型。

#include <chrono>
#include <memory>
#include <string>

#include "rclcpp/rclcpp.hpp"
#include "std_msgs/msg/string.hpp"

using namespace std::chrono_literals;

这些行表示节点的依赖项。 回想一下,必须将依赖项添加到“package.xml”和“CMakeLists.txt”,您将在下一节中执行此操作。

下一行通过继承“rclcpp::Node”来创建节点类“MinimalPublisher”。 代码中的每个“this”都引用该节点。

class MinimalPublisher : public rclcpp::Node

公共构造函数将节点命名为 minimal_publisher,并将 count_``初始化为 0。 在构造函数中,发布者使用 ``String 消息类型、主题名称 topic 和所需的队列大小进行初始化,以在备份时限制消息。 接下来,声明一个名为 timer_callback``的 `lambda function <https://en.cppreference.com/w/cpp/language/lambda>`_  它执行当前对象 ``this``的引用捕获,不接受输入参数并返回 void。 ``timer_callback 函数创建一个新的 ``String``类型的消息,用所需的字符串设置其数据并发布它。 ``RCLCPP_INFO``宏确保每个已发布的消息都打印到控制台。 最后, ``timer_``被初始化,这导致 ``timer_callback``函数每秒执行两次。

public:
  MinimalPublisher()
  : Node("minimal_publisher"), count_(0)
  {
    publisher_ = this->create_publisher<std_msgs::msg::String>("topic", 10);
    auto timer_callback =
      [this]() -> void {
        auto message = std_msgs::msg::String();
        message.data = "Hello, world! " + std::to_string(this->count_++);
        RCLCPP_INFO(this->get_logger(), "Publishing: '%s'", message.data.c_str());
        this->publisher_->publish(message);
      };
    timer_ = this->create_wall_timer(500ms, timer_callback);
  }

该类的底部是计时器、发布者和计数器字段的声明。

private:
  rclcpp::TimerBase::SharedPtr timer_;
  rclcpp::Publisher<std_msgs::msg::String>::SharedPtr publisher_;
  size_t count_;

MinimalPublisher 类之后是 main,节点在此实际执行。 rclcpp::init 初始化 ROS 2,rclcpp::spin 开始处理来自节点的数据,包括来自计时器的回调。

int main(int argc, char * argv[])
{
  rclcpp::init(argc, argv);
  rclcpp::spin(std::make_shared<MinimalPublisher>());
  rclcpp::shutdown();
  return 0;
}

2.2 添加依赖项

导航回上一级到 ros2_ws/src/cpp_pubsub 目录,其中已为您创建了 CMakeLists.txtpackage.xml 文件。

使用文本编辑器打开 package.xml

上一个教程 中所述,请确保填写 <description><maintainer><license> 标签:

<description>Examples of minimal publisher/subscriber using rclcpp</description>
<maintainer email="you@email.com">Your Name</maintainer>
<license>Apache-2.0</license>

在“ament_cmake”构建工具依赖项后添加一个新行,并粘贴与您的节点的包含语句相对应的以下依赖项:

<depend>rclcpp</depend>
<depend>std_msgs</depend>

这声明了包在构建和执行其代码时需要“rclcpp”和“std_msgs”。

确保保存该文件。

2.3 CMakeLists.txt

现在打开“CMakeLists.txt”文件。 在现有依赖项“find_package(ament_cmake REQUIRED)”下,添加以下行:

find_package(rclcpp REQUIRED)
find_package(std_msgs REQUIRED)

之后,添加可执行文件并将其命名为“talker”,以便您可以使用“ros2 run”运行您的节点:

add_executable(talker src/publisher_lambda_function.cpp)
ament_target_dependencies(talker rclcpp std_msgs)

最后,添加“install(TARGETS…)”部分,以便“ros2 run”可以找到您的可执行文件:

install(TARGETS
  talker
  DESTINATION lib/${PROJECT_NAME})

您可以通过删除一些不必要的部分和注释来清理您的“CMakeLists.txt”,因此它看起来像这样:

cmake_minimum_required(VERSION 3.5)
project(cpp_pubsub)

# Default to C++14
if(NOT CMAKE_CXX_STANDARD)
  set(CMAKE_CXX_STANDARD 14)
endif()

if(CMAKE_COMPILER_IS_GNUCXX OR CMAKE_CXX_COMPILER_ID MATCHES "Clang")
  add_compile_options(-Wall -Wextra -Wpedantic)
endif()

find_package(ament_cmake REQUIRED)
find_package(rclcpp REQUIRED)
find_package(std_msgs REQUIRED)

add_executable(talker src/publisher_lambda_function.cpp)
ament_target_dependencies(talker rclcpp std_msgs)

install(TARGETS
  talker
  DESTINATION lib/${PROJECT_NAME})

ament_package()

您现在可以构建您的包,获取本地安装文件并运行它,但让我们先创建订阅者节点,以便您可以看到整个系统的运行情况。

3 编写订阅者节点

返回“ros2_ws/src/cpp_pubsub/src”以创建下一个节点。 在您的终端中输入以下代码:

wget -O subscriber_lambda_function.cpp https://raw.githubusercontent.com/ros2/examples/rolling/rclcpp/topics/minimal_subscriber/lambda.cpp

检查以确保这些文件存在:

publisher_lambda_function.cpp  subscriber_lambda_function.cpp

使用文本编辑器打开“subscriber_lambda_function.cpp”。

#include <memory>

#include "rclcpp/rclcpp.hpp"
#include "std_msgs/msg/string.hpp"

class MinimalSubscriber : public rclcpp::Node
{
public:
  MinimalSubscriber()
  : Node("minimal_subscriber")
  {
    auto topic_callback =
      [this](std_msgs::msg::String::UniquePtr msg) -> void {
        RCLCPP_INFO(this->get_logger(), "I heard: '%s'", msg->data.c_str());
      };
    subscription_ =
      this->create_subscription<std_msgs::msg::String>("topic", 10, topic_callback);
  }

private:
  rclcpp::Subscription<std_msgs::msg::String>::SharedPtr subscription_;
};

int main(int argc, char * argv[])
{
  rclcpp::init(argc, argv);
  rclcpp::spin(std::make_shared<MinimalSubscriber>());
  rclcpp::shutdown();
  return 0;
}

3.1 检查代码

订阅者节点的代码与发布者几乎相同。

现在节点名为“minimal_subscriber”,构造函数使用节点的“create_subscription”函数执行回调。

没有计时器,因为订阅者只是在数据发布到“topic”主题时做出响应。

“topic_callback”函数接收通过主题发布的字符串消息数据,并使用“RCLCPP_INFO”宏将其写入控制台。

回想一下:doc:主题教程<../Beginner-CLI-Tools/Understanding-ROS2-Topics/Understanding-ROS2-Topics>”,发布者和订阅者使用的主题名称和消息类型必须匹配才能允许它们进行通信。

public:
  MinimalSubscriber()
  : Node("minimal_subscriber")
  {
    auto topic_callback =
      [this](std_msgs::msg::String::UniquePtr msg) -> void {
        RCLCPP_INFO(this->get_logger(), "I heard: '%s'", msg->data.c_str());
      };
    subscription_ =
      this->create_subscription<std_msgs::msg::String>("topic", 10, topic_callback);
  }

此类中唯一的字段声明是订阅。

private:
  rclcpp::Subscription<std_msgs::msg::String>::SharedPtr subscription_;

main 函数完全相同,只是现在它旋转 MinimalSubscriber 节点。 对于发布者节点,旋转意味着启动计时器,但对于订阅者来说,它只是意味着随时准备接收消息。

由于此节点与发布者节点具有相同的依赖关系,因此无需在 package.xml 中添加任何新内容。

3.2 CMakeLists.txt

重新打开 CMakeLists.txt 并在发布者的条目下方添加订阅者节点的可执行文件和目标。

add_executable(listener src/subscriber_lambda_function.cpp)
ament_target_dependencies(listener rclcpp std_msgs)

install(TARGETS
  talker
  listener
  DESTINATION lib/${PROJECT_NAME})

确保保存文件,然后您的发布/订阅系统就准备好了。

4 构建并运行

您可能已经安装了“rclcpp”和“std_msgs”包作为 ROS 2 系统的一部分。 在构建之前,最好在工作区(“ros2_ws”)的根目录中运行“rosdep”来检查是否缺少依赖项:

rosdep install -i --from-path src --rosdistro rolling -y

仍然在工作区的根目录“ros2_ws”中构建新包:

colcon build --packages-select cpp_pubsub

打开一个新终端,导航到“ros2_ws”,并获取安装文件:

. install/setup.bash

现在运行 talker 节点:

ros2 run cpp_pubsub talker

终端应该每 0.5 秒开始发布一次信息消息,如下所示:

[INFO] [minimal_publisher]: Publishing: "Hello World: 0"
[INFO] [minimal_publisher]: Publishing: "Hello World: 1"
[INFO] [minimal_publisher]: Publishing: "Hello World: 2"
[INFO] [minimal_publisher]: Publishing: "Hello World: 3"
[INFO] [minimal_publisher]: Publishing: "Hello World: 4"

打开另一个终端,再次从“ros2_ws”内部获取安装文件,然后启动监听器节点:

ros2 run cpp_pubsub listener

监听器将开始将消息打印到控制台,从发布者当时的消息计数开始,如下所示:

[INFO] [minimal_subscriber]: I heard: "Hello World: 10"
[INFO] [minimal_subscriber]: I heard: "Hello World: 11"
[INFO] [minimal_subscriber]: I heard: "Hello World: 12"
[INFO] [minimal_subscriber]: I heard: "Hello World: 13"
[INFO] [minimal_subscriber]: I heard: "Hello World: 14"

在每个终端中输入“Ctrl+C”以停止节点旋转。

摘要

您创建了两个节点来发布和订阅主题上的数据。 在编译和运行它们之前,您已将它们的依赖项和可执行文件添加到包配置文件中。

后续步骤

接下来,您将使用服务/客户端模型创建另一个简单的 ROS 2 包。 同样,您可以选择使用 C++Python 编写它。

相关内容

有几种方法可以用 C++ 编写发布者和订阅者;查看 ros2/examples repo 中的 minimal_publisherminimal_subscriber 包。