AWS-IoT ShadowからLEDのオン・オフを行う（AWS IoT Device SDK for Pythonを用いて）


#はじめに
この記事は、JAWS-UG 上越妙高支部,ギークラボ長野,学校法人信学会コードアカデミー高等学校協力によるギークラボ上越@上越教育大学 開催の「[IoTハンズオン AWS-IoT ShadowからLEDをオン・オフしよう](http://peatix.com/event/197369)」のハンズオン用資料です。

<hr>

AWS−IoTをAWSコンソール画面から始めると、以下の画面のようにサポートするSDKはNode.js(JavaScript)ほか3種類しか表示されません。
![Kobito.PTGnd6.png](https://qiita-image-store.s3.amazonaws.com/0/4092/3b612dde-012a-dfbf-ba26-1f762a68a24b.png "Kobito.PTGnd6.png")

Raspberry PiでNode.jsを利用するとなると、Node.jsの再インストール（しかもバージョンに注意しながら）や、GPIOを扱いやすくするJohnny-Fiveライブラリのインストール（これは好みによりますがw）など、準備に手間がかかります（手順などは[AWS-IoT ShadowからLEDのオン・オフやDCモーターを回す](http://qiita.com/dietposter/items/577bec337ae28acb5e6e)で紹介しています ）。

Raspberry Piの標準プログラミング言語であるPythonでAWS-IoTを扱うことができると、準備やコード記述が楽になります。
[AWS IoT デバイス SDK](https://aws.amazon.com/jp/iot/sdk/)サイトでは、Pythonをはじめ7種類のSDKが紹介されています。
![Kobito.zEEcVP.png](https://qiita-image-store.s3.amazonaws.com/0/4092/0e958e9d-32cc-cef0-6cf0-18a59fcedda5.png "Kobito.zEEcVP.png")

ということで、Python 2.7でAWS-IoT Shadowを記述し、LEDをオン・オフさせてみましょう！

#[1]AWS IoT Device SDK for Pythonのインストール

Raspberry PiにSSHなどでログインしたら、以下のコマンドを打ち、AWS IoT Device SDK for Pythonのインストールを行います。

```
$ sudo pip install AWSIoTPythonSDK
```

後ほど扱う実習用ディレクトリ`python_aws_iot`を作成し、移動しておきます。

```
$ mkdir python_aws_iot
$ cd  python_aws_iot
```

#[2]Raspberry PiでLEDの配線を行う

以下のように、LEDのマイナス側と抵抗を９番ピンに、LEDのプラス側を11番ピンに接続しておきます。

![Kobito.A96I7S.png](https://qiita-image-store.s3.amazonaws.com/0/4092/477b7f8b-2128-37f8-ac39-6328c403b80c.png "Kobito.A96I7S.png")

LED点滅のPythonソースを以下のコマンドでダウンロードし、実行して動作の確認をします。

```python
$ curl -O https://gist.githubusercontent.com/yoshidaken1/b4821b8520c098ec96d166e3f812a53b/raw/8a543683f7f8df3e7cb15ce6ed1ba426b3100597/blink.py
$ python blink.py
```

LEDが点滅したら、`[Ctrl]+C`キーを押してブレイクします。


#[3]AWS-IoTのThings Shadowでラズパイにプッシュを行う

AWS-IoTでThingsを作成し、Shadow画面からLEDのオン・オフのための制御データをラズパイ側に送信します。ShadowはThingが物理的に接続されていなくても送信動作を行うことができ、後ほど物理的に接続された際にプッシュ通信を行います。

* 参考サイト1 [初めてのAWS IoTでDeviceSDK(Node.js)を使ってラズパイのLEDチカチカ](http://qiita.com/toshihirock/items/05ef496bc96b38e7fd70)
* 参考サイト2 [AWS IoT Thing ShadowでLEDをつけてみた](https://iotnews.jp/archives/6599)


まず[AWSマネジメントコンソール](https://aws.amazon.com/jp/console/)からAWS IoTの管理画面にアクセスします。
![Kobito.UjO87j.png](https://qiita-image-store.s3.amazonaws.com/0/4092/4ed2ade5-ed52-8c1d-da04-eaefd142af80.png "Kobito.UjO87j.png")
 
[Get started]ボタンをクリックして始めます。
![Kobito.EsQmhx.png](https://qiita-image-store.s3.amazonaws.com/0/4092/53023ef3-88c7-768d-0b83-ca07cb64fbb4.png "Kobito.EsQmhx.png")

[Create Things]ボタンが選択された状態になり、Thingの作成のため、[Name]欄に任意の名前を入れて、[Create]ボタンをクリックします。ここでは「MyNewThing」としました。
![Kobito.8HDeWC.png](https://qiita-image-store.s3.amazonaws.com/0/4092/c559a5f5-1db2-dacd-8ef4-7574bcae040b.png "Kobito.8HDeWC.png")
thingが作成され表示されています。
[View Thing]ボタンをクリックします。REST API endpointなどの詳細が表示されます。
![AWS_IoT01.png](https://qiita-image-store.s3.amazonaws.com/0/4092/7e972a57-85d6-c4ee-0182-5887d83aad38.png "AWS_IoT01.png")


##[3.1]キーペア(公開鍵、秘密鍵)のダウンロード及び証明書のダウンロード
AWS IoTでは通信を行う際にTLSによる暗号化を行って通信を行います。
TLS通信の際に必要な証明書やキーペアのダウンロードを行います。
先ほど登録したMyNewThingの詳細を表示している画面の右下の[Connect a device]というボタンを押下します。
![Kobito.vE8A90.png](https://qiita-image-store.s3.amazonaws.com/0/4092/fe715b89-427f-94a9-9a89-0f89943695ea.png "Kobito.vE8A90.png")

次の画面ではThingとAWS IoTとの通信は何を使って行うかの確認画面が表示されます。今回はPythonのDeviceSDKを使いたいのですが、はじめに述べた通りPythonは一覧にありません。とりあえず[Node.js]をチェックします。[Generate certificate and policy]というボタンが表示されるので選択し、証明書とポリシーを作成します。
しばらく待つと以下の画面になります。
![Kobito.47ECql.png](https://qiita-image-store.s3.amazonaws.com/0/4092/7d9a3641-12d5-963e-cef4-c7649573802c.png "Kobito.47ECql.png")

３つのリンクが現れます。

* 公開鍵のダウンロード
* 秘密鍵のダウンロード
* 証明書のダウンロード（今回Pythonソースでは使われない）

各リンクをクリックし、作業PC上に保存しておきます。

* xxxxx-certificate.pem.crt->証明書
* xxxxx-private.pem.key->秘密鍵
* xxxxx-public.pem.key->公開鍵

※証明書はいつでも取得できるようですが、公開鍵、秘密鍵はこのページのみしかダウンロードできないようなので忘れずにダウンロードしておきます。 

__作業PCにダウンロードした３つのファイルをラズベリーパイの作業ディレクトリ「python_aws_iot」にアップロードしておきます。__
(私は[CyberDuck](https://cyberduck.io/index.ja.html?l=ja)を使いました）

[Comfirm & start connecting]ボタンをタップします。

![AWS_IoT02.png](https://qiita-image-store.s3.amazonaws.com/0/4092/5307ad21-36c5-2088-3c41-d92b336b076d.png "AWS_IoT02.png")


Thingの接続情報が表示されますので、作業PCにコピーしておきます。
[Return to Thing Detail]ボタンをタップしResources画面に戻ります。PolicyとCertificationが一覧にリストアップされています。
![AWS_IoT03.png](https://qiita-image-store.s3.amazonaws.com/0/4092/53173af6-a10d-dca1-eeea-317096e7560d.png "AWS_IoT03.png")


次に、向かって右上のThingの詳細画面にある[Update Shadow]をクリックします。
![Kobito.akIXS9.png](https://qiita-image-store.s3.amazonaws.com/0/4092/e3889796-8ade-fb86-fb79-c3eb2134aac3.png "Kobito.akIXS9.png")

Shadow stateのJSONの値を以下のように変えて、[Update Shadow]ボタンをクリックします。

```
{
  "desired": {
    "flash": 0
  },
  "reported": {
    "flash": 0
  }
}
```

これでShadowが作成・更新されました。
設定が成功すると以下のようにShadow stateが更新されます。
![Kobito.p8oFqk.png](https://qiita-image-store.s3.amazonaws.com/0/4092/b87bc310-c5b8-2999-8add-d676539f1649.png "Kobito.p8oFqk.png")
ラズパイ側で、通知を受け取るようにしましょう。

##[3.2]ルート証明書をダウンロードする
SSL/TLS通信ではサーバーから取得したデジタル証明書が正当なものであるか確認する必要があります。幾つかの認証局はルート証明書としてブラウザなどに最初から登録されているため、我々が通常見るようなサイトを閲覧する際にわざわざルート証明書をダウンロードする必要はありません。ただし、今回は通信をするのがブラウザではないため、対象のルート証明書だけ予めダウンロードする必要があります。
ラズベリーパイのコンソール画面にて以下のコマンドでルート証明書をダウンロードをします。

```
# root-CA.crtというファイル名でルート証明書を取得
$ wget -O root-CA.crt https://www.symantec.com/content/en/us/enterprise/verisign/roots/VeriSign-Class%203-Public-Primary-Certification-Authority-G5.pem
```


##[3.3]Pythonソースを用いてShadowからの更新データを受け取りLEDのオン・オフを行う

Shadowからの更新データを受け取りLEDに反映させるサンプルソース`shadowLed.py`を`python_aws_iot`ディレクトリ内に作成します。
`shadowLed.py`ソース内のコメント行`# Shadowの設定--あらかじめコピーしておいたThingの接続情報のJSONデータを用いて設定`を見つけて先ほど作業PCにコピーしておいたThingの接続情報をペーストします。

```python:shadowLed.py
# coding: utf-8
from AWSIoTPythonSDK.MQTTLib import AWSIoTMQTTShadowClient
import sys
import logging
import time
import json
import RPi.GPIO as GPIO

# Shadow JSON schema: Shadowから送られてくるJSONのスキーマ
#
# Name: Bot
# {
#	"state": {
#		"desired":{
#			"property":<INT VALUE>
#		}
#	}
#}

# ピン番号の割り当て方式を「コネクタのピン番号」に設定
GPIO.setmode(GPIO.BOARD)
# 使用するピン番号を代入
LED = 11
# 11 番ピンを出力ピンに設定し、初期出力をローレベルにする
GPIO.setup(LED, GPIO.OUT, initial=GPIO.LOW)

# DELTA（ShadowのJSONの内容に差異がある場合）を受けた時のカスタム処理を行うコールバック関数
def customShadowCallback_Delta(payload, responseStatus, token):
	# payload is a JSON string ready to be parsed using json.loads(...)
	# in both Py2.x and Py3.x
	print(responseStatus)
	payloadDict = json.loads(payload)

	print("++++++++DELTA++++++++++")
	print(payloadDict)
	property = "flash"
	print("property: " + str(payloadDict["state"][property]))
	#print("version: " + str(payloadDict["version"]))
	print("+++++++++++++++++++++++\n")

	if int(payloadDict["state"][property]) == 1:
		print("ON")
		# LED点灯 ハイレベルを出力
		GPIO.output(LED, GPIO.HIGH)
	else:
		print("OFF")
		# LED消灯 ローレベルを出力
		GPIO.output(LED, GPIO.LOW)

# Shadowの設定--あらかじめコピーしておいたThingの接続情報のJSONデータを用いて設定
host = "foo.bar.amazonaws.com"
port = 8883
clientId = "MyNewThing"
thingName = "MyNewThing"
rootCAPath = "root-CA.crt"
certificatePath = "**********-certificate.pem.crt"
privateKeyPath = "**********-private.pem.key"
##################

# Configure logging ログ出力指定
logger = None
if sys.version_info[0] == 3:
	logger = logging.getLogger("core")  # Python 3
else:
	logger = logging.getLogger("AWSIoTPythonSDK.core")  # Python 2
logger.setLevel(logging.DEBUG)
streamHandler = logging.StreamHandler()
formatter = logging.Formatter('%(asctime)s - %(name)s - %(levelname)s - %(message)s')
streamHandler.setFormatter(formatter)
logger.addHandler(streamHandler)

# AWSIoTMQTTShadowClientの初期化
myAWSIoTMQTTShadowClient = None
#clientIdを指定してShadowクライアントを作成
myAWSIoTMQTTShadowClient = AWSIoTMQTTShadowClient(clientId)
myAWSIoTMQTTShadowClient.configureEndpoint(host, port)
myAWSIoTMQTTShadowClient.configureCredentials(rootCAPath, privateKeyPath, certificatePath)

# AWSIoTMQTTShadowClientのコンフィグ設定
myAWSIoTMQTTShadowClient.configureAutoReconnectBackoffTime(1, 32, 20)
myAWSIoTMQTTShadowClient.configureConnectDisconnectTimeout(10)  # 10 sec
myAWSIoTMQTTShadowClient.configureMQTTOperationTimeout(5)  # 5 sec

# AWS IoTへの接続
myAWSIoTMQTTShadowClient.connect()

# Create a deviceShadow with persistent subscription
#thingNameを指定してハンドラのセットの準備
Bot = myAWSIoTMQTTShadowClient.createShadowHandlerWithName(thingName, True)

# DELTA（ShadowのJSONの内容に差異がある場合）にコールバック関数を呼び出す
Bot.shadowRegisterDeltaCallback(customShadowCallback_Delta)

try:
        # 無限ループ
        while True:
                pass
# キーボード例外を検出
except KeyboardInterrupt:
        pass

# GPIO を解放
GPIO.cleanup()
```

サンプルソースshadowLed.pyを入力し終えたら、以下のコマンドで実行してみましょう。うまく接続されていれば上記のようなログ表示がづらづらと表示されます。


```
$ python shadowLed.py
2016-09-06 16:39:54,390 - AWSIoTPythonSDK.core.protocol.mqttCore - DEBUG - Paho MQTT Client init.
2016-09-06 16:39:54,391 - AWSIoTPythonSDK.core.protocol.mqttCore - INFO - ClientID: MyNewThing
2016-09-06 16:39:54,392 - AWSIoTPythonSDK.core.protocol.mqttCore - INFO - Protocol: MQTTv3.1.1
2016-09-06 16:39:54,392 - AWSIoTPythonSDK.core.protocol.mqttCore - DEBUG - Register Paho MQTT Client callbacks.
2016-09-06 16:39:54,393 - AWSIoTPythonSDK.core.protocol.mqttCore - DEBUG - mqttCore init.
…（中略）…
2016-09-06 16:39:55,647 - AWSIoTPythonSDK.core.protocol.mqttCore - DEBUG - Recover subscribe context for the next request: subscribeSent: False
2016-09-06 16:39:57,650 - AWSIoTPythonSDK.core.shadow.deviceShadow - INFO - Subscribed to delta topic for deviceShadow: MyNewThing
```
次に、Shadow Stateを更新して通知されるか確認します。

ブラウザから[Update shadow]ボタンをクリックし、desiredキー内のflashキーの値を1にして、[Update shadow]ボタンをクリックします。
![Kobito.8bNLoR.png](https://qiita-image-store.s3.amazonaws.com/0/4092/baf9820e-31d7-778e-5c03-e2de9772dafb.png "Kobito.8bNLoR.png")
すると、ラズパイ側で実行しているshadowLed.pyの表示が以下のように更新されます。

```
$ python shadowLed.py
…（中略）…
2016-09-06 16:39:55,647 - AWSIoTPythonSDK.core.protocol.mqttCore - DEBUG - Recover subscribe context for the next request: subscribeSent: False
2016-09-06 16:39:57,650 - AWSIoTPythonSDK.core.shadow.deviceShadow - INFO - Subscribed to delta topic for deviceShadow: MyNewThing
delta/MyNewThing
++++++++DELTA++++++++++
{u'timestamp': 1473180252, u'state': {u'flash': 1}, u'version': 8, u'metadata': {u'flash': {u'timestamp': 1473180252}}}
property: 1
+++++++++++++++++++++++


ON
```

この時、flashキーの値が1となったためLEDが点灯します。
再度Shadow stateで、以下のようにdesiredキー内のflashキーの値を0にし、なおかつreportedキー内のflashキーの値を1にして、[Update shadow]ボタンをクリックすると、flashキーの値が0となりLEDが消灯します。

```JSON
{
  "desired": {
    "flash": 0
  },
  "reported": {
    "flash": 1
  }
}

```

ラズパイ側で実行しているshadowLed.pyの表示は以下のように更新されます。

```
delta/MyNewThing
++++++++DELTA++++++++++
{u'timestamp': 1473180456, u'state': {u'flash': 0}, u'version': 9, u'metadata': {u'flash': {u'timestamp': 1473180456}}}
property: 0
+++++++++++++++++++++++


OFF
```

以上で、AWS IoT Device SDK for Pythonを用いて、AWS-IoT ShadowからLEDのオン・オフを行うことができました！