serviceとactionlib

Learn

ここまでトピックを使った通信を使ってロボットシステムを構築してきました. トピック通信は,メッセージを出版(publish,配信とも訳される)・購読(subscribe)することで通信する,相手を仮定しない非同期な通信方法でした.

しかし,もっと複雑なシステムを組む場合には,「相手の処理の結果を呼び出し側で受け取って知りたい」など様々な場合が考えられます.

このようなより複雑な通信を実現するための通信方式として,ROSにはサービス(service)とアクション(actionlib)が用意されています.

service

これまで利用してきたトピック通信は,通信の相手を仮定しない(相手がいようといまいと関係ない)ため,ロボットシステムに特有な非同期通信・処理を実現するために簡単な方法でした.

一方で,他のノードに対して「特定の処理の依頼をして,その結果を待ちたい」場合など,同期的・双方向な通信が必要になることがあります. 例えば,あるノードの設定を変更をして,それが成功したかどうかを知りたい場合などに使えます. サービスを使った通信は,「クライアント・サーバ」型の通信(クライアントサーバモデル, client-server model)となり,クライアントがサーバにリクエストを送ると,サーバがレスポンスを返すような仕組みになっています.

pythonではrospy.serviceを使ってサーバを,rospy.service_proxyを使ってクライアントを簡単に実装できます(参考URL).

また,コマンドラインからは

rosservice call [service] [args]

として,簡単にクライアントを作成できますし,システム上に存在するサービスの一覧は

rosservice list

とすることで表示できます.あるサービスのメッセージがどのように定義されているかは,

rosservice type [service]

で調べられます.

actionlib

ここまで,トピック通信を使うことで相手を仮定しない非同期通信を,サービスを使った通信を行うことで相手のレスポンスを待つ同期的な通信を実現できることを見てきました.

サービスによる通信では,クライアントはサーバからのレスポンスを待つため,サーバで長い時間がかかるような処理を行う(計算量が大きい,または,移動に時間がかかるなど)場合には,クライアントの処理が長い間停止してしまうという問題があります.

そのため,処理の呼び出し側のプログラムをブロックせずに,かつ,処理の結果(や途中経過)を知れるような非同期通信が欲しくなります. この要求を満たすのが,ROSのアクション(actionlib)です.

actionlibは,実はトピック通信の組み合わせとして構成されており,goal(命令),result(処理の結果),feedback(途中経過),status(サーバの状態),cancel(命令の取り消し)の5つのトピックからなります. このあたりの仕様は,qiitaのROS講座が詳しいので参照してください.

pythonでは,actionlibのサーバやクライアントも,

import actionlib

したのちに,他の通信方式と同様にactionlib.SimpleActionServerとして,簡単に作成できます(ドキュメント).

今回の演習では,簡単のためaction serverの作成は行いません. 変わりに,移動のためのactionとして,move_baseパッケージの中で定義されているmove_baseというactionを使うことにしましょう.

実はこのパッケージは

roslaunch navigation_tutorial navigation.launch

してmove_baseノードを起動した際に既に利用されていました(これまでは,そのパッケージの中でサブスクライバとして定義されたmove_base_simple/goalというトピックにpublishすることで移動をしていました).

move_baseのパッケージの詳細はドキュメントを見て確認してみてください.

同様に,action clientもactionlib.SimpleActionClientを利用することで簡単に作成できます.

例えば,move_baseのaction clientの実装する際には,

import actionlib
import tf
from move_base_msgs.msg import MoveBaseAction, MoveBaseGoal
from geometry_msgs.msg import Quaternion

action_client = actionlib.SimpleActionClient('move_base', MoveBaseAction)
action_client.wait_for_server()  # action serverの準備ができるまで待つ

goal = MoveBaseGoal()  # goalのメッセージの定義
goal.target_pose.header.frame_id = 'map'  # マップ座標系でのゴールとして設定
goal.target_pose.header.stamp = rospy.Time.now()  # 現在時刻
# ゴールの姿勢を指定
goal.target_pose.pose.position.x = X
goal.target_pose.pose.position.y = Y
q = uaternion_from_euler(0, 0, YAW)  # 回転はquartanionで記述するので変換
goal.target_pose.pose.orientation = Quaternion(q[0], q[1], q[2], q[3])

action_client.send_goal(goal)  # ゴールを命令

のようにクライアントのsend_goalメソッドでゴールを指定できます.

その後,

action_client.wait_for_result(rospy.Duration(30))

とすると,結果が返ってくるまで(この場合30秒間),クライアントの処理をブロックできますし,

result = action_client.wait_for_result(rospy.Duration(30))

とすることで,result変数に処理の結果が格納され,確認できます.

演習

【jetson・開発マシン】起動準備

cd roomba_hack
git fetch
git checkout feature/integrate
(jetson) ./RUN-DOCKER-CONTAINER.sh
(開発マシン) ./RUN-DOCKER-CONTAINER.sh 192.168.10.7x

【開発マシン】scriptベースのnavigationを実行してみる

(開発マシン)(docker) roslaunch navigation_turtorial navigation.launch

(開発マシン)(docker) rosrun navigation_turtorial topic_goal.py
(開発マシン)(docker) rosrun navigation_turtorial action_goal.py

【開発マシン】RealSenseで検出した障害物をコストマップに追加してみよう

(開発マシン)(docker) roslaunch three-dimensions_tutorial detection_pc.launch

(総合課題)障害物を避けながらnavigationする

Lidarに映らない物体も画像ベースで検出しコストマップに追加することでナビゲーション時にぶつからないようにしましょう。

ヒント

  • 物体検出結果に基づいて物体部分以外をマスクしたデプス画像をpublishする
  • depth2pc.launchでそれをsubscribeし、point(cloud)に変換する
  • 変換されたpointからmap座標系での位置を取得する
  • costmapに反映する
  • move_baseアクションを使ってナビゲーションを実装しよう.
    • するとactionがタイムアウトした場合や,KeyboardInterruptされた場合にcancel_goalメソッドを使うことでactionをキャンセルできるように拡張できるはずです.

さらに,PyTorchを使用した自作の分類器やネット上の分類器をシステムに組み込んで(例えばセグメンテーションモデルなど),よりよく動作するように改良してみましょう.