SORACOM LTE-M Buttonのボタンを押したら、Slackに通知する

SORACOMさんから発売された「SORACOM LTE-M Button powered by AWS」を入手して、ちょっとやってみたメモです。


# まず繋がるの？

エリア検索で調べたら、自宅、エリア外疑惑。。。
[KDDI IoT通信サービス LPWA: エリア検索](http://www.kddi.com/business/mobile/m2m-solution/iot-lpwa/area/)

・・・結果。

つながりました。ふー。

さて、気を取り直して、色々やって見ます。

# 作って見た

SORACOMのAPIリファレンスを見ていたら、ボタンの情報がAPIで取れるようになっていたので、
LambdaでAPI叩いて、ボタンの情報取得して、その情報含めてSlackに投稿するようにしました。
（[API Reference#Gadget](https://dev.soracom.io/jp/docs/api/#/Gadget)）

<img width="1155" alt="soracom-slack.png" src="https://qiita-image-store.s3.amazonaws.com/0/72752/36f926c6-16d1-8816-7e54-483677ba61fe.png">

突貫感すみません。

流れは以下。

1. ボタンクリック
2. AWS IoT 1-Clickで受ける
3. Lambda起動
4. SORACOM APIでボタンの情報取得
5. SlackのIncoming WebHooksのURL叩いて、メッセージ投稿

こんな感じ。ボタン名はAPIで取ってます。１号機なんで。。。w（ガンダムネタです）
#### シングルクリック
<img width="281" alt="button-single.png" src="https://qiita-image-store.s3.amazonaws.com/0/72752/b0cf5f8d-d502-5447-3c89-952e215fff4b.png">

#### ダブルクリック
<img width="298" alt="button-double.png" src="https://qiita-image-store.s3.amazonaws.com/0/72752/899d8038-5a70-69fb-3101-3d1815bbb23c.png">

#### 長押し
<img width="276" alt="button-long.png" src="https://qiita-image-store.s3.amazonaws.com/0/72752/b01bbd1f-d26c-3612-7302-65c2a9aef053.png">



Lambda関数(最近node.js使いなので、node.jsで書いてます。)
slack投げる部分は、LambdaのBluePrintとか参考にしてます。

```index.js
'use strict'
const request = require('request');
const moment = require('moment-timezone');
const AWS = require('aws-sdk');
AWS.config.update({ region: 'ap-northeast-1' });
const kms = new AWS.KMS();

const encryptedSlackWebHookUrl = process.env['SLACK_WEBHOOK_URL']; // slackのURL
let decryptedSlackWebHookUrl;
const encryptedSoracomKey = process.env['SORACOM_KEY']; // SORACOM API認証キー
let decryptedSoracomKey;
const encryptedSoracomKeyId = process.env['SORACOM_KEY_ID'];SORACOM API認証キー ID
let decryptedSoracomKeyId;

/**
 * 初期化
 * @param  {[type]} event   [description]
 * @param  {[type]} context [description]
 * @return {[type]}         [description]
 */
const initialize = (event, context) => {
  return new Promise((resolve) => {
　　　　　　　　　// クリックのタイプで文字変えてます。
    let clickTypeName = 'クリック';
    if (event.deviceEvent.buttonClicked.clickType == "DOUBLE") {
      clickTypeName = 'ダブルクリック';
    } else if (event.deviceEvent.buttonClicked.clickType == "LONG") {
      clickTypeName = '長押し';
    }
    const stash = {
      apiKey:'',
      token:'',
      serialNumber: event.deviceInfo.deviceId,
      remainingLife: event.deviceInfo.remainingLife,
      clickType: event.deviceEvent.buttonClicked.clickType,
      clickTypeName,
      reportedTime: event.deviceEvent.buttonClicked.reportedTime,
      gadgetsInfo:{},
    };
    console.log(stash);
    resolve(stash);
  });
};
/**
 * キー情報復号処理
 * @param  {[type]} stash [description]
 * @return {[type]}       [description]
 */
const decryptedUrl = (stash) => {
  return new Promise((resolve, reject) => {
      if (!decryptedSlackWebHookUrl) {

        kms.decrypt({ CiphertextBlob: new Buffer(encryptedSlackWebHookUrl, 'base64') }, (err, data) => {
            if (err) {
                console.log('Decrypt error:', err);
                reject(err);
            }
            decryptedSlackWebHookUrl = data.Plaintext.toString('ascii');
            resolve(stash);
        });
      } else {
        resolve(stash);
      } 
  });
};
/**
 * キー情報復号処理
 * @param  {[type]} stash [description]
 * @return {[type]}       [description]
 */
const decryptedKey = (stash) => {
  return new Promise((resolve, reject) => {
    if (!decryptedSoracomKey && !decryptedSoracomKeyId) {
        kms.decrypt({ CiphertextBlob: new Buffer(encryptedSoracomKey, 'base64') }, (err, data) => {
            if (err) {
                console.log('Decrypt error:', err);
                reject(err);
            }
            decryptedSoracomKey = data.Plaintext.toString('ascii');
            kms.decrypt({ CiphertextBlob: new Buffer(encryptedSoracomKeyId, 'base64') }, (err, data) => {
                if (err) {
                    console.log('Decrypt error:', err);
                    reject(err);
                }
                decryptedSoracomKeyId = data.Plaintext.toString('ascii');
                resolve(stash);
            });
        });

    } else {
      resolve(stash);
    }
  });
};
/**
 * SORACOM API 認証
 * @param  {[type]} stash [description]
 * @return {[type]}       [description]
 */
const getSoracomApiAuth = (stash) => {
  return new Promise((resolve, reject) => {
    const optionsAuth = {
      url: 'https://api.soracom.io/v1/auth',
      method: 'POST',
      headers: {
        'Content-Type': 'application/json',
        'Accept': 'application/json'
      },
      json: {
        'authKeyId':decryptedSoracomKeyId,
        'authKey':decryptedSoracomKey,
        'tokenTimeoutSeconds':86400
      }
    };
    // SORACOM Auth APIコール
    request(optionsAuth, function (error, response, body) {
      if (error) {
        console.log('Auth API Error: ' + error);
        reject(error);
      } else if (response.statusCode != 200) {
        console.log('Auth API Error response.statusCode: ' + response.statusCode);
        const err = {
          'statusCode': response.statusCode
        }
        reject(err);
      } else {
        console.log('APIコール成功');
        stash.apiKey = body.apiKey;
        stash.token = body.token;
        resolve(stash);
      }
    });
  });
};
/**
 * SORACOM API ボタン情報取得 
 * @param  {[type]} stash [description]
 * @return {[type]}       [description]
 */
const getGadgetInfo = (stash) => {
  return new Promise((resolve, reject) => {
      const apiKey = stash.apiKey;
      const token = stash.token;
      const optionsGetGadgetsData = {
        url: `https://api.soracom.io/v1/gadgets/button/${stash.serialNumber}`,
        method: 'GET',
        headers: {
          'Content-Type': 'application/json',
          'Accept': 'application/json',
          'X-Soracom-API-Key': apiKey,
          'X-Soracom-Token': token
        }
      };
      // APIコール
      request(optionsGetGadgetsData, function (error, response, body) {
        if (error) {
          console.log('Gadgets API Error: ' + error);
          reject(error);

        } else if (response.statusCode != 200) {
          console.log('Gadgets API Error Response.statusCode: ' + response.statusCode);
          const err = {
            'statusCode': response.statusCode
          }
          reject(err);
        } else {
          console.log('APIコール成功 body:' + JSON.parse(body).operatorId);
          const gadget = JSON.parse(body);
          stash.gadgetInfo = gadget;
          resolve(stash);
        }
      });
  });
};
/**
 * Slack POST
 * @param  {[type]} stash [description]
 * @return {[type]}       [description]
 */
const postSlackMessage = (stash) => {
  return new Promise((resolve, reject) => {
      console.log('slack Call');
      const gadget = stash.gadgetInfo;
      let messageArray = [];
      
      messageArray.push(`ボタン${gadget.tags.name}が${stash.clickTypeName}されました。`);
      const useCount = 1500 - gadget.attributes.remainingCount;
      messageArray.push(`現在のクリック数は${useCount}で残り${gadget.attributes.remainingCount}です。`);
      messageArray.push(`有効期限は${moment(gadget.attributes.contractEndingTime).tz("Asia/Tokyo").format('YYYY-MM-DD hh:mm:ss')}です。`);
      const message = messageArray.join('\n');
      // リクエスト設定
      const options = {
        url: decryptedSlackWebHookUrl,
        headers: {
          'Content-type': 'application/json'
        },
        body: {
          "text": message
        },
        json: true
      };
    
      // メッセージ送信
      request.post(options, function(error, response, body) {
        if (!error && response.statusCode == 200) {
          resolve(stash);
        } else {
          if (error) {
              console.log('Slack API Error: ' + error);
              reject(error);
          } else {
            console.log('Slack API Error: ' + response.statusCode);
            const err = {
              'statusCode': response.statusCode
            }
            reject(err);            
          }
        }
      });
  });
};
/**
 * Main処理
 * @param  {[type]}   event    [description]
 * @param  {[type]}   context  [description]
 * @param  {Function} callback [description]
 * @return {[type]}            [description]
 */
exports.handler = (event, context, callback) => {

  initialize(event, context)
    .then(decryptedKey)
    .then(getSoracomApiAuth)
    .then(getGadgetInfo)
    .then(decryptedUrl)
    .then(postSlackMessage)
    .then(callback.bind(null, null))
    .catch(callback);
};
```

他にも色々できそうですので、それは引き続き...


# 最後に

**/gadgets** の公開、待ってました！

あと、APIでもっとボタンの情報取れるといいですね。 これは #ソラコムサンタ かな。