ROS 2においてC++を用いたPub&Sub通信を行う方法(コンポーネントを使用)

# はじめに
　今回の記事では、ROS 2でC++を用いてPub&Sub通信を行う方法について記載します。今回の書き方では、コンポーネントを用いた方法でコーディングを行っていきます。このコンポーネントは、ROSにおけるノードレットに相当するものになります。コンポーネントを使用しない書き方でもPub&Sub通信は実行できますが、[ドキュメント](https://docs.ros.org/en/humble/Concepts/Intermediate/About-Composition.html)に自身でmainを書くスタイルのノードは推奨できないといった旨の記載がありましたので、今回はコンポーネントを用いた方法でコーディングを行っていきます。

:::note info
この記事は[ROSに関する投稿](https://qiita.com/kccs_yoshiteru-imamura/items/ef418e9beb508ac52c5d)の一部です。
目次は[こちら](https://qiita.com/kccs_yoshiteru-imamura/items/ef418e9beb508ac52c5d#%E7%9B%AE%E6%AC%A1)
:::

### 前提条件
　今回の記事は、以下の環境で動かすことを前提に記載しています。
|            |     条件     |
|:-----------|:-------------|
| OS         | Ubuntu 22.04 |
| ROS        | ROS 2 humble |

# パッケージ作成
　以下のコマンドでC++用パッケージを作成します。今回パッケージ名は「pub_sub_cpp_01」としています。
``` shell
cd ~/ros2_ws/src/
ros2 pkg create --build-type ament_cmake pub_sub_cpp_01
```

# ソースコード
　以下がROS 2でコンポーネントを用いてPub&Sub通信を行うためのコードになります。ROSと比較した際に特徴的な箇所としては、QoSの設定があります。今回の場合ですと「rclcpp::QoS qos(rclcpp::KeepLast{10});」と記載していますが、これはROSでPub&Sub通信を行う際に設定していたqueue_sizeに相当します。この他にも、Durability、Livelinessなどの設定が存在しますが、少し量があるため今回は割愛させて頂きます。詳細は以下のリンクなどを参考にしてください。また、その他に特徴的な点としては、冒頭でもふれたコンポーネントを用いた書き方の部分が挙げられます。publisher.cppとsubscriber.cppはmain文を持ちませんが、代わりに最後の行にRCLCPP_COMPONENTS_REGISTER_NODE()が記述されています。この行でコンポーネントの登録を行い、このクラスがコンポーネントであることを明示します。

https://docs.ros.org/en/humble/Concepts/Intermediate/About-Quality-of-Service-Settings.html

https://docs.ros2.org/foxy/api/rclcpp/classrclcpp_1_1QoS.html

``` cpp:~/ros2_ws/src/pub_sub_cpp_01/include/pub_sub_cpp_01/publisher.hpp
#ifndef PUBLISHER_H_
#define PUBLISHER_H_

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

using namespace std::chrono_literals;

namespace kccs_pub_sub_cpp_01 {

class Publisher : public rclcpp::Node {
 public:
  Publisher(const rclcpp::NodeOptions& options);

 private:
  void timer_callback();
  rclcpp::TimerBase::SharedPtr timer_;
  rclcpp::Publisher<std_msgs::msg::String>::SharedPtr pub_;
  size_t count_;
};

}  // namespace kccs_pub_sub_cpp_01

#endif
```

``` cpp:~/ros2_ws/src/pub_sub_cpp_01/include/pub_sub_cpp_01/subscriber.hpp
#ifndef SUBSCRIBER_H_
#define SUBSCRIBER_H_

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

namespace kccs_pub_sub_cpp_01 {

class Subscriber : public rclcpp::Node {
 public:
  Subscriber(const rclcpp::NodeOptions& options);

 private:
  void topic_callback(const std_msgs::msg::String::SharedPtr msg);
  rclcpp::Subscription<std_msgs::msg::String>::SharedPtr sub_;
};

}  // namespace kccs_pub_sub_cpp_01

#endif
```

``` cpp:~/ros2_ws/src/pub_sub_cpp_01/src/publisher.cpp
#include "pub_sub_cpp_01/publisher.hpp"

#include <chrono>
#include <cstdio>
#include <memory>
#include <utility>

using namespace std::chrono_literals;

namespace kccs_pub_sub_cpp_01 {

Publisher::Publisher(const rclcpp::NodeOptions& options)
    : Node("publisher", options) {
  rclcpp::QoS qos(rclcpp::KeepLast{10});
  pub_ = this->create_publisher<std_msgs::msg::String>("chatter", qos);

  timer_ =
      this->create_wall_timer(500ms, [this]() -> void { timer_callback(); });
};

void Publisher::timer_callback() {
  std::unique_ptr<std_msgs::msg::String> msg_ = std::make_unique<std_msgs::msg::String>();
  msg_->data = "Hello World: " + std::to_string(count_++);
  RCLCPP_INFO(this->get_logger(), "Publishing: '%s'", msg_->data.c_str());
  pub_->publish(std::move(msg_));
}

}  // namespace kccs_pub_sub_cpp_01

RCLCPP_COMPONENTS_REGISTER_NODE(kccs_pub_sub_cpp_01::Publisher)
```

``` cpp:~/ros2_ws/src/pub_sub_cpp_01/src/subscriber.cpp
#include "pub_sub_cpp_01/subscriber.hpp"

namespace kccs_pub_sub_cpp_01 {

Subscriber::Subscriber(const rclcpp::NodeOptions& options)
    : Node("subscriber", options) {
  rclcpp::QoS qos(rclcpp::KeepLast{10});
  sub_ = this->create_subscription<std_msgs::msg::String>(
      "chatter", qos,
      [this](const std_msgs::msg::String::SharedPtr msg) -> void {
        topic_callback(std::move(msg));
      });
}

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

}  // namespace kccs_pub_sub_cpp_01

RCLCPP_COMPONENTS_REGISTER_NODE(kccs_pub_sub_cpp_01::Subscriber)
```

``` cmake:~/ros2_ws/src/pub_sub_cpp_01/CMakeLists.txt
cmake_minimum_required(VERSION 3.8)
project(pub_sub_cpp_01)

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(rclcpp_components REQUIRED)
find_package(std_msgs REQUIRED)

include_directories(include)

function(add_dependencies_func library)
  target_compile_definitions(${library}
    PRIVATE "KCCS_PUB_SUB_TEST_NODES_CPP_BUILDING_LIBRARY")
  ament_target_dependencies(${library}
    "rclcpp"
    "rclcpp_components"
    "std_msgs")
endfunction()

add_library(pub_sub_cpp_01_lib SHARED
  src/publisher.cpp
  src/subscriber.cpp
)

add_dependencies_func(pub_sub_cpp_01_lib)

rclcpp_components_register_node(pub_sub_cpp_01_lib
  PLUGIN "kccs_pub_sub_cpp_01::Publisher"
  EXECUTABLE publisher)
rclcpp_components_register_node(pub_sub_cpp_01_lib
  PLUGIN "kccs_pub_sub_cpp_01::Subscriber"
  EXECUTABLE subscriber)

install(TARGETS
  pub_sub_cpp_01_lib
  ARCHIVE DESTINATION lib
  LIBRARY DESTINATION lib
  RUNTIME DESTINATION bin)

if(BUILD_TESTING)
endif()

ament_package()
```

# ビルド
　以下のコマンドでビルドします。
``` shell
cd ~/ros2_ws/src/
colcon build
```

# 実行
``` shell :ターミナル1(publisher)
ros2 run pub_sub_cpp_01 publisher
```

``` shell :ターミナル2(subscriber)
ros2 run pub_sub_cpp_01 subscriber
```

# 実行結果
・ターミナル1(publisher)
![Screenshot from 2023-11-15 16-14-12.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3635173/758e5928-64de-c9b7-fb5e-c2a51a7c6571.png)


・ターミナル2(subscriber)
![Screenshot from 2023-11-15 16-18-41.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3635173/bf64ece6-5472-9f86-30bd-65d638e3d93a.png)

# おわりに
　今回の記事ではコンポーネントを用いたPub&Sub通信の方法について記載しました。次回の記事では今回作成したコンポーネントをlaunchファイルから実行する方法について記載する予定ですので、興味がある方は是非ご覧ください。

# 参考文献
https://docs.ros.org/en/humble/Tutorials/Beginner-Client-Libraries/Writing-A-Simple-Cpp-Publisher-And-Subscriber.html