ROS2を使ったロボットシステムの開発では、ノード間のデータ通信が非常に重要です。
特に、パブリッシャーノードとサブスクライバーノードは、ロボットのセンサーや制御システムの情報をやり取りするための基本的な仕組みです。
本記事では、Pythonを使ってROS2のパブリッシャーノードとサブスクライバーノードを作成し、データを定期的に送受信する方法を詳しく解説します。
パッケージ構造
ROS2では、すべてのコードやリソースはパッケージという単位で管理されます。
各パッケージには、ノード(プログラム)やメッセージ型、サービス、アクションなどが含まれ、ROS2アプリケーションの構成要素となります。パッケージには明確なディレクトリ構造があり、ビルドシステムがこれを元に依存関係を解決し、ソフトウェアを構築します。
ここでは、今回使用しているプロジェクトの具体的なディレクトリ構造を例に、ROS2のパッケージ構造について説明します。
パッケージ構造の全体像
以下は、ros2_ws/
というワークスペース内に作成したmy_sample_package
というROS2パッケージのディレクトリ構造です。
ros2_ws/
├── src/
│ └── my_sample_package/
│ ├── package.xml
│ ├── setup.py
│ ├── setup.cfg
│ ├── resource/
│ │ └── my_sample_package
│ └── my_sample_package/
│ ├── __init__.py
│ ├── sample_publisher.py
│ └── sample_subscriber.py
├── build/
├── install/
└── log/
パブリッシャーノードの作成
ROS2(Robot Operating System 2)では、ロボットやシステム間のデータ通信に「パブリッシュ/サブスクライブ」モデルが使用されます。
このモデルは、情報を送信する側のパブリッシャーノードと、情報を受信する側のサブスクライバーノードによって成り立ちます。それぞれの役割や動作の概略について解説します。
パブリッシャーノードの概略
パブリッシャーノードは、ある特定の情報を他のノードに向けて送信する役割を持つノードです。パブリッシャーは定義されたトピックに対してデータを「パブリッシュ」し、これにより他のノードがそのトピックを通じてデータを受け取れるようにします。
パブリッシャーノードの動作
- トピックの作成: パブリッシャーノードは、送信したいデータに対応するトピックを作成します。例えば、温度センサーからのデータを送信する場合、
temperature
というトピックが考えられます。 - メッセージ型の選択: トピックに送信するデータの形式(メッセージ型)を指定します。例えば、文字列データであれば
std_msgs/String
、整数であればstd_msgs/Int32
を使います。 - データのパブリッシュ: 一定の間隔やイベントに応じて、データをトピックに対してパブリッシュします。
パブリッシャーノードのソースコード (sample_publisher.py
)
パブリッシャーノードの処理概要
このパブリッシャーノードは、ROS2で定期的にメッセージを送信する役割を担っています。具体的には、SamplePublisher
というクラスを使い、一定間隔ごとにメッセージを生成してsample_topic
というトピックにパブリッシュ(送信)します。
処理の流れ
- ノードの初期化:
SamplePublisher
クラスは、rclpy.node.Node
を継承したROS2のノードとして動作します。初期化時に、sample_publisher_node
という名前でノードを生成します。
- パブリッシャーの作成:
self.publisher
でパブリッシャーを作成し、String
型のメッセージをsample_topic
というトピックに送信できるように設定します。キューサイズは10に設定されており、送信待ちのメッセージが10件までキューに保持されます。
- タイマーによるメッセージ送信:
create_timer
関数を使用して、1秒ごとにpublish_message
メソッドが呼び出されるように設定されています。このタイマーにより、定期的にメッセージを送信します。
- メッセージの生成と送信:
publish_message
メソッドでは、String
型のメッセージオブジェクトを生成し、その内容にカウンター値を付加します(例:”サンプルメッセージ番号: 0″)。生成されたメッセージは、self.publisher.publish()
を使ってトピックに送信されます。- また、送信されたメッセージは
get_logger()
を使ってログに表示されます。
- ノードのスピン:
rclpy.spin(publisher_node)
によって、ノードが実行状態に入り、メッセージの送信が続けられます。Ctrl+C
が押されると、ノードが停止し、クリーンにシャットダウンされます。
このパブリッシャーノードは、1秒ごとに"サンプルメッセージ番号"
というメッセージを生成し、トピックsample_topic
に送信します。カウンターがインクリメントされることで、送信されるメッセージは毎回異なります。ROS2の基本的なパブリッシュ機能を利用して、データを定期的に発信するノードとして設計されています。
作成した全体のソースコード
今回作成したパブリッシャーノードのソースコードは以下の通りです。
import rclpy # ROS2のPythonライブラリをインポート
from rclpy.node import Node # ノードクラスをインポート
from std_msgs.msg import String # 標準メッセージタイプStringをインポート
class SamplePublisher(Node):
"""
SamplePublisherクラスはROS2のパブリッシャーノードを定義します。
このノードは定期的にメッセージを送信します。
"""
def __init__(self):
super().__init__('sample_publisher_node') # ノードの名前を設定
self.publisher = self.create_publisher(String, 'sample_topic', 10)
# パブリッシャーを作成
# メッセージタイプ: String
# トピック名: 'sample_topic'
# キューサイズ: 10
self.publish_interval = 1.0 # メッセージを送信する間隔(秒)
self.timer = self.create_timer(self.publish_interval, self.publish_message)
# タイマーを作成し、指定した間隔でpublish_messageメソッドを呼び出す
self.counter = 0 # メッセージのカウンターを初期化
def publish_message(self):
"""
タイマーによって定期的に呼び出されるコールバック関数。
メッセージを作成してパブリッシュします。
"""
message = String() # String型のメッセージオブジェクトを作成
message.data = f"サンプルメッセージ番号: {self.counter}"
# メッセージ内容を設定
self.publisher.publish(message) # メッセージをパブリッシュ
self.get_logger().info(f"送信: '{message.data}'") # ログに送信内容を表示
self.counter += 1 # カウンターをインクリメント
def main(args=None):
"""
パブリッシャーノードのエントリーポイント。
ノードを初期化し、スピン(実行)します。
"""
rclpy.init(args=args) # ROS2の初期化
publisher_node = SamplePublisher() # SamplePublisherノードのインスタンスを作成
try:
rclpy.spin(publisher_node) # ノードをスピンさせて実行
except KeyboardInterrupt:
# Ctrl+Cが押された場合の処理
pass
finally:
publisher_node.destroy_node() # ノードを明示的に破棄
rclpy.shutdown() # ROS2をシャットダウン
if __name__ == '__main__':
main() # スクリプトが直接実行された場合にmain関数を呼び出す
サブスクライバーノードの作成
サブスクライバーノードは、パブリッシャーノードが送信するデータを受信するノードです。サブスクライバーは、特定のトピックに「サブスクライブ(購読)」することで、そのトピックに関連するすべてのメッセージを受信し、適切に処理します。
サブスクライバーノードの概略
- トピックへのサブスクライブ: サブスクライバーノードは、パブリッシャーノードが送信するデータを受信するために、特定のトピックにサブスクライブします。
- メッセージの受信: サブスクライブしたトピックに新しいデータがパブリッシュされると、サブスクライバーノードがそのデータを自動的に受信します。
- データの処理: 受信したデータは、サブスクライバーノード内のコールバック関数で処理されます。例えば、メッセージをログに記録する、他のデバイスに転送するなどの処理が行われます。
サブスクライバーノード のソースコード(sample_subscriber.py
)
このサブスクライバーノードは、ROS2で他のノードから送信されるメッセージを受信する役割を持っています。具体的には、SampleSubscriber
というクラスを使い、sample_topic
というトピックにパブリッシュされたメッセージを受信し、その内容をログに表示します。
処理の流れ
- ノードの初期化:
SampleSubscriber
クラスは、rclpy.node.Node
を継承したROS2のノードとして動作します。初期化時に、sample_subscriber_node
という名前でノードを生成します。
- サブスクライバーの作成:
self.subscription
でサブスクライバーを作成し、String
型のメッセージをsample_topic
というトピックから受信できるように設定します。キューサイズは10で、トピックに送信されるメッセージを保持するために使用します。
- メッセージの受信と処理:
receive_message
というコールバック関数が、sample_topic
にメッセージがパブリッシュされるたびに呼び出されます。この関数では、受信したメッセージの内容をself.get_logger().info()
を使ってログに表示します。
- ノードのスピン:
rclpy.spin(subscriber_node)
により、ノードが実行状態に入り、sample_topic
に送信されたメッセージを受信し続けます。Ctrl+C
が押されるとノードが停止し、クリーンにシャットダウンされます。
このサブスクライバーノードは、sample_topic
にパブリッシュされたメッセージを受信し、その内容をログに出力します。パブリッシャーノードと連携することで、特定のトピックを通じてデータを共有し、受信した情報をもとに処理を行うことができます。ROS2の通信モデルである「パブリッシュ/サブスクライブ」を実現する基本的なノードです。
作成した全体のソースコード
今回作成したサブスクライバーノードのソースコードは以下の通りです。
import rclpy # ROS2のPythonライブラリをインポート
from rclpy.node import Node # ノードクラスをインポート
from std_msgs.msg import String # 標準メッセージタイプStringをインポート
class SampleSubscriber(Node):
"""
SampleSubscriberクラスはROS2のサブスクライバーノードを定義します。
このノードは特定のトピックからメッセージを受信します。
"""
def __init__(self):
super().__init__('sample_subscriber_node') # ノードの名前を設定
self.subscription = self.create_subscription(
String, # 受信するメッセージタイプ
'sample_topic', # サブスクライブするトピック名
self.receive_message, # メッセージ受信時に呼び出すコールバック関数
10 # キューサイズ
)
self.subscription # サブスクリプションオブジェクトを保持(警告回避のため)
def receive_message(self, msg):
"""
メッセージ受信時に呼び出されるコールバック関数。
受信したメッセージをログに出力します。
"""
self.get_logger().info(f"受信: '{msg.data}'") # 受信したメッセージをログに表示
def main(args=None):
"""
サブスクライバーノードのエントリーポイント。
ノードを初期化し、スピン(実行)します。
"""
rclpy.init(args=args) # ROS2の初期化
subscriber_node = SampleSubscriber() # SampleSubscriberノードのインスタンスを作成
try:
rclpy.spin(subscriber_node) # ノードをスピンさせて実行
except KeyboardInterrupt:
# Ctrl+Cが押された場合の処理
pass
finally:
subscriber_node.destroy_node() # ノードを明示的に破棄
rclpy.shutdown() # ROS2をシャットダウン
if __name__ == '__main__':
main() # スクリプトが直接実行された場合にmain関数を呼び出す
実行手順
ここからは実際に作成したソースコードを実行するための手順を解説します。
ROS2パッケージの作成
まずはROS2パッケージを作成します。
cd ~/ros2_ws/src
ros2 pkg create --build-type ament_python my_sample_package --dependencies rclpy std_msgs
このコマンドは、ROS2で新しいPythonベースのパッケージを作成するために使用されます。
ros2 pkg create
: 新しいROS2パッケージを作成します。--build-type ament_python
: Pythonで作成されるパッケージであることを指定します。ament_python
はROS2のビルドツールです。my_sample_package
: 作成するパッケージの名前です。--dependencies rclpy std_msgs
: このパッケージが依存するROS2パッケージを指定します。rclpy
はROS2のPythonクライアントライブラリ、std_msgs
は標準メッセージ型を含むパッケージです。
このコマンドを実行することで、my_sample_package
という名前のROS2 Pythonパッケージが生成され、依存するライブラリも設定されます。
[WARNING]: Unknown license 'TODO: License declaration'. This has been set in the package.xml, but no LICENSE file has been created.
It is recommended to use one of the ament license identitifers:
Apache-2.0
BSL-1.0
BSD-2.0
BSD-2-Clause
BSD-3-Clause
GPL-3.0-only
LGPL-3.0-only
MIT
MIT-0
このように表示されたら成功です。
スクリプトの配置:
- 作成したパッケージの
my_sample_package
ディレクトリ内にscripts
フォルダを作成(存在しない場合)。 - 上記の
sample_publisher.py
とsample_subscriber.py
をscripts
フォルダに配置。
cd my_sample_package/
mkdir scripts
実行権限の付与
作成したソースコードに実行権限を付与します。
cd scripts
chmod +x sample_publisher.py sample_subscriber.py
このコマンドは、ROS2パッケージ内のスクリプトを実行可能にするために使用されます。
cd scripts
: 現在のディレクトリをscripts
ディレクトリに移動します。ここには実行するスクリプト(sample_publisher.py
、sample_subscriber.py
)が配置されています。chmod +x sample_publisher.py sample_subscriber.py
:- **
chmod +x
**は、指定したファイルに実行権限を付与するコマンドです。 sample_publisher.py
およびsample_subscriber.py
のPythonスクリプトに実行権限を追加し、これらのスクリプトが直接実行可能になります。
- **
これにより、ros2 run
コマンドでノードを実行できるようになります。
setup.py
の編集
setup.py
は、ROS2パッケージでPythonスクリプトをビルド・インストールするための設定ファイルです。主にPythonベースのパッケージにおいて、パッケージのビルド方法や依存関係、実行可能なスクリプトの設定を記述します。
このファイルの役割は以下の通りです:
- パッケージのメタデータ管理: パッケージ名、バージョン、メンテナ情報などを定義します。
- 依存関係の指定: パッケージが必要とするライブラリや他のROS2パッケージの依存関係を設定します。
- 実行スクリプトの指定:
entry_points
を使って、ros2 run
コマンドで実行可能なノードやスクリプトを指定します。
つまり、setup.py
はROS2のPythonパッケージが適切にビルドされ、インストール後に正しく動作するための重要な役割を果たします。
以下のコマンドを実行してsetup.pyを編集します。
cd ..
sudo nano setup.py
パッケージの setup.py
にスクリプトをエントリーポイントとして追加。
entry_points={
'console_scripts': [
'sample_publisher = sample_publisher:main',
'sample_subscriber = sample_subscriber:main',
],
},
setup.py
の該当部分は以下のようになります。
from setuptools import setup
import os
from glob import glob
package_name = 'my_sample_package'
setup(
name=package_name,
version='0.0.0',
packages=[package_name],
data_files=[
('share/ament_index/resource_index/packages',
['resource/' + package_name]),
('share/' + package_name, ['package.xml']),
# パッケージのモジュールディレクトリ内のスクリプトをインストール
(os.path.join('lib', package_name), glob(os.path.join(package_name, '*.py'))),
],
install_requires=['setuptools'],
zip_safe=True,
maintainer='your_name', # あなたの名前に置き換えてください
maintainer_email='your_email@example.com', # あなたのメールアドレスに置き換えてください
description='Sample ROS2 publisher and subscriber nodes',
license='Apache License 2.0',
tests_require=['pytest'],
entry_points={
'console_scripts': [
'sample_publisher = my_sample_package.sample_publisher:main',
'sample_subscriber = my_sample_package.sample_subscriber:main',
],
},
)
package.xmlの編集
package.xml
は、ROS2パッケージに関するメタデータや依存関係を管理するための設定ファイルです。このファイルは、ROS2のビルドシステムや他のパッケージが、そのパッケージの情報を理解し、正しく動作するために必要です。
主な役割は以下の通りです:
- パッケージの基本情報: パッケージ名、バージョン、説明、メンテナ情報、ライセンスなどを定義します。
- 依存関係の管理: パッケージがビルドや実行時に必要とする他のROS2パッケージやライブラリを指定します。
- ビルド設定の管理: ビルドツールの種類やパッケージのビルド方法を定義し、正しいビルドプロセスをサポートします。
つまり、package.xml
は、ROS2パッケージ全体の構成と依存関係を明確にし、パッケージを正しくビルド・実行できるようにするための基盤となる重要なファイルです。
実際に使用するpackage.xml
を以下のように作成して保存してください。
<?xml version="1.0"?>
<package format="3">
<name>my_sample_package</name>
<version>0.0.0</version>
<description>Sample ROS2 publisher and subscriber nodes</description>
<maintainer email="your_email@example.com">your_name</maintainer>
<license>Apache License 2.0</license>
<buildtool_depend>ament_python</buildtool_depend>
<build_depend>rclpy</build_depend>
<build_depend>std_msgs</build_depend>
<exec_depend>rclpy</exec_depend>
<exec_depend>std_msgs</exec_depend>
<export>
<build_type>ament_python</build_type>
</export>
</package>
package.xmlについては詳細を以下の記事で解説していますのであわせてご覧ください。
ビルドとソースの設定
最後に作成したROS2パッケージをビルドします。以下のコマンドを実行してください。
cd ~/ros2_ws # ワークスペースに移動(適宜変更)
colcon build # パッケージをビルド
source install/setup.bash # 環境をセットアップ
colcon build
コマンド
このコマンドは、ワークスペース内のパッケージをビルドするために使用します。colcon
はROS2の標準ビルドツールで、すべての依存パッケージやソースコードをビルドし、実行可能な状態にします。ビルドが成功すれば、パッケージが実行できるようになります。
source install/setup.bash
コマンド
このコマンドは、ビルド後の環境設定を行います。install/
ディレクトリに生成されたセットアップスクリプトを実行することで、ROS2の環境が現在のターミナルセッションに適用されます。これにより、ビルドしたパッケージがターミナルから実行可能になります。
ノードの起動
最後に実際にノードを起動して動作を確認します。
パブリッシャーノードの起動
新しいターミナルを起動し、パブリッシャーノードを実行します。
source ~/ros2_ws/install/setup.bash
ros2 run my_sample_package sample_publisher
サブスクライバーノードの起動
続いて別のターミナルを起動し、サブスクライバーノードを実行します。
source ~/ros2_ws/install/setup.bash
ros2 run my_sample_package sample_subscriber
それぞれのノードを実行すると以下のようにパブリッシャーノードノードから送信したメッセージがサブスクライバーノードで受信できていることが確認できました。
まとめ
今回の記事では、ROS2でのパブリッシャーノードとサブスクライバーノードの作成手順と、それらを実行する流れを解説しました。
パブリッシュ/サブスクライブモデルを理解することで、ノード間の通信をスムーズに行うことができ、より複雑なロボットシステムの開発にも応用できます。
ROS2の基本であるパッケージ管理や依存関係の設定も押さえつつ、実際のプログラムを動かすまでの流れをしっかり習得できたのではないでしょうか。これを基に、さらに高度なノードを作成し、実践的なロボットアプリケーションを開発していきましょう。
コメント