AWSとBLE人感センサーで会議室の利用状況を可視化してみる[第4話]

# 概要
エヌシーアイ総合システムのtaguchyです。
普段は主にBtoCのWEBシステムや、BtoB向けWEB APIの開発などをしています。
最近はIoT関連でもお仕事させていただいております。

さて、前回はAWSIoT、Lambda、Firehoseを利用してElasticSearchとS3にセンシングデータを保存することを行いました。
(前回は[こちら](https://qiita.com/taguchy/items/179362a33413a3816c05 "AWSとBLE人感センサーで会議室の利用状況を可視化してみる[第3話]"))

今回は保存したデータを使ってKibana上で会議室の使用状況を可視化します。

# データの可視化
## センサーの人感検知数を時系列でグラフ化する
時系列表示を行うため、Timelion(タイムライン)を利用します。
「Visualize」⇒「Create new visualization」⇒「Timelion」でTimelionの新規画面を開きます。

<img src="https://qiita-image-store.s3.amazonaws.com/0/197194/cdde224f-61a4-941a-8121-99774299d108.png" width="683px">

とりあえず、条件式を何も表示せずにグラフ表示をしてみます。

<img src="https://qiita-image-store.s3.amazonaws.com/0/197194/e4d9a7e4-c3a0-84a6-0048-debc677d3d55.png" width="683px">

はい。なんかでました。
全センサーからあがってきているデータの件数が時系列ですべて表示されています。

以下の条件を入れてみます。
1. センサーのMACアドレスを指定(3つのセンサーの内、1つを指定)
2. 人感検知したステータスを指定
3. なんとなくかっこいいから棒グラフで表示
4. グラフに凡例を付ける

⇒出来上がった条件式
　```.es(index=meeting-room-*,metric=count,q='status:04 AND sensor_mac:24718902B258').bars().label("検知数")```

<img src="https://qiita-image-store.s3.amazonaws.com/0/197194/e9f4d56b-0996-9634-1823-c382f763e1b9.png" width="683px">

なかなか良いです。
時系列にて検知数をデータ化しました。

## 会議室の在室有無を可視化する
センサーの仕様上、センサー内で人が動いた場合、何度も検知データが送信されます。
その為、特定の時間範囲で検知数が1以上であった場合に”在室”扱いとします。

単純にデータをただ溜めるだけだと、Kibana側でどうしようもできなかったため、データ変換にひと工夫加えて、以下のようなことを行います。

1. 新しい数値型のデータフィールド`exist`を作成
2. センサーからあがったデータが人感検知した状態であった場合、`exist`に`1`を設定
3. 検知していない状態であった場合、`exist`に`0`を設定
4. Kibana側で時間範囲指定(例:30秒)と数値型フィールドの最大値を取得
⇒時間範囲の中で、一度でも検知していれば`exist`が`1`となる

### Lambdaを変更
以下のコードに変更しました。

```
'use strict';
console.log('Loading function');
const csv = require('csvtojson');
const asyn = require('async');
require('date-utils');

exports.handler = (event, context, callback) => {
        // all record convert.
        asyn.map (event.records, function (record, callback) {
            // need to base64 decode.
            let sensor_data = Buffer.from(record.data, 'base64').toString('utf8');
            // "$GPRP" is not need.
            var sensor_data_non_gprp = sensor_data.replace(/^(\$GPRP,)/g, "");
            // split payload.
            let payload = sensor_data_non_gprp.match(/^(.{12},.{12},.*,)(.*)/);
            let split = payload[2].match(/^(02010612FF0D0082BC)(.{4})(.{2})(.{8})(.{4})(.{2})(.{6})/); 
            let fix = split[1];
            let battery = split[2];
            let status = split[3];
            let reserve = split[4];
            let user = split[5];
            let fix2 = split[6];
            let reserve2 = split[7];
            
            // make @timestamp
            let now = new Date();
            
            // make existFlg
            var existFlg = 0;
            if (status !== "00") {
                existFlg = 1;
            }

            
            let sensor_data_split_payload = payload[1]
                                + fix + "," + battery + ","
                                + status + "," + reserve + ","
                                + user + "," + fix2+ ","
                                + reserve2 + "," + now.toFormat('YYYY-MM-DDTHH24:MI:SS') + ","
                                + existFlg;
            csv({
                noheader: true,
                headers: [
                    'sensor_mac',
                    'gw_mac',
                    'rssi',
                    'fix',
                    'battery',
                    'status',
                    'reserve',
                    'user',
                    'fix2',
                    'reserve2',
                    '@timestamp',
                    'exist'],
                colParser:{
                    "exist":"number"
                }
            }).fromString(sensor_data_split_payload)
                .on('json',(jsonObj)=>{
                // Called for the number of rows.
                console.log(jsonObj);
                let jsonData = Buffer.from(JSON.stringify(jsonObj), 'utf8').toString('base64');
                callback (null, {
                                recordId : record.recordId,
                                result : 'Ok',
                                data : jsonData
                                }
                );
            });
        }, function (err, results) {
            if (err) {
                throw err;
            }
            console.log(results);
            callback (null, {records: results});
        });
};
```

### Kibanaでデータを確認
Kibanaにてindexの作り直しが済んだら、グラフを作成します。
Gauge(ゲージ)を利用します。
「Visualize」⇒「Create new visualization」⇒「Gauge」⇒「From a New Search, Select Index」⇒「meeting-room-*」でGaugeの新規画面を開きます。
 
<img src="https://qiita-image-store.s3.amazonaws.com/0/197194/5c2a06d1-bb64-c68e-d8f0-be8136b2f9b4.png" width="683px">

Timelionと同様、全データの件数が表示されました。

以下の条件を入れてみます。
1. センサーのMACアドレスを指定(3つのセンサーの内、1つを指定)
2. existの最大値を指定
3. 表示ラベルを修正
4. 目盛り線は削除

画面は以下のようになりました。

<img src="https://qiita-image-store.s3.amazonaws.com/0/197194/49f3e2a3-9eb2-ab7b-26b2-ad104aa073be.png" width="683px">

時間範囲内に人感を検知した場合に赤、それ以外は白となるゲージができました。
うん、微妙です。他にいい方法がないかもう少し研究したいところ。。

## 全会議室のグラフをDashboardにまとめる
Dashboardにまとめると以下のようになります。

<img src="https://qiita-image-store.s3.amazonaws.com/0/197194/faebcd64-64ec-b0ba-0c4a-ac85f020b553.png" width="683px">

うん、こんなものでしょうか。
左の`検知数`と右の`在室有無表示`の時間範囲の指定を変えたかったですが、それはKibanaの機能ではできませんでした。

`検知数`と`在室有無表示`のDashboardをそれぞれ作り、`iframe`を取得し、自分で作成したHTMLページにそれぞれの`iframe`を埋め込みばいけそうです。

今回はKibanaを利用しての画面表示までが目標でしたので、ここで記事を終了します。
総じての感想は、nodeを触ったことが無かったため、コーディングには苦労しましたが、AWS操作に関しては参考記事も豊富にあり、苦労しませんでした。
ほぼノーコーディングでここまでのことができるのはすごいですね。
ただAWSの謳う
```IoT デバイスを接続
AWS IoT を利用した場合　～5 分```
は私には無理でした。。今後精進いたします。

それでは、しばらくこの形で運用し、AWS料金が算出できたらまた記事を書こうと思います。