Skip to content

簡単なサービスとクライアントの作成 (Python)

目標: Pythonを使用してサービスとクライアントノードを作成して実行する。

チュートリアルレベル: 初心者

所要時間: 20分

背景

ノードサービスを使用して通信する場合、データの要求を送信するノードはクライアントノードと呼ばれ、要求に応答するノードはサービスノードと呼ばれます。 要求と応答の構造は.srvファイルによって決定されます。

ここで使用される例は、シンプルな整数加算システムです。1つのノードが2つの整数の合計を要求し、もう1つのノードが結果で応答します。

前提条件

前のチュートリアルで、ワークスペースの作成パッケージの作成方法を学びました。

タスク

1 パッケージの作成

新しいターミナルを開き、ROS 2インストールをソースしてros2コマンドが動作するようにします。

前のチュートリアルで作成したros2_wsディレクトリに移動します。

パッケージはワークスペースのルートではなく、srcディレクトリに作成する必要があることを思い出してください。 ros2_ws/srcに移動し、新しいパッケージを作成します:

bash
ros2 pkg create --build-type ament_python --license Apache-2.0 py_srvcli --dependencies rclpy example_interfaces

ターミナルがパッケージpy_srvcliとそのすべての必要なファイルとフォルダの作成を確認するメッセージを返します。

--dependencies引数は、必要な依存関係行をpackage.xmlに自動的に追加します。 example_interfacesは、要求と応答の構造化に必要な.srvファイルを含むパッケージです:

int64 a
int64 b
---
int64 sum

最初の2行は要求のパラメータで、ダッシュの下が応答です。

1.1 package.xmlの更新

パッケージ作成時に--dependenciesオプションを使用したため、package.xmlに手動で依存関係を追加する必要はありません。

ただし、いつものように、package.xmlに説明、メンテナーのメールと名前、ライセンス情報を必ず追加してください:

xml
<description>Pythonクライアントサーバーチュートリアル</description>
<maintainer email="you@email.com">Your Name</maintainer>
<license>Apache License 2.0</license>

1.2 setup.pyの更新

setup.pyファイルのmaintainermaintainer_emaildescriptionlicenseフィールドに同じ情報を追加します:

python
maintainer='Your Name',
maintainer_email='you@email.com',
description='Pythonクライアントサーバーチュートリアル',
license='Apache License 2.0',

2 サービスノードの作成

ros2_ws/src/py_srvcli/py_srvcliディレクトリ内で、service_member_function.pyという新しいファイルを作成し、以下のコードを貼り付けます:

python
from example_interfaces.srv import AddTwoInts

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)

    def add_two_ints_callback(self, request, response):
        response.sum = request.a + request.b
        self.get_logger().info('Incoming request\na: %d b: %d' % (request.a, request.b))

        return response

def main():
    rclpy.init()

    minimal_service = MinimalService()

    rclpy.spin(minimal_service)

    rclpy.shutdown()

if __name__ == '__main__':
    main()

2.1 コードの確認

最初のimport文は、example_interfacesパッケージからAddTwoIntsサービスタイプをインポートします。 次のimport文は、ROS 2 Pythonクライアントライブラリ、特にNodeクラスをインポートします。

python
from example_interfaces.srv import AddTwoInts

import rclpy
from rclpy.node import Node

MinimalServiceクラスのコンストラクタは、minimal_serviceという名前でノードを初期化します。 その後、サービスを作成し、タイプ、名前、コールバックを定義します。

python
def __init__(self):
    super().__init__('minimal_service')
    self.srv = self.create_service(AddTwoInts, 'add_two_ints', self.add_two_ints_callback)

サービスコールバックの定義は、要求データを受信し、それを合計して、応答として合計を返します。

python
def add_two_ints_callback(self, request, response):
    response.sum = request.a + request.b
    self.get_logger().info('Incoming request\na: %d b: %d' % (request.a, request.b))

    return response

最後に、mainクラスはROS 2 Pythonクライアントライブラリを初期化し、MinimalServiceクラスをインスタンス化してサービスノードを作成し、ノードをスピンしてコールバックを処理します。

2.2 エントリーポイントの追加

ros2 runコマンドでノードを実行できるようにするには、setup.pyros2_ws/src/py_srvcliディレクトリにある)にエントリーポイントを追加する必要があります。

'console_scripts':ブラケット内に以下の行を追加します:

python
'service = py_srvcli.service_member_function:main',

3 クライアントノードの作成

ros2_ws/src/py_srvcli/py_srvcliディレクトリ内で、client_member_function.pyという新しいファイルを作成し、以下のコードを貼り付けます:

python
import sys

from example_interfaces.srv import AddTwoInts
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')
        while not self.cli.wait_for_service(timeout_sec=1.0):
            self.get_logger().info('service not available, waiting again...')
        self.req = AddTwoInts.Request()

    def send_request(self, a, b):
        self.req.a = a
        self.req.b = b
        return self.cli.call_async(self.req)

def main():
    rclpy.init()

    minimal_client = MinimalClientAsync()
    future = minimal_client.send_request(int(sys.argv[1]), int(sys.argv[2]))
    rclpy.spin_until_future_complete(minimal_client, future)
    response = future.result()
    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()

3.1 コードの確認

サービスコードと同様に、まず必要なライブラリをimportします。

python
import sys

from example_interfaces.srv import AddTwoInts
import rclpy
from rclpy.node import Node

MinimalClientAsyncクラスのコンストラクタは、minimal_client_asyncという名前でノードを初期化します。 コンストラクタの定義は、サービスノードと同じタイプと名前のクライアントを作成します。 クライアントとサービスが通信できるように、タイプと名前が一致する必要があります。 コンストラクタのwhileループは、クライアントのタイプと名前に一致するサービスが利用可能かどうかを1秒に1回チェックします。 最後に、新しいAddTwoInts要求オブジェクトを作成します。

python
def __init__(self):
    super().__init__('minimal_client_async')
    self.cli = self.create_client(AddTwoInts, 'add_two_ints')
    while not self.cli.wait_for_service(timeout_sec=1.0):
        self.get_logger().info('service not available, waiting again...')
    self.req = AddTwoInts.Request()

コンストラクタの下にあるsend_requestメソッドは、要求を送信し、応答を受信するか失敗するまでスピンします。

python
def send_request(self, a, b):
    self.req.a = a
    self.req.b = b
    return self.cli.call_async(self.req)

最後にmainメソッドがあり、MinimalClientAsyncオブジェクトを構築し、渡されたコマンドライン引数を使用して要求を送信し、rclpy.spin_until_future_completeを呼び出して結果を待機し、結果をログに記録します。

python
def main():
    rclpy.init()

    minimal_client = MinimalClientAsync()
    future = minimal_client.send_request(int(sys.argv[1]), int(sys.argv[2]))
    rclpy.spin_until_future_complete(minimal_client, future)
    response = future.result()
    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()

警告

ROS 2コールバック内でrclpy.spin_until_future_completeを使用しないでください。 詳細については、sync deadlock articleを参照してください。

3.2 エントリーポイントの追加

サービスノードと同様に、クライアントノードを実行できるようにエントリーポイントを追加する必要があります。

setup.pyファイルのentry_pointsフィールドは以下のようになるはずです:

python
entry_points={
    'console_scripts': [
        'service = py_srvcli.service_member_function:main',
        'client = py_srvcli.client_member_function:main',
    ],
},

4 ビルドと実行

ビルド前に、ワークスペースのルート(ros2_ws)でrosdepを実行して、不足している依存関係をチェックするのが良い習慣です:

bash
rosdep install -i --from-path src --rosdistro humble -y

ワークスペースのルートros2_wsに戻り、新しいパッケージをビルドします:

bash
colcon build --packages-select py_srvcli

新しいターミナルを開き、ros2_wsに移動して、セットアップファイルをソースします:

bash
source install/setup.bash

サービスノードを実行します:

bash
ros2 run py_srvcli service

ノードはクライアントの要求を待機します。

別のターミナルを開き、ros2_ws内から再度セットアップファイルをソースします。 クライアントノードを開始し、その後にスペースで区切られた任意の2つの整数を続けます。 例えば23を選択した場合、クライアントは以下のような応答を受信します:

bash
ros2 run py_srvcli client 2 3

出力:

[INFO] [minimal_client_async]: Result of add_two_ints: for 2 + 3 = 5

サービスノードが動作しているターミナルに戻ります。 要求を受信したときにログメッセージを公開したのが見えます:

[INFO] [minimal_service]: Incoming request
a: 2 b: 3

サーバーターミナルでCtrl+Cを入力して、ノードのスピンを停止します。

まとめ

サービス上でデータを要求および応答する2つのノードを作成しました。 それらをビルドして実行できるように、パッケージ設定ファイルに依存関係と実行可能ファイルを追加し、サービス/クライアントシステムの動作を確認しました。

次のステップ

最後のいくつかのチュートリアルでは、トピックとサービス間でデータを渡すためにインターフェースを利用してきました。 次に、カスタムインターフェースの作成方法を学びます。

関連コンテンツ

  • Pythonでサービスとクライアントを書く方法はいくつかあります。ros2/examplesリポジトリのminimal_clientminimal_serviceパッケージをチェックしてください。

  • このチュートリアルでは、クライアントノードでサービスを呼び出すためにcall_async() APIを使用しました。 Pythonには同期呼び出しと呼ばれる別のサービス呼び出しAPIも利用可能です。 同期呼び出しの使用は推奨しませんが、詳細を学びたい場合は、同期vs非同期クライアントのガイドを読んでください。

Released under the MIT License.