Edited at

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

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