AWS RoboMaker応用 ~AWS IoT Coreとの連携~

この記事は、[TIS Advent Calendar 2018](https://qiita.com/advent-calendar/2018/tis)の24日目の記事です。

# はじめに
とうとうロボットのプログラムがクラウド上で書ける時代にまでなりましたが、皆様ロボットはお好きでしょうか？

先日公開した[AWS RoboMaker入門 ~デモ起動からコード修正、実機デプロイまで~](https://qiita.com/nmatsui/items/6721c820000cf5115bdc)という記事では、クラウド上で書いたロボットアプリケーションをクラウド上でシミュレートし、その後インターネット越しに実機にデプロイする、という流れを説明しました。

しかしこの記事で動かしたロボットアプリケーションは、起動するとロボットがひたすらグルグル回り続けるだけで、外界とは何も連携しないものでした。これでは全然楽しくないので、今回は[AWS IoT Core](https://aws.amazon.com/jp/iot-core/)からロボットへ動作を命令できるようにしたいと思います。

# 今回の記事で作成したROSアプリケーションのリポジトリ
今回の記事で作成したROSアプリケーションは、[githubのリポジトリ](https://github.com/nmatsui/robomaker_awsiotcore)で公開しています。記事にあわせてご確認ください。

# シミュレーション環境を準備する
[前回の記事](https://qiita.com/nmatsui/items/6721c820000cf5115bdc)を参考に、AWS RoboMakerのシミュレーション環境を立ち上げます。

注意点は、RoboMakerシミュレーション環境を**インターネットゲートウェイを持つマルチAZなVPC内**で動作させることです。

RoboMakerのシミュレーション環境は、VPCを指定しないと外界と接続しないクローズドな仮想ネットワーク上で起動します。今回動作させるロボットアプリケーションは、インターネット経由でAWS IoT Coreに接続しますので、シミュレーション環境でもインターネットに接続できなければなりません。そのため、インターネットゲートウェイを持つVPCを明示的に指定し、その上でシミュレーション環境を起動させたいと思います。[^1]

[^1]: 検証なのにわざわざマルチAZにしているのは、AWS RoboMakerがシングルAZのVPCを受け付けてくれないためです。

## SubnetとSecurity Groupを作成する
ローカルのアドレス以外はインターネットへルーティングするSubnetを、us-east-1cとus-east-1dに作ります。

![01.png](https://qiita-image-store.s3.amazonaws.com/0/37648/93dca107-998a-74a5-de63-58288800261e.png)

また、Inboundは全部Denyで、Outboundは全部AllowするSecurity Groupも一つ作っておきます。

![02.png](https://qiita-image-store.s3.amazonaws.com/0/37648/8c0fc7e4-cfdd-cdc4-442b-5b39f8176f31.png)
![03.png](https://qiita-image-store.s3.amazonaws.com/0/37648/ebc73f18-ad3f-d337-ee55-386822234ba3.png)

## RoboMakerシミュレーション環境を起動する
前回の記事の "[Hello World デモを動かしてみよう](https://qiita.com/nmatsui/items/6721c820000cf5115bdc#hello-world-%E3%83%87%E3%83%A2%E3%82%92%E5%8B%95%E3%81%8B%E3%81%97%E3%81%A6%E3%81%BF%E3%82%88%E3%81%86)" と同様に、まずはVPC外でHello worldデモのサンプルシミュレーション環境を起動します。シミュレーション環境が正しく起動したことを確認したら、そのシミュレーション環境で "Clone" をクリックします。

![04.png](https://qiita-image-store.s3.amazonaws.com/0/37648/e4f7fdc1-ad61-f62b-7ec0-cef2359ae4b4.png)

"Step 1: Condigure simulation" の "Edit" をクリックします。

![05.png](https://qiita-image-store.s3.amazonaws.com/0/37648/21fffaa0-137d-7265-b5f1-db94167849ae.png)

先ほど作成したVPCを選択し、Subnetを二つとも指定します。また先ほど作成したSecurity Groupも指定し、 "Next" をクリックします。

![06.png](https://qiita-image-store.s3.amazonaws.com/0/37648/5270808f-8ea2-d638-b0fb-0acef78dcdfc.png)

"Step 2: Specify robot application." はそのままにしておき、 "Step 3: Specify simulation application." の "Edit" をクリックします。

"TURTLEBOT3_MODEL" 環境変数の値として "waffle" を指定することで、シミュレータ上に出現するロボットを "Turtlebot3 Waffle" に変更しておきます。

![07.png](https://qiita-image-store.s3.amazonaws.com/0/37648/a2dfaf83-a999-6184-d09f-48004108f10b.png)

上記のように設定を変更し、 "Create" すると、新たにシミュレーション環境が起動します。指定したSubnetとSecurity Group内で起動していること、Simulation applicationの環境変数 "TURTLEBOT3_MODEL" の値が "waffle" として設定されていることを確認してください。
（VPC外で起動させた最初のシミュレーション環境はもう使わないので、 "Cancel" してしまってかまいません。）

![08.png](https://qiita-image-store.s3.amazonaws.com/0/37648/33dc83f0-c1ef-38aa-ddbc-29f402ba1e09.png)
![09.png](https://qiita-image-store.s3.amazonaws.com/0/37648/a6132abb-6594-f561-c7da-c46977a1f03b.png)

では、起動したシミュレーション環境がインターネットに接続できることを確認しましょう。シミュレーション環境の "Terminal" を起動し、次のコマンドを実行してください[^2]。

[^2]: シミュレーション環境のdockerコンテナは、`ping`や`nslookup`等のネットワーク関連のコマンドが入っていません。また`sudo`もできないため、rootになって "iputils-ping" や "net-tools" をインストールすることもできません。Python2.7は動作するため、結局このようなワンライナーを使うことにしました。

```bash
$ python -c "import urllib;print(urllib.urlopen('https://www.google.com').read())"
```

正しく設定されていれば、 www.google.com のHTMLが表示されるはずです。

# AWS IoT Coreを準備する
シミュレーション環境が起動したので、次は[AWS IoT Core](https://aws.amazon.com/jp/iot-core/)を準備します。

## Thingを登録し、証明書をダウンロードする
[AWS IoTのコンソール](https://console.aws.amazon.com/iot/)を用いて、ロボットに相当するモノ（Thing）を登録します。

"Manage > Things" から、 "Register a Thing" をクリックします。

![10.png](https://qiita-image-store.s3.amazonaws.com/0/37648/b878bc1d-384d-6586-638e-6acd19081cf1.png)

"Create a single Thing" をクリックし、名前として "turtlebot3_waffle" を入力した後に "Next" をクリックします（"Thing Type" や "Thing Group" はデフォルトのままで大丈夫です）。

![11.png](https://qiita-image-store.s3.amazonaws.com/0/37648/243962cf-98c4-4509-2ca0-71f329542b77.png)
![12.png](https://qiita-image-store.s3.amazonaws.com/0/37648/dcfd035a-d213-c5d4-cdde-53ccc6a15359.png)

"Create certificate" をクリックし、AWS IoT Coreと接続するためのクライアント証明書と公開鍵、秘密鍵のペアを生成します。

![13.png](https://qiita-image-store.s3.amazonaws.com/0/37648/4864cf12-26c7-e157-7b11-691b61d5ab1f.png)

生成されたクライアント証明書と秘密鍵をダウンロードします。
また、["root CA for AWS IoT" のリンク先](https://docs.aws.amazon.com/iot/latest/developerguide/managing-device-certs.html#server-authentication)から、AWS IoT Coreのroot証明書をダウンロードします（Amazon Root CA 1で大丈夫です）。

![14.png](https://qiita-image-store.s3.amazonaws.com/0/37648/01c05701-f187-2c35-8317-bb6e04776c3c.png)
![15.png](https://qiita-image-store.s3.amazonaws.com/0/37648/aa2e3faf-66ce-18de-461f-178d49d12177.png)

ダウンロードした後に "Done" をクリックすれば、 "turtlebot3_waffle" というThingが登録されます（Policyはまだ作っていないので、後からアタッチします）。

![16.png](https://qiita-image-store.s3.amazonaws.com/0/37648/e56943d8-c306-d1e2-9027-b2b0b1a21142.png)

## 証明書にPolicyを設定する
次に、このThingに許可するPolicyを設定しましょう。今回のROSアプリケーションでは、AWS IoT Coreへ次の操作をする権限が必要です。

* AWS IoT CoreへMQTT Clientとして接続する
* `/hello_world_robot/#`というMQTT Topicをsubscribeする

またAWS IoT Core経由でロボットへ動作命令を出すためには、AWS IoT CoreへMQTTメッセージを送りつける権限が必要です。別の役割なので本来は別のThingとして扱ったほうが良いのですが、今回は横着して同じThingを使い回すことにします。そのため、次の権限も合わせて付与します。

* `/hello_world_robot/sub`というMQTT Topicへメッセージをpublishする

では、実際にコンソールからPolicyを作成しましょう。 "Secure > Policies" から、 "Create a policy" をクリックします。

![17.png](https://qiita-image-store.s3.amazonaws.com/0/37648/47c41901-98d4-28c3-9071-0f3bd3e995a9.png)

今回はjson形式でPolicyを設定するので、 "Advanced mode" をクリックします。名前として "robomaker_iot_policy" を入力し、PolicyのStatementとして以下のjsonを入力して "Create" します（MQTTの記法としては、トピックフィルタのワイルドカードは`+`と`#`ですが、IAMの記法に合わせて `*`を用いることに注意してください）。

![18.png](https://qiita-image-store.s3.amazonaws.com/0/37648/5f3ff745-d641-b878-57bc-99559a9b6398.png)

**注意： `999999999999`は自身のアカウントIDに置き換えてください**

```json:Policy
{
  "Version": "2012-10-17",
  "Statement": [
    {
      "Effect": "Allow",
      "Action": [
        "iot:Connect"
      ],
      "Resource": "*"
    },
    {
      "Effect": "Allow",
      "Action": [
        "iot:Subscribe"
      ],
      "Resource": [
        "arn:aws:iot:us-east-1:999999999999:topicfilter//hello_world_robot/*"
      ]
    },
    {
      "Effect": "Allow",
      "Action": [
        "iot:Receive",
        "iot:Publish"
      ],
      "Resource": [
        "arn:aws:iot:us-east-1:999999999999:topic//hello_world_robot/sub"
      ]
    }
  ]
}
```

無事にPolicyが登録できたら、先ほど作成した証明書にアタッチします。
"Secure > certificates" から、生成済みの証明書の "Attach policy" をクリックします。

![19.png](https://qiita-image-store.s3.amazonaws.com/0/37648/a96d3507-fbd5-025d-e64a-18c4d2c1186d.png)

作成した "robomaker_iot_policy" を選択し、 "Attach" します。

![20.png](https://qiita-image-store.s3.amazonaws.com/0/37648/34d70ee1-279f-e9c5-5fc4-5ae88fcbb1a8.png)

最後に、証明書を "Activate" します。これにより、この証明書が使えるようになります。

![21.png](https://qiita-image-store.s3.amazonaws.com/0/37648/27a6fad3-463e-d918-3378-b27950bc0bc6.png)

## AWS IoT Coreのエンドポイントを確認する
登録したThingが接続するAWS IoT Coreのエンドポイントは、 "Settings" から確認できます。後で必要になりますので、メモしておきましょう。

![22.png](https://qiita-image-store.s3.amazonaws.com/0/37648/2e92f572-d930-5d48-ef2a-3ea623ab3076.png)

## Macから接続を確認する
では、AWS IoTへ正しくThingが登録できたか、ダウンロードした証明書や秘密鍵を用いてMacから接続してみます。事前にMQTT Client（`mosquitto_sub`と`mosquitto_pub`）をインストールしておいてください。

なお注意点ですが、**QoS=1**を明示的に指定してください[^5]。
`mosquitto_sub`も`mosquitto_pub`も、デフォルト（`-q`オプションを指定しない）ではQoS=0でMQTT Brokerへ接続します。ローカルのMQTT Brokerに接続する場合はQoS=0でも問題ありませんが、AWS IoT Coreはインターネットの向こう側にあるため、QoS=0だとsubscriberまでメッセージが届かない場合があります。

[^5]: QoS 0 (At most once) ：メッセージは最大で1回送信される（まったく送信されないこともある）。メッセージが届くことは保証されない。<br/>QoS 1 (At least once) ：メッセージは最低1回送信される。受信側は同じメッセージを複数回受け取る場合がある。<br/>QoS 2 (Exactly once) ：メッセージは常に、正確に1回送信される。

### subscriberを起動
ダウンロードしたクライアント証明書と秘密鍵、AWS IoT Coreのroot証明書を用いて、先ほど確認したエンドポイントの8883ポートへ接続し、QoS=1で`/hello_world_robot/#` をsubscribeします。

```bash
$ mosquitto_sub -d \
--cafile <<<root証明書のpath>>> \
--cert <<<クライアント証明書のpath>>> \
--key <<<秘密鍵のpath>>> \
-h <<<AWS IoT Coreのエンドポイント>>> -p 8883 \
-t /hello_world_robot/# \
-q 1
```

AWS IoT Coreが正しく設定されてれば、subsciribeに成功するはずです。

![23.png](https://qiita-image-store.s3.amazonaws.com/0/37648/f0a7728c-a2e5-1661-def3-b066593ce131.png)

次に、別のTerminalから `/hello_world_robot/sub` へメッセージを送ってみます。

```bash
mosquitto_pub -d \
--cafile <<<root証明書のpath>>> \
--cert <<<クライアント証明書のpath>>> \
--key <<<秘密鍵のpath>>> \
-h <<<AWS IoT Coreのエンドポイント>>> -p 8883 \
-t /hello_world_robot/sub \
-q 1 \
-m "{\"message\": \"robomaker iot\"}"
```

最初のTerminalへメッセージが届けば、AWS IoT Coreの準備は完了です。

# AWS IoT Coreに接続するROSアプリケーションを書く
諸々準備が整いましたので、RoboMakerの開発環境を立ち上げて、AWS IoT Coreから命令を受け取るROSアプリケーションを書きましょう。

## RoboMakerの開発環境を起動する
前回の記事の[Hello Worldデモのコードを修正する](https://qiita.com/nmatsui/items/6721c820000cf5115bdc#hello-world-%E3%83%87%E3%83%A2%E3%81%AE%E3%82%B3%E3%83%BC%E3%83%89%E3%82%92%E4%BF%AE%E6%AD%A3%E3%81%99%E3%82%8B)を参考に、1. RoboMakerのROS開発環境を起動 2. Hello worldデモのソースコードの取り込み 3. ROSワークスペースの初期化 まで実行します（このRoboMakerの開発環境は、シミュレーション環境用に作ったVPCに同居させてかまいません）。

## 開発環境へ証明書をコピーする
`HelloWorld/robot_ws/src/hello_world_robot`直下に`certs`ディレクトリを作成し、ダウンロードしたクライアント証明書と秘密鍵、及びAWS IoT Coreのroot証明書をドラッグ＆ドロップして開発環境へコピーします。

![24.png](https://qiita-image-store.s3.amazonaws.com/0/37648/53b50aa8-0d74-4d0f-d6af-dfc01db34545.png)
![25.png](https://qiita-image-store.s3.amazonaws.com/0/37648/6d79d6e7-5803-f3d6-090c-fdf658e03e44.png)

## 証明書をバンドル対象に指定する
RoboMaker開発環境にディレクトリを作成してファイルを配置しただけでは、シミュレーション環境や本番環境へデプロイするバンドルファイルに取り込まれません。そのため次のように、`CMakeLists.txt`の `install(DIRECTORY ...)` 命令に、`certs` ディレクトリを追加してください。ビルドやバンドルする際には、`colcon`（が起動する`catkin`）がこの`CMakeLists.txt`を参照し、イイカンジに処理してくれます。

```diff:CMakeLists.txt
   DESTINATION ${CATKIN_PACKAGE_BIN_DESTINATION}
 )

-install(DIRECTORY launch
+install(DIRECTORY launch certs
 	DESTINATION ${CATKIN_PACKAGE_SHARE_DESTINATION}
 )
```

## PythonのMQTTクライアントライブラリをインストールする
ROSでは`rosdep`というツールを用いて、ROSアプリケーションが依存するOSパッケージやPythonライブラリ等をインストールすることができます。`/etc/ros/rosdep/sources.list.d/`以下の.listファイル（が参照しているYAMLファイル）を確認すれば、`rosdep`によってインストールできるライブラリが探せます[^3]。

RoboMakerで用いているROS（Kinetic）では、Python用のMQTTクライアントライブラリである[paho-mqtt](https://pypi.org/project/paho-mqtt/)が`python-paho-mqtt-pip`という名前でリストアップされていますので、今回はこれを使うことにします。（デフォルトでリストアップされているPythonライブラリの詳細は、[python.yaml](https://raw.githubusercontent.com/ros/rosdistro/master/rosdep/python.yaml)を確認してください。）

[^3]: デフォルトで対応していないライブラリをインストールしたい場合は、別途YAMLファイルを書いて`rosdep`に認識させる必要があります。

では、`python-paho-mqtt-pip`に依存することを宣言しましょう。次のように、`package.xml`に`python-paho-mqtt-pip`を追加してください。

```diff:package.xml
   <build_export_depend>message_runtime</build_export_depend>
   <exec_depend>message_runtime</exec_depend>
   <exec_depend>turtlebot3_bringup</exec_depend>
+  <depend>python-paho-mqtt-pip</depend>
 </package>
```

`package.xml`を修正した後に下記のコマンドを再実行すると、`rosdep`が "paho-mqtt" をインストールします。

```bash
$ cd $HOME/environment/HelloWorld/robot_ws/
$ rosdep install --from-paths src --ignore-src -r -y
```

`Successfully installed paho-mqtt-<<バージョン番号>>`というログが出力されることを確認してください。

## AWS IoT Coreに接続するソースコードを書く
それでは、AWS IoT Coreに接続するソースコードを書きましょう。

今回のROSアプリケーションはある程度複雑になりますので、AWS IoT Coreから命令を受け取ってロボットを操作するクラスを作ることにします。`src/hello_world_robot`以下[^4]に`awsiot.py`を作成し、AWSIoTクラスを実装しましょう。

[^4]: RoboMakerのHello worldデモは、`setup.py`の設定と`CMakeLists.txt`の`catkin_python_setup()`命令により、`src/hello_world_robot`以下がPythonのライブラリパスに含まれるように設定されています。また`src/hello_world_robot/__init__.py`も最初から作成されています。

```python:src/hello_world_robot/awsiot.py
# -*- coding: utf-8 -*-
import ssl
import json
import time

import rospy
from geometry_msgs.msg import Twist

import paho.mqtt.client as mqtt


class AWSIoT(object):
    QOS = 1
    HZ = 10
    
    def __init__(self):
        rospy.loginfo("AWSIot#__init__")
        self.is_connected = False
        self.__client = mqtt.Client(protocol=mqtt.MQTTv311)
        self.__client.on_connect = self._on_connect
        self.__client.on_message = self._on_message
        rospy.on_shutdown(self._on_shutdown)
        self.__params = rospy.get_param("~awsiot") # get parameters from ros parameter server
        
    def run(self):
        rospy.loginfo("AWSIoT#run")
        
        # set certification files
        self.__client.tls_set(
            ca_certs=self.__params["certs"]["rootCA"],
            certfile=self.__params["certs"]["certificate"],
            keyfile=self.__params["certs"]["private"],
            tls_version=ssl.PROTOCOL_TLSv1_2)
        
        # connect to AWS IoT Core
        self.__client.connect(
            self.__params["endpoint"]["host"],
            self.__params["endpoint"]["port"],
            keepalive=120)
        self.__client.loop_start()
        
    # this method is called when connected to AWS IoT Core successfully
    def _on_connect(self, client, userdata, flags, response_code):
        rospy.loginfo("AWSIoT#_on_connect response={}".format(response_code))
        # subscribe '/hello_world_robot/sub' mqtt topic
        client.subscribe(self.__params["mqtt"]["topic"]["sub"], qos=AWSIoT.QOS)
        self.is_connected = True
        # create a ROS publisher to publish a Twist message to '/cmd_vel' ROS topic
        self.__cmd_pub = rospy.Publisher("/cmd_vel", Twist, queue_size=1)
        
    # this method is called when received a message from AWS IoT Core
    def _on_message(self, client, userdata, data):
        topic = data.topic
        payload = str(data.payload)
        rospy.loginfo("AWSIoT#_on_message payload={}".format(payload))
        twist = Twist()
        try:
            params = json.loads(payload)
            if "x" in params and "z" in params and "sec" in params:
                start_time = time.time()
                d = float(params["sec"])
                r = rospy.Rate(AWSIoT.HZ)
                # publish Twist message to '/cmd_vel' ROS topic in order to operate Turtlebot3
                while time.time() - start_time < d:
                    twist.linear.x = float(params["x"])
                    twist.angular.z = float(params["z"])
                    self.__cmd_pub.publish(twist)
                    r.sleep()
        except (TypeError, ValueError):
            pass
        
        twist.linear.x = 0.0
        twist.angular.z = 0.0
        self.__cmd_pub.publish(twist)
    
    # this method is called when terminated ROS node
    def _on_shutdown(self):
        logmsg = "AWSIoT#_on_shutdown is_connected={}".format(self.is_connected)
        rospy.loginfo(logmsg)
        if self.is_connected:
            self.__client.loop_stop()
            self.__client.disconnect()
```

このAWSIoTクラスは、次のような処理を行います。

1. **run**メソッドが呼び出されると、バンドルされている証明書を用いてAWS IoT CoreにQoS=1で接続します。証明書のパスやAWS IoT Coreのエンドポイントは、ROSのParameter Server（後述）から取得します。
2. 接続に成功すれば**_on_connect**メソッドがコールバックされ、Parameter Serverから取得したMQTTトピックをsubscribeします。またTurtlebot3を操作するために、ROSの`/cmd_vel`トピックへメッセージをpublishするpublisherも作成しておきます。
3. subscribeしているMQTTトピックへメッセージが到着すると、**_on_message**メソッドがコールバックされ、そのメッセージが配信されてきます。今回の実装では、メッセージを受信すると、次のような処理を行っています。
    1. メッセージのjsonが "x", "z", "sec" という数値型の属性を持っているjsonの場合、次のようなTwistメッセージを "sec" 秒経過するまで0.1秒ごとにROSの`/cmd_vel`トピックへpublishする。

        ```json
        {
            linear:  {
                x: <<受信した"x"の数値>>, 
                y: 0.0,
                z: 0.0
            },
            angular: {
                x: 0.0,
                y: 0.0,
                z: <<受信した"z"の数値>>
            }
        }
        ```
    2. 最後に、linearとangularのx, y, zが全て0.0のTwistメッセージを１回だけROSの`/cmd_vel`トピックへpublishする（直進速度と回転速度が0なので、Turtlebot3がその場で停止することになる）。

## ROSノードの起動スクリプトを修正する
次に、ただグルグル回るだけだった`nodes/rotate`を修正し、AWSIoTインスタンスへ実際の処理を委譲するように書き換えます。

```python:nodes/rotate
#!/usr/bin/env python

# TODO fix to set appropriate PYTHONPATH by configurations
import sys
import os
sys.path.append(os.path.join(os.path.dirname(__file__), 
"../../../../../usr/local/lib/python2.7/dist-packages"))

import rospy
from hello_world_robot.awsiot import AWSIoT


def main():
    rospy.init_node('awsiot')
    try:
        rospy.loginfo("main start")
        AWSIoT().run() # call AWSIoT#run method
        rospy.spin() # keep python from exiting until this node is stopped
        rospy.loginfo("main end")
    except rospy.ROSInterruptException:
        pass

if __name__ == '__main__':
    main()
```

`rospy.spin()`を入れ忘れると、このROSアプリケーションは起動直後に終了してしまいますので、ご注意ください。

**注意:**
Hello worldデモを改造して作った今回のROSアプリケーションは、`colcon`がバンドルしたファイルをシミュレータや実機にデプロイした際、なぜか `rosdep`がインストールし`colcon`によってバンドルされたPythonライブラリへのパスを通してくれません。`colcon-core`や`colcon-bundle`まわりの設定がどこかにあるのだと思いますが、いまいちよくわかりません。
仕方ないので、`nodes/rotate`内で`rosdep`のインストール先を直接ライブラリパスに追加しちゃってます。美しくないので、正しいやり方をご存知の方は、こっそり私に教えてください（笑

## launchファイルにROSパラメータを設定する
`rotate.launch`に`<param>`タグを追加し、今回のROSアプリケーションで必要となるパラメータを設定します。これらのパラメータは、ROSアプリケーション起動時にROSのParameter Serverに設定され、以降ROSプログラムから読み書きできるようになります。

```diff:launch/rotate.launch
   <!-- Rotate the robot on launch -->
-  <node pkg="hello_world_robot" type="rotate" name="rotate" output="screen"/>
+  <node pkg="hello_world_robot" type="rotate" name="rotate" output="screen">
+    <param name="awsiot/endpoint/host" value="<<AWS IoT Coreのエンドポイント"/>
+    <param name="awsiot/endpoint/port" value="8883"/>
+    <param name="awsiot/certs/rootCA" value="$(find hello_world_robot)/certs/AmazonRootCA1.pem"/>
+    <param name="awsiot/certs/certificate" value="$(find hello_world_robot)/certs/<<クライアント証明書のファイル名>>"/>
+    <param name="awsiot/certs/private" value="$(find hello_world_robot)/certs/<<秘密鍵のファイル名>>"/>
+    <param name="awsiot/mqtt/topic/sub" value="/hello_world_robot/sub"/>
+  </node>
 </launch>
```

[Hello Worldデモのコードを修正する](https://qiita.com/nmatsui/items/6721c820000cf5115bdc#hello-world-%E3%83%87%E3%83%A2%E3%81%AE%E3%82%B3%E3%83%BC%E3%83%89%E3%82%92%E4%BF%AE%E6%AD%A3%E3%81%99%E3%82%8B)を参考に、 "HelloWorld Robot" と "HelloWorld Simulation" をビルドしてバンドルし、それらを用いてシミュレーション環境を再起動してください。

## ROSアプリケーションが起動していることを確認する

シミュレーション環境が再起動したら、シミュレータの "Terminal" を開き、`rosnode list`コマンドで起動しているros nodeの一覧を表示します。`/rotate`nodeが起動していることを確認してください。

![26.png](https://qiita-image-store.s3.amazonaws.com/0/37648/a9f9d554-00b6-2822-1a5c-901c9406c85e.png)

## うまく動かなかった場合には
何らかのミスがあり、ROSアプリケーションが上手く起動できなかった場合、シミュレータの "Terminal" から次のコマンドを叩けば、シミュレーション環境のdockerコンテナ上でROSアプリケーションの起動を試みることができます。

```bash
$ source $HOME/workspace/robot-application/bundle/opt/install/local_setup.bash
$ roslaunch hello_world_robot rotate.launch
```

そもそも起動に失敗したROSアプリケーションですから、やっぱりエラーが発生して落ちるでしょう。が、その際に、`rospy.loginfo()`や`print()`で出力したログメッセージや、エラー発生箇所のスタックトレースが "Terminal" 上に表示されます。それを頼りにデバッグすると良いでしょう。

# シミュレータ上のロボットをAWS IoT Coreから操作する
それでは、デプロイしたROSアプリケーションの動作を確認してみましょう。
Macのターミナルから、AWS Iot Coreへ次のようなメッセージをpublishします。ロボットがこのメッセージを受信したら、並進速度0.1m/s、回転速度0.5rad/sで、6.28秒間だけ動くはずです。

```bash
$ mosquitto_pub -d \
--cafile <<<root証明書のpath>>> \
--cert <<<クライアント証明書のpath>>> \
--key <<<秘密鍵のpath>>> \
-h <<<AWS IoT Coreのエンドポイント>>> -p 8883 \
-t /hello_world_robot/sub \
-q 1 \
-m "{\"message\": \"start node\", \"x\": 0.1, \"z\": 0.5, \"sec\": 6.28}"
```

![simulation.gif](https://qiita-image-store.s3.amazonaws.com/0/37648/b3208c5e-4038-327d-5799-3c17ef6feb78.gif)

無事に動作しましたね！
（後半でシミュレータのロボットがガクガクしているのは、Macのネットワークの調子が良くなかったせいです・・・）

# 実機のロボットをAWS IoT Coreから操作する
では最後に、このROSアプリケーションをTurtlebot3の実機へデプロイして、動作を確認してみましょう。
シミュレータと同様に、`{"message": "start node", "x": 0.1, "z": 0.5, "sec": 6.28}`というメッセージをAWS IoT Coreにpublishします。

![turtlebot3.gif](https://qiita-image-store.s3.amazonaws.com/0/37648/ad7fc904-81ad-4b60-9f08-75efe58a91f9.gif)

MQTTメッセージを受け取ると、ロボットが命令に従って動作しました！

# まとめ
AWS RoboMakerを使ってロボットをAWS IoT Coreに接続することにより、外部からロボットに動作を命令することができました。またロボット本体で特に作業をせずとも、外部のPythonライブラリを利用するROSアプリケーションをインターネット越しにロボットへデプロイすることもできました。

加えて、AWS RoboMakerとAWS IoTの認証認可機構をうまく組み合わせることで、昨今問題になっているIoTデバイスのセキュリティ問題へも一貫した手順で対策が可能となっています（証明書がロボット側に同梱されてしまうため、ロボットが物理的に盗難された場合への対応は、別途考える必要がありますが）。

今回はROSアプリケーションをあまり複雑にしたくなかったため、ロボットの動作仕様に即したメッセージ（"x", "z", "sec"）をAWS IoT Coreから送信する形で実装しました。しかし送信するメッセージをより抽象的な命令セットにし、それらの命令セットをロボットの仕様に合わせて翻訳するトランスレータをROSアプリケーションとして実装する形にすれば、別機種のロボットへ入れ替えても動作を命令する側は変更する必要が無くなり、より柔軟なシステムを組み立てることができるようになると思います。

まだ始まったばかりのAWS RoboMakerですが、ぜひ試してみていただければと思います。