# サービスの書き方

## ROSチュートリアルの流れ

1. [ROSパッケージの作り方](https://raspimouse-sim-tutorial.gitbook.io/project/ros_tutorial/how_to_create_pkg)
2. [トピックの書き方](https://raspimouse-sim-tutorial.gitbook.io/project/ros_tutorial/how_to_write_topic)
3. [独自のメッセージファイルの作り方](https://raspimouse-sim-tutorial.gitbook.io/project/ros_tutorial/how_to_create_msg)
4. [まとめて起動するやり方](https://raspimouse-sim-tutorial.gitbook.io/project/ros_tutorial/how_to_use_launch)
5. [サービスの書き方](https://raspimouse-sim-tutorial.gitbook.io/project/ros_tutorial/how_to_write_service) ←今ここ
6. [独自のサービスファイルの作り方](https://raspimouse-sim-tutorial.gitbook.io/project/ros_tutorial/how_to_create_srv)

## はじめに

この章では、サービスの書き方について説明したいと思います。

サービスにはデータを要求する「クライアント」と応答する「サーバ」があります。

これはトピックの「サブスクライバ」と「パブリッシャ」の関係に似ていますが、サービスは更に応答の**成否**を知ることができます。

またトピックではメッセージ(.msg)を使用しましたが、サービスではサービスのファイル(`.srv`)を使用します。

詳しくは[ROSでよく使用する用語](https://raspimouse-sim-tutorial.gitbook.io/project/appendix/ros_word#service)を御覧ください。

## サービスファイル

サービスでは`.srv`という拡張子になっているファイルを使用します。 まず標準のサービス`std_srvs`の`SetBool.srv`を見てみましょう。

```
rossrv show std_srvs/SetBool
```

以下のように表示されるはずです。

```
bool data
---
bool success
string message
```

`---`の上が入力、下が出力になります。

## サービスサーバ

最初にサーバ側から書いていきます。

`server.py`を作成します。

```
roscd ros_tutorial/
vim scripts/server.py
```

{% code title="server.py" %}

```python
#!/usr/bin/env python                                                           
import rospy
from std_srvs.srv import SetBool, SetBoolResponse

def callback_srv(data):
    resp = SetBoolResponse()
    if data.data == True:
        resp.message = "called"
        resp.success = True
    else:
        resp.message = "ready"
        resp.success = False
    print(resp.message)
    return resp

if __name__ == "__main__":
    rospy.init_node("srv_server")
    srv = rospy.Service('service_call', SetBool, callback_srv)
    print("ready")
    rospy.spin()
```

{% endcode %}

実行権限を与えます。

```
chmod +x scripts/server.py
```

### コード解説

```
#!/usr/bin/env python                                                           

import rospy
```

ここまで今までと同じです。

```
from std_srvs.srv import SetBool, SetBoolResponse
```

標準のサービスの`std_srvs`の中にある`SetBool`とその出力に関する`SetBoolResponse`をインポートしています。

先にメイン関数について説明します。

```
if __name__ == "__main__":
    rospy.init_node("srv_server")
```

`srv_server`というノードの名前にしています。

```
      srv = rospy.Service('service_call', SetBool, callback_srv)
```

ここでサービスをインスタンスしています。`service_call`がサービス名、`SetBool`がサービスの型、`callback_srv`がサービスの引数を返すコールバック関数名になります。

```
      print("ready")
```

一番最初は`ready`と表示させておきます。

```
      rospy.spin()
```

プログラムを終了させないようにしています。

最後にコールバック関数について解説します。

```
def callback_srv(data):
```

`callback_srv`という関数名で、受け取った値を`data`という名前にしてます。

```
resp = SetBoolResponse()
```

`SetBoolResponse`を`resp`という名前でインスタンス化しています。

```
    if data.data == True:
        resp.message = "called"
        resp.success = True
    else:
        resp.message = "ready"
        resp.success = False
```

Trueが入力された場合、つまり呼び出しがあった時、`message`にcalledを書き込み、呼び出されたことを伝えるため`success`にTrueを書き込んでいます。

それ以外の場合やFalseが入力された場合は、`message`にreadyを書き込み、呼び出されていないことを伝えるため`success`にFalseを書き込んでいます。

```
    print(resp.message)
    return resp
```

確認のため、`message`を表示しています。

最後にサービスの型を返す必要があり、`SetBoolResponse`をインスタンス化した`resp`を返しています。

## サービスクライアント

次にクライアント側を書いていきます。

`client.py`を作成します。

```
roscd ros_tutorial/
vim scripts/client.py
```

{% code title="client.py" %}

```python
#!/usr/bin/env python
import rospy
from std_srvs.srv import SetBool

if __name__ == "__main__":
    rospy.wait_for_service('service_call')
    try:
        service_call = rospy.ServiceProxy('service_call', SetBool)
        service_call(True)
    except rospy.ServiceException, e:
        print ("Service call failed: %s" % e)
```

{% endcode %}

実行権限を与えます。

```
chmod +x scripts/client.py
```

### コード解説

```
#!/usr/bin/env python                                                           
import rospy
```

ここまで同じです。

```
std_srvs.srv import SetBool
```

`SetBool`型をインポートしています。

```
    rospy.wait_for_service('service_call')
```

ここで`service_call`というサービスが立ち上がるのを待ちます。

トピックを違い、サービスは一対一通信のため相手がいないとエラーを吐きます。 そのためサービスの立ち上がりを待っています。

```
    try:
        service_client = rospy.ServiceProxy('service_call', SetBool)
        service_client(True)
    except rospy.ServiceException, e:
        print ("Service call failed: %s" % e)
```

`sarvice_call`という名前の`SetBool`型のサービスプロキシ`service_client`を作成しています。

`service_client`にTrueを与えています。

## 実行方法

それぞれ別のターミナルで実行してください。

```
roscore
```

```
rosrun ros_tutorial server.py
```

```
rosrun ros_tutorial client.py
```

## 実行結果

```
rosrun ros_tutorial client.py
```

を実行した時、`rosrun ros_tutorial server.py`を実行しているターミナルで、

```
called
```

と表示されたら、正常に動作しています。

## 別の方法

```
roscore
```

```
rosrun ros_tutorial server.py
```

ここまで同じですが、こちらでは`rosservice`というコマンドを使用します。 このコマンドでもサーバに要求できます。

```
rosservice call /service_call "data: true"
```

```
rosservice call /service_call "data: false"
```
