enebular

enebular InfoMotion でオムロン環境センサー (2JCIE-BL01) を可視化してみた。

はじめに

enebular developer Meetuup Vol.3 で「enebular InfoMotion を使ってオムロン環境センサー (2JCIE-BL01) を可視化してみた。(仮)」というタイトルで LT する内容の記事です。

スクリーンショット 2018-09-23 20.26.56.png

Raspberry Piとオムロン環境センサーを使用して測定したデータを可視化する事例やハンズオンはいくつかあるが、データを Firebase へアップロードして、Firebase のデータを enebular InfoMotion で可視化してみた。

事前準備

  • Raspberry Pi
    • Raspberry Pi 3 Model B
    • Raspberry Pi 3 Model B+
    • Raspberry Pi Zero W
    • Raspberry Pi 2 Model B + Bluetooth 4.0 USBアダプタ(CSR V4.0、BT-Micro4 など)

 OS は "RASPBIAN STRETCH" Version:June 2018 (Release date:2018-06-27) を使用。

構成

スクリーンショット 2018-09-22 22.38.49.png

手順

Raspberry Pi

5分毎にデータをFirebaseへアップロードする場合 (Ambient のツールを流用しています)

# ライブラリをインストール
$ sudo apt-get install libglib2.0-dev
$ sudo pip3 install bluepy
$ sudo pip3 install pyrebase
# プログラムを実行
$ sudo nohup python3 omron_firebase.py < /dev/null &

omron_firebase.py プログラムは こちら

項目 設定値
config apiKey Firebase の apiKey を設定 (必須)
authDomain Firebase の authDomain を設定 (必須)
databaseURL Firebase の databaseURL を設定 (必須)
storageBucket Firebase の storageBucket を設定 (必須)
email Firebase の認証用 ID (必須)
password Firebase の認証用 Password (必須)
address オムロン環境センサーの BT アドレス (任意)
omron_firebase.py
# -*- coding: utf-8 -*-
#

from bluepy.btle import Peripheral, DefaultDelegate, Scanner, BTLEException, UUID
import bluepy.btle
import sys
import struct
from datetime import datetime
import argparse
import pyrebase

config = {
    "apiKey": "",
    "authDomain": "",
    "databaseURL": "",
    "storageBucket": ""
}
email = ''
password = ''
companyID = 'd502'  # OMRON company ID (Bluetooth SIG.)
address = 'FFA511nnnnnn'  # BT ADDRESS (replace nnnnnn to own device)

Debugging = False
def DBG(*args):
    if Debugging:
        msg = " ".join([str(a) for a in args])
        print(msg)
        sys.stdout.flush()

Verbose = True
def MSG(*args):
    if Verbose:
        msg = " ".join([str(a) for a in args])
        print(msg)
        sys.stdout.flush()

def send2firebase(dataRow):
    (temp, humid, light, uv, press, noise, accelX, accelY, accelZ, batt) = struct.unpack('<hhhhhhhhhB', bytes.fromhex(dataRow))
    MSG(temp/100, humid/100, light, uv/100, press/10, noise/100, (batt+100)/100)

    firebase = pyrebase.initialize_app(config)
    db = firebase.database()
    auth = firebase.auth()
    user = auth.sign_in_with_email_and_password(email, password)

    now = datetime.now()
    dt_s = now.strftime("%s") + str(int(now.microsecond/1000))
    data = {
        "timestamp": int(dt_s),
        "value": {
          "created": int(dt_s),
          "address": address,
          "battery": float((batt+100)/100),
          "temperature": float(temp/100),
          "humidity": float(humid/100),
          "light": float(light),
          "uv": float(uv/100),
          "pressure": float(press/10),
          "noise": float(noise/100)
        }
    }
    MSG(data)
    results = db.child("omron").child(address).push(data, user['idToken'])

class ScanDelegate(DefaultDelegate):
    def __init__(self):
        DefaultDelegate.__init__(self)
        self.lastseq = None
        self.lasttime = datetime.fromtimestamp(0)

    def handleDiscovery(self, dev, isNewDev, isNewData):
            if isNewDev or isNewData:
                for (adtype, desc, value) in dev.getScanData():
                    if desc == 'Manufacturer' and value[0:4] == companyID:
                        delta = datetime.now() - self.lasttime
                        if value[4:6] != self.lastseq and delta.total_seconds() > 11:
                            self.lastseq = value[4:6]
                            self.lasttime = datetime.now()
                            send2firebase(value[6:])

def main():
    parser = argparse.ArgumentParser()
    parser.add_argument('-d',action='store_true', help='debug msg on')

    args = parser.parse_args(sys.argv[1:])

    global Debugging
    Debugging = args.d
    bluepy.btle.Debugging = args.d

    scanner = Scanner().withDelegate(ScanDelegate())
    while True:
        try:
            scanner.scan(5.0)
        except BTLEException:
            MSG('BTLE Exception while scannning.')

if __name__ == "__main__":
    main()

一括でCSVファイルのデータをFirebaseへアップロードする場合 (オムロンのサンプルプログラムが出力するCSVファイルを使用)

# ライブラリをインストール
$ sudo apt-get install python-bluez
$ sudo pip3 install pyrebase
# プログラムを実行
$ python3 omron_csv_firebase.py ./log/env_sensor_log.csv

omron_csv_firebase.py プログラムは こちら

項目 設定値
config apiKey Firebase の apiKey を設定 (必須)
authDomain Firebase の authDomain を設定 (必須)
databaseURL Firebase の databaseURL を設定 (必須)
storageBucket Firebase の storageBucket を設定 (必須)
email Firebase の認証用 ID (必須)
password Firebase の認証用 Password (必須)
omron_csv_firebase.py
import sys
import csv
from datetime import datetime
import pyrebase

config = {
    "apiKey": "",
    "authDomain": "",
    "databaseURL": "",
    "storageBucket": ""
}
email = ''
password = ''

arguments = sys.argv

if len(arguments) == 1:
  print ("Usage: python omron_csv_firebase.py CSV_FILE")
  sys.exit()

csvfile = arguments[1]

def main():
    firebase = pyrebase.initialize_app(config)
    db = firebase.database()
    auth = firebase.auth()
    user = auth.sign_in_with_email_and_password(email, password)

    ##  CSV_FILE format
    ##  Time,Gateway,Address,Type,RSSI (dBm),Distance (m),Sequence No.,Battery (mV),Temperature (degC),Humidity (%%RH),Light (lx),UV Index,Pressure (hPa),Noise (dB),Discomfort Index,Heat Stroke Risk,Accel.X (mg),Accel.Y (mg),Accel.Z (mg)
    ##  2018-09-17 00:04:53.831591,raspberrypi,FFA511nnnnnn,EP,-66,1.77188476275,69,2830.0,22.68,82.6,0,0.01,996.3,34.64,71.4,23.37,0.0,0.0,0.0

    f = open(csvfile, 'r')
    reader = csv.reader(f)

    for row in reader:
      if row[0] == 'Time':
        continue
      dt = datetime.strptime(row[0][0:19], '%Y-%m-%d %H:%M:%S')
      dt_s = dt.strftime('%s') + row[0][20:23]

      data = {
        "timestamp": int(dt_s),
        "value": {
          "created": int(dt_s),
          "address": row[2],
          "rssi": float(row[4]),
          "distance": float(row[5]),
          "battery": float(row[7]),
          "temperature": float(row[8]),
          "humidity": float(row[9]),
          "light": float(row[10]),
          "uv": float(row[11]),
          "pressure": float(row[12]),
          "noise": float(row[13]),
          "di": float(row[14]),
          "heat": float(row[15])
        }
      }
##      print (data)
      results = db.child("omron").child(row[2]).push(data, user['idToken'])

    f.close()
    return

if __name__ == "__main__":
    main()

※ pyrebase をインストールした後、プログラムを実行すると「ImportError: cannot import name 'opentype' 」エラーが出力されるので、google-auth-oauthlib をアップグレードする。

$ sudo pip3 install --upgrade google-auth-oauthlib

Firebase

omron → BT アドレス (FFA511nnnnnn) 配下にデータが格納される。データ構造は以下。

第一階層 第二階層
timestamp タイムスタンプ Unixtime (ミリ秒)
value address オムロン環境センサーの BT アドレス
battery バッテリー電圧 (mV)
created タイムスタンプ Unixtime (ミリ秒)
di 不快指数
distance 距離 (m)
heat 熱中症警戒度
humidity 湿度 (%)
light 照度 (Lm)
noise 騒音 (dBm)
pressure 気圧 (hPa)
rssi 電波強度 (dBm)
temperature 気温 (℃)
uv UV Index

Firebase_2.png

InfoType

InfoMotion 作成の前に InfoType 作成を実施する必要があるが、InfoType 作成には InfoMotion Tool を導入する必要があるため、今回はサンプルで用意されている linechart をダウンロードして使用することにした。
(参考) https://docs.enebular.com/ja/infomotion/infomotiontool
(参考) https://docs.enebular.com/ja/infomotion/SampleInfoTypes.html

enebular_linechart.png

InfoMotion

InfoType と DataSource を指定して InfoMotion ダッシュボードを作成する。(参考)

TYPE に linechart を指定する。
DATASOURCE に Firebase DataSource を指定する。
Label に address データキーを指定する。
Value に 上記の第二階層のデータキーを指定する。(気温であれば temperature を指定する。)

enebular_add_graph_3.png

enebular_add_graph_2.png

結果

enebular.png