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


節點是通過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

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.編譯和運行

在工作空間中編譯新包:

colcon build --packages-select cpp_pubsub

打開一個新終端,source設置文件:

. install/setup.bash

現在運行tlaker節點:

ros2 run cpp_pubsub talker

終端應該每0.5秒發布一次信息消息,像這樣:

打開另一個終端,再次從dev_ws設置setup文件,然后啟動listener節點:

ros2 run cpp_pubsub listener

listener將開始向控制台打印消息,從當時打開的消息計數開始,如下所示:

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

5.總結

本文創建了兩個節點來發布和訂閱話題上的數據。在編譯和運行它們之前,需要將它們的依賴項和可執行文件添加到包配置文件中。

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


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM