LoginSignup
9
11

More than 5 years have passed since last update.

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

Last updated at Posted at 2016-12-01

概要

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

使うもの

完成予想図

下図のようなものを作ってみたいと思います。
完成予想図.png

作っていく流れ

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

1. myThings Developersの準備

1-1. 利用者登録

まずはmyThings Developersにアクセスしてみます。
すると下図のような画面が表示されます。
トップ画面.png
Yahoo! JAPAN IDでログインし、利用者登録を行います。
(無料で使えるので安心)

1-2. データを登録

デバイスから連携して動かしたいサービスを設定します。
細かい流れはチュートリアルがあるので、こちらを参考にしてください。
チュートリアル.png

今回はラズパイからメッセージをSlackへ投稿するケースです。
組み合わせエリア.png

カスタムトリガーの設定は、下図のようにします。
ラズパイからのメッセージをkey01から受け取ります。
カスタムトリガー設定.png

Slackの設定は、下図のようにします。
メッセージにラズパイから受け取ったメッセージを渡します。
アクション設定.png

1-3. テスト実行

チュートリアルの手順に沿って進め、テスト実行を行います。
下図のように設定し、テスト実行を行うとSlackにメッセージが投稿されます。
テスト実行.png

Slackには下記のようにメッセージが投稿されます。
slack投稿.png

1-4. 疎通確認

テスト実行でSlackに投稿できる事を確認したので、次は疎通確認を行います。
下記の画面に表示されているサンプルコードを表示ボタンを押して、サンプルコードを表示させます。
疎通確認タブ.png

ちょっとずるをして、サンプルコードの下に表示されている下図のサンプルレスポンスからurlをコピペしてブラウザのアドレスバーに入力するとユーザー設定画面が表示されます。
サンプルレスポンス.png

下図のようなユーザー設定画面が表示されるので、メッセージに{{キー01}}を設定します。
ユーザー設定.png

そして、Step.2のサンプルコードを表示します。
カスタムトリガーサンプル呼び出し.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

2-2. Create a Lambda function

Lambda functionを作成するために、Create a Lambda functionボタンを押します。
CreateLambda.png

myThings Developersへhttpsのリクエストを送りたいので、httpsで検索して、
https-requestのblueprintを選択します。
select_blueprint.png

AWS IoT をtriggerに選択し、適当なRule name(RaspberryPi3_rule)を設定し、
SQL statementにSELECT * FROM 'sdk/test/Python'を設定します。
(上記のSQL statementはAWS IoTのサンプルコードからメッセージを取得するためのものです。)
configure_trigger.png

適当なfunctionの名前(RaspberryPi3_function)を設定します。
configure_function.png

function codeに下記にあるサンプルコードを入力します。
サンプルコード内の<個別の値>と記載している部分は、myThings Developersのサンプルコードから取得した値を設定して下さい。
function_code.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

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

2-3. Test

最後にfunctionが正しく動いているかをテストします。
下記の画面でTestボタンを押し、
Execution result: succeededと表示されていれば実行成功です。
lambda_test.png

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

3. AWS IoTの準備

3-1. AWS IoTの選択

サービスからAWS IoTを選択します。
AWSサービス(IoT).png

3-2. Register a thing

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

下記のような画面が表示されます。
今回はNameにRaspberryPi3と入力します。
register_a_thing.png

3-3. Test

Publishにsdk/test/Pythonを入力し、Publish to topicボタンを押すとテストメッセージが飛びます。
Slackにもメッセージが飛んでいるので確認してください。
AWS_IoT_test.png

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

4. ラズパイの準備

4-1. SDKのインストール

ラズパイを起動し、ブラウザでAWS IoTのページを開きます。
そして、先程作ったThingsを選択します。
Things.png

表示されたページでInteractを選択すると下記のようなページが表示されます。
画面右上のConnect a deviceを選択します。
あとで使うので、この画面に表示されているEndpoint(XXXXX.amazonaws.com)をコピーしておいて下さい。
Things2.png

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

進めていくと、下記の画面が表示されるので、Download connection kit forの下のLinux/OSXボタンを押して、ZIPファイルをダウンロードします。
Things4.png

次のページの手順に沿って進めると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にメッセージが投稿されます。
Slack_send.png

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

参考記事

9
11
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
9
11