前回 は、ROS2 のインストールと、CUI、GUI のいくつかの動作確認を行いました。
今回は、自分でノード(ユーザアプリ)を作ってみたいと思います。これにより、既存のノードを読めるようになると思います。
それでは、やっていきます。
参考文献
環境構築については、こちらを参考にしました。Python よりも C++ の解説が多かったです。ROS2 について、しっかりと詳しく解説されていた印象です。
こちらは、逆に、Python が多く、ROS2 について、基本的なことは解説されていますが、応用的なところとして、AI と関連付けての説明に重点が置かれていた印象です。
はじめに
ROS の公式ページです。ただ、インストール手順や、その他の情報は、ROS の各ディストリビューションごとのページに書かれているので、使用するディストリビューションに応じた公式のドキュメントを参照する必要があります。このページで紹介されているディストリビューションは、Ubuntu と同じように 5年間の長期サポートをしている、最新の Jazzy Jalisco(以下、Jazzy)と、最初に長期サポートを行った Humble Hawksbill(以下、Humble)でした。現状、長期サポートしているのは、この 2つのディストリビューションのようです。
今回は、Humble を使っていこうと思います。Humble が対応している Ubuntu のバージョンは、22.04 のみであり、現在、私の PC の Virtual Box に入ってる Ubuntu が 22.04 だからです。ちなみに、Jazzy の方は、Ubuntu のバージョンは、24.04 のみ対応しています。ちなみに、書籍(改訂新版 ROS 2ではじめよう 次世代ロボットプログラミング〜ロボットアプリケーション開発のための基礎から実践まで)では、Jazzy のインストール方法が解説されています。
Humble のドキュメントは以下です。
参考文献の書籍(改訂新版 ROS 2ではじめよう 次世代ロボットプログラミング〜ロボットアプリケーション開発のための基礎から実践まで)のサポートサイトです。
この書籍のサンプルコードは、以下の GitHub で公開されています。
それでは、やっていきます。
はじめてのROS2プログラミング
ROS2 では、C++、Python、Java が使えますが、まずは、Python を使います。
参考文献の書籍(改訂新版 ROS 2ではじめよう 次世代ロボットプログラミング〜ロボットアプリケーション開発のための基礎から実践まで)は、C++ の解説が多いですが、第5章に、Python のプログラミングの解説があります。
もう一つの参考文献の書籍(ROS2とPythonで作って学ぶAIロボット入門 改訂第2版 (KS理工学専門書))は、2.4 に、はじめての ROS2 プログラミングということで、Python を使った解説があります。
RO2 の公式サイトのチュートリアルでは、以下が参考になります。
ROS2 プログラミングの流れとしては、以下となります。
- ワークスペースの作成
- パッケージの作成
- ソースコードの作成
- ビルド
- 設定ファイルの反映
- ノードの実行
ちなみに、これ以降では、ROS2 の環境変数の読み込み($ source /opt/ros/humble/setup.bash)は書いていませんが必要です。私の環境では、.bashrc に書いたので、常に読み込まれるようになっています。
では、順番にやっていきます。
ワークスペースの作成
ワークスペースとは、複数のパッケージを入れることが出来るディレクトリです。よって、ここで行うことは、ディレクトリを作成することだけです。ディレクトリ名は何でもいいです。ここでは、ros2_ws という名前のディレクトリにします。
ROS2 の公式サイトのチュートリアル(Creating a workspace — ROS 2 Documentation: Humble documentation)にあるように、ワークスペースのディレクトリの中に、src という名前のディレクトリを作っておきます。この srcディレクトリの中に、パッケージを作っていくのがベストプラクティスだと言われています。
$ mkdir -p ros2_ws/src $ cd ros2_ws/src/
パッケージの作成
パッケージとは、ROS2 のソースコードを整理するための単位とのことです。他の人とソースコードを共有する場合に、パッケージ単位で提供します。
ROS2 のパッケージ作成では、ビルドシステムとして ament(アメント)、ビルドツールとして colcon(コルコン)を使用します。
パッケージ名も任意ですが、ここでは、ex_rclpy_topic とします。
早速やってみます。
無事作られたようですが、警告が出ています。ライセンスを明示的に指定しないといけないようです。
$ ros2 pkg create --build-type ament_python ex_rclpy_topic going to create a new package package name: ex_rclpy_topic destination directory: /home/daisuke/svn_/ros2/ros2_ws/src package format: 3 version: 0.0.0 description: TODO: Package description maintainer: ['daisuke <daisuke@gmail.com>'] licenses: ['TODO: License declaration'] build type: ament_python dependencies: [] creating folder ./ex_rclpy_topic creating ./ex_rclpy_topic/package.xml creating source folder creating folder ./ex_rclpy_topic/ex_rclpy_topic creating ./ex_rclpy_topic/setup.py creating ./ex_rclpy_topic/setup.cfg creating folder ./ex_rclpy_topic/resource creating ./ex_rclpy_topic/resource/ex_rclpy_topic creating ./ex_rclpy_topic/ex_rclpy_topic/__init__.py creating folder ./ex_rclpy_topic/test creating ./ex_rclpy_topic/test/test_copyright.py creating ./ex_rclpy_topic/test/test_flake8.py creating ./ex_rclpy_topic/test/test_pep257.py [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
次は、ライセンスを指定します。上の警告のところに、いくつかの推奨のライセンスが表示されています。ここでは、Apache-2.0 を指定します。
$ ros2 pkg create --build-type ament_python --license Apache-2.0 ex_rclpy_topic_license going to create a new package package name: ex_rclpy_topic_license destination directory: /home/daisuke/svn_/ros2/ros2_ws/src package format: 3 version: 0.0.0 description: TODO: Package description maintainer: ['daisuke <daisuke@gmail.com>'] licenses: ['Apache-2.0'] build type: ament_python dependencies: [] creating folder ./ex_rclpy_topic_license creating ./ex_rclpy_topic_license/package.xml creating source folder creating folder ./ex_rclpy_topic_license/ex_rclpy_topic_license creating ./ex_rclpy_topic_license/setup.py creating ./ex_rclpy_topic_license/setup.cfg creating folder ./ex_rclpy_topic_license/resource creating ./ex_rclpy_topic_license/resource/ex_rclpy_topic_license creating ./ex_rclpy_topic_license/ex_rclpy_topic_license/__init__.py creating folder ./ex_rclpy_topic_license/test creating ./ex_rclpy_topic_license/test/test_copyright.py creating ./ex_rclpy_topic_license/test/test_flake8.py creating ./ex_rclpy_topic_license/test/test_pep257.py
ライセンスを指定した場合のディレクトリ構成を確認します。
$ tree . |-- LICENSE |-- ex_rclpy_topic_license | `-- __init__.py |-- package.xml |-- resource | `-- ex_rclpy_topic_license |-- setup.cfg |-- setup.py `-- test |-- test_copyright.py |-- test_flake8.py `-- test_pep257.py 3 directories, 9 files
2つのディレクトリの差分を確認しておきます(比較用に別のワークスペースを作っています)。
ライセンスファイル(LICENSE)の有無と、package.xml、setup.py に少しの差分があります。これを見る限りでは、ライセンスファイルの指定は行っておいた方が良さそうです。
$ diff -upr ros2_ws/src/ex_rclpy_topic/ ros2_ws2/src/ex_ rclpy_topic/ ros2_ws2/src/ex_rclpy_topic/ のみに存在: LICENSE diff -u -upr ros2_ws/src/ex_rclpy_topic/package.xml ros2_ws2/src/ex_rclpy_topic/package.xml --- ros2_ws/src/ex_rclpy_topic/package.xml 2025-06-02 23:00:07.373350497 +0900 +++ ros2_ws2/src/ex_rclpy_topic/package.xml 2025-06-02 23:08:12.246456963 +0900 @@ -5,7 +5,7 @@ <version>0.0.0</version> <description>TODO: Package description</description> <maintainer email="daisuke@gmail.com">daisuke</maintainer> - <license>TODO: License declaration</license> + <license>Apache-2.0</license> <test_depend>ament_copyright</test_depend> <test_depend>ament_flake8</test_depend> diff -u -upr ros2_ws/src/ex_rclpy_topic/setup.py ros2_ws2/src/ex_rclpy_topic/setup.py --- ros2_ws/src/ex_rclpy_topic/setup.py 2025-06-02 23:00:07.394350407 +0900 +++ ros2_ws2/src/ex_rclpy_topic/setup.py 2025-06-02 23:08:12.257456923 +0900 @@ -16,7 +16,7 @@ setup( maintainer='daisuke', maintainer_email='daisuke@gmail.com', description='TODO: Package description', - license='TODO: License declaration', + license='Apache-2.0', tests_require=['pytest'], entry_points={ 'console_scripts': [
あまり、ディレクトリ名が長いのは好きじゃないので、全部消してから、ライセンスの指定を行ったパッケージを作ります。
$ mkdir -p ros2_ws/src $ cd ros2_ws/src/ $ ros2 pkg create --build-type ament_python --license Apache-2.0 ex_rclpy_topic going to create a new package package name: ex_rclpy_topic destination directory: /home/daisuke/svn_/ros2/ros2_ws/src package format: 3 version: 0.0.0 description: TODO: Package description maintainer: ['daisuke <daisuke@gmail.com>'] licenses: ['Apache-2.0'] build type: ament_python dependencies: [] creating folder ./ex_rclpy_topic creating ./ex_rclpy_topic/package.xml creating source folder creating folder ./ex_rclpy_topic/ex_rclpy_topic creating ./ex_rclpy_topic/setup.py creating ./ex_rclpy_topic/setup.cfg creating folder ./ex_rclpy_topic/resource creating ./ex_rclpy_topic/resource/ex_rclpy_topic creating ./ex_rclpy_topic/ex_rclpy_topic/__init__.py creating folder ./ex_rclpy_topic/test creating ./ex_rclpy_topic/test/test_copyright.py creating ./ex_rclpy_topic/test/test_flake8.py creating ./ex_rclpy_topic/test/test_pep257.py $ tree . `-- ex_rclpy_topic |-- LICENSE |-- ex_rclpy_topic | `-- __init__.py |-- package.xml |-- resource | `-- ex_rclpy_topic |-- setup.cfg |-- setup.py `-- test |-- test_copyright.py |-- test_flake8.py `-- test_pep257.py 4 directories, 9 files
パッケージ作成時に、ノード名を指定すると、同時にノードを作成が出来るそうなので、そちらも試します。
$ mkdir -p ros2_ws2/src $ cd ros2_ws2/src/ $ ros2 pkg create --build-type ament_python --license Apache-2.0 --node-name hello ex_rclpy_topic going to create a new package package name: ex_rclpy_topic destination directory: /home/daisuke/svn_/ros2/ros2_ws2/src package format: 3 version: 0.0.0 description: TODO: Package description maintainer: ['daisuke <daisuke@gmail.com>'] licenses: ['Apache-2.0'] build type: ament_python dependencies: [] node_name: hello creating folder ./ex_rclpy_topic creating ./ex_rclpy_topic/package.xml creating source folder creating folder ./ex_rclpy_topic/ex_rclpy_topic creating ./ex_rclpy_topic/setup.py creating ./ex_rclpy_topic/setup.cfg creating folder ./ex_rclpy_topic/resource creating ./ex_rclpy_topic/resource/ex_rclpy_topic creating ./ex_rclpy_topic/ex_rclpy_topic/__init__.py creating folder ./ex_rclpy_topic/test creating ./ex_rclpy_topic/test/test_copyright.py creating ./ex_rclpy_topic/test/test_flake8.py creating ./ex_rclpy_topic/test/test_pep257.py creating ./ex_rclpy_topic/ex_rclpy_topic/hello.py
ノードを指定しない場合と、ノードを指定した場合を比較します。
なるほど、hello.py が作られたのと、setup.py に、hello というエントリポイントが追加されたようです。
$ diff -uprN ros2_ws ros2_ws2 diff -u -uprN ros2_ws/src/ex_rclpy_topic/ex_rclpy_topic/hello.py ros2_ws2/src/ex_rclpy_topic/ex_rclpy_topic/hello.py --- ros2_ws/src/ex_rclpy_topic/ex_rclpy_topic/hello.py 1970-01-01 09:00:00.000000000 +0900 +++ ros2_ws2/src/ex_rclpy_topic/ex_rclpy_topic/hello.py 2025-06-03 22:44:27.741240247 +0900 @@ -0,0 +1,6 @@ +def main(): + print('Hi from ex_rclpy_topic.') + + +if __name__ == '__main__': + main() diff -u -uprN ros2_ws/src/ex_rclpy_topic/setup.py ros2_ws2/src/ex_rclpy_topic/setup.py --- ros2_ws/src/ex_rclpy_topic/setup.py 2025-06-03 22:42:05.392850422 +0900 +++ ros2_ws2/src/ex_rclpy_topic/setup.py 2025-06-03 22:44:27.693240479 +0900 @@ -20,6 +20,7 @@ setup( tests_require=['pytest'], entry_points={ 'console_scripts': [ + 'hello = ex_rclpy_topic.hello:main' ], }, )
ビルド
ソースコードは変更してませんが、まずは、ビルドしてみます。ビルドは、ros2_wsディレクトリで実行してください。
ビルドが成功したようですが、結構な量のファイルが生成されています。
$ colcon build Starting >>> ex_rclpy_topic Finished <<< ex_rclpy_topic [7.87s] Summary: 1 package finished [10.8s] $ tree . |-- build | |-- COLCON_IGNORE | `-- ex_rclpy_topic | |-- build | | `-- lib | | `-- ex_rclpy_topic | | `-- __init__.py | |-- colcon_build.rc | |-- colcon_command_prefix_setup_py.sh | |-- colcon_command_prefix_setup_py.sh.env | |-- ex_rclpy_topic.egg-info | | |-- PKG-INFO | | |-- SOURCES.txt | | |-- dependency_links.txt | | |-- entry_points.txt | | |-- requires.txt | | |-- top_level.txt | | `-- zip-safe | |-- install.log | `-- prefix_override | |-- __pycache__ | | `-- sitecustomize.cpython-310.pyc | `-- sitecustomize.py |-- install | |-- COLCON_IGNORE | |-- _local_setup_util_ps1.py | |-- _local_setup_util_sh.py | |-- ex_rclpy_topic | | |-- lib | | | `-- python3.10 | | | `-- site-packages | | | |-- ex_rclpy_topic | | | | |-- __init__.py | | | | `-- __pycache__ | | | | `-- __init__.cpython-310.pyc | | | `-- ex_rclpy_topic-0.0.0-py3.10.egg-info | | | |-- PKG-INFO | | | |-- SOURCES.txt | | | |-- dependency_links.txt | | | |-- entry_points.txt | | | |-- requires.txt | | | |-- top_level.txt | | | `-- zip-safe | | `-- share | | |-- ament_index | | | `-- resource_index | | | `-- packages | | | `-- ex_rclpy_topic | | |-- colcon-core | | | `-- packages | | | `-- ex_rclpy_topic | | `-- ex_rclpy_topic | | |-- hook | | | |-- ament_prefix_path.dsv | | | |-- ament_prefix_path.ps1 | | | |-- ament_prefix_path.sh | | | |-- pythonpath.dsv | | | |-- pythonpath.ps1 | | | `-- pythonpath.sh | | |-- package.bash | | |-- package.dsv | | |-- package.ps1 | | |-- package.sh | | |-- package.xml | | `-- package.zsh | |-- local_setup.bash | |-- local_setup.ps1 | |-- local_setup.sh | |-- local_setup.zsh | |-- setup.bash | |-- setup.ps1 | |-- setup.sh | `-- setup.zsh |-- log | |-- COLCON_IGNORE | |-- build_2025-06-02_23-21-56 | | |-- events.log | | |-- ex_rclpy_topic | | | |-- command.log | | | |-- stderr.log | | | |-- stdout.log | | | |-- stdout_stderr.log | | | `-- streams.log | | `-- logger_all.log | |-- latest -> latest_build | `-- latest_build -> build_2025-06-02_23-21-56 `-- src `-- ex_rclpy_topic |-- LICENSE |-- ex_rclpy_topic | `-- __init__.py |-- package.xml |-- resource | `-- ex_rclpy_topic |-- setup.cfg |-- setup.py `-- test |-- test_copyright.py |-- test_flake8.py `-- test_pep257.py 34 directories, 66 files
書籍を見ると、--symlink-install というオプションを付けると、Python の場合、ソースコードを変更しても再ビルドが不要になるとのことです。それは付けた方が絶対いいですね。こちらも、結構な量のファイルが生成されています。
$ colcon build --symlink-install Starting >>> ex_rclpy_topic Finished <<< ex_rclpy_topic [10.3s] Summary: 1 package finished [13.2s] $ tree . |-- build | |-- COLCON_IGNORE | `-- ex_rclpy_topic | |-- colcon_build.rc | |-- colcon_command_prefix_setup_py.sh | |-- colcon_command_prefix_setup_py.sh.env | |-- ex_rclpy_topic -> /home/daisuke/svn_/ros2/ros2_ws2/src/ex_rclpy_topic/ex_rclpy_topic | |-- ex_rclpy_topic.egg-info | | |-- PKG-INFO | | |-- SOURCES.txt | | |-- dependency_links.txt | | |-- entry_points.txt | | |-- requires.txt | | |-- top_level.txt | | `-- zip-safe | |-- package.xml -> /home/daisuke/svn_/ros2/ros2_ws2/src/ex_rclpy_topic/package.xml | |-- prefix_override | | |-- __pycache__ | | | `-- sitecustomize.cpython-310.pyc | | `-- sitecustomize.py | |-- resource | | `-- ex_rclpy_topic -> /home/daisuke/svn_/ros2/ros2_ws2/src/ex_rclpy_topic/resource/ex_rclpy_topic | |-- setup.cfg -> /home/daisuke/svn_/ros2/ros2_ws2/src/ex_rclpy_topic/setup.cfg | |-- setup.py -> /home/daisuke/svn_/ros2/ros2_ws2/src/ex_rclpy_topic/setup.py | `-- share | `-- ex_rclpy_topic | `-- hook | |-- pythonpath_develop.dsv | |-- pythonpath_develop.ps1 | `-- pythonpath_develop.sh |-- install | |-- COLCON_IGNORE | |-- _local_setup_util_ps1.py | |-- _local_setup_util_sh.py | |-- ex_rclpy_topic | | |-- lib | | | `-- python3.10 | | | `-- site-packages | | | `-- ex-rclpy-topic.egg-link | | `-- share | | |-- ament_index | | | `-- resource_index | | | `-- packages | | | `-- ex_rclpy_topic -> /home/daisuke/svn_/ros2/ros2_ws2/build/ex_rclpy_topic/resource/ex_rclpy_topic | | |-- colcon-core | | | `-- packages | | | `-- ex_rclpy_topic | | `-- ex_rclpy_topic | | |-- hook | | | |-- ament_prefix_path.dsv | | | |-- ament_prefix_path.ps1 | | | |-- ament_prefix_path.sh | | | |-- pythonpath.dsv | | | |-- pythonpath.ps1 | | | `-- pythonpath.sh | | |-- package.bash | | |-- package.dsv | | |-- package.ps1 | | |-- package.sh | | |-- package.xml -> /home/daisuke/svn_/ros2/ros2_ws2/build/ex_rclpy_topic/package.xml | | `-- package.zsh | |-- local_setup.bash | |-- local_setup.ps1 | |-- local_setup.sh | |-- local_setup.zsh | |-- setup.bash | |-- setup.ps1 | |-- setup.sh | `-- setup.zsh |-- log | |-- COLCON_IGNORE | |-- build_2025-06-02_23-27-27 | | |-- events.log | | |-- ex_rclpy_topic | | | |-- command.log | | | |-- stderr.log | | | |-- stdout.log | | | |-- stdout_stderr.log | | | `-- streams.log | | `-- logger_all.log | |-- latest -> latest_build | `-- latest_build -> build_2025-06-02_23-27-27 `-- src `-- ex_rclpy_topic |-- LICENSE |-- ex_rclpy_topic | `-- __init__.py |-- package.xml |-- resource | `-- ex_rclpy_topic |-- setup.cfg |-- setup.py `-- test |-- test_copyright.py |-- test_flake8.py `-- test_pep257.py 33 directories, 63 files
ディレクトリ構成を見比べたのですが、違いが多すぎて、あまりいいコメントが出来ません。しかし、再ビルドが不要になるというのは、とても大きいことですので、ここでは、必ず、--symlink-install を付けることにします。
設定ファイルの反映
パッケージを作成し、それを実行するには、パッケージの位置などを ROS2 に伝える必要があります。そのため、アンダーレイ設定ファイルの反映と、オーバーレイ設定ファイルの反映の 2つを実行する必要があります。
アンダーレイ設定ファイルの反映というのは、最初に行う必要があると言っていた、以下の読み込みです。
$ source /opt/ros/humble/setup.bash
オーバーレイ設定ファイルの反映というのは、今回作成したパッケージの位置を ROS2 に伝えるもので、以下の読み込みです。ビルド時に、srcディレクトリと同じ位置に、installディレクトリが生成されていて、その中の setup.bashファイルを読み込むと ROS2 が、このパッケージを認識するようになります。
$ source ros2_ws/install/setup.bash
ノードの実行
では、実行してみます。
まず、パッケージ作成時に、ノードを指定しない場合です。タブキーによる補完が効きます。
ノードを作成していないので、まぁ、このようになると思います。
$ ros2 run ex_rclpy_topic node No executable found
次に、ノードを指定してパッケージを作成した場合です。
正しく実行できたようです。
$ ros2 run ex_rclpy_topic hello Hi from ex_rclpy_topic.
ここまでで、とりあえず、ROS2プログラミングの一通りの流れを実行できました。
トピックの実装
公式サイトのチュートリアルでも、書籍でも、ROS2 の公式の GitHub の examples のソースコードを参照してますので、ここでも、それを参照します。
その上で、以下のソースコードを使います。これを、上で作ったパッケージの中(ros2_ws/src/ex_rclpy_topic/ex_rclpy_topic)にコピーします。
自分で、ディレクトリを辿っていく場合は、Git のブランチを humble に切り替えてから、rclpy→topics→minimal_publisher→examples_rclpy_minimal_publisher→publisher_member_function.py に進んでください。
クローンする場合は、以下です。
$ git clone https://github.com/ros2/examples.git $ cd examples/ $ git checkout humble $ cp rclpy/topics/minimal_publisher/examples_rclpy_minimal_publisher/publisher_member_function.py ../ros2_ws/src/ex_rclpy_topic/ex_rclpy_topic/
このファイルは以下のようになっています。
self.create_publisher(String, 'topic', 10) は、Stringタイプのメッセージであり、topic という名前のトピックとし、キューサイズを 10 にする(QoS設定)という意味です。
タイマを作り、0.5秒ごとに、コールバック関数(timer_callback)が呼び出されるようにしています。
main関数の rclpy.spin(minimal_publisher) は、無限ループを作り、Ctrl+C が入力されるまで、Publisher が動き続けることを意味します。
あとは、だいたい予想できそうなソースコードかな、と思います。
# Copyright 2016 Open Source Robotics Foundation, Inc. # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. import rclpy from rclpy.node import Node from std_msgs.msg import String class MinimalPublisher(Node): def __init__(self): super().__init__('minimal_publisher') 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 = 'Hello World: %d' % self.i self.publisher_.publish(msg) self.get_logger().info('Publishing: "%s"' % msg.data) self.i += 1 def main(args=None): rclpy.init(args=args) minimal_publisher = MinimalPublisher() rclpy.spin(minimal_publisher) # Destroy the node explicitly # (optional - otherwise it will be done automatically # when the garbage collector destroys the node object) minimal_publisher.destroy_node() rclpy.shutdown() if __name__ == '__main__': main()
ros2_ws/src/ex_rclpy_topic/package.xml をエディタで開きます。
ここでは、先ほどのソースコードが使っているライブラリの依存関係を書く必要があります。rclpy と std_msgs を使っているので、その依存関係を記述しているということになります。
<?xml version="1.0"?> <?xml-model href="http://download.ros.org/schema/package_format3.xsd" schematypens="http://www.w3.org/2001/XMLSchema"?> <package format="3"> <name>ex_rclpy_topic</name> <version>0.0.0</version> <description>TODO: Package description</description> <maintainer email="daisuke@gmail.com">daisuke</maintainer> <license>Apache-2.0</license> <test_depend>ament_copyright</test_depend> <test_depend>ament_flake8</test_depend> <test_depend>ament_pep257</test_depend> <test_depend>python3-pytest</test_depend> <export> <build_type>ament_python</build_type> </export> </package>
以下の差分のように追記します。
$ diff -upr src/ex_rclpy_topic/package.org.xml s rc/ex_rclpy_topic/package.xml --- src/ex_rclpy_topic/package.org.xml 2025-06-07 21:18:51.818196200 +0900 +++ src/ex_rclpy_topic/package.xml 2025-06-07 20:59:23.963658630 +0900 @@ -6,6 +6,8 @@ <description>TODO: Package description</description> <maintainer email="daisuke@gmail.com">daisuke</maintainer> <license>Apache-2.0</license> + <exec_depend>rclpy</exec_depend> + <exec_depend>std_msgs</exec_depend> <test_depend>ament_copyright</test_depend> <test_depend>ament_flake8</test_depend>
ros2_ws/src/ex_rclpy_topic/setup.py をエディタで開きます。
ここでは、先ほどパッケージを作成したときに、ノードを指定した場合と、ノードを指定しない場合がありましたが、そこの部分に、今回追加したソースコードを実行するためのエントリポイントを追記する必要があります。
from setuptools import find_packages, setup package_name = 'ex_rclpy_topic' setup( name=package_name, version='0.0.0', packages=find_packages(exclude=['test']), data_files=[ ('share/ament_index/resource_index/packages', ['resource/' + package_name]), ('share/' + package_name, ['package.xml']), ], install_requires=['setuptools'], zip_safe=True, maintainer='daisuke', maintainer_email='daisuke@gmail.com', description='TODO: Package description', license='Apache-2.0', tests_require=['pytest'], entry_points={ 'console_scripts': [ ], }, )
以下の差分のように追記します。
$ diff -upr src/ex_rclpy_topic/setup.org.py src/ ex_rclpy_topic/setup.py --- src/ex_rclpy_topic/setup.org.py 2025-06-07 21:18:58.113404537 +0900 +++ src/ex_rclpy_topic/setup.py 2025-06-07 21:06:16.049194470 +0900 @@ -20,6 +20,7 @@ setup( tests_require=['pytest'], entry_points={ 'console_scripts': [ + 'talker = ex_rclpy_topic.publisher_member_function:main', ], }, )
ビルドします。ノードを増やした場合は、最初に一度はビルドがおそらく必要です。ros2_wsディレクトリから実行してください。
$ colcon build --symlink-install Starting >>> ex_rclpy_topic Finished <<< ex_rclpy_topic [7.39s] Summary: 1 package finished [9.72s]
設定ファイルを反映します。オーバーレイ設定ファイルの反映だけでいいと思います。
$ source install/setup.bash
実行してみます。
正しく動いてそうです。
$ ros2 run ex_rclpy_topic talker [INFO] [1749297998.439871494] [minimal_publisher]: Publishing: "Hello World: 0" [INFO] [1749297998.830894738] [minimal_publisher]: Publishing: "Hello World: 1" [INFO] [1749297999.331007439] [minimal_publisher]: Publishing: "Hello World: 2" [INFO] [1749297999.831704379] [minimal_publisher]: Publishing: "Hello World: 3" [INFO] [1749298000.327904624] [minimal_publisher]: Publishing: "Hello World: 4" [INFO] [1749298000.826767945] [minimal_publisher]: Publishing: "Hello World: 5" [INFO] [1749298001.337871749] [minimal_publisher]: Publishing: "Hello World: 6" ^CTraceback (most recent call last): File "/home/daisuke/svn_/ros2/ros2_ws/install/ex_rclpy_topic/lib/ex_rclpy_topic/talker", line 33, in <module> sys.exit(load_entry_point('ex-rclpy-topic', 'console_scripts', 'talker')()) File "/home/daisuke/svn_/ros2/ros2_ws/build/ex_rclpy_topic/ex_rclpy_topic/publisher_member_function.py", line 43, in main rclpy.spin(minimal_publisher) File "/opt/ros/humble/local/lib/python3.10/dist-packages/rclpy/__init__.py", line 226, in spin executor.spin_once() File "/opt/ros/humble/local/lib/python3.10/dist-packages/rclpy/executors.py", line 751, in spin_once self._spin_once_impl(timeout_sec) File "/opt/ros/humble/local/lib/python3.10/dist-packages/rclpy/executors.py", line 740, in _spin_once_impl handler, entity, node = self.wait_for_ready_callbacks(timeout_sec=timeout_sec) File "/opt/ros/humble/local/lib/python3.10/dist-packages/rclpy/executors.py", line 723, in wait_for_ready_callbacks return next(self._cb_iter) File "/opt/ros/humble/local/lib/python3.10/dist-packages/rclpy/executors.py", line 620, in _wait_for_ready_callbacks wait_set.wait(timeout_nsec) KeyboardInterrupt [ros2run]: Interrupt
Publisher の発行するメッセージを受信する Subscriber の方も追加します。
ソースコードは、同じ GitHub の以下になります。
私はクローンしたので、以下のようにコピーしました。
$ cp ../examples/rclpy/topics/minimal_subscriber/examples_rclpy_minimal_subscriber/subscriber_member_function.py src/ex_rclpy_topic/ex_rclpy_topic/
内容は以下です。
Publisher とほとんど同じ内容になっています。self.subscription = self.create_subscription(String, 'topic', self.listener_callback, 10) が、少し違いますが、対象のトピックを指定して、受信した場合のコールバック関数(self.listener_callback)を指定しているところが違います。
# Copyright 2016 Open Source Robotics Foundation, Inc. # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. import rclpy from rclpy.node import Node from std_msgs.msg import String class MinimalSubscriber(Node): def __init__(self): super().__init__('minimal_subscriber') 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('I heard: "%s"' % msg.data) def main(args=None): rclpy.init(args=args) minimal_subscriber = MinimalSubscriber() rclpy.spin(minimal_subscriber) # Destroy the node explicitly # (optional - otherwise it will be done automatically # when the garbage collector destroys the node object) minimal_subscriber.destroy_node() rclpy.shutdown() if __name__ == '__main__': main()
依存関係は同じライブラリを使っているため、package.xml に追記は必要ありません。
Subscriber もノードとして実行するので、setup.py に、エントリポイントを追加する必要があります。
以下の差分のように追記します。編集前の setup.py と比較してるため、Publisher エントリポイントを追加した差分も入っています。
$ diff -upr src/ex_rclpy_topic/setup.org.py src/ex_rclpy_topic/setup.py --- src/ex_rclpy_topic/setup.org.py 2025-06-07 21:23:49.340223302 +0900 +++ src/ex_rclpy_topic/setup.py 2025-06-07 23:02:16.976865707 +0900 @@ -20,6 +20,8 @@ setup( tests_require=['pytest'], entry_points={ 'console_scripts': [ + 'talker = ex_rclpy_topic.publisher_member_function:main', + 'listener = ex_rclpy_topic.subscriber_member_function:main', ], }, )
ビルドします。
$ colcon build --symlink-install Starting >>> ex_rclpy_topic Finished <<< ex_rclpy_topic [10.4s] Summary: 1 package finished [14.7s]
オーバーレイ設定ファイルを反映して、実行してみます。
$ source install/setup.bash $ ros2 run ex_rclpy_topic talker [INFO] [1749305249.157788926] [minimal_publisher]: Publishing: "Hello World: 0" [INFO] [1749305249.568920217] [minimal_publisher]: Publishing: "Hello World: 1" [INFO] [1749305250.067770284] [minimal_publisher]: Publishing: "Hello World: 2" [INFO] [1749305250.565504097] [minimal_publisher]: Publishing: "Hello World: 3" [INFO] [1749305251.064488536] [minimal_publisher]: Publishing: "Hello World: 4" [INFO] [1749305251.564976741] [minimal_publisher]: Publishing: "Hello World: 5" [INFO] [1749305252.062874459] [minimal_publisher]: Publishing: "Hello World: 6" [INFO] [1749305252.564983971] [minimal_publisher]: Publishing: "Hello World: 7" [INFO] [1749305253.062627703] [minimal_publisher]: Publishing: "Hello World: 8" [INFO] [1749305253.567195950] [minimal_publisher]: Publishing: "Hello World: 9" [INFO] [1749305254.145280067] [minimal_publisher]: Publishing: "Hello World: 10" [INFO] [1749305254.610332360] [minimal_publisher]: Publishing: "Hello World: 11" [INFO] [1749305255.064946204] [minimal_publisher]: Publishing: "Hello World: 12" [INFO] [1749305255.566667225] [minimal_publisher]: Publishing: "Hello World: 13" [INFO] [1749305256.068202391] [minimal_publisher]: Publishing: "Hello World: 14" [INFO] [1749305256.564076172] [minimal_publisher]: Publishing: "Hello World: 15" [INFO] [1749305257.065215361] [minimal_publisher]: Publishing: "Hello World: 16" [INFO] [1749305257.565358263] [minimal_publisher]: Publishing: "Hello World: 17" ^CTraceback (most recent call last): File "/home/daisuke/svn_/ros2/ros2_ws/install/ex_rclpy_topic/lib/ex_rclpy_topic/talker", line 33, in <module> sys.exit(load_entry_point('ex-rclpy-topic', 'console_scripts', 'talker')()) File "/home/daisuke/svn_/ros2/ros2_ws/build/ex_rclpy_topic/ex_rclpy_topic/publisher_member_function.py", line 43, in main rclpy.spin(minimal_publisher) File "/opt/ros/humble/local/lib/python3.10/dist-packages/rclpy/__init__.py", line 226, in spin executor.spin_once() File "/opt/ros/humble/local/lib/python3.10/dist-packages/rclpy/executors.py", line 751, in spin_once self._spin_once_impl(timeout_sec) File "/opt/ros/humble/local/lib/python3.10/dist-packages/rclpy/executors.py", line 740, in _spin_once_impl handler, entity, node = self.wait_for_ready_callbacks(timeout_sec=timeout_sec) File "/opt/ros/humble/local/lib/python3.10/dist-packages/rclpy/executors.py", line 723, in wait_for_ready_callbacks return next(self._cb_iter) File "/opt/ros/humble/local/lib/python3.10/dist-packages/rclpy/executors.py", line 620, in _wait_for_ready_callbacks wait_set.wait(timeout_nsec) KeyboardInterrupt [ros2run]: Interrupt
別のターミナルで、Subscriber を実行します。
Publisher が発行したメッセージを Subscriber で受信できていそうです。
$ source install/setup.bash $ ros2 run ex_rclpy_topic listener [INFO] [1749305254.272581336] [minimal_subscriber]: I heard: "Hello World: 10" [INFO] [1749305254.612006673] [minimal_subscriber]: I heard: "Hello World: 11" [INFO] [1749305255.068237569] [minimal_subscriber]: I heard: "Hello World: 12" [INFO] [1749305255.567216857] [minimal_subscriber]: I heard: "Hello World: 13" [INFO] [1749305256.069195889] [minimal_subscriber]: I heard: "Hello World: 14" [INFO] [1749305256.565059191] [minimal_subscriber]: I heard: "Hello World: 15" ^CTraceback (most recent call last): File "/home/daisuke/svn_/ros2/ros2_ws/install/ex_rclpy_topic/lib/ex_rclpy_topic/listener", line 33, in <module> sys.exit(load_entry_point('ex-rclpy-topic', 'console_scripts', 'listener')()) File "/home/daisuke/svn_/ros2/ros2_ws/build/ex_rclpy_topic/ex_rclpy_topic/subscriber_member_function.py", line 41, in main rclpy.spin(minimal_subscriber) File "/opt/ros/humble/local/lib/python3.10/dist-packages/rclpy/__init__.py", line 226, in spin executor.spin_once() File "/opt/ros/humble/local/lib/python3.10/dist-packages/rclpy/executors.py", line 751, in spin_once self._spin_once_impl(timeout_sec) File "/opt/ros/humble/local/lib/python3.10/dist-packages/rclpy/executors.py", line 740, in _spin_once_impl handler, entity, node = self.wait_for_ready_callbacks(timeout_sec=timeout_sec) File "/opt/ros/humble/local/lib/python3.10/dist-packages/rclpy/executors.py", line 723, in wait_for_ready_callbacks return next(self._cb_iter) File "/opt/ros/humble/local/lib/python3.10/dist-packages/rclpy/executors.py", line 620, in _wait_for_ready_callbacks wait_set.wait(timeout_nsec) KeyboardInterrupt [ros2run]: Interrupt
今回は以上になります。
おわりに
今回は、ROS2 のプログラミングをやってみました。トピック以外に、サービス、パラメータ、アクションがありますが、そのあたりは必要なときにやればいいとして、次回は、ROS2 の実装を見ていきたいと思います。
最後になりましたが、エンジニアグループのランキングに参加中です。
気楽にポチッとよろしくお願いいたします🙇
今回は以上です!
最後までお読みいただき、ありがとうございました。