無料大好きな僕が、タダでIoTデータを見える化する方法をまとめてみた

#IoTと見える化
ここ数年、RaspberryPi、Arduino、M5Stackなど、IoT向けマイコンモジュールが続々登場し、低コストにセンサデータを取得できる時代となっています。

一方で、価値を出すためには人間が分かりやすい形で
「つなぐ」「見える化する」
必要があります。
（下は内閣府のSociety 5.0の資料）
![society5_0-3.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/610167/b4715e7a-e796-a8fd-9555-e357ee10d691.jpeg)

例えば

```json
"smart_meter": {
    "echonetlite_properties": [
        {
            "name": "normal_direction_cumulative_electric_energy",
            "epc": 224,
            "val": "294263",
            "updated_at": "2020-08-12T10:09:14Z"
        },
        {
            "name": "measured_instantaneous",
            "epc": 231,
            "val": "432",
            "updated_at": "2020-08-12T10:09:14Z"
        }
    ]
}
```
のようなJSONの消費電力生データを見せられても困りますが、

![watt.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/610167/9bed27c2-eb03-c5e8-6735-582354eb6d42.png)
のように**グラフで表示**してくれると分かりやすいですね！

それが**外出先で**スマホやタブレットから見られたり、分析できたりすると、なお嬉しいです！

##タダで見える化したい！
上記のような見える化システムは、お金を掛ければいくらでも凄いものは作れます。
ここで問題となるのはコスト！

![game_kakin.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/610167/cc3df4e0-6f1e-368e-9837-69323b46c547.png)


個人開発ではなるべくタダにしたい、というのが一般的な感覚だと思います
今回はRaspberryPiを例に、**「タダ」**で**「見える化」**できる手段をまとめてみました

##「タダ」で「見える化」の定義
タダはタダでも、

**「30日間無料試用版ソフト使用！ラズパイとHDMIケーブル接続した画面でしか見えません！」**

では実用性があるとは言い難いので、
高付加価値な構成とするため**要件を定めたい**と思います

###「タダ」とは？
RaspberryPiや、データを取得するためのセンサはもちろんタダではありません。
今回は上記のようなハード購入費や、上記をインターネットに接続するための費用（ルータやネット回線）を除いてタダと定義したいと思います。

具体的には、
**①RaspberryPiを出た後の処理は無料サービスのみで構成**
→Raspberryから送信したデータを管理するDB、可視化ツールが相当します
**②永続無料サービス**
→「30日無料」等の期間付き無料サービスは除外。容量制限等は許容します

###「見える化」とは？
一般の人からすると、「目的に合わせたグラフ表示」をすることに価値があると思います。
上記を軸として使い勝手を考慮し、下記の3条件を必須とします
**①ダッシュボードでのグラフ表示をゴールとする**
**②自宅ネットワーク外からもアクセス可能とする**
**③維持コストの掛かるオンプレサーバは避け、クラウドサービスを使用する**


#調査結果まとめ
前章の条件を満たしつつ、「タダ」で「見える化」できるサービスをまとめてみました。

||①Ambient使用|②Google Spreadsheets + Data Portal|③クラウドDB + 自作可視化アプリ|
|---|---|---|---|
|データ管理|[Ambient](https://ambidata.io/)|[Google Spreadsheets](https://www.google.com/intl/ja_jp/sheets/about/)|クラウドDB|
|可視化|Ambient|[Google Data Portal](https://marketingplatform.google.com/intl/ja/about/data-studio/)|自作アプリ(Web, モバイル等)|
|メリット|最小工数で可視化できる|手軽さとUIの綺麗さのバランス|自由度の高さ、スケールアップの容易さ|
|デメリット|自由度があまりなく、高度な分析は難しい|数万行以上のデータでは動作が非常に重い|開発工数大、無料版の制約が分かりづらい|
|想定用途|センサのテスト、電子工作|個人、少人数チームでのデータ共有効率化|高度な分析、サービス展開を前提とした開発|

###①Ambientを使用
下記のような構成となります（公式ホームページより）
![structure3.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/610167/334a9e72-8f5d-2c7a-c8b9-1fbc63eff236.jpeg)
ストレージと可視化を一括管理できるので
**最小工数で見える化したい**人向けだと思います。

###②Google Spreadsheets + Google Data Portalを使用
下記のような構成となります
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/610167/9275e2d6-8baa-fd89-bd4f-2527a46cde94.png)
Googleの既存UIをフル活用できるので、
**手軽にキレイなダッシュボードを作りたい**人向けだと思います

###③クラウドDB + 自作アプリで可視化
下記のような構成となります
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/610167/4ecf432a-5084-f216-5fc5-3100ac309000.png)
**拡張性が高く**、無限の可能性が広がる方法だと思いますが、
選択肢が多い分、システム構成の検討や実装に手間が掛かります。

[こちら](https://qiita.com/nh321/items/e8c63b02e3fa9c28837f)を参考にさせて頂き、**無料枠のある主要クラウドDB**を表にまとめました

|プラットフォーム|Heroku Postgre|MongoDB Atlas|Google Cloud Platform|Microsoft Azure|AWS|
|---|---|---|---|---|---|---|
|DB|Postgre|MongoDB|BigQuery|Cosmos DB|DynamoDB|
|方式|RDBMS|NoSQL|列志向|NoSQL|NoSQL|
|無料可視化ツール|自作が必要|自作が必要|Google Data Portal|Power BI|自作が必要|
|無料枠でできること|上限1万行|上限500MB|上限10GB([参考リンク](https://cloud.google.com/free/?hl=ja))|上限5GB[参考リンク](https://qiita.com/okazuki/items/71a08ef7b5a14a83eacb)|上限25GB([参考リンク](https://qiita.com/suzukenz/items/4ecafd341e012102a6e9))|
|メリット|貴重なリレーショナルDB|**プラットフォーム依存低＆モバイル連携**|Google Data Portalでお手軽UI|高機能なPowerBIを使える|市場シェアNo1で情報が多い|

※無料枠があるとはいえ、上限を超えると予想外の金額が掛かってしまうこともあるので、あくまで最新情報を調査の上、自己責任でご使用ください ([こんな恐ろいことも‥](https://qiita.com/itkr/items/745d54c781badc148bb9))

### 作ってみた

説明文や構成図だけではイメージが沸かないかと思うので、
**下記構成で実際に実装した方法を下の章で解説します。**

**【構成】**
**クラウドDB：[MongoDB Atlas](https://www.mongodb.com/cloud/atlas)**
**可視化ツール：Jupyter Notebook ＆ 自作Androidアプリ([別記事](https://qiita.com/c60evaporator/items/31d9d405b9904af54e87))**

###※なぜMongoDB Atlasを選んだか？
上表太字のメリットが理由となりますが、さらに詳しく言うと下記のようになります。
(本音を言うとAWS等は記事数が多いので、独自性を出したかったということもあります笑)

####1. オンプレミスへの移植が容易
他のクラウド専用DBとは異なり、オンプレDBのMongoDBをベースとしており、少ない改造量で移植可

####2. プラットフォーム依存性が低い
　企業として当たり前の戦略ですが、例えばAzureのDBからGCPのサービスを使用するには、互換性の壁があるようです。
　MongoDBであれば各プラットフォーマーが互換性を（自社DBほどでないにしろ）考慮しているので、どのプラットフォームとも連携、移植の工数が少なく済みます。

※逆に言うと、「私はGoogle一本に絞る」「チームのサービスがAzureで統一されている」という方は、Google Cloud DatastoreやAzure Cosmos DBにした方が、分析サービスとも容易に連携できますし、サポートも手厚いのでおすすめです

####3. モバイル連携
[こちら](https://codezine.jp/article/detail/12422)で記事となっているように、モバイル向けDBのRealmをMongoDB社が買収し、双方向のデータ同期を始めとした、モバイルアプリとAtlasとの連携性を大幅に強化しています。しかも無料枠が充実！
実際に使用してAndroidアプリを作ってみたので、[紹介記事](https://qiita.com/c60evaporator/items/8cd83abfeb3b232413d1)や、[実際にアプリを作成した記事](https://qiita.com/c60evaporator/items/31d9d405b9904af54e87)をご覧いただければと思います。


#具体的な手順：①Ambient
[公式サイト](https://ambidata.io/samples/temphumid/python/)や[こちら](https://qiita.com/AmbientData/items/794bf7eab6b7143b41a3)のサイトを参考にすれば、実装できると思います。

このツールで、下記グラフのような見える化をクラウド上で実現できます
![chart2-1024x712.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/610167/7f0e0daf-76c2-6c68-2c7a-fab93c72c216.jpeg)

とても簡単に操作・管理できる素晴らしいサービスなので、まずは一度触ってみるのが良いかと思います。

#具体的な手順：②Google Spreadsheets + Google Data Portal
[こちら](https://qiita.com/c60evaporator/items/283d0569eba58830f86e)や[こちら](https://qiita.com/c60evaporator/items/4040040095aa58dcb5b1)の記事に、手順をまとめました。

下の図のようなダッシュボードが出力されます。
![DashBoard.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/610167/ba1dc893-9375-15bb-442c-708441b386ea.png)
構成は下記のようになります
![センサ値取得システム.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/610167/0c7d2bdf-1a32-3a84-c0d3-8d41ead2a61c.png)
GoogleDataPortalは綺麗なUIをノンプログラミングで簡単に作る事ができます。
また使い慣れたGoogleのサービスを使用しており、安心感があるのも良いですね。


#具体的な手順：③クラウドDB＋自作可視化アプリ
クラウドDBも可視化ツールも種類が多いですが、
ここでは分析用途を想定し、MongoDB Atlas＋Jupiter Notebookによる可視化を試してみました。

※下図のような自作Androidアプリも作ってみましたが、こちらは[別記事](https://qiita.com/c60evaporator/items/31d9d405b9904af54e87)で紹介します。
![all.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/610167/8b917693-05d5-8d3d-8fef-d258bf630930.png)

##1. MongoDB AtlasへのDB作成と、RaspberryPiからのデータアップロード
[こちらの記事の1章(MongoDB Atlasへの登録)と2章(RaspberryPiからのアップロード処理作成)](https://qiita.com/c60evaporator/items/92d0ebde001f568f2707)の内容を実施してください

##2. PythonからDBへのアクセス確認
PythonからMongoDB AtlasのDBにアクセスし、データが取得できることを確認します。

###pymongoのインストール
PythonからMongoDBにアクセスするためのライブラリ「PyMongo」をインストールします

```
pip3 install pymongo
```
これだけだと
The "dnspython" module must be installed to use mongodb+srv
というエラーが出るので、下記コマンドでdnspythonをインストールします

```
pip3 install dnspython
```

###データの取得
取得したデータをPandasに読み込ませます

```atlastest.py
import pandas as pd
import pymongo

def get_collection_to_df(collection_name, filter=None, projection=None):
    #各種DB情報の定義
    USER_NAME = "1章で作成したMongoDB Atlasのユーザ名"
    CLUSTER_NAME = "1章で作成したMongoDB AtlasのCluster名"
    DB_NAME = "1章で作成したMongoDB AtlasのDB名"
    PASSWORD = "DBのパスワード"
    #MongoDBにアクセス（pymongoのMongoClientインスタンス作成）
    client = pymongo.MongoClient(f"mongodb+srv://{USER_NAME}:{PASSWORD}@{CLUSTER_NAME}.jipvx.mongodb.net/{DB_NAME}?retryWrites=true&w=majority")
    #データを取得してPandasのDataFrameに入力
    collection = client[DB_NAME][collection_name]
    cursor = collection.find(filter=None, projection=None)
    df = pd.DataFrame(list(cursor))
    return df
```
※引数filterおよびprojectionはDBから取得するデータにフィルタや射影を掛ける際に使用します

### Jupyter Notebookでデータの冒頭だけ表示
上記に対してJupyter Notebook等でデータが取得できていることを確認できれば成功です。
なお、Jupyter Notebookのインストール方法は[こちらに分かりやすくまとめられております](https://ai-inter1.com/jupyter-notebook/#st-toc-h-4)
※[Anacondaを使用する場合ライセンスに注意](https://qiita.com/tfukumori/items/f8fc2c53077b234384fc)

```python
import atlastest
import pandas as pd

SENSOR_COLLECTION_NAME = "1章で作成したセンサデータアップロード用コレクション名"
df = get_collection_to_df(SENSOR_COLLECTION_NAME)
df.head()
```

```:実行結果
_id	Date_Master	Date_ScanStart	no01_DeviceName	no01_Date	no01_Temperature	no01_Humidity	no01_Light	no01_Human_last	no01_HumanMotion	...	no07_DeviceName	no07_Date	no07_Temperature	no07_Humidity	no07_BatteryVoltage	no08_DeviceName	no08_Date	no08_HumanLast	no08_HumanMotion	_partition
0	5f68cf4cadc32a9b7fbce8ae	2020-09-22 01:05:00	2020-09-22 01:05:04.159	Nature_Remo_1	2020-09-22 01:05:06.337	27.4	47.0	53.0	2020-09-21T16:04:58Z	1.0	...	SwitchBot_Thermo_1	2020-09-22 01:05:30.018	23.3	55.0	100.0	Sony_MeshHuman_1	2020-09-22 01:05:32.399	None	0	Project HomeIoT
1	5f68d0723c72e8deb4315773	2020-09-22 01:10:00	2020-09-22 01:10:04.524	Nature_Remo_1	2020-09-22 01:10:06.702	27.4	47.0	42.0	2020-09-21T16:09:15Z	1.0	...	SwitchBot_Thermo_1	2020-09-22 01:10:23.870	23.3	56.0	100.0	Sony_MeshHuman_1	2020-09-22 01:10:26.213	None	0	Project HomeIoT
2	5f68d1a3d4916d1fe1d054dc	2020-09-22 01:15:00	2020-09-22 01:15:03.862	Nature_Remo_1	2020-09-22 01:15:06.092	27.4	47.0	42.0	2020-09-21T16:09:15Z	0.0	...	SwitchBot_Thermo_1	2020-09-22 01:15:28.639	23.3	55.0	100.0	Sony_MeshHuman_1	2020-09-22 01:15:31.024	None	0	Project HomeIoT
3	5f68d2cb85e3ac1c7982ef6a	2020-09-22 01:20:00	2020-09-22 01:20:04.550	Nature_Remo_1	2020-09-22 01:20:06.749	27.4	47.0	42.0	2020-09-21T16:15:44Z	1.0	...	SwitchBot_Thermo_1	2020-09-22 01:20:24.392	23.2	55.0	100.0	Sony_MeshHuman_1	2020-09-22 01:20:26.968	None	0	Project HomeIoT
4	5f68d3f93fa649955fe4a5a5	2020-09-22 01:25:00	2020-09-22 01:25:04.178	Nature_Remo_1	2020-09-22 01:25:06.387	27.4	47.0	42.0	2020-09-21T16:15:44Z	0.0	...	SwitchBot_Thermo_1	2020-09-22 01:25:27.275	23.3	55.0	100.0	Sony_MeshHuman_1	2020-09-22 01:25:29.709	None	0	Project HomeIoT
```

##3. Jupyter Notebookでグラフ表示
2章で取得したデータを、Jupyter Notebook (Matplotlib, Seaborn)でグラフ化してみます。

### 1週間分の気温をセンサごとに折れ線グラフ化
```python
import atlastest
import pandas as pd
import pymongo
from datetime import datetime, timedelta
import matplotlib.pyplot as plt

SENSOR_COLLECTION_NAME = "1章で作成したセンサデータアップロード用コレクション名"
LIST_COLLECTION_NAME = "1章で作成したセンサ一覧コレクション名"
#1週間分のデータをフィルタで取得
startdate = datetime.now() - timedelta(days=7)
flt = {"Date_Master":{"$gte":startdate}}
df = atlasclient.get_collection_to_df(COLLECTION_NAME, filter=flt)
#センサ情報を取得してdict化
df_sensor_list = atlasclient.get_collection_to_df(LIST_COLLECTION_NAME)
name_dict = {'no'+format(device.no,'02d'):device.sensorname for device in df_sensor_list.itertuples()}
#抽出対象列（'Date_Master' + '_Temperature'で終わる列）
datecols = df.columns.str.endswith('Date_Master')
tempcols = df.columns.str.endswith('_Temperature')
extractcols = np.logical_or(datecols, tempcols)
df_temp = df.iloc[:, extractcols]
#列名から'_Temperature'を削除
for colname in df_temp.drop('Date_Master', axis=1).columns:
    df_temp = df_temp.rename(columns={colname:colname.replace('_Temperature', '')})
display(df_temp.rename(columns=name_dict).tail(1))
#全て表示
df_temp.rename(columns=name_dict).plot(x='Date_Master', rot=90, figsize=(18, 6))
```
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/610167/d9022246-fa0f-d2a6-1ed9-2b4062acd299.png)


### 1週間分の湿度を場所ごとに折れ線グラフ化

```python
import atlastest
import pandas as pd
import pymongo
from datetime import datetime, timedelta
import matplotlib.pyplot as plt
import seaborn as sns

SENSOR_COLLECTION_NAME = "1章で作成したセンサデータアップロード用コレクション名"
LIST_COLLECTION_NAME = "1章で作成したセンサ一覧コレクション名"
#1週間分のデータをフィルタで取得
startdate = datetime.now() - timedelta(days=7)
flt = {"Date_Master":{"$gte":startdate}}
df = atlasclient.get_collection_to_df(COLLECTION_NAME, filter=flt)
#センサ情報を取得してdict化
df_sensor_list = atlasclient.get_collection_to_df(LIST_COLLECTION_NAME)
name_dict = {'no'+format(device.no,'02d'):device.sensorname for device in df_sensor_list.itertuples()}
#抽出対象列（'Date_Master' + '_Humidity'で終わる列）
datecols = df.columns.str.endswith('Date_Master')
humidcols = df.columns.str.endswith('_Humidity')
extractcols = np.logical_or(datecols, humidcols)
df_humid = df.iloc[:, extractcols]
#列名から'_Humidity'を削除
for colname in df_humid.drop('Date_Master', axis=1).columns:
    df_humid = df_humid.rename(columns={colname:colname.replace('_Humidity', '')})
#屋外の温度列名をリスト化
outdoor_humidcol_list = ['no'+format(device.no,'02d') for device in df_sensor_list.itertuples() if device.place.split('_')[0] == 'outdoor' and device.humidity == True]
#キッチンの温度列名をリスト化
kitchen_humidcol_list = ['no'+format(device.no,'02d') for device in df_sensor_list.itertuples() if device.place == 'kitchen' and device.humidity == True]
#屋内の温度列名をリスト化
indoor_humidcol_list = ['no'+format(device.no,'02d') for device in df_sensor_list.itertuples() if device.place == 'indoor' and device.humidity == True]
#屋外、屋内、キッチンの平均をとってプロット
df_humid_inout = df_humid[["Date_Master","no01"]]
df_humid_inout['Outdoor'] = df_humid[outdoor_humidcol_list].mean(axis=1)
df_humid_inout['Kitchen'] = df_humid[kitchen_humidcol_list].mean(axis=1)
df_humid_inout['Indoor'] = df_humid[indoor_humidcol_list].mean(axis=1)
df_humid_inout = df_humid_inout.drop('no01', axis=1)
#表示
df_humid_inout.plot(x='Date_Master', rot=90, figsize=(12, 4))
```
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/610167/e4b86f19-a395-d3cc-6262-f73d3a9fe226.png)

### 1週間分の湿度を場所ごとに箱ひげ図表示
前節スクリプトの最後の行を下記のように置き換えて、箱ひげ図表示することも可能です(Seaborn使用)

```python
#場所ごとの列を行に分解して、箱ひげ表示
df_humid_stack = df_humid_inout.set_index('Date_Master')
df_humid_stack = df_humid_stack.stack()
df_humid_stack = df_humid_stack.reset_index()
df_humid_stack = df_humid_stack.rename(columns={'level_1':'place', 0:'Humidity'})
sns.boxplot(data=df_humid_stack ,x='place', y='Humidity')
```
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/610167/2de91504-5cef-b6bc-7844-afd390c2bb6c.png)
屋外より屋内の方がばらつきが大きく、またキッチンは外れ値(お湯を沸かすと湿度UP)が多くみられます

**以上のように、RaspberryPiで取得したデータを可視化することが出来ました！**
PandasのDataFrameに入れさえしてしまえば、あとはどうとでもなる。という印象です。

Jupyter Notebookは気軽に可視化できますが、多人数での共有には向かないので、その場合はWebアプリを自作するかBIツールを使用するのが良いかと思います。

###まとめ
**・RaspberryPi取得データをタダで見える化する方法
　　①Ambient使用
　　②Google Spreadsheets + Data Portal
　　③クラウドDB + 自作可視化アプリ」**

**・クラウドDBは無料枠のあるサービスも多い。容量上限や連携サービスを見て選定がGood**

**・MongoDB Atlas + Jupter Notebook（＆別記事でスマホアプリ作成）で実際に可視化してみた**