kintone に保管したセンサ値を AWS Lambda で15分に1回チェックし警告をメールで送信

kintone に保管したセンサ値を AWS Lambda で15分に1回チェックし警告をメールで送信:


処理内容

・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 )

コメント

このブログの人気の投稿

投稿時間:2021-06-17 05:05:34 RSSフィード2021-06-17 05:00 分まとめ(1274件)

投稿時間:2021-06-20 02:06:12 RSSフィード2021-06-20 02:00 分まとめ(3871件)

投稿時間:2020-12-01 09:41:49 RSSフィード2020-12-01 09:00 分まとめ(69件)