ROS2学习笔记之C++编写简单发布订阅节点篇

学习目标:用C++创建并且运行订阅发布的简单节点

背景

节点是一个通过ROS网络进行数据交互的可执行文件的进程。在这篇教程当中,我们编写一个节点通过话题的方式进行字符串消息的传递。在这个例子中我们使用“talker” 和 “listener” 的模式,一个发布数据一个接收数据,以便更好的接收数据。

前期准备

了解前面的教程

学习内容

1. 为例程创建一个包

包应该放在src文件中,首先我们打开一个终端

cd dev_ws/src

然后我们创建一个包

ros2 pkg create --build-type ament_cmake cpp_pubsub

终端会显示创建了一些文件和目录表示创建成功。
然后我们进入到包的src文件夹下,前面我们讲过一个包的源文件一般放在这个地方。

cd cpp_pubsub/src

2. 编写publisher发布者节点

我们通过下面的命令下载talker的样例代码

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

下载完成过后我们选择一个编辑器打开,我们看到代码如下。ROS2这一来感觉就好麻烦,感觉和ros1的nodelet比较像

#include <chrono>
#include <memory>

#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 代码解释

第一行的chrono头文件这个熟悉C++14就知道这是一个关于时间的库,然后我们可以使用chrono_literals500ms,总之就是给时间操作提供了方便。memory不多说关于指针的头文件。rclcpp/rclcpp.hpp让我们可以使用ROS2中功能的头文件,至于为啥是这个名字,rclcpp是ros官方提供的一个C++的客户端。std_msgs/msg/string.hpp很显然是消息类型的头文件。
这些include就代表了该节点的依赖在前面我们讲过依赖关系需要体现在package.xmlCMakeLists.txt中,我们讲完这个cpp过后就会去将这些文件。

#include <chrono>
#include <memory>

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

using namespace std::chrono_literals;

接下来我们创建了一个名为MinimalPublisher的类,它继承自rclcpp::Nodethis指针就代表了这个类。

class MinimalPublisher : public rclcpp::Node

public部分是构造函数,初始化了节点的名字为minimal_publisher,并且初始化计数的count_为0.在构造函数内部,对发布者publisher进行了初始化,指定消息类型为String类型,同时设置了话题名称为topic,和话题的消息缓冲池长度为10。然后初始化了一个定时器,每隔500ms执行一次timer_callback函数。

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是一个宏,和ros_info类似在终端打印消息。这样每发送一个消息我们就可以在终端看到输出。

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_;

MinimalPublisher这个类完了就是main函数,这个节点真正执行的地方。rclcpp::init对节点进行了初始化,rclcpp::spin开始处理回调,包括定时器。

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

2.2 添加依赖

打开dev_ws/src/cpp_pubsub文件夹下的package.xml文件
根据前面讲的内容我们首先对下面关于这个包的描述、维护者、许可证进行编辑

<description>Examples of minimal publisher/subscriber using rclcpp</description>
<maintainer email="[email protected]">Your Name</maintainer>
<license>Apache License 2.0</license>

ament_cmake描述的编译依赖过后之后粘贴下面两行

<exec_depend>rclcpp</exec_depend>
<exec_depend>std_msgs</exec_depend>

这两行表示了在执行的时候需要rclppstd_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_member_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_member_function.cpp)
ament_target_dependencies(talker rclcpp std_msgs)

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

ament_package()

现在我们就可以编译节点然后source一下就可以运行了,但是我们等写好订阅者过后一起编译方便查看整体运行效果。

3. 编写subscriber订阅者节点

首先进入这个包的src文件夹

cd ~/dev_ws/src/cpp_pubsub/src

我们通过下面的命令下载listener的样例代码

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

下载完成过后我们通过ls命令查看当前文件下的文件,我们可以看到现在目录下面有两个文件。

publisher_member_function.cpp  subscriber_member_function.cpp

选择一个编辑器打开subscriber_member_function.cpp

#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 代码解释

订阅者的代码和发布者的代码差不多。现在这个节点的名字叫minimal_subscriber。在构造函数中create_subscription创建了回调。
在订阅者的代码中我们这次没有写定时器,因为订阅者我们不需要发布消息,只需要接收消息。

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

还有一点话题和消息类型和发布者的保持一致,话题名字为topic,消息类型为String

topic_callback函数接收话题上的数据,然后将数据通过RCLCPP_INFO打印到控制台
然后是subscription_的声明。

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函数和发布者的差不多。spin的作用这个和ros1中差不多。
由于发布者和订阅者的依赖是一样的,所以我们不需要对package.xml添加任何内容。

3.2 CMakeLists.txt

打开CMakeLists.txt然后在发布的下面添加生成订阅者可执行文件(add_executable)已经他的依赖链接(ament_target_dependencies),在install部分talker下面添加listener

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

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

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

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

ament_package()

然后记得保存CMakeLists.txt,到现在我们就准备完成了,可以进行编译了。

4. 编译节点并运行

虽然我们已经安装了rclppstd_msgs这些依赖,但是在编译之前进行依赖检查是一个好习惯。--from-path后面根表示的是要进行依赖检查的目录。

cd ~/dev_ws
sudo rosdep install -i --from-path src/cpp_pubsub  --rosdistro eloquent -y

开始编译,我们选择只编译我们新建的包

colcon build --packages-select cpp_pubsub

打开一个新的终端,我们运行发布者

cd ~/dev_ws
source install/setup.bash
ros2 run cpp_pubsub talker

发布者开始运行,同时在终端打印出了消息,频率为2hz

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

打开另外一个新的终端,我们运行订阅者

cd ~/dev_ws
source install/setup.bash
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关掉两个节点。

总结

经过了这么多的教程终于讲到了编写话题订阅的代码,ros2引入了不少的新特性,用到了很多C++14的新特性。

发布了67 篇原创文章 · 获赞 50 · 访问量 3万+

猜你喜欢

转载自blog.csdn.net/qq_38649880/article/details/104418424
今日推荐