rclpy pkgのフォルダ構造&Pythonファイルの分割(ROS2)
更新🐍:モジュールの読み込み方法が変更され、通常のPythonモジュールと同じ読み込み方法のみでロードする必要があります。
from . import a
from .B import b
from .C.C_child import c
みなさんはrclpyをよく使いますか?
積極的に使う人もいれば、そもそもPython嫌いな人もいると思います。
私は、画像処理関連のプログラムをROSに移植するときにrclpyをよく使います。やっぱりそのまま移植できるのは楽ですからねーー。
ところで、みなさんはrclpyのファイル分割をしたことはありますか?
どこのGitHub上のrclpy使用の例を見ても1ファイルで完結している場合が多く、あまり参考にできるファイルがなさそうです。
ここでは、最新のROS-Foxyを使ったrclpyファイル分割のサンプルを示します。
以下のリンクにもプログラムを掲載しています。
ファイル構造
最初にファイル命名規則について書こうと思います。
…というのもROS1のPythonファイルの配置方法とROS2のPythonファイルの配置方法は大きく違い、なれないうちは移植に少し苦労するためです。
以下にファイル構造の図を示します。
枠の色は名称の指定を示していて、赤枠は名称が固定、青枠はROSパッケージ名に依存、緑枠はsetup.pyに依存しています。
紫枠はROS2が実行するPythonプログラムが参照します。
パッケージ名の統一
ROSにはパッケージ名が存在します。具体的には、
ros2 run <pkg-name> <executable>
の<pkg-name>
に相当する部分です。この例ではexample_pkg_py
です。この名称で統一すべき箇所は次の通りです。
-
setup.py
のsetup()
のname=
の部分 -
package.xml
の<name>
タグの部分 -
setup.cfg
のパス。例:$base/lib/example_pkg_py
-
resource/
の中にある空ファイル -
その他パス指定時
setup.py
ROS2にとってsetup.py
はCMakeLists.text
に相当するものです。data_files
はlaunchファイルやその他インストールすべきファイルを指定します(複数指定可)。また、entry_points
はros2 run
で実行するPythonファイルと実行開始の関数を指定します(複数指定可)。
この場合は実行名がscripts_main
となり、実行時にscripts_main.py
のros_main()
から始まります。
from setuptools import setup, find_packages
from glob import glob
import os
package_name = 'example_pkg_py'
setup(
name=package_name,
version='0.0.0',
packages=find_packages(),
data_files=[
('share/ament_index/resource_index/packages',
['resource/' + package_name]),
('share/' + package_name, ['package.xml']),
(os.path.join('share', package_name), glob('./launch/*.launch.py')),
],
entry_points={
'console_scripts': [
'scripts_main = '+ package_name +'.scripts_main:ros_main',
],
}
)
setup.cfg
ROS2ファイルがインストールされる場所を指定します。
パッケージ名を変更するときはexample_pkg_py
の部分を適宜変更してください。
[develop]
script-dir=$base/lib/example_pkg_py
[install]
install-scripts=$base/lib/example_pkg_py
resource
ROS2パッケージ名と同じ空ファイルを作ってください。
Pythonファイル
実際に動作するPythonファイルを配置します。ディレクトリ名はROS2パッケージと同じにします。
このときにすべての子ディレクトリに__init__.py
を配置してください。
Pythonファイルの分割
__init__.py
をすべての子ディレクトリで作成した状態でビルドすることでモジュールの読み込みができるようになります。
scripts_main.py
#!/bin/python3
import rclpy
from rclpy.node import Node
from . import a
from .B import b
from .C.C_child import c
class example_node(Node):
def __init__(self) -> None:
super().__init__("scripts_main")
self.self_introduction()
def hello(self):
print("Hello! I'm main")
def self_introduction(self):
class_a = a.classA()
class_b = b.classB()
class_c = c.classC()
self.hello()
class_a.hello()
class_b.hello()
class_c.hello()
print("\ntomorrow...")
self.hello()
a.classA.hello(self)
b.classB.hello(self)
c.classC.hello(self)
def ros_main(args = None):
rclpy.init(args=args)
ros_class = example_node()
try:
rclpy.spin(ros_class)
except KeyboardInterrupt:
pass
finally:
ros_class.destroy_node()
rclpy.shutdown()
if __name__=='__main__':
ros_main()
./a.py
from . import a2
class classA:
def hello(self):
print("Hello! I'm A.")
a2.hello2()
./a2.py
def hello2():
print("Hello! I'm A2.")
./B/b.py
class classB:
def hello(self):
print("Hello! I'm B.")
./C/C_child/c.py
※./C/__init__.py
と./C/C_child/__init__.py
をどちらも作ってください
class classC:
def hello(self):
print("Hello! I'm C.")
ビルド
成功するとWarningが出ません。
cd <your ros2 workspace>
colcon build --symlink-install
実行例
runのほう
git clone https://github.com/Ar-Ray-code/rclpy_separate_example.git
ros2 run example_pkg_py scripts_main
# 実行結果 --------------------------------------------
Hello! I'm main
Hello! I'm A.
Hello! I'm A2.
Hello! I'm B.
Hello! I'm C.
tomorrow...
Hello! I'm main
Hello! I'm A.
Hello! I'm A2.
Hello! I'm B.
Hello! I'm C.
^C
launchのほう
ros2 launch example_pkg_py example.launch.py
# 実行結果 --------------------------------------------
[INFO] [launch]: All log files can be found below /home/ubuntu/.ros/log/2021-09-08-21-57-57-108328-ubuntu-laptop-49524
[INFO] [launch]: Default logging verbosity is set to INFO
[INFO] [scripts_main-1]: process started with pid [49526]
^C[WARNING] [launch]: user interrupted with ctrl-c (SIGINT)
[scripts_main-1] Hello! I'm main
[scripts_main-1] Hello! I'm A.
[scripts_main-1] Hello! I'm A2.
[scripts_main-1] Hello! I'm B.
[scripts_main-1] Hello! I'm C.
[scripts_main-1]
[scripts_main-1] tomorrow...
[scripts_main-1] Hello! I'm main
[scripts_main-1] Hello! I'm A.
[scripts_main-1] Hello! I'm A2.
[scripts_main-1] Hello! I'm B.
[scripts_main-1] Hello! I'm C.
[INFO] [scripts_main-1]: process has finished cleanly [pid 49526]
補足
実行時に__pycache__/
という名前のディレクトリができるので、GitHubにアップするときなどは.gitignore
ファイルで除外しましょう。
# .gitignore
__pycache__/
Discussion