社内のコーヒー淹れたよボタンができるまで

タンバリンの狩野です。

弊社大阪オフィスでは1階にキッチンがあります。

f:id:tamb-kano:20191119113653g:plain
1階のキッチンの様子

そこでよくコーヒーを淹れている(淹れてもらっている)のですが、淹れた事を他の人に通知するためにSlackでメッセージを送ったりするのは面倒…ということでボタンを押すだけで通知できるようにするべく、AWS IoTボタンを使ってコーヒー淹れたよ通知ボタンを作りました。ので、その手順を書いておきます。

AWS IoT Enterprise Buttonとは?

以下を参考にどうぞ。

バイスセットアップ

バイスのネットワーク設定

バイスのネットワーク設定はスマホアプリで行います。

AWS IoT 1-Click モバイルアプリ - AWS IoT 1-Click

インストールして起動するとログインボタンが目立ちますが、ログインしなくてもデバイスのネットワーク設定だけなら可能です。
右上にある丸いネットワークっぽいボタンをタップします。

AWS IoT Enterprise Button向け、というセクションでインターネットに接続、と出るので、Wi-Fiを設定 というボタンをタップします。

バイスIDの登録はデバイスの箱についているバーコードで読み取ることが出来ます。
QRコードでもスキャン出来るような書かれかただったので、デバイスの裏にある小さいQRコードを読ませてみましたが、認識してくれませんでした)
認識後、正しければ設定をタップ。 画面の指示に従って、デバイスを6秒間押してデバイスと接続し、繋ぎたいネットワークを設定します。 接続に成功するとLEDが緑に光る。で、完了で接続は完了です。

AWS IoT 1-Clickに登録

次に、ネット接続したデバイスAWSに登録します。AWSにログインして以下から登録します。
https://ap-northeast-1.console.aws.amazon.com/iot1click/home?region=ap-northeast-1#/onboard

f:id:tamb-kano:20191119113807j:plain

右側のデバイスの登録をクリック。デバイスIDの入力画面になるので、入力します。(スキャンできればスキャンの方が楽ですが、出来なかったです…)

f:id:tamb-kano:20191119113920j:plain

登録後、デバイスのボタンを一度押して(もしくは自動で検出される)紐付けます。

f:id:tamb-kano:20191119113939j:plain

完了。

f:id:tamb-kano:20191119113956j:plain

プロジェクト

次に、このボタンが押されたら何をするかを設定するために、プロジェクトを作成します。今回は全て東京リージョンで統一したいので、ここで作成前に東京リージョンになっているかを確認しておきます。

f:id:tamb-kano:20191119114017j:plain

プロジェクト名と説明を入力。

f:id:tamb-kano:20191119114037j:plain

どのデバイスで何をするか、と決めます。デバイステンプレートの定義、をクリックします。

f:id:tamb-kano:20191119114051j:plain

接続しているデバイスを選択し、テンプレート名を入力し、アクションを選択します。

f:id:tamb-kano:20191119114108j:plain

アクションはLambda関数の選択、としますが、まだLambda関数を作っていないのでその下にある注意書きの、新しいLambda関数を作成をクリック。

Lambda関数

デフォルトではリージョンが米国東部などになっていると思いますが、今回は日本で使用するのでアジアパシフィック(東京)に変更しておきます。

f:id:tamb-kano:20191119114127j:plain

新規で作成するので、一から作成を選択、名前を入力。ランタイムは Node.js 6.10にします。ロールはこのLambda関数が実行する権限を与えるものですが、カスタムロールの作成を選ぶと、自動的にLambda用のロールを作成する画面が起動します。

f:id:tamb-kano:20191119114144j:plain

ですので、このまま作成してやり、それを選択。で、関数の作成をクリックするとLambda関数が作成されます。

プレイスメントの作成

これでLambda関数を作成出来たのでとりあえずプロジェクトに組み込みます。プロジェクトに戻り、先程作成したLambda関数を選択します。

f:id:tamb-kano:20191119114207j:plainf:id:tamb-kano:20191119114207j:plain

プレイスメントの属性はとりあえず何も入れずに、プロジェクトの作成をクリックし完了します。

f:id:tamb-kano:20191119114224j:plain

あとはこのプロジェクトとデバイスを紐付けるためにプレイスメントを作成します。真ん中のプレイスメントの作成をクリック。プレイスメント名入力し、デバイスを選択。プレイスメントの属性は空とします。

f:id:tamb-kano:20191119114240j:plain

これで作成完了です。

動作テスト

ボタンを押して動作させてみます。 AWS IoT 1-Clickのデバイス画面からデバイスIDをクリックし、イベントを選択すると、今までに動作したログを確認することが出来ます。

f:id:tamb-kano:20191119114257j:plain

イベントログの内容は以下のようになっています。

{
  "device": {
    "type": "button",
    "deviceId": "xxx",
    "attributes": {
      "projectRegion": "ap-northeast-1",
      "projectName": "info_coffee",
      "placementName": "osaka-1f",
      "deviceTemplateName": "coffee_button"
    },
    "deviceArn": "arn:aws:iot1click:us-west-2:xxx:devices/xxx"
  },
  "stdEvent": {
    "clickType": "SINGLE",
    "reportedTime": "2018-07-13T07:51:48.458Z",
    "certificateId": "xxx",
    "remainingLife": 99.7,
    "testMode": false
  }
}

押し方で、シングルクリック、ダブルクリック、長押しクリック、と制御可能です。

動作
一度 SINGLE
二度 DOUBLE
長押し LONG

Lambdaでのリクエスト確認

上記はデバイスのログなのですが、実際にLambdaに送られてくるJSONは別の内容になっています。確認するために、Lambdaの関数内にcosole.logを入れてやります。

exports.handler = (event, context, callback) => {
    console.log(event);
};

保存後、モニタリンクタブ内のログにジャンプをクリックするとログが表示されます。

f:id:tamb-kano:20191119114317j:plain

ログ内から出力したリクエストの内容を取得可能。

f:id:tamb-kano:20191119114331j:plain

{
    "deviceInfo": {
        "deviceId": "xxx",
        "type": "button",
        "remainingLife": 99.3,
        "attributes": {
            "projectRegion": "ap-northeast-1",
            "projectName": "info_coffee",
            "placementName": "osaka-1f",
            "deviceTemplateName": "coffee_button"
        }
    },
    "deviceEvent": {
        "buttonClicked": {
            "clickType": "SINGLE",
            "reportedTime": "2018-07-13T08:09:59.237Z"
        }
    },
    "placementInfo": {
        "projectName": "info_coffee",
        "placementName": "osaka-1f",
        "attributes": {
            "key1": "test",
            "key2": "hoge"
        },
        "devices": {
            "coffee_button": "xxx"
        }
    }
}

placementInfoの中のattributesはプロジェクトやプレイスメントで設定可能。プロジェクトではデフォルトの値を設定でき、デバイス毎のプレイスメントでその値を上書き出来るので、個別に設定することが可能です。 今回はプレイスメントに設定して、デバイス毎に押した時に表示されるメッセージを変えられるようにしてみます。デバイス名を投稿者の名前にして、アイコンの絵文字も指定出来るようにする想定としました。

f:id:tamb-kano:20191119114350j:plain

Slackへの投稿

Incoming Webhooks

Slackへ投稿するためのWebhookのURLを取得します。以下のリンクから、Slackにログインし、投稿したいチャンネルのWebhookを生成することが出来ます。

https://slack.com/services/new/incoming-webhook

f:id:tamb-kano:20191119114409j:plain

作成したWebhookの一覧は以下から確認出来ます。

https://slack.com/apps/A0F7XDUAZ-incoming-webhooks

投稿

投稿はこのWebhookにJSONをPOSTで投げてやることで出来ます。

curl -X POST https://hooks.slack.com/services/XXXXXXXXX/XXXXXXXXX/XXXXXXXXXXXXXXXXXXXXXXXX -d '{"text": "FIRE!!!!", "username": "tofu on fire", "icon_emoji": ":name_badge:"}'

f:id:tamb-kano:20191119114433j:plain

送信するJSONの仕様は以下らへんにさくっとだけ書かれていました。 https://api.slack.com/incoming-webhooks#advanced_message_formatting

Lambda関数

設定した属性などを利用して投稿するためのJavascriptを作成します。

exports.handler = (event, context, callback) => {
    const https = require('https');
    const url = require('url');
    const webhook_url = process.env.SLACK_WEBHOOK_URL;
    const slack_req_opts = url.parse(webhook_url);
    slack_req_opts.method = 'POST';
    slack_req_opts.headers = {'Content-type': 'application/json'};
    
    var clickType = event.deviceEvent.buttonClicked.clickType;
    var text = null;
    switch (clickType) {
        case 'SINGLE':
            text = event.placementInfo.attributes.single;
            break;
        case 'DOUBLE':
            text = event.placementInfo.attributes.double;
            break;
        case 'LONG':
            text = event.placementInfo.attributes.long;
            break;
    }
    if(text == null) return;
    
    var req = https.request(slack_req_opts, function(res){
        if(res.statusCode === 200){
            context.succeed('posted to slack');
        } else {
            context.fail('status code: ' + res.statusCode);
        }
    });
    
    req.on('error', function(e){
        console.log('problem with request: ' + e.message);
        context.fail(e.message);
    });
    
    req.write(JSON.stringify({
        text: text,
        username: event.placementInfo.placementName,
        icon_emoji: event.placementInfo.attributes.icon_emoji
    }));
    req.end();
};

slackのwebhookのURLは環境変数で保持します。

  • SLACK_WEBHOOK_URL : https://hooks.slack.com/services/xxx..

作成後、保存して、動作テストをします。ヘッダにある保存ボタンの左にあるテストからテストイベントを作成します。 イベントテンプレートはHello Worldのまま、イベント名は適当に入力。ボタンデバイスから送信されるJSONをテストとして入力してやります。

{
  "deviceInfo": {
    "deviceId": "xxx",
    "type": "button",
    "remainingLife": 99.3,
    "attributes": {
      "projectRegion": "ap-northeast-1",
      "projectName": "info_coffee",
      "placementName": "osaka-1f",
      "deviceTemplateName": "coffee_button"
    }
  },
  "deviceEvent": {
    "buttonClicked": {
      "clickType": "SINGLE",
      "reportedTime": "2018-07-13T08:09:59.237Z"
    }
  },
  "placementInfo": {
    "projectName": "info_coffee",
    "placementName": "osaka-1f",
    "attributes": {
      "single": "コーヒー淹れたよ",
      "double": "売り切れました",
      "long": "おやつもあるよ",
      "icon_emoji": ":coffee:"
    },
    "devices": {
      "coffee_button": "xxx"
    }
  }
}

f:id:tamb-kano:20191119114545j:plain

作成すると、テストのボタンの横のプルダウンに作成したイベントが選択された状態になるので、その状態でテストを再度クリックするとイベントが発火します。 posted to slackと返ってきて投稿されたら成功。

f:id:tamb-kano:20191119114558j:plain

無事、Slackに投稿されました!

f:id:tamb-kano:20191119114612j:plain

完成

ここまででデバイスの設定、Lambdaの設定が完了したので実際のデバイスを押してみて動作確認します。

f:id:tamb-kano:20191119114629j:plain

ちゃんとクリックの違いも検出して投稿仕分けてくれました! というわけで完成です。

参考リンク

もろもろ参考にさせてもらった記事などです。