ROS2學習之旅(14)——編寫簡單的釋出者和訂閱者(C++)

Love&Robot發表於2021-07-14

節點是通過ROS Graph進行通訊的可執行程式。在本文中,節點將通過話題以字串訊息的形式相互傳遞資訊。這裡使用的例子是一個簡單的"talker"和“listener”系統,一個節點發布資料,另一個節點訂閱話題,以便接收該資料。

這些示例中使用的程式碼可以在這裡找到。

1.建立一個功能

上一節的基礎上,即擁有dev_ws功能包的前提下,執行以下命令:

cd dev_ws/src
ros2 pkg create --build-type ament_cmake cpp_pubsub

終端將返回一條訊息,驗證cpp_pubsub包及其所有必要的檔案和資料夾的建立。

導航到dev_ws/src/cpp_pubsub/src中,這就是包含可執行檔案的原始檔所在的目錄。

2.編寫一個釋出節點

首先,通過以下命令下載示例talker程式碼(在src目錄下):

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

開啟剛剛下載的檔案publisher_member_function.cpp:

#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審閱程式碼

程式碼的頂部為程式所需要的C++標頭檔案,在標頭檔案之後是rclcpp/rclcpp.hpp,它包含了ROS2系統中最常見的部分,最後一是std_msgs/msg/string.hpp,它包含了用於釋出資料的內建訊息型別。

這些程式碼行表示了節點的依賴關係:

#include <chrono>
#include <memory>

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

using namespace std::chrono_literals;

下一行通過繼承rclcpp:: node來建立節點類MinimalPublisher。程式碼中的每個this都指向該節點。

class MinimalPublisher : public rclcpp::Node

公共建構函式將節點命名為minimal_publisher,並將coun_初始化為0。在建構函式內部,使用String訊息型別、話題名稱topic,並且限制訊息佇列的大小來初始化釋出者。接下來,初始化timer_,這將導致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巨集確保將每個釋出的訊息列印到控制檯:

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);
  }

最後是定時器(timer)、釋出器(publisher)和計數器(counter)欄位的宣告:

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新增依賴

返回到dev_ws/src/cpp_pubsub目錄,其中已經包含CMakeLists.txtpackage.xml檔案。

開啟package.xml檔案,確保<description>, <maintainer> and <license> 這些標籤填寫完畢:

<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構建工具依賴項後新增一行,並貼上以下依賴項對應於節點的include語句:

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

tempsnip

這表明了程式執行是需要依賴rclcppstd_msgs

不要忘記儲存檔案。

2.3CMakeLists.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()

現在可以編譯包,生成本地設定檔案並執行它(不要忘記設定環境變數,否則提示包不存在):

ros2 run cpp_pubsub talker
ROS2學習之旅(14)——編寫簡單的釋出者和訂閱者(C++)

3.編寫訂閱者節點

回到dev_ws/src/cpp_pubsub/src來建立訂閱者節點,在終端輸入:

wget -O subscriber_member_function.cpp https://raw.githubusercontent.com/ros2/examples/foxy/rclcpp/topics/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類來執行回撥。

訂閱者沒有計時器,因為訂閱者只是在資料被髮布到topic時進行響應。

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

釋出者和訂閱者使用的話題名稱和訊息型別必須匹配,才能進行通訊。

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函式完全相同,只是現在它啟動MinimalSubscriber節點。對於釋出者節點,spin意味著啟動計時器,但對於訂閱者,它僅僅意味著準備在訊息到來時接收它們。

由於此節點與釋出者節點具有相同的依賴關係,因此無需向package.xml新增任何新內容。

3.2CMakeLists.txt

重新開啟CMakeLists.txt,並在釋出者條目下面新增訂閱者節點的可執行檔案:

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

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

儲存檔案。

4.編譯和執行

可以使用如下命令解決依賴問題:

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

接下來在工作空間中編譯新包:

colcon build --packages-select cpp_pubsub

開啟一個新終端,source設定檔案:

. install/setup.bash

現在執行tlaker節點:

ros2 run cpp_pubsub talker

終端應該每0.5秒釋出一次資訊訊息,像這樣:

ROS2學習之旅(14)——編寫簡單的釋出者和訂閱者(C++)

開啟另一個終端,再次從dev_ws設定setup檔案,然後啟動listener節點:

ros2 run cpp_pubsub listener

listener將開始向控制檯列印訊息,從當時開啟的訊息計數開始,如下所示:

ROS2學習之旅(14)——編寫簡單的釋出者和訂閱者(C++)

在每個終端中輸入Ctrl+C,停止節點執行。

5.總結

本文建立了兩個節點來發布和訂閱話題上的資料。在編譯和執行它們之前,需要將它們的依賴項和可執行檔案新增到包配置檔案中。

如果給您帶來幫助,希望能給點個關注,以後還會陸續更新有關機器人的內容,點個關注不迷路~歡迎大家一起交流學習。
都看到這了,點個推薦再走吧~
未經允許,禁止轉載。

相關文章