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的新特性。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章