AWS IoT で Pepper と RaspberryPi 間を MQTT でやり取りしてみる

RaspberryPi のセンサーをトリガーに Pepper の会話をキックしたい！を実現すべく、考えついたのが「AWS IoT で Pepper、RaspberryPi 間で MQTT のやり取りを行う」です。

「Pepper の中身は Linux ベースの OS で、しかも Python 環境が備わっている。更に、持ってる RaspberryPi に Python 環境作ってある。となると。。AWS IoT の Python 組込 SDK を使えば、楽勝じゃね？？？」

と思って、AWS IoT の Python SDK を利用する方向性ではじめました。

その方向性で、やり方を色々ググったのですが、AWS IoT のバージョンが新しくなっていたり、内容が難しかったりでピンとくる記事・レポートを見つけられませんでした。で、「AWS公式をちゃんと見よう！」ということで「AWS 公式のドキュメント、サンプルを使って、実装してみたよ！」ということをレポートとして残しておきます。


# 概要

主に参照するのは、「[AWS IoT 開発者ガイド](https://docs.aws.amazon.com/ja_jp/iot/latest/developerguide/what-is-aws-iot.html)」。

使用するデバイス

| デバイス名 | OS | Pythonバージョン |
|:---------:|:--------:|:-------:|
| Pepper | naoqi 2.5.5 | 2.7 |
| RaspberryPi | Raspbian 8.0 (Jessie) | 2.7 |

両デバイス共、Python の組込 SDK である「AWS IoT Device SDK for Python」を利用します。

流れとしては、まず「AWS IoT Device SDK for Python」に同梱されているサンプルプログラムを実行して MQTT のやり取りを行っているのを確認し、動いたサンプルプログラムのコードを必要な箇所だけ書き換えて、RaspberryPi のセンサーに反応して、Pepper がしゃべるというプログラムを作成していきます。（センサー実装方法はまた別の話なので割愛。）

反応させるセンサーは色々なものが候補に上がりますが、とりあえず過去に試しで実装したことがある「温湿度センサーを利用し、湿度があるしきい値を超えると Pepper が『蒸せる』としゃべる」ものを作ります。


# AWS IoT コンソールでの操作

まず最初にたどるべきリンクは、[AWS IoT SDK チュートリアル](https://docs.aws.amazon.com/ja_jp/iot/latest/developerguide/sdk-tutorials.html) 下にある [Raspberry Pi の接続](https://docs.aws.amazon.com/ja_jp/iot/latest/developerguide/iot-sdk-setup.html) になります。

5.の証明書のダウンロードのところで、"AWS IoT のルート CA"の「ダウンロード」のリンクだけはクリックしても中身が開かれ、ダウンロードは行われないので、右クリックして保存します。（設定・ブラウザによるもの？）

<img width="984" alt="Screen Shot 2018-05-14 at 9.28.49.png" src="https://qiita-image-store.s3.amazonaws.com/0/92506/ebfea981-2127-9962-3e7b-46eea842a08f.png">

[Raspberry Pi の接続](https://docs.aws.amazon.com/ja_jp/iot/latest/developerguide/iot-sdk-setup.html) のページ内容を最後まで実行します。
詳細な内容は、AWS IoT ボタンの接続方法を詳細に書いている [AWS IoT の開始方法](https://docs.aws.amazon.com/ja_jp/iot/latest/developerguide/iot-gs.html) 下のページを参照してください。[Raspberry Pi の接続](https://docs.aws.amazon.com/ja_jp/iot/latest/developerguide/iot-sdk-setup.html) よりもページを贅沢に使って書かれているのでわかりやすいかもしれません。

[AWS IoT SDK チュートリアル](https://docs.aws.amazon.com/ja_jp/iot/latest/developerguide/sdk-tutorials.html) の流れのままで、Python SDK の内容まで実装できるかと思いきや、その配下には"C"と"JavaScript"での実装方法のリンクしかありません。なので、メニューバーから [AWS IoT SDK](https://docs.aws.amazon.com/ja_jp/iot/latest/developerguide/iot-sdks.html) のリンクを選択します。

ページ内の、"AWS IoT Device SDK for Python"に関する記述まで行き、[AWS IoT Device SDK for Python on GitHub](https://github.com/aws/aws-iot-device-sdk-python) の Github のページに飛びます。


# AWS IoT Device SDK for Python on GitHub

**※この内容は Pepper、RaspberryPi でそれぞれで行います。**

"AWS IoT Device SDK for Python"をデバイスに組み込んでいきます。

"README"の"Installation"項目にいくつかやり方が書いてあるので、どれかを実行します。オススメは、OS標準のコマンドだけでできる"Download the zip file"項目の内容です。
"wget"コマンドで、zipファイルをダウンロードし、"unzip"コマンドで解凍して、記載通りに"setup.py"をインストールします。Pepper では、管理者の大本に SDK を書き込める権限がないので、末尾に"--user"オプションを付けましょう。

```:オススメInstallation実行コマンド
wget https://s3.amazonaws.com/aws-iot-device-sdk-python/aws-iot-device-sdk-python-latest.zip

unzip aws-iot-device-sdk-python-latest.zip

python setup.py install
```

まず、用意されているサンプルプログラムを利用して、デバイス間の MQTT やり取りを見ていきます。
使うサンプルプログラムは、"basicPubSub.py"になります。

```:basicPubSub.py位置
zip展開ディレクトリ -> samples -> basicPubSub -> basicPubSub.py
```

実行ファイルのあるディレクトリまで移動して、"basicPubSub.py"を実行していきますが、その前にAWS IoT に接続するための証明書等を RaspberryPi 内に配置する必要があります。

必要なファイルは、公式ドキュメントにもあったようにダウンロードした3種類。

- 証明書
- プライベートキー
- ルートCA

以下はファイル名の一例。

- 2a540e2346-certificate.pem.crt
- 2a540e2346-private.pem.key
- VeriSign-Class 3-Public-Primary-Certification-Authority-G5.pem

ファイルをダウンロードした PC から"scp"コマンドなどの方法で移動します。


# サンプルプログラムの実行

証明書等のファイルを移動した後、サンプルプログラムの実行を行っていきますが、実行の際にはオプションを指定する必要があります。

オプションの内容は、"basicPubSub.py"の35~49行に詳細がコーディングされています。

```python:basicPubSub.py(35行~49行)
# Read in command-line parameters
parser = argparse.ArgumentParser()
parser.add_argument("-e", "--endpoint", action="store", required=True, dest="host", help="Your AWS IoT custom endpoint")
parser.add_argument("-r", "--rootCA", action="store", required=True, dest="rootCAPath", help="Root CA file path")
parser.add_argument("-c", "--cert", action="store", dest="certificatePath", help="Certificate file path")
parser.add_argument("-k", "--key", action="store", dest="privateKeyPath", help="Private key file path")
parser.add_argument("-w", "--websocket", action="store_true", dest="useWebsocket", default=False,
                    help="Use MQTT over WebSocket")
parser.add_argument("-id", "--clientId", action="store", dest="clientId", default="basicPubSub",
                    help="Targeted client id")
parser.add_argument("-t", "--topic", action="store", dest="topic", default="sdk/test/Python", help="Targeted topic")
parser.add_argument("-m", "--mode", action="store", dest="mode", default="both",
                    help="Operation modes: %s"%str(AllowedActions))
parser.add_argument("-M", "--message", action="store", dest="message", default="Hello World!",
                    help="Message to publish")
```

ここでのオプションは、MQTT において"Publish"する役割（メッセージを発行する側） と"Subscribe"する役割（メッセージを購読する側）で異なるものを指定します。（共通の内容もあります。）

センサー検知して Pepper に言葉を発するようにキックする RaspberryPi が"Publish"、RaspberryPi からのキックを受けて言葉を発する Pepper が"Subscribe"の役割になります。

同じ内容を指定するオプション

- ホスト：-e, --endpoint
- ルートCA：-r, --rootCA
- 証明書：-c, --cert
- プライベートキー：-k, --key
- トピック：-t, --topic

トピックは、"Publish"、"Subscribe"する先になります。同じ値を指定する必要があります。

異なる内容を指定するオプション

- モード：-m, --mode
- クライアントID：-id, --cliendId

"Publish"側のモードは、"-m publish"、"Subscribe"側のモードは"-m subscribe"と指定、クライアントIDは、それぞれ一意となるように指定します。

上記を踏まえた実行コマンド例になります。

```:Publish側
python basicPubSub.py -e abcde12345.iot.ap-northeast-1.amazonaws.com -r VeriSign-Class\ 3-Public-Primary-Certification-Authority-G5.pem -c 2a540e2346-certificate.pem.crt -k 2a540e2346-private.pem.key -t mqtttopic -m publish -id clientpub
```

```:Subscribe側
python basicPubSub.py -e abcde12345.iot.ap-northeast-1.amazonaws.com -r VeriSign-Class\ 3-Public-Primary-Certification-Authority-G5.pem -c 2a540e2346-certificate.pem.crt -k 2a540e2346-private.pem.key -t mqtttopic -m subscribe -id clientsub
```

上記のように RaspberryPi と Pepper で役割を分けて"basicPubSub.py"を実行すると、デフォルトに指定される「Hello World!」というメッセージが"発行"され、"購読"するというMQTTのやり取りが2つのデバイス間で行われるのがわかるかと思います。


# サンプルプログラムの解析・改造

サンプルプログラム内の"Publish"している箇所、"Subscribe"している箇所を修正して、「温湿度センサーを利用し、湿度があるしきい値を超えると Pepper が『蒸せる』としゃべる」というものを作成していきます。

## Publish部

"Publish"が実装されている箇所は、104行目から116行目。

```python:basicPubSub.py(104行~116行)
# Publish to the same topic in a loop forever
loopCount = 0
while True:
    if args.mode == 'both' or args.mode == 'publish':
        message = {}
        message['message'] = args.message
        message['sequence'] = loopCount
        messageJson = json.dumps(message)
        myAWSIoTMQTTClient.publish(topic, messageJson, 1)
        if args.mode == 'publish':
            print('Published topic %s: %s\n' % (topic, messageJson))
        loopCount += 1
    time.sleep(1)
```

そこを下記のように修正します。

```python:湿度40%超えたらPublishするやつ.py
# Publish to the same topic in a loop forever
chflg = 0
loopCount = 0
while True:
    if args.mode == 'both' or args.mode == 'publish':
        farm = AwsCondition()
        h, t = farm.get_DHT11()
        if float(h) > 40.0:
            if chflg == 0:
                message = {}
                message['temp'] = t
                message['hum'] = h
                message['sequence'] = loopCount
                messageJson = json.dumps(message)
                myAWSIoTMQTTClient.publish(topic, messageJson, 1)
                if args.mode == 'publish':
                    print('Published topic %s: %s\n' % (topic, messageJson))
                loopCount += 1
                chflg = 1
        if float(h) <= 40.0:
            chflg = 0
    time.sleep(1)
```

センサーに関して色々必要な内容を省略していますが、今回重要じゃないのでそのまま。。"h"に湿度の値、"t"に温度の値が取れています。しきい値を超えた後、何度も繰り返し"Publish"されて「蒸せる」を連発されたくないので、しきい値を一度下回らないともう一度"Publish"しないようにフラグを設けています。


## Subscribe部

"Subscribe"が実装されている箇所は、100行目から102行目。それに加え、26行目から32行目に"Subscribe"した後に実行される処理が実装されています。

コンソールに出力するだけならば、修正せずそのまま使用してもOKです。
今回は Pepper に「蒸せる」と言わせたいので、26行目から32行目の内容を修正します。

```python:basicPubSub.py(98行~102行：Connect部含む)
# Connect and subscribe to AWS IoT
myAWSIoTMQTTClient.connect()
if args.mode == 'both' or args.mode == 'subscribe':
    myAWSIoTMQTTClient.subscribe(topic, 1, customCallback)
time.sleep(2)
```

上記はそのまま使用します。

```python:basicPubSub.py(26行~32行)
# Custom MQTT message callback
def customCallback(client, userdata, message):
    print("Received a new message: ")
    print(message.payload)
    print("from topic: ")
    print(message.topic)
    print("--------------\n\n")
```

Pepper にしゃべらせるコードを追加します。

```python:Pepper蒸せるSubscribe.py
# Custom MQTT message callback
def customCallback(client, userdata, message):
    print("Received a new message: ")
    print(message.payload)
    print("--------------\n")
    tts = ALProxy('ALTextToSpeech', "127.0.0.1", 9559)
    # tts.post.say(str(json.loads(message.payload)['hum']))
    tts.post.say("蒸せる")
```

message のやり取りは JSON を使っているので、"json.loads()"を使えば、欲しい項目の値が取れるようになっています。（コメントアウトしてるやつ。）
話す言葉に日本語を使用してしまったので、プログラムの最初に文字コードを指定するのを追加しておくのを忘れないようにしましょう。


# まとめ

以上、やってみた記録です。
今回なぜ Pepper にもセンサーが存在するのに、RaspberryPi のセンサーをトリガーにしようと思ったのか？というのは、Pepper のセンサーが身の回り数メートルほどしか反応しないということにあります。その仕様の何に満足できなかったかというと、例えば、通りがかりの人に Pepper から声をかけさせようとした時、このセンサーの仕様では、Pepper が反応して言葉を発するまでに、声をかけたい人が既に目の前を通り過ぎてしまっている、ということが多々あるように感じたからです。

その問題を解決するのに、対象が Pepper に接触する少し手前に RaspberryPi のセンサーを設置しておけばいいのでは？という案を思いつきました。その案を実現するのに、検知から言葉を発するまでにラグがありすぎると意味がないので、できるだけリアルタイムになるように頻繁にイベントをキックして、それでいてデータ量がかさばらない通信、というのを意識して MQTT を使ってみようということを考え、AWS IoT の MQTT を使用してみました。

あと普通に AWS IoT で色々なデバイスを連携させてみたかったので。。。

当記事の内容は、ほとんど公式ドキュメントどおりですが、実行時のモードとか、ClientID で詰まったのでやり方のメモとして。