AWS IoT の Thing Shadow と AWS Lambda の Schedule Event で Crystal Signal Pi に通知する

Raspberry Pi で作る LED 警告灯ソリューション、Crystal Signal Pi のプリント基板キットが届いたので早速組み立て、AWS IoT の Thing Shadow と AWS Lambda の Schedule Event で通知するところまでやってみました。

![crystalsignalpi.gif](https://qiita-image-store.s3.amazonaws.com/0/23579/82e5d0cd-53b3-7dc3-a8af-2af4c6d0b274.gif)

Schedule Event で Lambda 関数が実行される毎に点灯の仕方を変えています。

# Crystal Signal Pi とは
詳しくは [Crystal Signal Pi 公式サイト](http://crystal-signal.com/)を参照ください。

# 事前準備

本記事では主に AWS CLI を使ってリソースを作成していきます。

##### AWS CLI をアップデート

```bash
$ sudo pip install --upgrade awscli
```

##### バージョン確認

```bash
$ aws --version
aws-cli/1.11.32 Python/2.7.12 Darwin/15.6.0 botocore/1.4.89
```

# AWS IoT 側の準備

##### Thing を作成

```bash
$ aws iot create-thing --thing-name crystalSignalPi
```

##### Shadow を登録

```bash
$ aws iot-data update-thing-shadow --thing-name crystalSignalPi  \
  --payload '{"state": {"desired" : {"blinkColor" : "green"}}}' \
  outfile.json
```

# AWS Lambda 側の準備

今回の AWS Lambda 関数作成に必要なものから作成していきます。

##### assume ロールの決定

```bash
$ cat << EOF > lambdaExecution.json
{
  "Version": "2012-10-17",
  "Statement": [
    {
      "Action": "sts:AssumeRole",
      "Principal": {
        "Service": "lambda.amazonaws.com"
      },
      "Effect": "Allow",
      "Sid": ""
    }
  ]
}
EOF
```

##### IAM ロールの作成

```bash
$ aws iam create-role \
        --role-name lambdaExecution \
        --assume-role-policy-document file://lambdaExecution.json
```

##### ポリシーの適用

```bash
$ aws iam attach-role-policy \
        --role-name lambdaExecution \
        --policy-arn arn:aws:iam::aws:policy/AWSIoTDataAccess
```

##### Lambda 関数

```bash
$ cat << EOF > changeBlinkColor.js
console.log('Loading function');
var aws = require('aws-sdk');

var endpoint  = 'XXXXXXXXXXXXX.iot.ap-northeast-1.amazonaws.com';
var thingName = 'crystalSignalPi';

exports.handler = function(event, context) {
    var iotdata = new aws.IotData( { endpoint: endpoint } );
    var params = { thingName: thingName };
    iotdata.getThingShadow(params, function (err, data) {
        if (!err) {
            var payload = JSON.parse(data.payload);
            var currentBlinkColor = payload.state.desired.blinkColor;
            console.log("Current Color : " + currentBlinkColor);
 
            var desiredBlinkColor;
            if(currentBlinkColor == 'red') {
                desiredBlinkColor = 'green';
            } else {
                desiredBlinkColor = 'red';
            }
            var desiredState = {
                state: {
                    desired: {
                        blinkColor: desiredBlinkColor
                    },
                    reported: {
                        blinkColor: currentBlinkColor
                    }
                }
            };
             
            var params = {
                thingName: thingName,
                payload: JSON.stringify(desiredState)
            };
            iotdata.updateThingShadow(params, function (err, data) {
                if (!err) {
                    context.succeed();
                } else {
                    context.fail(err);      
                }
            });
        } else {
            context.fail(err);      
        }
    });
};
EOF
```

```bash
$ zip changeBlinkColor.zip changeBlinkColor.js
```

##### Lambda 関数の作成

```bash
$ aws lambda create-function \
        --function-name changeBlinkColor \
        --zip-file fileb:///path/to/changeBlinkColor.js.zip \
        --runtime nodejs4.3 \
        --role arn:aws:iam::XXXXXXXXXXXX:role/lambdaExecution \
        --handler changeBlinkColor.handler
```

##### Schedule 作成

```bash
$ aws lambda add-permission \
        --function-name changeBlinkColor \
        --statement-id "changeBlinkColor00" \
        --action 'lambda:InvokeFunction' \
        --principal events.amazonaws.com \
        --source-arn arn:aws:events:ap-northeast-1:XXXXXXXXXXXX:rule/changeColorBatch
```

##### Event のルールを設定

```bash
$ aws events put-rule \
        --name "changeColorBatch" \
        --schedule-expression "cron(*/5 * * * ? *)" \
        --state ENABLED
```

##### 

```bash
$ aws events put-targets \
        --rule "changeColorBatch" \
        --targets Arn=arn:aws:lambda:ap-northeast-1:XXXXXXXXXXXX:function:changeBlinkColor,Id=XXXXX
```

# Crystal Signal Pi を組み立てる

公式サイトのマニュアルを見ながら Crystal Signal Pi を組み立て、設定していきます。

- [半田付け、組み立てマニュアル](http://crystal-signal.com/other/Crystal_Signal_Pi_construction.pdf)
- [ソフトウェア使用マニュアル](http://crystal-signal.com/other/Crystal_Signal_Pi_software.pdf)

# Raspberry Pi と AWS IoT の連携

##### pip のインストール

```bash
$ sudo apt-get install python-pip
```

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

```bash
$ sudo pip install AWSIoTPythonSDK
```

##### git のインストール

```bash
$ sudo apt-get install git
```

##### サンプルコードのダウンロード

```bash
$ git clone https://github.com/aws/aws-iot-device-sdk-python.git
$ cd aws-iot-device-sdk-python
$ sudo python setup.py install
```

##### IAM ユーザーの作成
デバイス用に IAM ユーザーを作成、Managed policy AWSIoTFullAccess をアタッチ

##### アクセスキーの設定
クレデンシャル情報を設定

##### サンプルコードをコピー

```bash
$ mkdir -p crystal-signal-pi
$ cd crystal-signal-pi
$ cp ../samples/basicShadow/basicShadowDeltaListener.py ./shadowDeltaListener.py
```

##### 証明書の準備、配置
コピーしたサンプルコードと同じディレクトリに配置

##### コードを編集

```shadowDeltaListener.py
# coding: utf-8
from AWSIoTPythonSDK.MQTTLib import AWSIoTMQTTShadowClient
import sys
import logging
import time
import json
import getopt
import pycurl

# Shadow JSON schema:
#
# Name: Bot
# {
#	"state": {
#		"desired":{
#			"property":<INT VALUE>
#		}
#	}
#}

# Custom Shadow callback
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++++++++++")
	property = "blinkColor"
	blinkColor = str(payloadDict["state"][property])
	print("property: " + blinkColor)
	#print("version: " + str(payloadDict["version"]))
	print("+++++++++++++++++++++++\n\n")
	if blinkColor == "red":
		url = "http://localhost/ctrl/?color=100,0,0&mode=1&repeat=0&period=250&json=1"
	else:
		url = "http://localhost/ctrl/?color=0,80,0&mode=1&repeat=0&period=1000&json=1"

	curl = pycurl.Curl()
	curl.setopt(pycurl.URL, url)
	curl.setopt(pycurl.CUSTOMREQUEST, 'GET')
	curl.perform()

# Read in command-line parameters
useWebsocket = True
host = "XXXXXXXXXXXXX.iot.ap-northeast-1.amazonaws.com"
rootCAPath = "rootCA.pem"
certificatePath = "XXXXXXXXXX-certificate.pem.crt"
privateKeyPath = "XXXXXXXXXX-private.pem.key"
thingName = "crystalSignalPi"

# Configure logging
logger = logging.getLogger("AWSIoTPythonSDK.core")
logger.setLevel(logging.DEBUG)
streamHandler = logging.StreamHandler()
formatter = logging.Formatter('%(asctime)s - %(name)s - %(levelname)s - %(message)s')
streamHandler.setFormatter(formatter)
logger.addHandler(streamHandler)

# Init AWSIoTMQTTShadowClient
myAWSIoTMQTTShadowClient = None
myAWSIoTMQTTShadowClient = AWSIoTMQTTShadowClient("basicShadowDeltaListener", useWebsocket=True)
myAWSIoTMQTTShadowClient.configureEndpoint(host, 443)
myAWSIoTMQTTShadowClient.configureCredentials(rootCAPath)

# AWSIoTMQTTShadowClient configuration
myAWSIoTMQTTShadowClient.configureAutoReconnectBackoffTime(1, 32, 20)
myAWSIoTMQTTShadowClient.configureConnectDisconnectTimeout(10)  # 10 sec
myAWSIoTMQTTShadowClient.configureMQTTOperationTimeout(5)  # 5 sec

# Connect to AWS IoT
myAWSIoTMQTTShadowClient.connect()

# Create a deviceShadow with persistent subscription
Bot = myAWSIoTMQTTShadowClient.createShadowHandlerWithName(thingName, True)

# Listen on deltas
Bot.shadowRegisterDeltaCallback(customShadowCallback_Delta)

# Loop forever
while True:
	pass
```

### python-pycurl のインストール

```bash
$ sudo apt-get install python-pycurl
```

### コードを実行

```bash
$ python shadowDeltaListener.py
```

# おわりに
今回はまず、AWS IoT との連携までやってみました。
次回は光での通知だけでなく、Polly を使って音声と連携した使い方も試してみたいと思います。