ROSの勉強　第4弾：サービス

# #プログラミング ROS< サービス >

##はじめに
1つの参考書に沿って，ROS(Robot Operating System)を難なく扱えるようになることが目的である．改めて初めから学び，復習も兼ねながら学習を進めていくこととする．その第4弾として，サービスを扱う．

##環境
#####仮想環境
|||
|:-:|:--|
|ソフト|VMware Workstation 15|
|実装RAM|2 GB|
|OS|Ubuntu 64 ビット|
|isoファイル|ubuntu-mate-20.04.1-desktop-amd64.iso|

#####コンピュータ
|||
|:-:|:--|
|デバイス|MSI|
|プロセッサ|Intel(R) Core(TM) i5-7300HQ CPU @ 2.50GHz 2.50GHz|
|実装RAM|8.00 GB (7.89 GB 使用可能)|
|OS|Windows (Windows 10 Home, バージョン：1909)|

#####ROS
|||
|:-:|:--|
|Distribution|noetic|
|プログラミング言語|Python 3.8.5|

##サービスとは？
ROSのノード間でデータを渡すもう１つの方法．
あるノード（サーバー）から別のノード（クライアント）で実行させる関数を呼び出すことを実現．そのイメージ図を次に示す．
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/951300/7f6142f9-ac26-624b-c217-db7d02dbac9b.png)

このサービスコールは「たまにしか行う必要がないもので比較的短い一定時間内で処理が終わるもの」適している．例えば，次のようなものが候補として挙げられる．
・センサをオンにする
・カメラで高解像度の写真を撮る　　など

##サービスを定義
サービス定義ファイルはメッセージ定義ファイルと似た構造をもつが，サービスコールには**入力と出力の両方がある**．実際に作成したものを次に示す．![キャプチャ8.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/951300/1a91d164-1944-0020-1aec-3a023904b1fc.png)

``---``で入力と出力を仕切っている．しかし，型とフィールド名のみを指定しており，はじめに述べたように構造はメッセージ定義ファイルとよく似ていることが分かる．``catkin_make``までの流れは，メッセージとほとんど同じである．

ここまででサービスを定義できたはずである．ここで次のように定義されていることを確認する．
![キャプチャ5.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/951300/b636a960-fbf3-67cc-1e45-7a1a4f577e15.png)
結果がうまく返されたので，しっかりと定義できたことを確認することができた．

##実装
サービスを定義できたので，実際にその定義したサービスを利用してみる．サーバーとクライアントを2つのプログラムで作成した．以下にそのソースコードを示す．

#####ソースコード（サーバー）
```python:service_server.py
#! /usr/bin/env python3

import rospy

from service_lesson.srv import WordCount, WordCountResponse	#

def count_words(request):
	count = len(request.words.split())	#split()でスペース区切りで配列を生成し，その数を数えることで，単語数をカウントできる

	return WordCountResponse(count)

rospy.init_node('service_server')	#ノードを初期化

service = rospy.Service('word_count', WordCount, count_words)	#'word_count'という名前で．型がWordCount，callback関数がcount_wordsのサーバーを公開
#リクエスト受付の準備完了

rospy.spin()
```

これで，サービスを提供する側であるサービスを作成することができた．サービスは提供できるので，ターミナル(端末)からサービスを利用することができる．これをすることで，わざわざクライアントファイルを作らなくともサーバーの動作確認ができる．以下にその様子を示す．
![キャプチャ6.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/951300/e16fb372-832f-b996-f31f-6ce750eb512d.png)
``service_server.py``を実行後，``rosservice list``でサーバーが存在することを確認．``rosservice info /word_count``でより詳細な情報を見ることができる．``rosservice call ...``でサービスを利用できる．""で囲まれた部分は入力部分である．その入力に対しての返答をしてくれる．ここでは，'one two three'という3単語を入力したため，``count: 3``というような返答がなされている．これで，確かに単語をカウントするサービスを提供できていることが確認できた．

#####ソースコード（クライアント）
```python:service_client.py
#! /usr/bin/env python3

import rospy

from service_lesson.srv import WordCount

import sys

rospy.init_node('service_client')

rospy.wait_for_service('word_count')	#'word_count'という名前のサーバーが公開されるまで待つ

#サービスが公開されるとローカルプロキシをセットアップできる
#ここでは，サービス名と型を指定する．こうすることで，word_counterをローカルな関数のように使うことができる．
word_counter = rospy.ServiceProxy('word_count', WordCount)

words = ' '.join(sys.argv[1:])	#.py実行時に.pyの隣に入力できるようにしている．

word_count = word_counter(words)	#word_counter関数に引数としてwordsを渡し，出力を格納

print(f"{words} → {word_count}")
```
``service_server.py``を実行後，``service_client.py``を実行することで，そのときの入力に応じた応答がなされる．なお，サービスの場合，実行する順番は特別気にする必要もない．``service_client.py``ではサービスが公開されるまで待つような仕組みとしているからである．以下に，``service_client.py``を実行したターミナル(端末)を示す．
![キャプチャ7.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/951300/40546ff3-d6c4-234c-91f7-544ccf1f627c.png)
上図の赤線は文字の入力を示している．何も入力しなければ出力は0，入力すれば，その入力に応じて単語数を出力してくれる．

##注意
・サービスは，たまに実行する必要がある処理や，同期してリプライする必要がある場合に使うようにする．
・サービス用のコールバック関数で行う処理は短く，一定時間内に完了するようにする．
・処理に長い時間がかかったり，処理時間が大きく変動する場合には，アクションの使用を考える．

##感想
サービスの基本を今回だけでまとめた．前回までのトピックでの失敗に気を付けながら学習を進めたため，あまりエラーは出ずにうまく進められた．クライアントのpythonファイルだけ少し複雑であったが，それ以外は非常にトピックと似ている．とりあえず，トピックとサービスを学びその違いも理解できたと思う．次回からは最後に記述したアクションについての学習が始まる．

##参考文献
プログラミングROS Pythonによるロボットアプリケーション開発
　　　　　　　　Morgan Quigley, Brian Gerkey, William D.Smart 著
　　　　　　　　　　　　　　　　　　　　　　　河田　卓志　監訳
　　　　　　　　　　  　松田　晃一，福地　正樹，由谷　哲夫　訳
　　　　　　　　　　　　　　　　　　オイラリー・ジャパン　発行