#はじめに
RHEMS技研の西田です。
ところで皆さん(特に男性)、立ち小便派ですか?
弊社オフィスでは立ち小便用便器がないので禁止です。なのに何故掃除する時に便器の周りに尿がハネているのでしょうか。。
そんな事で、今回は立ち小便した人をslackで 公開処刑 通知する仕組みを作ってみました。
#使用技術
Serverless Framework
AWS
| - Lambda
| - API Gateway
| - DynamoDB
ESP-WROOM-02(開発ボード)
#流れ
ESP-WROOM-02を使って、トイレの便座が開閉時や機器の電源投入時に、APIGatewayで発行したエンドポイントURIにPOSTメソッドでボディを付けデータを送信。 - ①/②
送られてきたボディをLambdaで解析し、現在の状況の取得と、最新の状況をDynamoDBに書き込む。 - ③
開いたよ!メッセージが届いた時のみslackで作ったアプリのWebhookUrlにリクエスト送信 - ④
本記事ではServerless Frameworkを使ってAWSを設定した所まで(③ ~ ④)を扱います。ESP-WROOM-02(① ~ ②)についてはこちらの記事で紹介しています。
#詳細
今回、一連の処理をするAWSのサービス群はServerless Frameworkを使って管理しています。
service: sls-rhems-iot
frameworkVersion: ">=1.1.0 <2.0.0"
provider:
name: aws
runtime: nodejs6.10
region: ap-northeast-1
environment:
DYNAMODB_TABLE: ${self:service}-${opt:stage, self:provider.stage}
iamRoleStatements:
- Effect: Allow
Action:
- dynamodb:Query
- dynamodb:Scan
- dynamodb:GetItem
- dynamodb:PutItem
- dynamodb:UpdateItem
- dynamodb:DeleteItem
Resource: "arn:aws:dynamodb:${opt:region, self:provider.region}:*:table/${self:provider.environment.DYNAMODB_TABLE}"
functions:
manage:
handler: lambdas/manage.manage
events:
- http:
path: rhems/manage/{id}
method: put
cors: true
- http:
path: rhems/manage/{id}
method: get
cors: true
- http:
path: rhems/manage/{id}
method: delete
cors: true
wciot:
handler: lambdas/wciot.wciot
events:
- http:
path: rhems/wc
method: post
cors: true
resources:
Resources:
DynamoDbTable:
Type: 'AWS::DynamoDB::Table'
DeletionPolicy: Retain
Properties:
AttributeDefinitions:
-
AttributeName: id
AttributeType: S
KeySchema:
-
AttributeName: id
KeyType: HASH
ProvisionedThroughput:
ReadCapacityUnits: 1
WriteCapacityUnits: 1
TableName: ${self:provider.environment.DYNAMODB_TABLE}
lambdaではnode.js6.10を使いました。
また、関数はマネージメント用(今後トイレ以外にも追加する予定があるため)、トイレ用の2つを用意しています。
'use strict';
const AWS = require('aws-sdk');
const db = new AWS.DynamoDB.DocumentClient();
module.exports.manage = (event, context, callback) => {
let params = {
TableName: process.env.DYNAMODB_TABLE,
ConsistentRead: true,
Key: { id: event.pathParameters.id, },
};
let response = { };
switch (event.httpMethod) {
case 'DELETE': //------------------------------------------------------------------
db.delete(params, (err) => {
if (err) {
response.statusCode = err.statusCode || 501;
response.body = 'Couldn\'t remove item.';
} else {
response.statusCode = 200;
response.body = JSON.stringify(params.Key);
}
callback(null,response);
});
break;
case 'GET': //------------------------------------------------------------------
db.get(params, (err, result) => {
if (err) {
response.statusCode = err.statusCode || 501;
response.body = 'Couldn\'t fetch item.';
} else {
response.statusCode = 200;
response.body = JSON.stringify(result.Item);
}
callback(null,response);
});
break;
case 'PUT': //------------------------------------------------------------------
params.Item = {
id: event.pathParameters.id,
message: "new"
};
db.put(params, (err) => {
if (err) {
response.statusCode = err.statusCode || 501;
response.body = 'Couldn\'t create item.';
} else {
response.statusCode = 200;
response.body = JSON.stringify(params.Item);
}
callback(null,response);
});
break;
default:
response.statusCode = 500;
response.body = 'Unexpected error';
callback(null,response);
}
};
IoT装置で扱うエンドポイントは全てPOSTメソッドで、ボディを {id:NUM, message:STRING} の形で受け取ります。
'use strict';
const request = require('request');
const AWS = require('aws-sdk');
const db = new AWS.DynamoDB.DocumentClient();
const timestamp = new Date().getTime();
module.exports.wciot = (event, context, callback) => {
const data = JSON.parse(event.body);
let params = {
TableName: process.env.DYNAMODB_TABLE,
ConsistentRead: true,
Key: {
id: data.id,
},
ExpressionAttributeValues: {
':message':data.message,
':updateAt': timestamp
},
UpdateExpression: 'set message = :message, updateAt=:updateAt',
ReturnValues: 'UPDATED_NEW'
};
const notificationPromise = function (options) {
return new Promise(function (resolve, reject) {
request(options, function (error, response, body) {
if (!error && response.statusCode == 200) {
resolve(body);
} else {
reject(error);
}
});
});
};
db.update(params).promise()
.then(function (result) {
return new Promise(resolve => {
let promiseObject = {};
if (data.message == "ON") {
promiseObject.notificationFlag = true;
promiseObject.notificationMessage = "電源がONになりました";
} else if ((data.message == "UP")) {
promiseObject.notificationFlag = true;
promiseObject.notificationMessage = "便座が上がってます!!!";
}
promiseObject.response = {
statusCode: 200,
body: JSON.stringify(result.Attributes),
}
resolve(promiseObject);
});
}).then(object => {
if (object.notificationFlag) {
const slackOption = {
url: 'https://hooks.slack.com/services/hOge1/fUga2/Piyopiyo3',
method: 'POST',
headers: {
'Content-type': 'application/json'
},
body: JSON.stringify({
"text": object.notificationMessage
})
};
notificationPromise(slackOption).then(slackResult => {
console.log(slackResult);
}).catch(function (err) {
callback(err);
});
}
callback(null, object.response);
}).catch(function (err) {
callback(err);
});
};
また、今回はrequestライブラリを読み込んでいるので、npm install request
をしています。
#やってみた結果
うまく行きました!
掃除する時は電源を切ることで対応をしています。
#今後
Rekognitionを使って、トイレのドアの前にカメラをつけて、誰が立ち小便をしたかを検知できるようにする予定です。