AIロボット入門2022:第2章7節 サービス通信プログラムのつくり方

拙著「ROS2とPythonで作って学ぶAIロボット入門」(講談社)を授業で使用する場合の講義資料のたたき台です.

今回は, [第2章 はじめてのROS2] の2.7節 サービス通信プログラムのつくり方を説明します.

 

 

 

概 要

サービス(service)はROSの通信方法の一つで、双方向通信に使います。ある仕事をお願いするクライアントノード(client node)とそれを処理して返すサービスノード(service node)からなります。今回、作成する簡単なプログラムは、この本で実現するブリングミー(bring me)タスクを実行するプログラムの超簡易版です.クライアントがサービスに取ってきてもらいたい物をリクエスト(request、要求)し,サービスは,それがリストの中にあれば,”はい.これです”とレスポンス(response、応答)するプログラムです。リクエストとレスポンスはサービス定義ファイル(拡張子srv)で定義します.次の手順でやります。

  1. サービス定義ファイルを含むパッケージの作成
  2. サービスノードの作成
  3. クライアントノードの作成

ハンズオン

1.  サービス定義ファイルを含むパッケージの作成

  • パッケージ作成:カスタムサービス型を使うので,それ用のパッケージairobot_interfacesを作成します.この本のDockerイメージやサポートサイトからクローンする場合はこの作業は必要ありません.
    • cd ~/airobot_ws/src/chapter2
    • ros2 pkg create airobot_interfaces --dependencies rosidl_default_generators --build-type ament_cmake  
  • サービス定義ファイルの作成:サービス定義ファイルStringCommand.srvを作成します.以下の内容(プログラムリスト2.12と同じ)の同じ内容のサービスファイル˜/airobot_
    ws/src/chapter2/airobot_interfaces/srv/StringCommand.srv をエディタVSCodiumを使って作成する.
string command
---
string answer
      • mkdir -p ˜/airobot_ws/src/chapter2/airobot_interfaces/srv
      • cd ˜/airobot_ws
      • codium .
  • CMakeLists.txt の編集:CMakeLists.txt の26 行目にプログラムリスト2.15 を追加する.
    プログラムリスト2.15 CMakeLists.txt
    26 rosidl_generate_interfaces(
    27 ${PROJECT_NAME}
    28 "srv/StringCommand.srv"
    29 )
    
  • package.xml の編集:rosidl_default_generators に依存関係があるので,プログラムリスト2.16 の2 行をpackage.xml に追加する.
    プログラムリスト2.16 package.xml
    1 <build_depend>rosidl_default_generators</build_depend>
    2 <member_of_group>rosidl_default_generators</member_of_group>
    
  • ビルド
    • cd ˜/airobot_ws
    • colcon build

 

2.パッケージの作成

bringme_service パッケージを次のコマンドで作成する.今回新しく出た--dependenciesのオプションは依存関係のあるモジュールをpackage.xmlに追記してくれる。

  • cd ~/airobot_ws/src/chapter2
  • ros2 pkg create bringme_service --build-type ament_python --node-name bringme_service_node
    --dependencies rclpy airobot_interfaces  

ソースコードを書く前にmy_serviceパッケージをビルドしておく。

  • $ cd ~/colcon_ws
  • $  colcon build --symlink-install

3.サービスノードの作成

では、依頼された仕事を処理するサービスノードのプログラムを作る。次のプログラムをエディタを使いbringme_server_node.pyというファイル名を付けて~/airobot_ws/src/chapter2/bringme_service/bringme_service/bringme_service_node.pyとして保存する。

import time
import rclpy
from rclpy.node import Node
from airobot_interfaces.srv import StringCommand

class BringmeService(Node):  # ハッピーサービスクラス
    def __init__(self):  # コンストラクタ
        super().__init__('bringme_service')
        # サービスの生成(サービス型,サービス名, コールバック関数)
        self.service = self.create_service(
            StringCommand, 'command',self.callback)
        self.food = ['apple', 'banana', 'candy']   

    def callback(self, request, response):  # コールバック関数
        time.sleep(5)
        for item in self.food:
            if item in request.command:
                response.answer = 'はい,これです.'
                return response
        response.answer = '見つけることができませんでした.'
        return response


def main():  # main関数
    rclpy.init()
    node = BringmeService()
    try:
        rclpy.spin(node)
    except KeyboardInterrupt:
        print("Ctrl+CLが押されました.")
    finally:
        rclpy.shutdown()  

ソースコードの説明は教科書p.64を参照.

4.クライアントノードの作成
次に仕事を依頼するのクライアントノードのソースコートを作成する。以下のソースコードをbringme_client_node.pyという名前で~/airobot_ws/src/chapter2/bringme_service/bringme_servicee/bringme_client_node.pyとして保存する。

import rclpy
from rclpy.node import Node
from airobot_interfaces.srv import StringCommand


class BringmeClient(Node):
    def __init__(self):
        super().__init__('bringme_client_node')
        self.client = self.create_client(StringCommand, 'command') # クライアントの生成
        # サービスが利用できるまで待機
        while not self.client.wait_for_service(timeout_sec=1.0):
            self.get_logger().info('サービスは利用できません.待機中...')        
        self.request = StringCommand.Request()  # リクエストのインスタンス生成

    def send_request(self, order):
        self.request.command = order  # リクエストに値の代入   
        self.future = self.client.call_async(self.request) # サービスのリクエスト


def main(args=None):
    rclpy.init(args=args)
    node = BringmeClient()
    order = input('何を取ってきますか:')
    node.send_request(order)

    while rclpy.ok():
        rclpy.spin_once(node)
        if node.future.done():  # サービスの処理が終了したら
            try:
                response = node.future.result()  # サービスの結果をレスポンスに代入                  
            except Exception as e:
                node.get_logger().info(f"サービスのよび出しは失敗しました.{e}")
            else:                
                node.get_logger().info( # 結果の表示
                    f"\nリクエスト:{node.request.command} -> レスポンス: {response.answer}")
                break  
    rclpy.shutdown()

ソースコードの説明は教科書p.64を参照.なお,このプログラムは非同期通信のプログラムで、ROS2ではその実装にFutureというPythonのオブジェクトが広く使われている。Futureオブジェクトについては以下を参照して欲しい。

5.  package.xmlの編集

ここではパッケージを一般公開しないので変更しない.

6.  setup.pyの編集

パッケージを作成するときにノードbringme_service_nodeを指定したので、エントリーポイントは23行目のように既に設定されている。新しく作成したbringme_client_nodeのエントリーポイントは作成されていないので、23行目の最後に, (カンマ)を挿入し、24行目のように設定する(追加部分は赤文字)。

  • 23行目 ’bringme_service_node = bringme_service.bringme_service_node:main’,
  • 24行目 ’bringme_client_node = bringme_service.bringme_client_node:main’,


7. ビルド

次のコマンドでパッケージを指定してビルドする。

  • cd ~/airobot_ws
  • rosdep install -i --from-path src -y
  • colcon build

8.実行

端末を開き,上下に2分割する.

(1) サービスノードの起動

上の端末で、次のコマンドでサービスノードを起動する。

  • cd ~/airobot_ws
  • source install/setup.bash
  • ros2 run bringme_service bringme_service_node

(2)  クライアントノードの起動

下の端末で、次のコマンドでクライアントノードを実行する。

  • ros2 run bringme_service bringme_client_node
    何を取ってきますか:

と聞かれるので,とってきてほしい食べ物(英語)を入力してください.apple, banana, candy を入すると’ はい,これです.’ とレスポンスが返り,それ以外は’ 見つけることができませんでした.’とレスポンスが返る.教科書図2.23(p.68) のように端末に表示される.

成功したら終わり。うまく動かない場合は、打ち間違えや手順に間違いがないか確認し、再度実行しましょう。お疲れ様!

ホームワーク

  1. チャレンジ2.10 (教科書p.68)をやってみよう!
  2. チャレンジ2.11 (教科書p.68)をやってみよう!
  3. ミニプロ2.3(教科書p.69)をやってみよう!

終わり

 

コメント

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