雑食エンジニアの気まぐれレシピ

日ごろ身に着けた技術や見知った知識などの備忘録的なまとめ.主にRaspberry Piやマイコンを使った電子工作について綴っていく予定.機械学習についても書けるといいな.

Metabase+NatureRemoで部屋環境を可視化する【プロト実装編】

前回,Go言語とRaspberryPiの環境構築の話を書きました.
shikky-lab.hatenablog.com

実はこれが今回の話に繋がるのですが,Go言語+ラズパイで部屋環境(温度,湿度ほか)の可視化にチャレンジしています.
とはいえポイントはそちらではなく,今回タイトルに挙げたMetabaseと,温湿度計を一応内部に備えているNatureRemoになります.

まだまだ未完成なのですが,ひとまずプロト実装が動いたので,その内容について書いていきたいと思います.

f:id:shikky_lab:20210131223648p:plain
部屋の温湿度をMetabaseで可視化した様子

システム構成

初めにシステム構成載せときます.

f:id:shikky_lab:20210131224729p:plain
システム構成

1. Metabase

Metabaseはオープンソースのデータ可視化ツールです.データだけ食わせれば,それを使ってグラフ等をカスタマイズして表示することができます.Excelのグラフ機能のようなものですね.

こういった可視化ツールは実は世の中に沢山あって,

あたりが有名みたいです.

他にもいろいろあるようですが,
OSSのデータ可視化ツール「Metabase」が超使いやすい - Qiita
様のエントリなどを見るとMetabaseが簡単かつ良さげだなと思い,こちらを使うことにしました.
なおMetabaseの魅力はそちらで詳細に紹介されているので,今回は割愛します.

さておき,次に考えるのはMetabaseが使用するデータを格納するためのデータベースとなります.

2. MySQL(MariaDB)

正直DBは何でもよかったので,一番一般的なヤツを採用しました.
ちなみにRaspbian(というかdebian系のLinux)ではMySQLではなく,それをフォークしたMariaDBというものを採用しているらしいです.
MariaDB - Wikipedia

なのでaptでインストールするときもmariadbを指定しましょう.

3. 常駐スクリプト

これも定期的に値をとってきてDBに書き込むだけなので,なんでもOKでした.なので今回はGo言語を使ってみることにしたというわけですね.

NatureRemoへのアクセスは,要はREST-APIを叩くだけとなります.アクセスのためには事前に生成したトークンを読み込ませる必要があるのですが,手順は以下のエントリを参考にさせて頂きました.
Nature Remoの公式APIの使い方 - Qiita

以下,軽くコードを解説していきます.コード全体はコチラ.
GitHub - shikky-lab/omni_dashboard at 34aa6b039699ec081e98dc2381c842382e3b23ff

Nature Remoへのアクセス

取得部分のコードはこんな感じ

token:="XXX"
client := http.Client{}
url := "https://api.nature.global/1/devices"
req, err := http.NewRequest("GET", url, nil)
req.Header.Add("accept", "application/json")
req.Header.Add("Authorization", "Bearer "+string(token))

resp, err := client.Do(req)
if err != nil {
    panic(err)
}
defer resp.Body.Close()

//ここで受け取ったrespのbodyにnatureRemoから取得した値がJsonとして格納されている.
//そのjsonに合わせたtypeを事前に定義しておくと,以下のように格納できる.

byteArray, _ := ioutil.ReadAll(resp.Body)

var devices []remoDevice
if err := json.Unmarshal(byteArray, &devices); err != nil {
    panic(err)
}

↑で使っているremoDeviceなる構造体は↓のような感じです.Remoが返してくるjsonに合わせて定義してあります.

type illuminanceValue struct {
    Val       float32   `json:"val"`
    CreatedAt time.Time `json:"created_at"`
}

type humidityValue struct {
    Val       float32   `json:"val"`
    CreatedAt time.Time `json:"created_at"`
}

type movedValue struct {
    Val       float32   `json:"val"`
    CreatedAt time.Time `json:"created_at"`
}

type temperatureValue struct {
    Val       float32   `json:"val"`
    CreatedAt time.Time `json:"created_at"`
}

type events struct {
    Hu humidityValue    `json:"hu"`
    Il illuminanceValue `json:"il"`
    Mo movedValue       `json:"mo"`
    Te temperatureValue `json:"te"`
}

type remoDevice struct {
    Name              string    `json:"name"`
    ID                string    `json:"id"`
    CratedAt          time.Time `json:"crated_at"`
    MacAddress        string    `json:"mac_address"`
    SerialNumber      string    `json:"serial_number"`
    FirmwareVersion   string    `json:"firmware_version"`
    TemperatureOffset int       `json:"temperature_offset"`
    HumidityOffset    int       `json:"humidity_offset"`
    Users             []user    `json:"users"`
    NewestEvents      events    `json:"newest_events"`
}

こうしておくと自動的に構造体のフィールドとして値が格納されるので,非常に使いやすくなります.

DBへの書き込み

プログラム上でDBを扱う場合,SQL文を直接叩いても良いのですが,いずれ面倒になりそうなのでORMapperを導入します.
Go言語におけるORMapperとしては,GORMというものが良く使われているようです.
GORMガイド | GORM - The fantastic ORM library for Golang, aims to be developer friendly.

これも先ほどのRESTの時と同様に,DBのスキーマに合わせた構造体を定義しておくと,構造体を経由してDB書き込みなどができるようになります.

つまりこんな感じで構造体を定義しておくと,

type humidity struct {
    ID    int `gorm:"cloumn:id"` //タグつけるとcloumn名と紐づけ.なけらばField名が使われる.
    Value float32
    Time  time.Time
}

こんな感じで書き込みできます.(テーブルは事前に用意されている前提)

humidityOrm := humidity{}
humidityOrm.Value = devices[0].NewestEvents.Hu.Val//deviceはさっきのRESTのスニペットで取得したやつ
humidityOrm.Time = devices[0].NewestEvents.Hu.CreatedAt

//dbinfoは後述
CONNECT := dbinfo.user + ":" + dbinfos.Pass + "@" + dbinfos.Protocol + "/" + dbinfos.Dbname + "?" + dbinfos.Param
db, err := gorm.Open(dbinfos.Dbms, CONNECT)
db := gormConnect()
defer db.Close()

db.Save(&humidityOrm)

なおスクリプト内に出てきたdbinfoは要はdbへの接続情報ですが,私は以下のようにyamlファイルとして書き出していたやつを読み込ませてます.
(パスワードとかをコードに入れるとGitHubにpushできないので)

# dbinfo.yml

dbms : mysql
user : root
pass : [パスワード]
protocol : tcp(localhost:3306)
dbname : omni_dash_dev
param : charset=utf8mb4&parseTime=True&loc=Asia%2FTokyo

あとはこれを5分に一回くらいの頻度で定期的に叩くようにすれば,DBにデータを蓄積していけます.

ちなみにNatureRemoはセンサの値に変化があるたびに,その値をタイムスタンプとともに内部に保持しています.
そしてRESTで叩いた時に得られる情報はその最新の値になります.

つまり,センサの値に変化がなければ同じデータが返ってきます.

特に温度計のデータは分解能が0.6度なので,数時間値が変わらないこともザラです.

ですのでNatureRemoに頻繁にアクセスしたとしても,常に新しいデータが返ってくるわけではないので,DBのデータがあふれる心配はあまりしなくて大丈夫です.

これからやること

さしあたりここまでで温度と湿度の可視化はできました.これからの展望としては,下記を考えています.

  • 温湿度の精度向上
    • NatureRemoの温度計の分解能は0.6度と大き目 &温湿度計の精度が提示されていない
    • ⇒精度を謳っているSwitchBotの温湿度計を買ってみました.現在実装中.
    • www.switchbot.jp
  • CO2濃度の可視化
    • AliExpressで買ったCO2センサが届いたので,こちらも搭載予定
  • インターネット上からアクセス可能にする
  • Andoroidのウィジェット対応
  • スマートウォッチなどの情報を可視化する
    • ゆくゆくは取得しているデータすべてをグラフ化したい

ざっと挙げただけでも山盛りですね(笑).

なかなか長い闘いになりそうですが,気長に進めていこうかなと思います.

ちなみにプロジェクト名は"Omni Dashboard"と名付けました.
どうでもいいですが,プロジェクト名つけるとモチベーション上がりません?私だけ?

プロジェクトが頓挫しないよう,粛々と頑張っていきたいと思います.

それでは.