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 )
コメント
コメントを投稿