先日までにCloudantにデータが格納されました。
- Raspberry Pi + L-03D + SORACOM SIM で通信を行う
- Raspberry PiからSORACOM Beam経由でBluemixのMQTTへデータ送信を行う
- Raspberry PiのLEDを Node-RED を使ってLチカする
- SORACOM Beam を利用してMQTTプロトコル経由でIMSI情報を付与してみる
- MQTT経由のデータをSORACOM APIでSIM単位の認証/受信したデータをCloudantに格納
- Cloudantに格納したデータを可視化
- MQTTのデータをリアルタイムに可視化
今回はこのデータを加工してみたいと思います。その前に少しCloudantについてふれておきます。
目次
Cloudantとは
Cloudant社は、DBaaSのサービスプロバイダーであり「Cloudant」は「Apache CouchDB」をベースにしたNoSQLデータベースです。Cloudantはフルマネージドサービスとして提供されておりBluemixからも利用可能になっています。従ってドキュメント的には「CouchDB」のドキュメントが役に立ちそうです。
また同じように「Couchbase」と呼ばれる「CouchDB」が元にできたNoSQLデータベースがありますがこちらは「Membase」の技術が合わさり生まれたDBとなりCouchDBを作ったメンバーにより作られています。
Cloudantは、NoSQLの中でもドキュメント思考型DBと呼ばれておりJSON型式のデータを格納することが出来ます。データは事前に型(従来のRDBMSで言う所の列の定義)をする必要が無いためIoT等で利用するデータのように何が来るか変動する可能性があるものを格納するには非常に便利です。
様々な特徴がありますが今回使用する範囲の特徴を載せておきます。
- データベースの概念はありますがTableはありません。
- Cloudantでは全てAPI経由(https)で操作をします。
- SQLは利用することは出来ません。
- SQLでの ” select * “のような結果を求めるためには「View」を利用します。
- Viewは、MapReduceでデータを取得します。
Cloudantの管理画面と操作
管理画面は以下の様になります。管理はWebインターフェースまたはAPI経由で行います。
Cloudantへのログイン情報の取得
Bluemixの管理コンソールからCloudantサービスを表示すると「サービス資格情報」というメニューが左側に表示されます。初期の段階では、資格情報がないので「資格情報の追加」を行うと新たに発行されます。
ここに記載されている、username, password, host , port , url を利用して接続することが出来ます(Cloudantは、Bluemixの環境からだけなくInternet経由で使うことが出来ます)
ダッシュボードのURLは、https://”username”.cloudant.com/dashboard.html で接続することが出来ます。
DBの選択/作成
まずは、DBを作成します。ここではサンプルとして「raspi_senser」を作成してデータを格納している状態だとします。DBを作成する画面はあれど「削除」がないのですがAPIから実行することが出来ます。 httpの「DELETE」を送ると削除ができます、この辺りは別途APIドキュメントに記載されています。
サンプルデータ
今回はセンサーから取得した以下の型式のデータが格納されているとします。
{
"_id": "00e7e632d4341e111ce9550b3dafe907",
"_rev": "1-c9a879c1ac0fea7cb89f370c7e6309a3",
"timestamp": "2015-10-17T19:44:50Z",
"temp": 25.45,
"pressure": 1017.17,
"hum": 66.07
}
Viewを利用したデータの取得
最終的にデータを取得する方法はいくつかあります。Indexを作成して全文検索などをすることも出来ますし、ここで説明するViewを利用してデータを取得することも出来ます。
今回はこのセンサーデータをグラフで表示するために幾つかのViewを作成してみたいと思います。CloudantではSQLでSelectする結果を得たい場合には事前にViewを作成しておく必要があります。
select temp form raspi_senser where timestamp > starttime and timestamp < endtime
※SQLは適当です
これを実現するためには、Viewを作成して取得します。select * from * の箇所をViewで作りwhere以降はAPIからViewの取得時にOptionで設定する要領になります。
新規にViewを作成するために「Query」の右側の+をクリックし「New View」を選択します。
最もシンプルなViewを作ります。
CloudantのViewは、MapReduce方式でデータを取得します。従って全てのデータに対して、Mapでデータを「探し、出力する型式を定義」、Reduce処理でデータを「整形・加工」することになります。
単純に select temestamp, temp from raspi_senser をしたいので
- Map Function
function (doc) {
msec = Date.parse(doc.timestamp.trim());
if ( msec ) {
date = new Date(msec);
emit(
doc.timestamp,
doc.temp);
}
}
データは一レコードずつ、doc
という中に格納されます。今回は doc.timestamp.trim() した値がData.parse() することできる値を対象にします。(つまりデータのformatがアワない場合には対象外とします)。値を返す値は emit() の中に定義していきます。この場合には doc.timestamp, doc.temp を返します。
- Reduce Function
none
今回は取得したデータに対しての処理はしない状態です。実際にデータを取得するために curl コマンドでは以下のようになります。
curl 'https://****bluemix.cloudant.com/raspi_senser/_design/Index/_view/data'
引数無しで実行すると
{"total_rows":1753,"offset":0,"rows":[
{"id":"73c4ea859080666a478fbaf991d05d68","key":"2015-10-17T18:37:51Z","value":24.64},
{"id":"bd2f2dbee5bd91edced2a54d162ac461","key":"2015-10-17T18:38:47Z","value":24.68},
{"id":"2904f064d1cbc9e79541d0c7165a690d","key":"2015-10-17T18:39:47Z","value":24.76},
{"id":"38efaa3a792e36990697c44f2e426ed6","key":"2015-10-17T18:40:47Z","value":24.83},
{"id":"38efaa3a792e36990697c44f2ecb3a37","key":"2015-10-17T18:41:47Z","value":24.86},
{"id":"fecc80f1ba0f9d065621f94cf31e002a","key":"2015-10-17T18:42:47Z","value":24.88},
{"id":"fecc80f1ba0f9d065621f94cf3a7e8da","key":"2015-10-17T18:43:47Z","value":24.9},
{"id":"217ef97748e06f625303ecef1fd0c26d","key":"2015-10-17T18:44:47Z","value":24.91},
のような型式で取得することが出来ます。一度に取得できる数(limit)が定義されているため欲しいデータ範囲を指定して取得していく必要があります。 データは小さい値からlimit数までが取得されるため、「最新の何件」のような取得が単純には出来ません。もし実施する場合には skipを利用して先頭からデータをオフセットして取得する必要があります。
emit()の最初の値が key としてされています。代表的な引数として startkey , endkey を指定することで範囲が指定可能です。
JSON型式で値が取得できますので簡単にグラフを描画することとも出来ます。
実際に作成してみるとわかりますが非常に早くデータが取得できます。
平均値を取得するViewを作る
Cloudantでは、MapReduceの機能を利用することにより例えば1時間あたりの平均値データを取得するViewであったり一定の期間の合計値や最大値を取得するViewを作ることが出来ます。RDBMSの中には関数が用意されており計算ができるものもありますが同様に考えて良いと思います。
- Map Function
function (doc) {
msec = Date.parse(doc.timestamp.trim());
if ( msec ) {
date = new Date(msec);
emit(
[
date.getFullYear(),
date.getMonth()+1,
date.getDate(),
date.getHours(),
date.getMinutes(),
date.getSeconds()
],
doc.temp);
}
}
ここでは時間に対して処理をしたいのでまずはデータを分解します。この結果は key": [ 2015, 10, 17, 18, 37, 51 ],
のような配列として格納されます。
次に平均を作る Reduceを記述します。標準では_sum,_count,_statなどが用意されています。
- Reduce Function
function (keys, values, rereduce) {
var length;
if (!rereduce){
length = values.length;
return [sum(values) / length, length];
}else{
length = sum(values.map(function(v){return v[1]}));
var avg = sum(values.map(function(v){
return v[0] * (v[1] / length);
}));
return [avg.toFixed(2), length];
}
}
戻り値は配列で [少数第2位までの平均値 , データの個数 ] が得られます。集計は group_level を引数に利用して行います。
group_level = 1
{"rows":[
{"key":[2015],"value":["25.82",1780]}
]}
group_level = 2
{"rows":[
{"key":[2015,10],"value":["25.82",1783]}
]}
group_level = 3
{"rows":[
{"key":[2015,10,17],"value":["25.39",137]},
{"key":[2015,10,18],"value":["26.21",176]},
{"key":[2015,10,19],"value":["25.80",1439]},
{"key":[2015,10,20],"value":["25.93",33]}
]}
group_level = 4
{"rows":[
{"key":[2015,10,17,18],"value":["24.97",23]},
{"key":[2015,10,17,19],"value":["25.36",60]},
{"key":[2015,10,17,20],"value":["25.59",54]},
{"key":[2015,10,18,20],"value":["26.54",3]},
{"key":[2015,10,18,21],"value":["26.45",60]},
{"key":[2015,10,18,22],"value":["26.22",59]},
{"key":[2015,10,18,23],"value":["25.91",54]},
group_level=5
{"rows":[
{"key":[2015,10,17,18,37],"value":["24.64",1]},
{"key":[2015,10,17,18,38],"value":["24.68",1]},
{"key":[2015,10,17,18,39],"value":["24.76",1]},
{"key":[2015,10,17,18,40],"value":["24.83",1]},
{"key":[2015,10,17,18,41],"value":["24.86",1]},
{"key":[2015,10,17,18,42],"value":["24.88",1]},
{"key":[2015,10,17,18,43],"value":["24.90",1]},
このように集計したデータをJSON型式で取得することが出来ます。これを利用すれば日別のグラフを描画したりすることもできるようになります。mapを上手く記述することにより5分平均とか様々な事が出来そうです。
group_level=3 & startkey = [2015,10,17] & endkey = [2015,10,19]
この引数を利用する事で範囲を指定する事が出来ます。
{"rows":[
{"key":[2015,10,17],"value":["25.39",137]},
{"key":[2015,10,18],"value":["26.21",176]}
]}
まとめ
センサーデータを考えた場合には、値を可視化するためにはこのようなCloudantの様なデータベースにデータを格納しその値を元に描画するのが良いでしょう。今回のViewを必要な数定義することにより様々なグラフの作成が可能です。
バッチ処理的な利用としてはCloudantを利用し、リアルタイムに変化するデータはMQTTから直接描画してしまうのがシンプルな構成になります。次回は、MQTTから直接データを受信してグラフを描画する事も実施してみたいと思います。
注意点
Cloudantは、NoSQLデータベースで「Table」の概念がないため全てのデータが同じストアの中に格納されます。従ってデータ構造が全く違うデータなどを取り扱うケースも考慮して「type=senser_data」や「device=xxxxxxx 」といった分類分けするためのメタ情報を事前に検討して付与しておく必要があります。
{
"_id": "00e7e632d4341e111ce9550b3dafe907",
"_rev": "1-c9a879c1ac0fea7cb89f370c7e6309a3",
"timestamp": "2015-10-17T19:44:50Z",
"type" : "senser_data",
"de vice" : "xxxxxxxxxx",
"temp": 25.45,
"pressure": 1017.17,
"hum": 66.07
}