5
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 5 years have passed since last update.

myThingsAdvent Calendar 2016

Day 22

myThingsとAmazon Dash ButtonをAWS経由で繋げてみる

Last updated at Posted at 2016-12-22

やってみること

myThingsで勤怠連絡ボタンを作ってみた」を参考に、Amazon Dash Buttonを押したらAWS経由のmyThings Developersで連絡を送れる的なことをやってみたいと思います!

用意するもの

  • Amazon Dash Button
  • AWS API Gateway
  • AWS DynamoDB
  • AWS Lambda
  • myThings Developers

myThings Developersの準備

こちらは「myThingsで勤怠連絡ボタンを作ってみた」をご参照ください
Slackの設定などは丸々同じものを利用するで問題ございません。

手順

1.Amazon Dash Buttonの設定
AmazonDashButton
基本的には**公式セットアップ方法**に従って設定する。ただし、最後までセットアップはしないこと!下記のような画面でセットアップを途中でやめる

セットアップ画面

2.dasherの設定
dasherという、libpcapを利用してボタンのMACアドレスからのパケット送信を捉え、設定ファイルに書かれたURLにリクエスト投げるものがあるので、今回はそちらを利用します。
2-a. dasherの準備 (環境によっては、npmとlibpcap-devのインストールが必要です)

$ git clone https://github.com/maddox/dasher.git
$ cd dasher
$ npm install

2-b. Amazon Dash ButtonのMACアドレスの読み取り。下記コマンドを実行し、Amazon Dash Buttonを押す。

$ ./script/find_button 
Watching for arp & udp requests on your local network, please try to press your dash now
Dash buttons should appear as manufactured by 'Amazon Technologies Inc.' 
Possible dash hardware address detected: XX:XX:XX:XX:XX:XX Manufacturer: unknown Protocol: arp
Possible dash hardware address detected: XX:XX:XX:XX:XX:XX Manufacturer: unknown Protocol: udp

※期待値は「Amazon Technologies Inc.」のようですが、実際は「unknown」のものが出てくると思います

2-c. HTTPリクエスト先の設定

config/config.json
{"buttons":[
  {
    "name": "Morning rest button",
    "address": "XX:XX:XX:XX:XX:XX",
    "timeout": "5000",
    "url": "<<のちに用意するAWS API GatewayのURL>>",
    "method": "POST",
    "headers": {"x-api-key": "<<AWS API Gatewayで取得するAPIキー>>"},
    "json": true,
    "body": {"macAddress": "XX:XX:XX:XX:XX:XX"}
  }
]}

2-d. dasherの起動。ボタンを押して下記のようなものがコマンドに流れれば完了

$ sudo npm run start

> dasher@1.1.1 start /Users/yumatsud/git/dasher
> node app.js

[2016-12-10T15:33:38.687Z] Morning rest button added.
[2016-12-10T15:33:49.339Z] Morning rest button pressed.

3.AWS DynamoDBの準備
単純にmyThings Developersにリクエストを投げるだけであれば、上記のdasherのリクエストURLに先に設定したwebscript.ioのURLを指定するだけで良いので、今回はボタンを押したログも保存するようにします
API Gateway
※logId、createdAt、macAddressをjson形式で持つイメージ

4.AWS API Gatewayの準備
詳細な説明は省きますが、今回はボタンを押されたときにPOSTを受ける口を用意して、受けた先をLambdaにしておきます。
API Gateway
ポイントとしては、下記の3つ

  • 誰でもAPIにリクエストできるのも良くないので、最低限APIキーの設定
  • レスポンスにはパラメータがおかしかったときなどように400エラーも設定
  • GETでボタンを押したログの一覧を取得でき、さらにmacAddressを指定するとMacAddressに紐づくログも取れる部分も作成

5.AWS Lambdaの準備
AWS API GatewayとLambda関数の対応表は下記

リソース メドッド Lambdaファンクション 説明
/v1/logs GET list_logs 全てのログ一覧取得
/v1/logs POST created_log ログ登録
/v1/logs/{macAddress} GET get_logs MACアドレスごとのログ一覧取得
その中でも、created_log関数部分で、DynamoDBへの保存とmyThings Developersへのリクエストの実施 (サンプルコードは下記)
created_log.js
'use strict';

//var uuid = require('node-uuid');
const qs = require("querystring");
var https = require("https");
var date = new Date();
var dateString = createPostTimeString();

var AWS = require("aws-sdk");
var dynamo = new AWS.DynamoDB.DocumentClient();

// myThings Developersに必要なリクエスト項目
var appid = "<<appid>>";
var secret = "<<secret>>";
var accessToken = "<<access token>>";
var refreshToken = "<<refresh token>>";

exports.handler = (event, context, callback) => {
    //console.log("handler start. event:", JSON.stringify(event, null, 2));
    // パラメータチェック
    if (!event.macAddress) {
        context.fail('macAddress is not specified')
    }
    
    // uuidを生成
    //var uuid = uuid.v4();
    var uuid = createUuid();
    
    // 更新内容をセット
    var item = {
        "logId": uuid,
        "createdAt":  Math.floor(date.getTime() / 1000),
        "macAddress": event.macAddress
    };
    
    var param = {
        TableName: 'Button',
        Item: item
    }
    dynamo.put(param, function(err, data) {
        if (err) {
            context.fail(err);
        } else {
            //context.succeed(item)
            // myThings Developersへリクエスト
            requestDevelopers(context);
        }
    });
};

/**
 * UUID(ランダム文字列)の生成
 * @return string UUID
 */
function createUuid() {
    var S4 = function() {
        return (((1+Math.random())*0x10000)|0).toString(16).substring(1);
    }   
    return (S4()+S4()+"-"+S4()+"-"+S4()+"-"+S4()+"-"+S4()+S4() +S4());
}

/**
 * "2016/12/21(水)"の形式の文字列を返す
 * @return string 日付文字列
 */
function createPostTimeString() {
    // 年
    var year = date.getFullYear();
    // 月
    var month = date.getMonth() + 1;
    if (month < 10) {
        month = '0' + month;
    }
    // 日
    var day = date.getDate();
    if (day < 10) {
        day = '0' + day;
    }
    // 曜日
    var weekDayList = [ "", "", "", "", "", "", "" ];
    var weekDay = weekDayList[ date.getDay() ];
    
    return year+"/"+month+"/"+day+"("+weekDay+")";
}

/**
 * myThings Developersへのリクエスト
 * @return void
 */
function requestDevelopers(context) {
    // リクエストパラメータの生成
    var postArgs = {
        date: dateString
    }
    var postData = qs.stringify({
        "entry": JSON.stringify(postArgs),
    });
    
    // リクエスト設定
    var options = {
        hostname: "mythings-developers.yahooapis.jp",
        path: "/v2/services/<<要設定>>/mythings/<<要設定>>/run",
        port: 443,
        method: "POST",
        headers: {
            "Content-Type": "application/x-www-form-urlencoded; charset=utf-8",
            "Authorization": "Bearer " + accessToken,
        },
    };
    
    // リクエスト
    var req = https.request(options, function(res){
        // 401のとき
        if (res.statusCode == 401) {
            // コールバック付きのrefreshAccessTokenを呼ぶ
            refreshAccessToken(context);
        }

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

/**
 * アクセストークンのリフレッシュ
 */
function refreshAccessToken(context) {
    console.log("refreshAccessTokenにきたよ");
    // リフレッシュ用データのセット
    var reqData = qs.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"),
        }
    };

    // リクエスト実行
    var req = https.request(options, function(res) {
        console.log("refreshAccessTokenのrequestのなかにきたよ");
        // 401の場合
        if(res.statusCode == 401) {
            context.fail("リフレッシュトークンの有効期限が切れました。myThings Developersのサンプルコードからリフレッシュトークンを再取得して下さい。");
        } else if(res.statusCode != 200) {
            context.fail("カスタムトリガーの実行リクエストの受付に失敗しました。:"+res.content);
        }

        // レスポンス処理
        res.on('data', function(body){
            var parseData = JSON.parse(body);
            accessToken = parseData['access_token'];
            requestDevelopers(context);
        }); 
    });

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

6.実行結果
IMG_4081.GIF
うまくいきました!!

やってみて感じたこと

  • Amazon Dash Buttonとdasherを動かすマシンが同一ネットワーク内にないといけない
  • dasherを動かすマシンが別途必要(PCでも可能)

課題はあるものの、マシンもラズパイとかを用意しておいておけば良いのと、手軽にハード(ボタン)を準備できるのは良いですね。
何より電源が不要で、枕元に置いておけば手軽に押せるので、実際に使っても良いと思えるものでした!!

ちなみに

今回はAPI Gateway -> Lambda -> DynamoDBという構成でしたが、DynamoDBのストリームという機能を使えば、

  • API Gateway -> DynamoDB -> Lambda -> myThings Developers

とか、DynamoDBへのinsert時にカスタマイズしたい場合は、

  • API Gateway -> Lambda -> DynamoDB -> Lambda -> myThings Developers

というような構成にしても良いかもしれません。
AWSを使えば、「サーバレスアーキテクチャー」をより手軽に実現できるのが素晴らしいと感じました。

参考

5
0
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
5
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?