概要
今回は、myThings Developersとデバイスを繋いで、IoTっぽいものを作るための準備を行います。
使うもの
完成予想図
作っていく流れ
こんな流れで進めていきます。
- myThings Developersの準備
- AWS Lambdaの準備
- AWS IoTの準備
- ラズパイの準備
- 動かしてみる
1. myThings Developersの準備
1-1. 利用者登録
まずはmyThings Developersにアクセスしてみます。
すると下図のような画面が表示されます。
Yahoo! JAPAN IDでログインし、利用者登録を行います。
(無料で使えるので安心)
1-2. データを登録
デバイスから連携して動かしたいサービスを設定します。
細かい流れはチュートリアルがあるので、こちらを参考にしてください。
今回はラズパイからメッセージをSlackへ投稿するケースです。
カスタムトリガーの設定は、下図のようにします。
ラズパイからのメッセージをkey01から受け取ります。
Slackの設定は、下図のようにします。
メッセージにラズパイから受け取ったメッセージを渡します。
1-3. テスト実行
チュートリアルの手順に沿って進め、テスト実行を行います。
下図のように設定し、テスト実行を行うとSlackにメッセージが投稿されます。
1-4. 疎通確認
テスト実行でSlackに投稿できる事を確認したので、次は疎通確認を行います。
下記の画面に表示されているサンプルコードを表示ボタンを押して、サンプルコードを表示させます。
ちょっとずるをして、サンプルコードの下に表示されている下図のサンプルレスポンスからurlをコピペしてブラウザのアドレスバーに入力するとユーザー設定画面が表示されます。
下図のようなユーザー設定画面が表示されるので、メッセージに**{{キー01}}**を設定します。
サンプルコードに表示されている以下の値をメモ帳などに保存しておきます。(あとで使います。)
// カスタムトリガー実行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を選択します。
2-2. Create a Lambda function
Lambda functionを作成するために、Create a Lambda functionボタンを押します。
myThings Developersへhttpsのリクエストを送りたいので、httpsで検索して、
https-requestのblueprintを選択します。
AWS IoT をtriggerに選択し、適当なRule name(RaspberryPi3_rule)を設定し、
SQL statementに**SELECT * FROM 'sdk/test/Python'**を設定します。
(上記のSQL statementはAWS IoTのサンプルコードからメッセージを取得するためのものです。)
適当なfunctionの名前(RaspberryPi3_function)を設定します。
function codeに下記にあるサンプルコードを入力します。
サンプルコード内の<個別の値>と記載している部分は、myThings Developersのサンプルコードから取得した値を設定して下さい。
サンプルコード
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) {
あとは次のページへ遷移して、Create functionボタンを押して終了です。
2-3. Test
最後にfunctionが正しく動いているかをテストします。
下記の画面でTestボタンを押し、
Execution result: succeededと表示されていれば実行成功です。
以上でAWS Lambdaの準備は完了です。
3. AWS IoTの準備
3-1. AWS IoTの選択
3-2. Register a thing
AWS IoT を開くと左にメニューがある画面が開かれます。
デバイスを繋ぐためにまずはthingを登録します。
左メニューのRegister>Thingsを選択し、Register a thingボタンを押します。
下記のような画面が表示されます。
今回はNameにRaspberryPi3と入力します。
3-3. Test
Publishにsdk/test/Pythonを入力し、Publish to topicボタンを押すとテストメッセージが飛びます。
Slackにもメッセージが飛んでいるので確認してください。
以上でAWS IoTの準備は完了です。
4. ラズパイの準備
4-1. SDKのインストール
ラズパイを起動し、ブラウザでAWS IoTのページを開きます。
そして、先程作ったThingsを選択します。
表示されたページでInteractを選択すると下記のようなページが表示されます。
画面右上のConnect a deviceを選択します。
あとで使うので、この画面に表示されているEndpoint(XXXXX.amazonaws.com)をコピーしておいて下さい。
環境と言語を設定する画面が表示されるので、
Linux/OSX > Pythonを選択します。
進めていくと、下記の画面が表示されるので、Download connection kit forの下のLinux/OSXボタンを押して、ZIPファイルをダウンロードします。
次のページの手順に沿って進めるとSDKのインストールが完了します。
4-2. 証明書の準備
サンプルコードを動かす場合、下記の3つの証明書が必要です。
- 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にメッセージが投稿されます。
以上でデバイス(ラズパイ)からmyThings Developersを経由してのSlackへの投稿は完了です。