【ROS2】トピック

ROS2トピックとは?

​トピックは、ROS2における一方向の非同期的なデータストリームです。この仕組みは「出版/購読モデル(Publish/Subscribe Model)」に基づいています。

  • パブリッシャー (Publisher): 特定のトピックに対して、メッセージ(データ)を送信するノード(ROS2の実行プログラムの単位)。

  • サブスクライバー (Subscriber): 特定のトピックを購読し、そこへパブリッシュされたメッセージを受信するノード。

  • トピック (Topic): パブリッシャーとサブスクライバーを繋ぐ「チャンネル」のようなもので、名前で識別されます。同じトピック名とメッセージ型を持つノード同士が通信できます。

​このモデルの利点は、パブリッシャーとサブスクライバーが互いに存在を知る必要がない点です。パブリッシャーはただデータを送り、サブスクライバーはただデータを受け取るだけです。これにより、システム全体のモジュール性が高まり、柔軟でスケーラブルなロボットアプリケーションの構築が可能になります。

​トピック利用時の注意点

​トピックを効果的に利用するためには、いくつかの重要な点に注意する必要があります。

  • 命名規則:

    • 明確で階層的な名前: トピック名は、そのデータが何であるかを明確に示すべきです。例えば、/robot/front_camera/image_raw のように階層構造を持たせると、データの出所が分かりやすくなります。

    • 小文字とアンダースコア: ROSの慣習として、トピック名は小文字のスネークケース(例: laser_scan)で命名します。

    • グローバルとプライベート: 先頭に / を付けるとグローバルな名前になり、付けないとノードの名前空間に属します。これにより、同じノードを複数起動した際のトピック名の衝突を防げます。

  • QoS (Quality of Service):

    • ​ROS2の大きな特徴の一つがQoS設定です。これにより、通信の信頼性、耐久性、履歴の深さなどを細かく調整できます。

    • 信頼性 (Reliability): RELIABLE(信頼性が高いが、パフォーマンスに影響が出る可能性)と BEST_EFFORT(最善を尽くすが、メッセージの損失を許容)があります。センサーデータのような高頻度で最新の値が重要な場合は BEST_EFFORT、設定値のような必ず届いてほしいデータには RELIABLE を選択します。

    • パブリッシャーとサブスクライバーのQoS設定は互換性がなければ通信が確立しません。 ros2 topic info -v <トピック名> コマンドでQoSの互換性を確認できます。

  • メッセージの型:

    • ​パブリッシャーとサブスクライバーは、全く同じメッセージ型を使用する必要があります。異なる型のメッセージを送受信しようとしても、通信は行われません。

    • ​ros2 topic info <トピック名> で、そのトピックが使用しているメッセージ型を確認できます。

​C++による使い方

​C++でトピックを利用するには、rclcppライブラリを使用します。ここでは、簡単な文字列メッセージを送受信する例を示します。

​パブリッシャー (C++)

#include "rclcpp/rclcpp.hpp"
#include "std_msgs/msg/string.hpp"
#include <chrono>

using namespace std::chrono_literals;

class MinimalPublisher : public rclcpp::Node
{
public:
  MinimalPublisher()
  : Node("minimal_publisher"), count_(0)
  {
    // "topic" という名前で std_msgs::msg::String 型のメッセージを配信
    // QoSの履歴の深さは10に設定
    publisher_ = this->create_publisher<std_msgs::msg::String>("topic", 10);
    
    // 500msごとに timer_callback を呼び出すタイマーを設定
    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;
}


サブスクライバー (C++)

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

using std::placeholders::_1;

class MinimalSubscriber : public rclcpp::Node
{
public:
  MinimalSubscriber()
  : Node("minimal_subscriber")
  {
    // "topic" という名前のトピックを購読
    // メッセージを受信すると topic_callback が呼び出される
    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 & 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;
}

Pythonによる使い方

​Pythonでは、rclpyライブラリを使用します。C++と同様の機能をよりシンプルに記述できます。

​パブリッシャー (Python)

import rclpy
from rclpy.node import Node
from std_msgs.msg import String

class MinimalPublisher(Node):

    def __init__(self):
        super().__init__('minimal_publisher')
        # "topic" という名前で String 型のメッセージを配信
        self.publisher_ = self.create_publisher(String, 'topic', 10)
        timer_period = 0.5  # seconds
        self.timer = self.create_timer(timer_period, self.timer_callback)
        self.i = 0

    def timer_callback(self):
        msg = String()
        msg.data = f'Hello, world! {self.i}'
        self.publisher_.publish(msg)
        self.get_logger().info(f'Publishing: "{msg.data}"')
        self.i += 1


def main(args=None):
    rclpy.init(args=args)
    minimal_publisher = MinimalPublisher()
    rclpy.spin(minimal_publisher)
    minimal_publisher.destroy_node()
    rclpy.shutdown()


if __name__ == '__main__':
    main()

サブスクライバー (Python)

import rclpy
from rclpy.node import Node
from std_msgs.msg import String

class MinimalSubscriber(Node):

    def __init__(self):
        super().__init__('minimal_subscriber')
        # "topic" という名前のトピックを購読
        self.subscription = self.create_subscription(
            String,
            'topic',
            self.listener_callback,
            10)
        self.subscription  # prevent unused variable warning

    def listener_callback(self, msg):
        self.get_logger().info(f'I heard: "{msg.data}"')


def main(args=None):
    rclpy.init(args=args)
    minimal_subscriber = MinimalSubscriber()
    rclpy.spin(minimal_subscriber)
    minimal_subscriber.destroy_node()
    rclpy.shutdown()


if __name__ == '__main__':
    main()

いいなと思ったら応援しよう!