ROS2 Tutorials – Writing a simple service and client (Python)

はじめに

今回はサービスノードを作ってみる.このチュートリアルでは整数を加算するサービスを実装する.

Writing a simple service and client (Python) — ROS 2 Documentation: Iron documentation

Request/Response してみる

service node を作る

いつもどおり,アンダーレイをソースして,パッケージのベースを作る.

$ source ~/ros2_iron/install/setup.bash
$ mkdir -p ros-ws/src
$ cd ros-ws/src
$ ros2 pkg create --build-type ament_python --license Apache-2.0 py_srvcli --dependencies rclpy example_interfaces

--dependencies オプションをつけることで,package.xml に依存関係を追記してくれる.description,maintainer email,name,license の項目はお好みでどうぞ.

<?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>py_srvcli</name>
  <version>0.0.0</version>
  <description>TODO: Package description</description>
  <maintainer email="*****@todo.todo">*****</maintainer>
  <license>Apache-2.0</license>

  <depend>rclpy</depend>                     # オプションで指定した依存関係
  <depend>example_interfaces</depend>        # オプションで指定した依存関係

  <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>

特に example_interfaces はサービスのリクエストとレスポンスのデータ構造の定義が含まれるパッケージで,中身はこんな感じ.最初の2つはリクエスト,最後の1つはレスポンスのデータ構造を表す.なお srv ファイルの実体はアンダーレイにおいてあるので探してみるといいかも.

int64 a
int64 b
---
int64 sum

サービスノードを実装する.

from example_interfaces.srv import AddTwoInts                                                 # Request/Response のデータ構造をインポート

import rclpy
from rclpy.node import Node


class MinimalService(Node):

    def __init__(self):
        super().__init__('minimal_service')
        self.srv = self.create_service(AddTwoInts, 'add_two_ints', self.add_two_ints_callback) # Req/Res のデータタイプ,サービス名,コールバックを指定してサービスを生成

    def add_two_ints_callback(self, request, response):
        response.sum = request.a + request.b                                                   # Req データから Res データとなる合計値を計算
        self.get_logger().info('Incoming request\na: %d b: %d' % (request.a, request.b))

        return response                                                                        # Res を送信


def main():
    rclpy.init()

    minimal_service = MinimalService()

    rclpy.spin(minimal_service)

    rclpy.shutdown()


if __name__ == '__main__':
    main()

setup.py にエントリーポイントを追記する.

from setuptools import find_packages, setup

package_name = 'py_srvcli'

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='*****',
    maintainer_email='*****@todo.todo',
    description='TODO: Package description',
    license='Apache-2.0',
    tests_require=['pytest'],
    entry_points={
        'console_scripts': [
            'service = py_srvcli.service_member_function:main', # エントリーポイントを追記
        ],
    },
)

client node を作る

先ほどと同様に,クライアントを実装する.リクエストとして渡す2つの値はメイン関数の引数から設定する.

import sys

from example_interfaces.srv import AddTwoInts                                   # Request/Response のデータ構造をインポート
import rclpy
from rclpy.node import Node


class MinimalClientAsync(Node):

    def __init__(self):
        super().__init__('minimal_client_async')
        self.cli = self.create_client(AddTwoInts, 'add_two_ints')               # Req/Res のデータタイプ,サービス名を指定してサービスを生成
        while not self.cli.wait_for_service(timeout_sec=1.0):                   # サービスが有効になるまでループ待ち
            self.get_logger().info('service not available, waiting again...')
        self.req = AddTwoInts.Request()                                         # Req オブジェクトを生成

    def send_request(self, a, b):
        self.req.a = a
        self.req.b = b
        self.future = self.cli.call_async(self.req)                             # Req 送信
        rclpy.spin_until_future_complete(self, self.future)                     # Res 待ち
        return self.future.result()


def main():
    rclpy.init()

    minimal_client = MinimalClientAsync()
    response = minimal_client.send_request(int(sys.argv[1]), int(sys.argv[2]))
    minimal_client.get_logger().info(
        'Result of add_two_ints: for %d + %d = %d' %
        (int(sys.argv[1]), int(sys.argv[2]), response.sum))

    minimal_client.destroy_node()
    rclpy.shutdown()


if __name__ == '__main__':
    main()

setup.py にエントリーポイントを追記する.

from setuptools import find_packages, setup

package_name = 'py_srvcli'

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='*****',
    maintainer_email='*****@todo.todo',
    description='TODO: Package description',
    license='Apache-2.0',
    tests_require=['pytest'],
    entry_points={
        'console_scripts': [
            'service = py_srvcli.service_member_function:main',
            'client = py_srvcli.client_member_function:main',   # エントリーポイントを追記
        ],
    },
)

ビルドして実行してみる

ワークスペースのルートに戻って依存関係を確認してから,colcon でビルドする.エラーメッセージ出てるけど無視しておく.

$ rosdep install -i --from-path src --rosdistro iron -y
#All required rosdeps installed successfully
$ colcon build --packages-select py_srvcli --symlink-install
Starting >>> py_srvcli
--- stderr: py_srvcli                   
/usr/lib/python3.12/site-packages/setuptools/command/develop.py:40: EasyInstallDeprecationWarning: easy_install command is deprecated.
!!

        ********************************************************************************
        Please avoid running ``setup.py`` and ``easy_install``.
        Instead, use pypa/build, pypa/installer, pypa/build or
        other standards-based tools.

        See https://github.com/pypa/setuptools/issues/917 for details.
        ********************************************************************************

!!
  easy_install.initialize_options(self)
/usr/lib/python3.12/site-packages/setuptools/_distutils/cmd.py:66: SetuptoolsDeprecationWarning: setup.py install is deprecated.
!!

        ********************************************************************************
        Please avoid running ``setup.py`` directly.
        Instead, use pypa/build, pypa/installer, pypa/build or
        other standards-based tools.

        See https://blog.ganssle.io/articles/2021/10/setup-py-deprecated.html for details.
        ********************************************************************************

!!
  self.initialize_options()
---
Finished <<< py_srvcli [4.10s]

Summary: 1 package finished [6.82s]
  1 package had stderr output: py_srvcli

オーバーレイをソースして,サービスノードを起動.

$ source install/setup.bash 
$ ros2 run py_srvcli service
[INFO] [minimal_service]: Incoming request
a: 2 b: 3

もう一つターミナル開いて,オーバーレイをソースして,クライアントノードを起動.

$ source install/setup.bash 
$ ros2 run py_srvcli client 2 3
[INFO] [minimal_client_async]: Result of add_two_ints: for 2 + 3 = 5

サービスノードが合計値を返してくれれば成功.

おわりに

チュートリアルに沿ってシンプルなサービスを実装した.

Action のように途中経過(Feedback)は要らないけど,任意のタイミングでなにかの状態確認したり,なにかの機能を ON/OFF したり,使い道はたくさんありそう.

コメント

タイトルとURLをコピーしました