いちろう’s blog

すーぱーえんじにあ

NatureRemoのセンサーデータをMongoDBに保存してMetabaseで可視化する

自宅ではスマートリモコンのNatureRemoをスマートスピーカーのリモコンとして利用しているが、リモコン操作以外にも部屋の温度や湿度を定期的に測定する機能が付いている。今までほとんど気にする事はなかったが、せっかく測定してくれているので、測定したデータを可視化したり、統計取ったりしたいところ。そういったことを簡単にできるツールがないか探したところ、MetabaseというOSSを見つけた。

色々試してみるとMetabaseがとても使いやすかったので、こちらを利用してNatureRemoで収集したセンサーデータを可視化するダッシュボードをMetabaseで作成してみた。

Metabaseは分析対象のデータを保存するDBが必要なので、今回はNoSQLのDBであるMongoDBを利用した。 全体構成は以下の通り。

家にあるNatureRemoは、以下の第二世代のものを利用している。

MongoDBとMetabaseの構築

MongoDBとMetabaseの構築は、docker-composeを用いて行う。また、MongoDBのブラウザツールであるmongo-expressは、MongoDBのcliを利用することなくGUI操作でMongoDBを操作できて便利なので、こちらも合わせて構築する。

services:
    mongodb:
      image: mongo
      container_name: mongodb
      restart: always
      environment:
        MONGO_INITDB_ROOT_USERNAME: mongodb_user
        MONGO_INITDB_ROOT_PASSWORD: mongodb_pass
      ports:
        - 27017:27017
      volumes:
        - ./mongo/db:/data/db
        - ./mongo/configdb:/data/configdb
  
    mongo-express:
      image: mongo-express
      container_name: mongo-express
      restart: always
      links:
        - 'mongodb:mongo'
      ports:
        - 8081:8081
      environment:
        ME_CONFIG_MONGODB_ADMINUSERNAME: mongodb_user
        ME_CONFIG_MONGODB_ADMINPASSWORD: mongodb_pass

    metabase:
        image: metabase/metabase:v0.33.5
        container_name: metabase
        ports:
            - 3333:3000
        volumes:
            - ./metabase:/metabase.db

実行の際は、以下のコマンドを用いる。

docker-compose up -d

各ツールのエンドポイントは以下の通り。

名称 エンドポイント
mongodb localhost:27017
mongo-express localhost:8081
metabase localhost:3333

注意すべき点は、Metabaseのバージョン。Metabaseの最新のバージョンv0.38、v0.39ではMongoDBと接続後、MongoDB内のデータを取得する際に、DBのコレクション内にデータが存在するにも関わらずコレクション内のデータを見つけられない事象が発生し、MongoDBのデータを取得できなかった。 いろいろ試した結果v0.33.5では、データを取得しMetabaseでデータを可視化できたので、今回はこちらのバージョンを利用する。

Nature RemoのセンサーデータをMongoDBに保存

NatureRemoのセンサーデータを取得し、先ほど作成したMongoDBに取得したデータを保存するスクリプトPythonで作成する。MongoDBに接続するライブラリはpymongoを利用する。

PyMongo — MongoDB Drivers

pip install pymongo

NatureRemoのAPITokenの取得は、以下のURLに自身のアカウントでログインして行う。

作成したスクリプトを、以下に示す。

import argparse
import requests
import datetime as dt
from pytz import timezone

from pymongo import MongoClient


class MongoRepository(object):
    def __init__(
        self, db_name, collection_name, username, password, host="localhost", port=27017
    ):
        _client = MongoClient(
            "mongodb://%s:%s@%s:%d" % (username, password, host, port)
        )
        _db = _client[db_name]
        # 認証情報を付与
        _db.add_user(
            username,
            password,
            roles=[
                {
                    "role": "dbAdmin",
                    "db": db_name,
                },
            ],
        )

        self.collection = _db[collection_name]

    def insert_list(self, insert_list) -> list:
        return self.collection.insert_many(insert_list)


def get_room_info(api_token: str) -> {}:
    """部屋の温度の情報を取得する。
    Args:
        api_token (str): NatureRemoのAPIToken
    """
    headers = {
        "accept": "application/json",
        "Authorization": "Bearer " + api_token,
    }
    response = requests.get("https://api.nature.global/1/devices", headers=headers)
    return response.json()


def collect_to_mongodb(api_token: str, mongo_client):
    """NatureRemoからデータを取得し、MongoDBに送信する。
    Args:
        api_token (str): NatureRemoのAPIToken
    """

    room_info_list = []
    # 取得したデータをデバイスごとに整形
    for room_info in get_room_info(api_token):
        request_at_date = timezone('Asia/Tokyo').localize(dt.datetime.now()).strftime('%Y-%m-%dT%H:%M:%SZ')
        tmp_room_info = {
            "name": room_info["name"],
            "newest_events": room_info["newest_events"],
            "updated_at": room_info["updated_at"],
            "request_at": request_at_date
        }
        room_info_list.append(tmp_room_info)

    print("room info: {}".format(room_info_list))
    # mongodbへ保存
    mongo_client.insert_list(room_info_list)
    print("save complete!")


if __name__ == "__main__":
    # 引数の設定
    parser = argparse.ArgumentParser()

    parser.add_argument("api_token", help="NatureRemoのAPIToken")
    parser.add_argument("mongo_username", help="ユーザ名")
    parser.add_argument("mongo_password", help="パスワード")
    parser.add_argument("mongo_db_name", help="保存するDatabase名")
    parser.add_argument("mongo_collection_name", help="保存するCollection名")
    parser.add_argument("--mongo_host", default="localhost", help="対象のMongoDBのホスト名")
    parser.add_argument("--mongo_port", default=27017, help="対象のMongoDBのポート番号")

    args = parser.parse_args()

    # Mongo Clientの初期化
    mongo_client = MongoRepository(
        db_name=args.mongo_db_name,
        collection_name=args.mongo_collection_name,
        username=args.mongo_username,
        password=args.mongo_password,
        host=args.mongo_host,
        port=args.mongo_port,
    )

    collect_to_mongodb(api_token=args.api_token, mongo_client=mongo_client)

NatureRemoのAPIは、アカウントに紐づく端末が複数ある場合に、それらの端末全ての情報を配列で返すようになっている。現在私の家にはNatureRemoは1台しかないが、今後増える可能性も考えてMongoDBに保存するデータを配列で渡せるようにしている。

18行目の_db.add_user(...の箇所は、実行時にDBにアクセス権限を付与するもの。MetabaseからMongoDBのコレクションにアクセスする際に、DBに対してユーザのアクセス許可がないと認証の段階でエラーになるためその対応として入れた。 本来はDB作成時、一度だけ実行すれば問題ない。

実行時のコマンドを以下に示す。database名をhome、collection名をenviromentとした。

# 各引数の説明
$ python main.py ${NatureRemoのAPIToken} ${MongoDBのユーザ名} ${MongoDBのパスワード} ${保存するデータベース名} ${保存するコレクション名}

# 実行のサンプル
$ python main.py ${NatureRemoのAPIToken} mongodb_user mongodb_pass home environment
room info: [{'name': 'Remo', 'newest_events': {'hu': {'val': 48, 'created_at': '2021-05-08T15:37:53Z'}, 'il': {'val': 203, 'created_at': '2021-05-08T13:43:51Z'}, 'mo': {'val': 1, 'created_at': '2021-05-05T23:39:27Z'}, 'te': {'val': 24, 'created_at': '2021-05-08T13:30:25Z'}}, 'updated_at': '2021-05-08T13:43:16Z', 'request_at': '2021-05-09T00:49:42Z'}]
save complete!

実行後、データベースとコレクションが自動で作成され、そちらにNatureRemoから取得したデータが保存される。以下のmongo-expressのURLから、データが保存されていることを確認する。

以下の画面のように、取得したデータがmongo-expressの画面上で閲覧できていれば、保存に成功していることとなる。

上記のpythonスクリプトを、cronなどで15分おきに実行するように設定することで、15分ごとのセンサーデータの値をMongoDBに保存できる。

Metabaseでダッシュボードの作成

MongoDBと接続

Metabaseには、以下のURLからアクセスする。

初回アクセス時はユーザの作成が求めらるので、任意のユーザを作成すること。ユーザ作成完了後、接続するMongoDBの設定を行う。 入力内容の例は、以下の通り。

「ホスト」の箇所は、今回はdocker-compsoeを利用して構築していることからdockerのdnsで名前解決されるため、localhostではなく、container名を指定する(今回の場合はmongodbを指定)。 加えて、Metabase+Mongoの接続の際は、こちらの方の説明にあるようにいくつかハマりポイントがあるので注意。

特にMongoDBでユーザ認証を利用する場合に、Authentication Databaseの欄に認証用のdatabase(デフォルト設定のままであればadmin)を入力する必要がある点などは忘れがちなので、MongoDBに認証を設定した場合は確認しておくこと。

ダッシュボードを作成

MongoDBとの接続が完了したら、画面上部の「照会する」パネルからグラフを作成していく。

ここでも一つ注意点があり、今回のようなネストされたデータの場合、ネストされているカラムが、グラフ作成時に選択可能なカラムの一覧に表示されない事象が発生する。この場合、グラフを作成しようとしても、ネストされた値が選択肢に表示されないので、グラフを作成することができない。 それを解決するためには、画面右上の「エディターを表示する」より、ネストされたカラムを手動で表示させる必要がある。

今回であれば、NatureRemoから取得したデータのうち温度に関する情報を利用したいため、赤枠で囲った「Newest Event > Te > Val」と「Newest Event > Te > Created At」にチェックを入れ、Metabase にカラムとして認識させた。

その後、Metabaseの画面に従いこのようなグラフを作成した。

最終的に照度や湿度も合わせてダッシュボードを作成した。自動更新機能があるため、リアルタイムに近い情報が常に確認できてIoTのようなセンサーデータの表示にも便利。

終わりに

Metabaseをほとんど触ったことなかったが、指示に従うだけで簡単にグラフを設定できてとても便利だった。一部バグとみられる箇所があったり、グラフ作成も細かい箇所の調整ができなかったりと不具合はあるが、開発も頻繁に行われているため今後の成長に期待。

参考