4
3

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.

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

Last updated at Posted at 2018-11-09

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

まず繋がるの?

エリア検索で調べたら、自宅、エリア外疑惑。。。
KDDI IoT通信サービス LPWA: エリア検索

・・・結果。

つながりました。ふー。

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

作って見た

SORACOMのAPIリファレンスを見ていたら、ボタンの情報がAPIで取れるようになっていたので、
LambdaでAPI叩いて、ボタンの情報取得して、その情報含めてSlackに投稿するようにしました。
API Reference#Gadget

soracom-slack.png

突貫感すみません。

流れは以下。

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

こんな感じ。ボタン名はAPIで取ってます。1号機なんで。。。w(ガンダムネタです)

シングルクリック

button-single.png

ダブルクリック

button-double.png

長押し

button-long.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でもっとボタンの情報取れるといいですね。 これは #ソラコムサンタ かな。

4
3
1

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
4
3

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?