拙著「ROS2とPythonで作って学ぶAIロボット入門」(講談社)を授業で使用する場合の講義資料のたたき台です.
今回は, [第2章 はじめてのROS2] の2.7節 サービス通信プログラムのつくり方を説明します.
概 要
サービス(service)はROSの通信方法の一つで、双方向通信に使います。ある仕事をお願いするクライアントノード(client node)とそれを処理して返すサービスノード(service node)からなります。今回、作成する簡単なプログラムは、この本で実現するブリングミー(bring me)タスクを実行するプログラムの超簡易版です.クライアントがサービスに取ってきてもらいたい物をリクエスト(request、要求)し,サービスは,それがリストの中にあれば,”はい.これです”とレスポンス(response、応答)するプログラムです。リクエストとレスポンスはサービス定義ファイル(拡張子srv)で定義します.次の手順でやります。
- サービス定義ファイルを含むパッケージの作成
- サービスノードの作成
- クライアントノードの作成
ハンズオン
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) のように端末に表示される.
成功したら終わり。うまく動かない場合は、打ち間違えや手順に間違いがないか確認し、再度実行しましょう。お疲れ様!
ホームワーク
- チャレンジ2.10 (教科書p.68)をやってみよう!
- チャレンジ2.11 (教科書p.68)をやってみよう!
- ミニプロ2.3(教科書p.69)をやってみよう!
終わり
コメント