enebularを使ってセンサデータ収集/可視化システムを作ってみる

# はじめに

 　株式会社ウフルでは、IoTシステムを構築するためのクラウドサービスである[enebular](https://enebular.com)を提供しています。[enebular](https://enebular.com)では、[Node-RED](https://nodered.org/)フローの開発、そのフローのクラウドやデバイスへの配布を行なうことでIoTシステムを構築できます。さらに、[enebular](https://enebular.com)ではInfoMotionというデータの可視化機能も提供しており、これを利用することでIoTシステムで集めたデータを可視化することもできます。
 　この記事では、センサデータを入出力するサーバ機能の開発と、そのサーバ機能に蓄積したセンサデータをInfoMotionで可視化する方法を示します(デバイスからセンサデータを出力する部分についてはダミーとします)。サーバ機能の実装にはクラウドアプリケーションプラットフォームであるherokuを利用します。

![image01.png](https://qiita-image-store.s3.amazonaws.com/0/380472/0c5b6d20-3f39-8963-5a15-b9fd4fb927c7.png)

　InfoMotionで可視化するデータは、[Firebase](https://firebase.google.com)、[PubNub](https://www.pubnub.com/)、API Gateway(AWSのAPI Gatewayを使って作成したサーバ機能を想定)のData Sourceに蓄積することを想定していますが、[Node-RED](https://nodered.org/)フローを使ってもData Sourceであるサーバ機能を作成できることを説明したいと思います。

開発するNode-REDフローはenebular上で確認することができます。必要に応じご自分のenebularのProjectにImportしてご利用ください。

* [センサデータを蓄積するサーバ機能](https://enebular.com/discover/flow/bb090479-f24d-47a2-a856-9bcfcb003a45)(利用するにはpostgresノードのプロパティの変更が必要です)
* [ダミーのセンサデータ送信](https://enebular.com/discover/flow/43ea6b22-28d5-4b7f-8d1e-bf6f3ed1c874)(利用するには`http request`ノードに設定したURLの変更が必要です)

# 準備

## 利用するサービスのユーザ登録

　開発を始める前に以下のクラウドサービスのアカウントを取得します。

* [enebular](https://enebular.com)  
  上記リンクをクリックし、右上の新規登録からユーザ登録を行ってください。
* [heroku](https://jp.heroku.com/)  
  上記のリンクをクリックし、新規登録からユーザ登録を行ってください。

今回の開発はどちらも無料利用の範囲で試すことができます。

## herokuのアプリケーション追加

 　enebularで作成したNode-REDフローを配置する場所となるherokuのアプリケーションを追加します。herokuにログインし、Dashboardからアプリケーションを追加します。センサデータを保存するサーバ機能になるのでアプリケーションの名前を`sensor-data-repo`としました。ただし、herokuの名前は重複できないため、試す際には別の名前をご指定ください。
 　次にPostgreSQLを準備します。herokuの`sensor-data-repo`のOverview画面から`Configure Add-ons`をクリックします。検索欄に`Heroku Postgres`と入力し、Planを`Hobby Dev - Free`を選んで`Provision`を押します。

次にこのPostgreSQLのデータベースにテーブルを追加します。下記の記事等を参考に実施されると良いと思います。

https://vavolab.com/article/2018/06/05/00/02/01/

今回は、データベースにテーブルを作るときに利用するSQLは下記とします。
これは部屋毎(roomname)にその部屋の温度(temperature)を記録し、記録した時刻(ts)を保存するためのテーブルを作るSQLです。

```
-- Table Definition ----------------------------------------------

CREATE TABLE sensordata_tbl (
    id SERIAL PRIMARY KEY,
    temperature numeric NOT NULL,
    roomname text,
    ts bigint NOT NULL
);

-- Indices -------------------------------------------------------

CREATE UNIQUE INDEX sensordata_tbl_pkey ON sensordata_tbl(id int4_ops);
```

# 開発

## センサデータを蓄積するサーバ機能

 　enebularのNode-REDのWeb Editorを使用して、センサデータを蓄積するサーバ機能を開発します。
 　まず、enebularにサインインし、Projectを作成します。画面上部の`Projects`をクリックし、右上の`Create Project`をクリックして適当なProject名を入力して`Submit`ボタンを押下します。
　次にFlowを作成します。左のメニューから`Flows`をクリックします。右下の`+`ボタンを押下し、表示された`Create Flow`ダイアログに適当な名前を入力して`Continue`ボタンを押下します。表示された画面にある`Edit`ボタンを押下するとNode-REDのWeb Editorが開きます。Web Editorでフローを編集した場合、右上の`デプロイ`ボタンを押下して保存します。このデプロイは、編集したフローをWeb Editor上で有効化することを意味し、クラウドやデバイスに配置するには、別途デプロイ作業が必要となります。

Web Editorでは以下の処理を開発します。

* センサデータの配信 　
PostgreSQLに保存したセンサデータをHTTP(GET)で配信します。InfoMotionでは、HTTPリクエスト時にURLに以下のように開始時刻と終了時刻を指定することで可視化したい範囲の時間帯を指定します。時間は1970/1/1 00:00:00.000(GMT)からの経過ミリ秒数で指定します。
`https://<サーバ名>/<path>?start=<開始時刻のミリ秒数>&end=<終了時刻のミリ秒数>`

* センサデータの受信 　
HTTPリクエスト(POST)でセンサデータを受け取りPostgreSQLに保存します。

作成するNode-REDフローは次のようになります。

<img width="830" alt="スクリーンショット 2019-04-14 7.40.42.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/380472/95f55f56-220b-b310-b1ed-1dab03247200.png">

　PostgreSQLを利用するノードは標準では含まれないため、`node-red-contrib-re-postgres`のノードをインストールします。[右上のメニュー]-[パレットの管理]-[ノードを追加]をクリックする。検索欄に`node-red-contrib-re-postgres`と入力し、`ノードを追加`をクリックします。
　各処理について説明します。

**2021/5/8: node-red-contrib-postgresが利用できなくなっていたため同様の機能を提供するnode-red-contrib-re-postgresを利用するように修正しました**

### センサデータの配信

#### `http`ノード

`http`ノードにより、データ取得のリクエストを受け付けます。
プロパティを以下のように設定します。
メソッド:　`GET`
URL:　`/apigw-datasource`

これは上記URLにHTTPのGETリクエストを受け付けることを表しています。

#### `function`ノード(query)

　PostgreSQLに対するqueryを行なうSQLを作る処理を行います。`function`ノードのプロパティに下記のJavaScriptを記載します。

```
const start = msg.payload.start;    //　startに設定された時刻を取得
const end = msg.payload.end;        //　endに設定された時刻を取得
const apiKey = msg.req.headers["x-api-key"];  // 認証に利用するx-api-keyヘッダの値を取得する
if( apiKey == null || apiKey != "test-api-key") {  // 認証の値が一致するか確認する
    node.error("authentication error", msg);  //　test-api-keyに一致しないならエラーとする
    return;
}
if( start == null || end == null) {
    node.error("parameter error", msg);  // start又はendに値が設定されていなければエラーとする
} else {
    msg.payload = "select * from sensordata_tbl where ts > " + start + " and ts < " + end + ";";  // msgにSQLを格納する(次のノードで利用する)
    return msg;
}
```

#### `postgres`ノード

　上のfunctionノードで作成したSQLをPostgreSQLで実行します。
　このノードには、PostgreSQLにアクセスするための情報をherokuのDashboardから取得し登録します。
　herokuにログインし、sensor-data-repoのOverview画面を表示し次のようにリンクを辿ります。[Heroku Postgres]-[上部のSettings]-[右のView Credentials...]により、Credentials情報を表示します。

　次に`postgres`ノードを配置して、ダブルクリックすると表示されるプロパティのServer項目の右にある鉛筆をクリックします。
　herokuのCredential情報を参照し、表示された各入力欄に値を入力します。
　`Receive query output ?`をチェックします。これにより、SQLの実行が終わったら次のノードが呼ばれるようになります。

#### `function`ノード(convert)

PostgreSQLから取得したデータをInfoMotionが読める形式に変換します。`function`ノードのプロパティに下記のJavaScriptを記載します。

```
let list = [];
const results = msg.payload;
results.forEach((val, index, arr) => {
    const obj = {
        temperature: Number(val.temperature),
        roomname: val.roomname,
        ts: Number(val.ts)
    };
    list.push(obj);
})
msg.payload = list;
return msg;
```

roomname(部屋名)、温度(temerature)、時刻(ts)を含むJavaScriptのオブジェクトを作成しています。

#### `http response`ノード

　生成したJavaScriptオブジェクトを呼び出し元に返します。HTTPでは正常終了時の状態コードは200にするルールであるため、プロパティの状態コードは200とします。

### センサデータの受信

#### `http`ノード

`http`ノードにより、センサデータを受信します。
プロパティを以下のように設定します。
メソッド:　`POST`
URL:　`/sensordata`

これは上記URLにHTTPのPOSTリクエストを受け付けることを表しています。

#### `function`ノード(insert)

　PostgreSQLに受信したデータをinsertするSQLを作る処理を行います。`function`ノードのプロパティに下記のJavaScriptを記載します。

```
const roomname = msg.payload.roomname;
const temperature = msg.payload.temperature;
const apiKey = msg.req.headers["x-api-key"];
if( apiKey == null || apiKey != "test-api-key") {
    node.error("authentication error", msg);
    return;
}
if( temperature == null) {
    node.error("parameter error", msg);
} else {
    msg.payload = "insert into sensordata_tbl (roomname, temperature, ts) values ('" + roomname + "', " + temperature + ", " + Date.now() + ");";
    return msg;
}
```

#### `postgres`ノード

　センサデータを保存するSQLを使ってPostgreSQLのデータベースに保存します。この設定内容は、`センサデータの配信`のために設置した`postgres`ノードと同じ内容とします。

#### 出力の`http response`ノード

　正常完了時の応答を返します。プロパティの状態コードに200を設定します。

### エラーが発生したときの処理

#### `catch`ノード

　`センサデータの配置`、`センサデータの受信`の処理の中で発生したエラーを捕まえるためのノードです。上記で記した`function`ノードに記載したJavaScriptの中で以下のように書いた箇所でエラーが出力され、これを捕まえることができます。

```
node.error("parameter error", msg);
```

#### 機能の`template`ノード

呼び出し元に応答するエラーの内容を定義します。プロパティ画面には以下のように定義します。

<img width="494" alt="スクリーンショット 2019-04-15 1.02.21.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/380472/4e6ba334-a25b-4218-1162-eb1bca55b561.png">

#### `http response`ノード

　`template`ノードで生成したJSONを呼び出し元に返します。HTTPの異常終了時の状態コードは内容により種類がありますが、今回は内部エラーを表す500をプロパティの状態コードに設定します。

# herokuへのデプロイ

　enebularを使って、作成したNode-REDフローをherokuに配置します。enebularのドキュメントの[Frow Deployment](https://docs.enebular.com/ja/GetStarted/FlowDeployment.html)を参照し進めてください。

# ダミーのセンサデータ送信

　今回は、温度センサの代わりにダミーのNode-REDのフローを定義します。作成するフローを以下に示します。

<img width="605" alt="スクリーンショット 2019-04-14 9.14.09.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/380472/96cf4216-3703-1ee9-39c1-0708d23dcbdb.png">

`inject`ノードを使って定期的(10秒毎)にイベントを発生させます。

<img width="492" alt="スクリーンショット 2019-04-15 1.17.25.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/380472/d20f3008-c539-0168-3a54-0d42643aa20a.png">

次の`function`ノード(ダミーデータ生成)ではダミーのセンサデータをランダムに作成します。プロパティに記載するJavaScriptを下記します。

```
const roomnames = ["living", "bedroom", "balcony"]
const roomname = roomnames[Math.floor(Math.random()*roomnames.length)]
msg.payload={roomname: roomname, temperature:15+Math.random()*5}
msg.headers = {"x-api-key":"test-api-key"}  //　認証に用いるAPIキーを設定する
return msg;
```

`http request`ノードでは、上記で作成したデータをherokuに配置した`センサデータの受信`機能に送付します。以下のように設定します。

<img width="492" alt="スクリーンショット 2019-04-15 1.24.04.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/380472/7af557b5-3c7c-983e-cc35-ee1d034f84d7.png">

URLは以下のように設定します。
`https://sensor-data-repo.herokuapp.com/apigw-datasource`

実行するには、上部のDeployボタンを押下した後、`inject`ノードの左のボタンを押下すると10秒毎にランダムデータを生成し、そのデータを`センサデータの受信`機能に送付するようになります。

# InfoMotionの作成

　格納したセンサデータを可視化するためにenebularの機能の一つであるInfoMotionを使います。

　まず`Data Source`を作成します。enebularにサインインし、次のようにリンクを辿ります。
　[上部のProjects]-[作成したProject名]-[左のメニューのData Sources]-[右下の+ボタン]
　表示されたダイアログのTitle(自由に決めてください)を入力し、`Data Source Type`には`API Gateway`を入力します。`Endpoint`と`API Key`の入力欄が表示されますので、下記のように入力します。

Endpoint: `https://sensor-data-repo.herokuapp.com/apigw-datasource`
API Key:　`test-api-key`

　次に、可視化に用いるInfoTypeをImnportします。enebularの上部にある`Discover`をクリックします。検索欄に`linechart`と入力し、enebularの帯が表示されたInfoTypeをクリックします。Preview画面が表示されますので、右上の`Import`ボタンをクリックし、今回作成したProject名を選択してInfoTypeを取得します。

　次に、左のメニューからInfoMotionsをクリックし、右下の`+`ボタンを押下します。表示されたダイアログで、`Name`と必要に応じ`Description`を入力します。

[`Manage Graphs`ボタン]-[`Create Graph`ボタン]を押下し、次のように設定します。

<img width="267" alt="スクリーンショット 2019-04-15 2.09.46.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/380472/535902ea-b0c6-1bb5-1874-87b4280eaeed.png">

　`Save`ボタンを押下し、作られたGraphを`Add`するとDashboardにグラフが追加されます。表示した例を以下に示します。

<img width="921" alt="スクリーンショット 2019-04-15 1.31.19.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/380472/167fe164-d6ab-0684-12f8-ed4fb330563b.png">

　上部の時刻表示をクリックすると表示する時間帯を選択できます。また、Dashboardのレイアウトを保存したい場合は`Save Layout`ボタンを押下します。

# まとめ

　heroku等の他社サービスをプラットフォームとして利用してはいますが、enebularの機能を主に利用してセンサデータの収集と、そのデータを可視化する機能を、ほぼプログラミングレスで開発できることを示しました。
　今回はダミーで実現したセンサデバイス側の開発も、enebularではNode-REDを利用して実現できるため、センサデータの収集や可視化も含めたIoTシステムを全体を少ない手間で開発できます。
　是非enebularの利用をご検討頂ければ幸いです。