编写简单的发布者和订阅者 (c ++) [待校准@9345]

Goal目标: using使用C++ 创建并运行发布者和订阅者节点。 [待校准@9346]

教程级别: 初学者 [Alyssa@7088]

Time时间: 20 20分钟 [待校准@7181]

背景

[需手动修复的语法] Nodes 是通过ROS图进行通信的可执行进程。在本教程中,节点将以string消息的形式通过 topic 相互传递信息。这里使用的例子是一个简单的 “说话者” 和 “听众” 系统; 一个节点发布数据,另一个节点订阅话题,这样它就可以接收数据。 [待校准@9347]

这些例子中使用的代码可以找到 here[待校准@9348]

先决条件

在之前的教程中,你学习了如何 create a workspace and create a package[待校准@9349]

任务

1创建包

打开一个新的终端和 source your ROS 2 installation ,这样 ros2 命令就可以工作了。 [待校准@9214]

导航到 dev_ws 目录创建 previous tutorial[待校准@9215]

重新调用应该在 src 目录中创建的包,而不是工作区的根目录。因此,导航到 dev_ws/src ,并运行包创建命令: [待校准@9350]

ros2 pkg create --build-type ament_cmake cpp_pubsub

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

导航到 dev_ws/src/cpp_pubsub/src 。重新调用这是包含可执行文件的源文件所属的任何CMake包中的目录。 [待校准@9352]

2写入发布者节点 [待校准@9353]

通过输入以下命令下载示例talker代码: [待校准@9354]

wget -O publisher_member_function.cpp https://raw.githubusercontent.com/ros2/examples/foxy/rclcpp/topics/minimal_publisher/member_function.cpp

现在将会有一个名为 publisher_member_function.cpp 的新文件。使用您喜欢的文本编辑器打开文件。 [待校准@9355]

#include <chrono>
#include <functional>
#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 std::bind() to register a
* member function as a callback from the timer. */

class MinimalPublisher : public rclcpp::Node
{
  public:
    MinimalPublisher()
    : Node("minimal_publisher"), count_(0)
    {
      publisher_ = this->create_publisher<std_msgs::msg::String>("topic", 10);
      timer_ = this->create_wall_timer(
      500ms, std::bind(&MinimalPublisher::timer_callback, this));
    }

  private:
    void timer_callback()
    {
      auto message = std_msgs::msg::String();
      message.data = "Hello, world! " + std::to_string(count_++);
      RCLCPP_INFO(this->get_logger(), "Publishing: '%s'", message.data.c_str());
      publisher_->publish(message);
    }
    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检查代码 [待校准@8007]

代码的顶部包括您将使用的标准C++ 标头。在标准的cheaders标头之后是 rclcpp/rclcpp.hpp ,它允许您使用ROS 2系统中最常见的部分。最后是 std_msgs/msg/string.hpp ,它包括将用于发布数据的内置消息类型。 [待校准@9356]

这些行表示节点的依赖关系。重新调用必须将依赖项添加到 package.xmlCMakeLists.txt 中,您将在下一节中执行此操作。 [待校准@9357]

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

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

using namespace std::chrono_literals;

下一行通过继承 “rclcpp:: node” 来创建节点类 MinimalPublisher 。代码中的每一个 this 都指的是节点。 [待校准@9358]

class MinimalPublisher : public rclcpp::Node

公共构造函数命名节点 minimal_publisher ,并将 count_ 初始化为0。在构造函数中,发布者使用 String 消息类型、话题名称 topic 以及在备份时限制消息所需的队列大小进行初始化。接下来,初始化 timer_ ,这将导致 timer_callback 函数每秒执行两次。 [待校准@9359]

public:
  MinimalPublisher()
  : Node("minimal_publisher"), count_(0)
  {
    publisher_ = this->create_publisher<std_msgs::msg::String>("topic", 10);
    timer_ = this->create_wall_timer(
    500ms, std::bind(&MinimalPublisher::timer_callback, this));
  }

[需手动修复的语法] timer_callback 函数是设置消息数据并实际发布消息的位置。 RCLCPP_INFO 宏可确保将每条发布的消息打印到控制台。 [待校准@9360]

private:
  void timer_callback()
  {
    auto message = std_msgs::msg::String();
    message.data = "Hello, world! " + std::to_string(count_++);
    RCLCPP_INFO(this->get_logger(), "Publishing: '%s'", message.data.c_str());
    publisher_->publish(message);
  }

最后是计时器、发布者和计数器字段的声明。 [待校准@9361]

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

追踪 MinimalPublisher 类是 main ,在这里节点实际执行。“Rclcpp::init” 初始化ROS 2,“rclcpp:: spin” 开始处理来自节点的数据,包括从计时器调用的数据。 [待校准@9362]

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

2.2添加依赖项 [待校准@8644]

导航到 dev_ws/src/cpp_pubsub 目录,在那里已经为您创建了 CMakeLists.txtpackage.xml 文件。 [待校准@9363]

用文本编辑器打开 package.xml[待校准@8719]

previous tutorial, make sure to fill in the <description>, <maintainer> and ``<license>` 标签中所述: [待校准@9364]

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

添加新行后 ament_cmake buildtool依赖并粘贴以下依赖对应节点的include语句: [待校准@9365]

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

这表明当执行其代码时,包需要 rclcppstd_msgs[待校准@9366]

确保保存文件。 [待校准@8647]

2.3 CMakeLists.txt [待校准@8648]

现在打开 CMakeLists.txt 文件。在现有的依存性 find_package(ament_cmake REQUIRED) 下,添加以下几行: [待校准@9367]

find_package(rclcpp REQUIRED)
find_package(std_msgs REQUIRED)

之后,添加可执行文件并将其命名为 talker ,以便您可以使用 ros2 run 运行节点: [待校准@9368]

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

最后,添加 “安装 (目标……) `` section so `` ros2运行” 可以找到您的可执行文件: [待校准@8640]

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

你可以通过删除一些不必要的部分和评论来清理你的 CMakeLists.txt ,所以它看起来像这样: [待校准@9369]

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_member_function.cpp)
ament_target_dependencies(talker rclcpp std_msgs)

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

ament_package()

您现在可以构建包,源文件本地安装文件,并运行它,但是让我们先创建订阅节点,这样您就可以看到整个系统都在工作。 [待校准@9370]

3写入订户节点 [待校准@9371]

返回 dev_ws/src/cpp_pubsub/src 创建下一个节点。在终端中输入以下代码: [待校准@9372]

wget -O subscriber_member_function.cpp https://raw.githubusercontent.com/ros2/examples/foxy/rclcpp/topics/minimal_subscriber/member_function.cpp

现在在控制台中输入 ls 将返回: [待校准@9373]

publisher_member_function.cpp  subscriber_member_function.cpp

使用文本编辑器打开 subscriber_member_function.cpp[待校准@9374]

#include <memory>

#include "rclcpp/rclcpp.hpp"
#include "std_msgs/msg/string.hpp"
using std::placeholders::_1;

class MinimalSubscriber : public rclcpp::Node
{
  public:
    MinimalSubscriber()
    : Node("minimal_subscriber")
    {
      subscription_ = this->create_subscription<std_msgs::msg::String>(
      "topic", 10, std::bind(&MinimalSubscriber::topic_callback, this, _1));
    }

  private:
    void topic_callback(const std_msgs::msg::String::SharedPtr msg) const
    {
      RCLCPP_INFO(this->get_logger(), "I heard: '%s'", msg->data.c_str());
    }
    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检查代码 [待校准@9375]

订阅节点的代码与发布者的代码几乎相同。现在节点被命名为 minimal_subscriber ,构造函数使用节点的 create_subscription 类来执行调用。 [待校准@9376]

没有计时器,因为只要数据发布到 topic 话题,用户就会简单地做出反应。 [待校准@9377]

public:
  MinimalSubscriber()
  : Node("minimal_subscriber")
  {
    subscription_ = this->create_subscription<std_msgs::msg::String>(
    "topic", 10, std::bind(&MinimalSubscriber::topic_callback, this, _1));
  }

topic tutorial 中重新调用,发布者和订阅者使用的话题名称和消息类型必须匹配以允许他们进行交流。 [待校准@9378]

[需手动修复的语法] topic_callback 函数接收在该话题上发布的string消息数据,并使用 RCLCPP_INFO 宏将其简单地写入控制台。 [待校准@9379]

此类中唯一的字段声明是订阅。 [待校准@9380]

private:
  void topic_callback(const std_msgs::msg::String::SharedPtr msg) const
  {
    RCLCPP_INFO(this->get_logger(), "I heard: '%s'", msg->data.c_str());
  }
  rclcpp::Subscription<std_msgs::msg::String>::SharedPtr subscription_;

[需手动修复的语法] main 函数完全一样,除了现在它旋转 MinimalSubscriber 节点。对于发布者节点来说,旋转意味着启动计时器,但对于订阅者来说,这仅仅意味着准备在消息来的时候接收消息。 [待校准@9381]

由于此节点与发布节点具有相同的依赖关系,因此没有新的内容要添加到 package.xml[待校准@9382]

3.2 CMakeLists.txt [待校准@9383]

重新打开 CMakeLists.txt ,并在发布者条目下方添加订户节点的可执行文件和目标。 [待校准@9384]

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

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

确保保存文件,然后您的发布/子系统应该可以使用了。 [待校准@9385]

4构建和运行 [待校准@7896]

您可能已经将 rclcppstd_msgs 包安装为ROS 2系统的一部分。在构建之前,最好在工作区 ( dev_ws ) 的根目录下运行 rosdep ,以检查是否缺少依赖项: [待校准@9386]

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

仍然在你的工作空间的根, dev_ws ,建立你的新的包: [待校准@9387]

colcon build --packages-select cpp_pubsub

打开一个新的终端,导航到 dev_ws ,源文件安装文件: [待校准@9231]

. install/setup.bash

现在运行talker节点: [待校准@8780]

ros2 run cpp_pubsub talker

终端应每0.5秒开始发布信息消息,如下所示: [待校准@8781]

[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"

打开另一个终端,再次从 dev_ws 内部源文件,然后启动监听节点: [待校准@9388]

ros2 run cpp_pubsub listener

侦听器将开始将消息打印到控制台,从发布者当时打开的任何消息计数开始,如下所示: [待校准@8782]

[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” 以停止节点旋转。 [待校准@9389]

总结

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

下一步

接下来,您将使用服务/客户端模型创建另一个简单的ROS 2包。同样,你可以选择用 C++ or Python 写。 [待校准@9391]