kintone に保管したセンサ値を AWS Lambda で15分に1回チェックし警告をメールで送信
kintone に保管したセンサ値を AWS Lambda で15分に1回チェックし警告をメールで送信:
・15分毎に AWS Lambda を起動
・kintone アプリから警告用の設定を取得
・kintone アプリからセンサ値を取得
・センサ値が警告レベルなら警告メールを送信する
・kintone アプリの警告状況を更新
AWS Lambda ( https://aws.amazon.com/jp/lambda/ )
AWS SES ( https://aws.amazon.com/jp/ses/ )
【Python3】AWS 「API Gateway」「Lambda」「SES」を使って、RaspberryPiでのセンサー検知をメール通知してみた ( http://raspi.seesaa.net/article/431653483.html )
AWS SES+Lambda で作る、ドメインまるっとメール転送 ( http://iseebi.hatenablog.com/entry/2016/05/05/123815 )
sync-request ( http://designetwork.hatenablog.com/entry/2016/11/19/sync-request-post-node-js )
then-request ( https://www.npmjs.com/package/then-request )
処理内容
・15分毎に AWS Lambda を起動・kintone アプリから警告用の設定を取得
・kintone アプリからセンサ値を取得
・センサ値が警告レベルなら警告メールを送信する
・kintone アプリの警告状況を更新
Lambdaコード
index.js
'use strict';
var aws = require('aws-sdk');
var ses = new aws.SES({apiVersion: '2010-12-01', region: 'ap-northeast-1' });
var request = require('sync-request');
var requestAsinc = require('request');
var domain = "cybozu.com";
var subdomain = "SUBDOMAIN";
var path = "/k/v1/records.json";
var protocol = "https://";
var setAppId = "KINTONE_SETTING_APP_ID";
var setToken = "SETTING_KINTONE_APP_TOKEN";
var dataAppId = "KINTONE_DATA_APP_ID";
var dataToken = "DATA_KINTONE_APP_TOKEN";
var to = ['TO_MAIL_ADDRESS']; // AWS SES でverify済みのメールアドレス(送信先)
var from = 'FROM_MAIL_ADDRESS'; // AWS SES でverify済みのメールアドレス(送信元)
exports.handler = function(event, context) {
// 15 分前を算出
require('date-utils');
var dt = new Date();
var dtAgo = new Date();
dtAgo.setTime(dt.getTime() - (15 * 60 * 1000));
// kintone アプリからデータチェック用設定情報を取得
var url = protocol+ subdomain + '.' + domain + path;
var query = "Status" + 'in ("有効") order by PlaceId asc limit ' + count;
var settingRecodes = funcGetKintoneRecodes(request, setAppId, url, setToken, query);
// kintone アプリから現在のデータを取得
var query = "DateTime" + ' < "' + dtAgo.toFormat("YYYY-MM-DDTHH24:MI:SSZ") + '"' + ' order by PlaceId asc, DateTime desc limit 50';
var dataRecodes = funcGetKintoneRecodes(request, dataAppId, url, dataToken, query);
// 取得したデータのチェック
var errorData = funcCheckData(settingRecodes, dataRecodes);
if(errorData.length > 0){
funcSendMails(ses, to, from, errorData);
funcPutRecodes(request, url, setAppid, setToken, errorData);
}
};
// データチェック処理
function funcCheckData(settingRecodes, dataRecodes)
{
var recodes = [];
if (settingRecodes.length == 0 || dataRecodes.length == 0){
return recodes;
}
// 計測場所毎のデータに集約
var dataPlaceId = "";
var uniqueData = [];
dataRecodes.forEach(function(record){
if(dataPlaceId != dataRecodes['PlaceId']['value']){
uniqueData[dataRecodes['PlaceId']['value']]['DateTime'] = dataRecodes['DateTime']['value'];
uniqueData[dataRecodes['PlaceId']['value']]['Temperature'] = dataRecodes['Temperature']['value'];
uniqueData[dataRecodes['PlaceId']['value']]['Humidity'] = dataRecodes['Humidity']['value'];
uniqueData[dataRecodes['PlaceId']['value']]['CO2'] = dataRecodes['CO2']['value'];
uniqueData[dataRecodes['PlaceId']['value']]['SaturationDeficit'] = dataRecodes['SaturationDeficit']['value'];
dataPlaceId = dataRecodes['PlaceId']['value'];
}
});
var i = 0;
settingRecodes.forEach(function(record){
// 場所毎に集約したデータを設定値と突き合わせ
var settingPlaceId = record['PlaceId']['value'];
if(uniqueData[settingPlaceId] != null){
var isNormal = true;
var item = "";
switch (record['DataType']['value']){
case "温度":
item = "Temperature";
break;
case "湿度":
item = "Humidity";
break;
case "飽差":
item = "SaturationDeficit";
break;
case "CO2濃度":
item = "CO2";
break;
}
// 警告対象外(通常値)かどうか判断
if(record['Condition']['value'] == "以上"
&& record['Value']['value'] <= uniqueData[settingPlaceId][item]){
isNormal = false;
}
if(record['Condition']['value'] == "以下"
&& record['Value']['value'] >= uniqueData[settingPlaceId][item]){
isNormal = false;
}
}
// 警告を通知
if(!isNormal && (record['StartDateTime']['value'] == "" || (record['StartDateTime']['value'] != "" && record['EndDateTime']['value'] != ""))){
recodes[i]['Normal'] = isNormal;
recodes[i]['Subject'] = "<警告発生>" + record['Title']['value'];
recodes[i]['Body'] = "場所:" + record['Title']['PlaceName'] + "\n";
recodes[i]['Body'] += "設定:" + record['DataType']['value'] + "が " + record['Value']['value'] + " " + record['Condition']['value'] + " 現在値( " + uniqueData[settingPlaceId][item] + ")";
recodes[i]['Body'] += "\n" + record['Message']['value'];
recodes[i]['No'] = record['Title']['レコード番号'];
recodes[i]['DateTime'] = uniqueData[settingPlaceId]['DateTime'];
i++;
// 警告を解除
}else if(isNormal && record['StartDateTime']['value'] != "" && record['EndDateTime']['value'] != ""){
recodes[i]['Normal'] = isNormal;
recodes[i]['Subject'] = "<警告解除>" + record['Title']['value'];
recodes[i]['Body'] = "場所:" + record['Title']['PlaceName'] + "\n";
recodes[i]['Body'] += "設定:" + record['DataType']['value'] + "が " + record['Value']['value'] + " " + record['Condition']['value'] + " 現在値( " + uniqueData[settingPlaceId][item] + ")";
recodes[i]['No'] = record['Title']['レコード番号'];
recodes[i]['DateTime'] = uniqueData[settingPlaceId]['DateTime'];
i++;
}
});
return recodes;
}
// 警告メールを送信
function funcSendMails(ses, to, from, errorData)
{
errorData.forEach(function(record){
funcSendMail(ses, to, from, record['Subject'], record['Body']);
});
}
// 警告状況をkintoneの設定ファイルに反映
function funcPutRecodes(request, url, appid, token, errorData)
{
errorData.forEach(function(record){
var id = record['No'];
var json;
if(record['Normal']){
json = { "EndDateTime": record['DateTime'] };
}else{
json = { "StartDateTime": record['DateTime'] };
}
funcPutKintoneRecode(request, url, appid, token, id, json)
});
}
// メールの送信
function funcSendMail(ses, to, from, subject, body)
{
var eParams = {
Destination: {
ToAddresses: to
},
Message: {
Body: {
Text: {
Data: body,
Charset: 'iso-2022-jp'
}
},
Subject: {
Data: subject,
Charset: 'iso-2022-jp'
}
},
Source: from
};
var email = ses.sendEmail(eParams, function(err, data){
if(err){
context.fail(new Error('Mail error occured.'));
} else {
context.succeed('Mail sucsess.');
}
});
}
// kintone のデータを取得する
function funcGetKintoneRecodes(request, appId, url, token, query)
{
if(count > 500) count = 500;
var appUrl = url + '?app=' + appId + '&query=' + encodeURI(query);
var respons = request('GET', appUrl, {
'headers': {
'X-Cybozu-API-Token': token
}
});
return JSON.parse(respons.getBody());
}
// kintone のデータを更新する
function funcPutKintoneRecode(request, url, appid, token, id, json)
{
var options = {
url: url,
method: 'PUT',
headers: {
'Content-type': 'application/json',
'X-Cybozu-API-Token': token
},
body: { app : appid, id : id, record: json },
json: true
};
console.log(options);
request.put(options, function (err, res, body) {
console.log('response' + res.statusCode);
if (!err && res.statusCode === 200) {
console.log('response SUCCESS!!');
console.log(body);
} else {
console.log('response error: ' + res.statusCode);
console.log(body);
}
console.log('response end');
});
}
参照情報
AWS Lambda ( https://aws.amazon.com/jp/lambda/ )AWS SES ( https://aws.amazon.com/jp/ses/ )
【Python3】AWS 「API Gateway」「Lambda」「SES」を使って、RaspberryPiでのセンサー検知をメール通知してみた ( http://raspi.seesaa.net/article/431653483.html )
AWS SES+Lambda で作る、ドメインまるっとメール転送 ( http://iseebi.hatenablog.com/entry/2016/05/05/123815 )
sync-request ( http://designetwork.hatenablog.com/entry/2016/11/19/sync-request-post-node-js )
then-request ( https://www.npmjs.com/package/then-request )
コメント
コメントを投稿