🐍

rclpy pkgのフォルダ構造&Pythonファイルの分割(ROS2)

2021/09/08に公開

更新🐍:モジュールの読み込み方法が変更され、通常の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ファイル分割のサンプルを示します。

以下のリンクにもプログラムを掲載しています。

https://github.com/Ar-Ray-code/rclpy_separate_example

ファイル構造

最初にファイル命名規則について書こうと思います。

…というのもROS1のPythonファイルの配置方法とROS2のPythonファイルの配置方法は大きく違い、なれないうちは移植に少し苦労するためです。

以下にファイル構造の図を示します。

枠の色は名称の指定を示していて、赤枠は名称が固定、青枠はROSパッケージ名に依存、緑枠はsetup.pyに依存しています。

紫枠はROS2が実行するPythonプログラムが参照します。

パッケージ名の統一

ROSにはパッケージ名が存在します。具体的には、

ros2 run <pkg-name> <executable>

<pkg-name>に相当する部分です。この例ではexample_pkg_pyです。この名称で統一すべき箇所は次の通りです。

  • setup.pysetup()name=の部分

  • package.xml<name>タグの部分

  • setup.cfgのパス。例:$base/lib/example_pkg_py

  • resource/の中にある空ファイル

  • その他パス指定時

setup.py

ROS2にとってsetup.pyCMakeLists.textに相当するものです。data_filesはlaunchファイルやその他インストールすべきファイルを指定します(複数指定可)。また、entry_pointsros2 runで実行するPythonファイルと実行開始の関数を指定します(複数指定可)。

この場合は実行名がscripts_mainとなり、実行時にscripts_main.pyros_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__/

https://github.com/Ar-Ray-code/rclpy_separate_example

Discussion