【入門編】myThings Developers と デバイスを繋いでみる 〜 Raspberry Pi、AWS IoT 編 〜

# 概要
今回は、myThings Developersとデバイスを繋いで、IoTっぽいものを作るための準備を行います。

# 使うもの
 - [myThings Developers][mlink]
 - [AWS IoT][alink]
 - [AWS Lambda][llink]
 - [Raspberry Pi 3][rlink]

# 完成予想図
下図のようなものを作ってみたいと思います。
![完成予想図.png](https://qiita-image-store.s3.amazonaws.com/0/147237/8113ff2c-405c-d619-b46f-994707b35d1b.png)


# 作っていく流れ
こんな流れで進めていきます。
 1. [myThings Developers][mlink]の準備
 2. [AWS Lambda][Llink]の準備
 3. [AWS IoT][alink]の準備
 4. ラズパイの準備
 5. 動かしてみる

# 1. myThings Developersの準備
## 1-1. 利用者登録
まずは[myThings Developers][mlink]にアクセスしてみます。
すると下図のような画面が表示されます。
![トップ画面.png](https://qiita-image-store.s3.amazonaws.com/0/147237/ba60d69b-0188-09bd-dc0e-643d2e656453.png)
Yahoo! JAPAN IDでログインし、利用者登録を行います。
（無料で使えるので安心）
## 1-2. データを登録
デバイスから連携して動かしたいサービスを設定します。
細かい流れは[チュートリアル][tlink]があるので、こちらを参考にしてください。
![チュートリアル.png](https://qiita-image-store.s3.amazonaws.com/0/147237/fcb04ecb-8e00-36ee-d5dd-108306a4bfdd.png)

今回はラズパイからメッセージをSlackへ投稿するケースです。
![組み合わせエリア.png](https://qiita-image-store.s3.amazonaws.com/0/147237/11e9c1e8-7dd9-71f4-fb21-0a4aca8b2f1c.png)

カスタムトリガーの設定は、下図のようにします。
ラズパイからのメッセージを**key01**から受け取ります。
![カスタムトリガー設定.png](https://qiita-image-store.s3.amazonaws.com/0/147237/d0602720-02f9-fb51-983d-e4404fa6a88d.png)

Slackの設定は、下図のようにします。
メッセージにラズパイから受け取ったメッセージを渡します。
![アクション設定.png](https://qiita-image-store.s3.amazonaws.com/0/147237/4be86fc4-e2cd-9afa-960f-205d1204bfdf.png)

## 1-3. テスト実行
[チュートリアル][tlink]の手順に沿って進め、テスト実行を行います。
下図のように設定し、テスト実行を行うとSlackにメッセージが投稿されます。
![テスト実行.png](https://qiita-image-store.s3.amazonaws.com/0/147237/395c209a-d754-16c1-9fdf-f78494cdc0d9.png)

Slackには下記のようにメッセージが投稿されます。
![slack投稿.png](https://qiita-image-store.s3.amazonaws.com/0/147237/caa75966-9d04-5489-aece-bab9a13f2269.png)

## 1-4. 疎通確認
テスト実行でSlackに投稿できる事を確認したので、次は疎通確認を行います。
下記の画面に表示されている**サンプルコードを表示**ボタンを押して、サンプルコードを表示させます。
![疎通確認タブ.png](https://qiita-image-store.s3.amazonaws.com/0/147237/dfa4a708-682c-2cd7-e065-4d38da1a55ed.png)

ちょっとずるをして、サンプルコードの下に表示されている下図のサンプルレスポンスからurlをコピペしてブラウザのアドレスバーに入力するとユーザー設定画面が表示されます。
![サンプルレスポンス.png](https://qiita-image-store.s3.amazonaws.com/0/147237/f58aa229-9e2d-819f-dc05-311198601e36.png)

下図のようなユーザー設定画面が表示されるので、メッセージに**{{キー01}}**を設定します。
![ユーザー設定.png](https://qiita-image-store.s3.amazonaws.com/0/147237/906418b3-c854-005d-4b85-5e85dd52e5ab.png)

そして、Step.2のサンプルコードを表示します。
![カスタムトリガーサンプル呼び出し.png](https://qiita-image-store.s3.amazonaws.com/0/147237/61f516f3-be80-56eb-4f06-eb1de9cfda16.png)

サンプルコードに表示されている以下の値をメモ帳などに保存しておきます。（あとで使います。）

```
// カスタムトリガー実行APIエンドポイントのホスト名とパス
var hostname = "mythings-developers.yahooapis.jp";
var path = "/v2/services/＜個別の値＞/mythings/＜個別の値＞/run";

// アプリケーションID
var appid = "＜個別の値＞";
// シークレット
var secret = "＜個別の値＞";

// アクセストークン
var accessToken = "＜個別の値＞";
// リフレッシュトークン
var refreshToken = "＜個別の値＞";
```

以上で**myThings Developersの準備**は完了です。

# 2. AWS Lambdaの準備
## 2-1. AWS Lambdaの選択
AWSにアカウント登録した後は、サービスから**Lambda**を選択します。
![AWSサービス(Lambda).png](https://qiita-image-store.s3.amazonaws.com/0/147237/cb8039ab-5a1a-e890-f1b0-1438be3fcecc.png)

## 2-2. Create a Lambda function
Lambda functionを作成するために、**Create a Lambda function**ボタンを押します。
![CreateLambda.png](https://qiita-image-store.s3.amazonaws.com/0/147237/748d9cc8-b101-bb4d-f83b-e3aedf0c76d5.png)

myThings Developersへhttpsのリクエストを送りたいので、httpsで検索して、
**https-request**のblueprintを選択します。
![select_blueprint.png](https://qiita-image-store.s3.amazonaws.com/0/147237/87c8494a-848d-3b9b-3139-ddb10a6fe62e.png)

AWS IoT をtriggerに選択し、適当なRule name(**RaspberryPi3_rule**)を設定し、
SQL statementに**SELECT * FROM 'sdk/test/Python'**を設定します。
（上記のSQL statementはAWS IoTのサンプルコードからメッセージを取得するためのものです。）
![configure_trigger.png](https://qiita-image-store.s3.amazonaws.com/0/147237/4833cd09-db89-952b-18d5-f13307930d17.png)

適当なfunctionの名前(**RaspberryPi3_function**)を設定します。
![configure_function.png](https://qiita-image-store.s3.amazonaws.com/0/147237/1e69439e-00e6-d6ca-d574-f8901de6a220.png)


function codeに下記にあるサンプルコードを入力します。
サンプルコード内の**＜個別の値＞**と記載している部分は、myThings Developersのサンプルコードから取得した値を設定して下さい。
![function_code.png](https://qiita-image-store.s3.amazonaws.com/0/147237/11a056ae-ce52-b634-db8a-6b72601b548f.png)


サンプルコード

```

var http = require("http");
var https = require("https");

// カスタムトリガー実行APIエンドポイントのホスト名とパス
var hostname = "mythings-developers.yahooapis.jp";
var path = "/v2/services/＜個別の値＞/mythings/＜個別の値＞/run";

// アプリケーションID
var appid = "＜個別の値＞";
// シークレット
var secret = "＜個別の値＞";

// アクセストークン
var accessToken = "＜個別の値＞";
// リフレッシュトークン
var refreshToken = "＜個別の値＞";

exports.handler = function(event, context) {

	// カスタムトリガーに設定したキー名と値を指定
	var postArgs = {
		"key01": event.message
	};

	var postData = require("querystring").stringify( {
			"entry": JSON.stringify(postArgs),
	});

	// カスタムトリガーの実行
	postRequestWithToken(hostname, path, accessToken, postData, refreshToken, appid, secret);
};

/**
 * postRequestWithToken
 *
 * @param string hostname myThingsのホスト
 * @param string path カスタムトリガー実行APIのパス
 * @param string accessToken アクセストークン
 * @param array postData POSTデータ
 * @param string refreshToken リフレッシュトークン。指定しない場合は、トークンのリフレッシュは行わない
 * @param string appid アプリーションID
 * @param string secret secret
 */
function postRequestWithToken(hostname, path, accessToken, postData, refreshToken, appid, secret)
{
	// デフォルト引数の処理
	if(typeof refreshToken === 'undefined') refreshToken = false;
	if(typeof appid === 'undefined') appid = false;
	if(typeof secret === 'undefined') secret = false;

	// curlのオプション設定
	var options = {
		hostname: hostname,
		path: path,
		port: 443,
		method: "POST",
		headers: {
			"Content-Type": "application/x-www-form-urlencoded; charset=utf-8",
			"Authorization": "Bearer " + accessToken,
		},
	};

	// curlでAPIエンドポイントへリクエストを実行
	var req = https.request(options, function(res) {
		// 401認証エラーが発生した場合はアクセストークンの有効期限が切れています
		if(res.statusCode == 401) {
			// フラグが立っている場合は、リフレッシュトークンでアクセストークンを更新
			if(refreshToken !== false) {
				// アクセストークンの更新
				refreshAccessToken(hostname, path, refreshToken, appid, secret, postData);
			}
		}

		// レスポンスの処理
		res.on('data', function(body){
			parseData = JSON.parse(body);
			console.log(parseData);	
			if(typeof( parseData["flag"] ) != "undefined")
			{
				var msg = "カスタムトリガーの実行リクエストを受け付けました。";
				console.log(msg);
			}
		});	
	})
	.on("error", function(res) {
		console.error("カスタムトリガーの実行リクエストの受付に失敗しました。");
	});

	// POSTデータのリクエスト
	req.end(postData);
}

/**
 * refreshAccessToken
 *
 * @param string hostname myThingsのホスト
 * @param string path カスタムトリガー実行APIのパス
 * @param string refreshToken リフレッシュトークン
 * @param string appid アプリーションID
 * @param string secret secret
 */
function refreshAccessToken(hostname, path, refreshToken, appid, secret, postDataWithToken)
{
	var postData = require("querystring").stringify( {
		"grant_type": "refresh_token",
		"refresh_token": refreshToken
	});

	// ベージック認証
	var buffer = new Buffer(appid + ":" + secret, "ascii");
	var options = {
		hostname: "auth.login.yahoo.co.jp",
		path: "/yconnect/v1/token",
		port: 443,
		method: "POST",
		headers: {
			"Content-Type": "application/x-www-form-urlencoded; charset=utf-8",
			"Authorization": "Basic " + buffer.toString("base64"),
		}
	};

	// curlでAPIエンドポイントへリクエストを実行
	var req = https.request(options, function(res) {
		// 401認証エラーが発生した場合はリフレッシュトークンの有効期限が切れています
		if(res.statusCode == 401) {
			console.error("リフレッシュトークンの有効期限が切れました。myThings Developersのサンプルコードからリフレッシュトークンを再取得して下さい。");
		}

		// レスポンスの処理
		res.on('data', function(body){
			parseData = JSON.parse(body);
			// 取得したアクセストークンでカスタムトリガーの再実行
			postRequestWithToken(hostname, path, parseData['access_token'], postDataWithToken);
		});	
	});

	// POSTデータのリクエスト
	req.end(postData);
}

```

Handerを下図のように設定します。（初期表示から変更なし）
Handerの設定は、上記サンプルコードの下記の太字部分と一致している必要があります。
exports.**handler** = function(event, context) {
![handler.png](https://qiita-image-store.s3.amazonaws.com/0/147237/6f05abfa-e1e0-b262-e7d5-2785feef2654.png)

あとは次のページへ遷移して、**Create function**ボタンを押して終了です。

## 2-3. Test
最後にfunctionが正しく動いているかをテストします。
下記の画面で**Test**ボタンを押し、
**Execution result: succeeded**と表示されていれば実行成功です。
![lambda_test.png](https://qiita-image-store.s3.amazonaws.com/0/147237/6297fee9-3dee-3b07-6742-96eeb21e8273.png)

以上で**AWS Lambda**の準備は完了です。

# 3. AWS IoTの準備
## 3-1. AWS IoTの選択
サービスから**AWS IoT**を選択します。
![AWSサービス(IoT).png](https://qiita-image-store.s3.amazonaws.com/0/147237/47e48892-62b0-e048-7a86-f2c42e2aa6f4.png)

## 3-2. Register a thing
AWS IoT を開くと左にメニューがある画面が開かれます。
デバイスを繋ぐためにまずはthingを登録します。
左メニューの**Register>Things**を選択し、**Register a thing**ボタンを押します。

下記のような画面が表示されます。
今回はNameに**RaspberryPi3**と入力します。
![register_a_thing.png](https://qiita-image-store.s3.amazonaws.com/0/147237/2fe99e3a-d587-1e5d-be4e-69385eb9ce81.png)

## 3-3. Test
Publishに**sdk/test/Python**を入力し、**Publish to topic**ボタンを押すとテストメッセージが飛びます。
Slackにもメッセージが飛んでいるので確認してください。
![AWS_IoT_test.png](https://qiita-image-store.s3.amazonaws.com/0/147237/7e572f48-97e9-815b-192c-de55629066cc.png)

以上で**AWS IoT**の準備は完了です。


# 4. ラズパイの準備
## 4-1. SDKのインストール
ラズパイを起動し、ブラウザでAWS IoTのページを開きます。
そして、先程作ったThingsを選択します。
![Things.png](https://qiita-image-store.s3.amazonaws.com/0/147237/d997e5fc-9e03-4041-8dee-17fedcc859ce.png)

表示されたページで**Interact**を選択すると下記のようなページが表示されます。
画面右上の**Connect a device**を選択します。
あとで使うので、この画面に表示されているEndpoint(**XXXXX.amazonaws.com**)をコピーしておいて下さい。
![Things2.png](https://qiita-image-store.s3.amazonaws.com/0/147237/d459e1b2-e92f-0ccb-9210-d26afc7b7390.png)

環境と言語を設定する画面が表示されるので、
**Linux/OSX > Python**を選択します。

進めていくと、下記の画面が表示されるので、Download connection kit forの下の**Linux/OSX**ボタンを押して、ZIPファイルをダウンロードします。
![Things4.png](https://qiita-image-store.s3.amazonaws.com/0/147237/d7b0ef38-3d40-1a39-6386-48c8befdb26b.png)

次のページの手順に沿って進めるとSDKのインストールが完了します。

## 4-2. 証明書の準備
サンプルコードを動かす場合、下記の３つの証明書が必要です。
 - RaspberryPi3.cert.pem
 - RaspberryPi3.private.key
 - rootCA.pem

rootCA.pemは以下のコマンドで取得します。
```
curl https://www.symantec.com/content/en/us/enterprise/verisign/roots/VeriSign-Class%203-Public-Primary-Certification-Authority-G5.pem -o rootCA.pem
```

## 4-3. サンプルコードの修正
今回は、下記のサンプルコードを実行しますが、PublishするメッセージはJSON形式である必要があるため、一部サンプルコードを修正します。
https://github.com/aws/aws-iot-device-sdk-python/blob/master/samples/basicPubSub/basicPubSub.py

修正したサンプルコード

```

'''
/*
 * Copyright 2010-2016 Amazon.com, Inc. or its affiliates. All Rights Reserved.
 *
 * Licensed under the Apache License, Version 2.0 (the "License").
 * You may not use this file except in compliance with the License.
 * A copy of the License is located at
 *
 *  http://aws.amazon.com/apache2.0
 *
 * or in the "license" file accompanying this file. This file is distributed
 * on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either
 * express or implied. See the License for the specific language governing
 * permissions and limitations under the License.
 */
 '''

from AWSIoTPythonSDK.MQTTLib import AWSIoTMQTTClient
import sys
import logging
import time
import getopt
import json

# 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")

# Usage
usageInfo = """Usage:
Use certificate based mutual authentication:
python basicPubSub.py -e <endpoint> -r <rootCAFilePath> -c <certFilePath> -k <privateKeyFilePath>
Use MQTT over WebSocket:
python basicPubSub.py -e <endpoint> -r <rootCAFilePath> -w
Type "python basicPubSub.py -h" for available options.
"""
# Help info
helpInfo = """-e, --endpoint
	Your AWS IoT custom endpoint
-r, --rootCA
	Root CA file path
-c, --cert
	Certificate file path
-k, --key
	Private key file path
-w, --websocket
	Use MQTT over WebSocket
-h, --help
	Help information
"""

# Read in command-line parameters
useWebsocket = False
host = ""
rootCAPath = ""
certificatePath = ""
privateKeyPath = ""
try:
	opts, args = getopt.getopt(sys.argv[1:], "hwe:k:c:r:", ["help", "endpoint=", "key=","cert=","rootCA=", "websocket"])
	if len(opts) == 0:
		raise getopt.GetoptError("No input parameters!")
	for opt, arg in opts:
		if opt in ("-h", "--help"):
			print(helpInfo)
			exit(0)
		if opt in ("-e", "--endpoint"):
			host = arg
		if opt in ("-r", "--rootCA"):
			rootCAPath = arg
		if opt in ("-c", "--cert"):
			certificatePath = arg
		if opt in ("-k", "--key"):
			privateKeyPath = arg
		if opt in ("-w", "--websocket"):
			useWebsocket = True
except getopt.GetoptError:
	print(usageInfo)
	exit(1)

# Missing configuration notification
missingConfiguration = False
if not host:
	print("Missing '-e' or '--endpoint'")
	missingConfiguration = True
if not rootCAPath:
	print("Missing '-r' or '--rootCA'")
	missingConfiguration = True
if not useWebsocket:
	if not certificatePath:
		print("Missing '-c' or '--cert'")
		missingConfiguration = True
	if not privateKeyPath:
		print("Missing '-k' or '--key'")
		missingConfiguration = True
if missingConfiguration:
	exit(2)

# 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 AWSIoTMQTTClient
myAWSIoTMQTTClient = None
if useWebsocket:
	myAWSIoTMQTTClient = AWSIoTMQTTClient("basicPubSub", useWebsocket=True)
	myAWSIoTMQTTClient.configureEndpoint(host, 443)
	myAWSIoTMQTTClient.configureCredentials(rootCAPath)
else:
	myAWSIoTMQTTClient = AWSIoTMQTTClient("basicPubSub")
	myAWSIoTMQTTClient.configureEndpoint(host, 8883)
	myAWSIoTMQTTClient.configureCredentials(rootCAPath, privateKeyPath, certificatePath)

# AWSIoTMQTTClient connection configuration
myAWSIoTMQTTClient.configureAutoReconnectBackoffTime(1, 32, 20)
myAWSIoTMQTTClient.configureOfflinePublishQueueing(-1)  # Infinite offline Publish queueing
myAWSIoTMQTTClient.configureDrainingFrequency(2)  # Draining: 2 Hz
myAWSIoTMQTTClient.configureConnectDisconnectTimeout(10)  # 10 sec
myAWSIoTMQTTClient.configureMQTTOperationTimeout(5)  # 5 sec

# Connect and subscribe to AWS IoT
myAWSIoTMQTTClient.connect()
myAWSIoTMQTTClient.subscribe("sdk/test/Python", 1, customCallback)
time.sleep(2)

# Publish to the same topic in a loop forever
loopCount = 0
while True:
	json_data = {'message' : "New Message " + str(loopCount)}
	myAWSIoTMQTTClient.publish("sdk/test/Python", json.dumps(json_data), 1)
	loopCount += 1
	time.sleep(1)

```

以上で**ラズパイ**の準備は完了です。


# 5. 動かしてみる
以下のコマンドでサンプルコードを動かすと1秒間隔でSlackにメッセージが投稿されます。

```
python aws-iot-device-sdk-python/samples/basicPubSub/basicPubSub.py -e XXXXX.amazonaws.com -r rootCA.pem -c RaspberryPi3.cert.pem -k RaspberryPi3.private.key
```

10秒間動かすと、下記のようにSlackにメッセージが投稿されます。
![Slack_send.png](https://qiita-image-store.s3.amazonaws.com/0/147237/9d2d740a-6d8a-1765-c94d-adb81defe338.png)

以上でデバイス（ラズパイ）からmyThings Developersを経由してのSlackへの投稿は完了です。


# 参考記事
 - [AWS IoT Device SDK for PythonをRaspberryPiで動かす](http://qiita.com/YamauchiHideki/items/13e1e6a28d4b71f7d159)


[mlink]:https://mythings-developers.yahoo.co.jp
[alink]:https://aws.amazon.com/jp/iot/
[llink]:https://aws.amazon.com/jp/lambda/
[rlink]:https://www.raspberrypi.org/products/raspberry-pi-3-model-b/
[tlink]:https://mythings-developers.yahoo.co.jp/document/tutorial_section1