Qiita Teams that are logged in
You are not logged in to any team

Log in to Qiita Team
Community
OrganizationAdvent CalendarQiitadon (β)
Service
Qiita JobsQiita ZineQiita Blog
3
Help us understand the problem. What is going on with this article?
@keni_w

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

More than 1 year has passed since last update.

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

3
Help us understand the problem. What is going on with this article?
Why not register and get more from Qiita?
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away
keni_w
ガンダムと銀河英雄伝説を養分に生きるアラフォーエンジニア。 なお、こちらへの掲載内容は私自身の見解であり、会社の立場、戦略、意見を代表するものではありません。

Comments

No comments
Sign up for free and join this conversation.
Sign Up
If you already have a Qiita account Login
3
Help us understand the problem. What is going on with this article?