Amazonのアソシエイトとして、ラズパイダ(raspida.com)は適格販売により収入を得ています。詳しくは当サイトの プライバシーポリシーをご覧ください。

センサーから取得した値を、なるべく簡単に表示させたいと思って、Metabaseに辿り着きました。前回の記事からオムロンの環境センサ(USB型)に興味が沸いたので試してみました。製品自体は少し前の発売で、あまり知りませんでした。

オムロンは、OMRON connectという形で製品を展開しています。いつ頃なんでしょうか。ネットには2021年くらいまでRaspberry Pi との連携に関する情報はたくさんありました。ちょうどコロナ前からコロナ明けで大変な時期だっただけに、個人的に気が付きませんでした。

その後、自分でもオムロン製の体重計を手に入れてスマホアプリで体重を記録しています。

スマホアプリは無料で、OMRON connectのダッシュボードみたいな位置付けです。オムロンの対応製品はもちろん、法人向けAPIで他社もこの仕組みに乗っかれます。血圧計も買おうかな。

対応製品の中でも、評価用に環境センサが販売されています。公式PDFで細かく情報の提供があるため、Raspberry Pi で試せば色々と学べますよ。

USBで接続すれば、BLEだけではなくUSBシリアル通信でデータを取得できます。Raspberry Pi でプログラムを書けば楽しそうですね。

1種類は販売終了していて、USB型がまだ販売しているうちにトライしてみました。

今回の環境

バッグ型の製品は生産終了でした。USB型を使います。

ラズパイと直接繋げるUSB型ですからシリアル通信になります。(USB-Serial)電波とは違って確実に取得できますね。

画像では分かりにくいですが、標準でフィルタカバーが付属しており精度に定評があります。画像はフィルタカバーを取り付けた状態です。外しても同じ形なんですけどね。

スマホアプリもあるようですが試していません。Iot向け製品でしょう。

扱える計測データ

今回のオムロン製USB型環境センサでは、主に次のデータが取り扱えます。

温度、湿度、環境光、気圧、騒音、eTVOC、eCO2、不快指数、熱中症、振動情報など。

Temperature:33.33
Relative humidity:50.22
Ambient light:782
Barometric pressure:1015.512
Sound noise:53.21
eTVOC:8
eCO2:457
Discomfort index:82.68
Heat stroke:28.53
Vibration information:0
SI value:0.0
PGA:0.0
Seismic intensity:0.0
Temperature flag:0
Relative humidity flag:0
Ambient light flag:0
Barometric pressure flag:0
Sound noise flag:0
eTVOC flag:0
eCO2 flag:0
Discomfort index flag:0
Heat stroke flag:0
SI value flag:0
PGA flag:0
Seismic intensity flag:0

eTVOC=総揮発性有機化合物濃度(室内環境下) eCO2=二酸化炭素濃度

実際の計測値だけではなく、計算して出した値もあります。 不快指数なんかは正に計算値ですよね。 このリストの順番でいえば、最初の6つは計測値です。(温度〜eTVOCまで)

やりたいこと

単純な話で申し訳ないですが、取得したデータを可視化したいなと思いました。

可視化といってもやり方は様々でして、バリバリなエンジニアさんのようなことは端からできない。

非エンジニアとしては、プログラミングができないため、先人の公開記事を見様見真似でトライしてみました。

ちょこっと修正して組み合わせるというやり方です。(いつもの元気玉方式)

体系的に理解していないため、今回もハマりました。その辺も踏まえてお伝えしていきます。

構築の手順

ラズパイはRaspberry Pi OSを通常通りセットアップしてあります。

Pythonプログラムで計測したデータをデータベースに書き込み、可視化する手段としてMetabaseを使うという流れです。

事前

Raspberry Pi 4をデスクトップ環境で使える状態

手順

オムロン環境センサの認識

手順

MariaDBのインストール

手順

Metabaseのインストール

手順

データ取得のPythonのプログラムを用意

PythonもDBもサーバ類もRaspberry Pi なら比較的に容易です。 非力な性能とはいえ、計測用途やデータベース保持はRaspberry Pi 4の性能でも充分ですね。

求める精度や規模感、運用方法によっては、業務でも役割を果たせると感じます。

可視化ツール

取得したデータを可視化するのに、BIツールを使う方法があります。 サービスとして知っていたのは、AmbientとMetabaseでしたが、他にもたくさんありますね。

BIツールと一口にいっても、ダッシュボード系だったり、分析に特化したもの、有料/無料とたくさんサービスはあります。

GoogleスプレッドシートもスクリプトGoogle Apps Script(GAS)が動くからBIツールですね。

BI(Business Intelligence)ツール 事業上の意思決定に用いられる知見およびそのためのデータ収集・分析・配布を意味する BIツール - Wikipedia

はてな? はてな?
2008年前後くらいだったでしょうか。BIツール・・・(製品名を忘れた)・・・は全然盛り上がっていなかったんです。展示会やら説明会やら出た記憶があります。全くサッパリだった。野暮ったさは否めないけど、同じようなUIで、フィルタリングが多かった気がする。今は流行っていますよね? だからBIツール、なんか懐かしい響きだ。

Metabaseで可視化

localhostのMetabase

BIツールの中でも「Metabase」に興味が沸きました。OSSということと、ローカルホスト環境で構築するなら無償で利用出来る点も、個人でトライするにはとても助かります。

ということで、ローカルに用意するMetabaseをインストールしました。Metabaseは、データベースと連携して使うため、今回は直接Raspberry Pi OS上にインストールしました。

Dockerで構築することもできます。Dcokerの方が後から再構築したり、やり直すのにも便利なんですけど、データベースとの連携でポート周りが分かりにくいため次回にします。

手順1:オムロンの環境センサを認識させる

ここから使えるまでの手順です。

はじめに、USB型の環境センサをRaspberry Pi で認識させます。ドライバを充てないとなりません。

早速、USBを繋いでlsusbをしてみると、ベンダーID:0590とプロダクトID:004dでした。

USBデバイスをポートttyUSB*と認識しないと使えません。USBシリアルは、ftdi_sioというドライバを利用します。

次のように追加します。

sudo modprobe ftdi_sio
sudo sh -c "echo 0590 00d4 > /sys/bus/usb-serial/drivers/ftdi_sio/new_id"

このままだと、再起動の度に毎回実行しないとならないためルールに記述します。

sudo nano /etc/udev/rules.d/80-2jcie-bu01-usb.rules

.rulesに追記する内容

ACTION=="add", \
KERNEL=="ttyUSB0", ATTRS{idVendor}=="0590", ATTRS{idProduct}=="00d4", \
RUN+="/sbin/modprobe ftdi_sio", RUN+="/bin/sh -c 'echo 0590 00d4 > /sys/bus/usb-serial/drivers/ftdi_sio/new_id'"

すぐに適用させるのにリロードします。

sudo udevadm control --reload

ttyUSB0にシリアルデバイスコンバーターとして認識されました。これでデータが取れます。dmesg | grep USBで確認すると、ttyUSB0などと出ているハズです。

シリアル通信と言っても、Raspberry Pi側のシリアルI/Fは何も設定しなくて大丈夫です。USBシリアルになります。

これでデータの取得は元より、抜き差ししたり再起動しても認識します。

Raspberry PiのケースでUSBアダプターを介しているせいか、どうもルールが適用されていない。再起動後に認識されませんでした。仕方ないので、今回はrc.localへ記述して対処しました。

sudo nano /etc/rc.local

# exit0前に挿入

# Load_ftdi_driver
modprobe ftdi-sio
# new_id
echo 0590 00d4 > /sys/bus/usb-serial/drivers/ftdi_sio/new_id

これでデータをUSBシリアル通信で取得できるようになりました。

手順2、3:MariaDBとMetabaseのインストール

MariaDBとMetabaseのインストール方法は別にまとめてあります。これが済んでいる状態で、プログラムを用意し実行しました。

同じようにトライしてみたい方は、まずこの2つの記事をご覧いただきながらセットアップし、今回の記事のPythonプログラムを実行してください。

Raspberry Pi 4が1台あればOKです。

■Pi 5は8GBモデルがオススメ

手順4:データ取得からデータベースに値を書き込むプログラム

元気玉の出番です。色々なサイトを参考に、Pythonプログラムを書きました。最終的にMariaDBとMetabaseを使い可視化することができたので、終わってみればそこまで難しいことはしていません。

結局一番苦労したのは、データベースへの書き込む部分です。単純にPythonでデータベースを操る以前に、データベース操作に不慣れなだけということになりました。非エンジニアにはツラい。

基本のサンプルを試す

オムロンから、Raspberry Pi 用にサンプルプログラムがgithubで公開されています。先ずはデータが検出できているか確認しました。

すでに5年前のコードため、今のOSバージョンで動くか心配でしたが、どうやら大丈夫でした。

GitHub - omron-devhub/2jciebu-usb-raspberrypi

git cloneなどでダウンロードして、.pyファイルを管理者権限sudoで実行すれば、ターミナルにエンドレスで毎秒毎にデータを取得し表示してくれます。

git clone https://github.com/omron-devhub/2jciebu-usb-raspberrypi

確認するだけなら、これだけでも面白いです。

この公式サンプルを元にして、データベースへ書き込みする処理を足すことにしました。

PythonでMySQLを操作するプラグインソフトのインストール

なんだか他にもPyMySQLや、import mariadbでイケるとかありましたが、以前から多くあるmysql-connector-pythonにしました。Raspberry Pi 4のbullseyeでも動作しました。

sudo pip3 install mysql-connector-python

Pythonで操作するなら、MariaDBをインストールする時に一緒にインストールしておきましょう。

これでPythonからSQL文を渡せます。

公式サンプルに追加修正したPythonプログラム

実際に動いたプログラムは、オムロン公式のサンプルプログラムにいくつか追加することで実現できました。

追記して動いたプログラムはこちら

omron_getdata_insert_db.pyダウンロード

import serial
import time
from datetime import datetime
import sys
import mysql.connector

# LED display rule. Normal Off.
DISPLAY_RULE_NORMALLY_OFF = 0

# LED display rule. Normal On.
DISPLAY_RULE_NORMALLY_ON = 1

def s16(value):
    return -(value & 0x8000) | (value & 0x7fff)

def calc_crc(buf, length):
    """
    CRC-16 calculation.

    """
    crc = 0xFFFF
    for i in range(length):
        crc = crc ^ buf[i]
        for i in range(8):
            carrayFlag = crc & 1
            crc = crc >> 1
            if (carrayFlag == 1):
                crc = crc ^ 0xA001
    crcH = crc >> 8
    crcL = crc & 0x00FF
    return (bytearray([crcL, crcH]))

def print_latest_data(data):
    """
    print measured latest value.
    """
    time_measured = datetime.now().strftime("%Y/%m/%d %H:%M:%S")
    temperature = str( s16(int(hex(data[9]) + '{:02x}'.format(data[8], 'x'), 16)) / 100)
    relative_humidity = str(int(hex(data[11]) + '{:02x}'.format(data[10], 'x'), 16) / 100)
    ambient_light = str(int(hex(data[13]) + '{:02x}'.format(data[12], 'x'), 16))
    barometric_pressure = str(int(hex(data[17]) + '{:02x}'.format(data[16], 'x') + '{:02x}'.format(data[15], 'x') + '{:02x}'.format(data[14], 'x'), 16) / 1000)
    sound_noise = str(int(hex(data[19]) + '{:02x}'.format(data[18], 'x'), 16) / 100)
    eTVOC = str(int(hex(data[21]) + '{:02x}'.format(data[20], 'x'), 16))
    eCO2 = str(int(hex(data[23]) + '{:02x}'.format(data[22], 'x'), 16))
    print("")
    print("日時:" + time_measured)
    print("温度:" + temperature)
    print("相対湿度:" + relative_humidity)
    print("環境光:" + ambient_light)
    print("気圧:" + barometric_pressure)
    print("騒音:" + sound_noise)
    print("eTVOC:" + eTVOC)
    print("eCO2:" + eCO2)

    #mysqlへ接続
    connection_SQL = mysql.connector.connect(
                user = 'root',
                password = '自分のパスワードを入れてください',
                host = 'localhost',
                port = 3306,
                database = 'metabase',
                autocommit = True
            )
    cursor_SQL = connection_SQL.cursor()

    # コネクションが切れた時に再接続してくれるよう設定
    connection_SQL.ping(reconnect=True)
    # 接続できているかどうか確認
    print(connection_SQL.is_connected())

    #DBに挿入
    try:
        sql = "INSERT INTO sensorLog (collect_datetime, temp, humid, light, press, noise, eTVOC, eCO2) VALUES (%s, %s, %s, %s, %s, %s, %s, %s)"
        cursor_SQL.execute(sql, (time_measured, temperature, relative_humidity, ambient_light, barometric_pressure, sound_noise, eTVOC, eCO2))
        # print(cursor_SQL.fetchone())
        # 全てのデータを出力
        cursor_SQL.execute("SELECT * FROM sensorLog ORDER BY id ASC")
        rows = cursor_SQL.fetchall()
        for row in rows:
            print(row)

    except mysql.connector.Error as e:
        print(e)
    finally:
        cursor_SQL.close()
        connection_SQL.close()

def now_utc_str():
    """
    Get now utc.
    """
    return datetime.utcnow().strftime("%Y-%m-%d %H:%M:%S")

if __name__ == '__main__':
    # Serial.
    ser = serial.Serial("/dev/ttyUSB0", 115200, serial.EIGHTBITS, serial.PARITY_NONE)

    try:
        # LED On. Color of Green.
        command = bytearray([0x52, 0x42, 0x0a, 0x00, 0x02, 0x11, 0x51, DISPLAY_RULE_NORMALLY_ON, 0x00, 0, 255, 0])
        command = command + calc_crc(command, len(command))
        ser.write(command)
        time.sleep(0.1)
        ret = ser.read(ser.inWaiting())

        num = 0
        while ser.isOpen():
            # Get Latest data Long.
            command = bytearray([0x52, 0x42, 0x05, 0x00, 0x01, 0x21, 0x50])
            command = command + calc_crc(command, len(command))
            tmp = ser.write(command)
            time.sleep(0.1)
            data = ser.read(ser.inWaiting())
            print_latest_data(data)
            time.sleep(1)
            if num >= 2:
              break
            else:
              print("num = " + str(num))
              num += 1
        print("End")

    except KeyboardInterrupt:
        # LED Off.
        command = bytearray([0x52, 0x42, 0x0a, 0x00, 0x02, 0x11, 0x51, DISPLAY_RULE_NORMALLY_OFF, 0x00, 0, 0, 0])
        command = command + calc_crc(command, len(command))
        ser.write(command)
        time.sleep(1)
        # script finish.
        sys.exit

当然ながら私はエンジニアではないため、例外はあまり考慮していませんし、より適切な書き方も分かりません。詳しい方はそれぞれ書き換えてください。

ターミナルで間違いに気が付くように、いくつかPrint文を挟んでいます。実行には必要ありません。

修正したポイント

すごく簡単に言うと、公式サンプルはprint_latest_data(data)関数で計算した値を代入しています。だから、そのままデータベースへ接続して書き込むようにしました。

    #mysqlへ接続
    connection_SQL = mysql.connector.connect(
                user = 'root',
                password = '自分のパスワードを入れてください',
                host = 'localhost',
                port = 3306,
                database = 'metabase',
                autocommit = True
            )
    cursor_SQL = connection_SQL.cursor()

    # コネクションが切れた時に再接続してくれるよう設定
    connection_SQL.ping(reconnect=True)
    # 接続できているかどうか確認
    print(connection_SQL.is_connected())

    #DBに挿入
    try:
        sql = "INSERT INTO sensorLog (collect_datetime, temp, humid, light, press, noise, eTVOC, eCO2) VALUES (%s, %s, %s, %s, %s, %s, %s, %s)"
        cursor_SQL.execute(sql, (time_measured, temperature, relative_humidity, ambient_light, barometric_pressure, sound_noise, eTVOC, eCO2))
        # print(cursor_SQL.fetchone())
        # 全てのデータを出力
        cursor_SQL.execute("SELECT * FROM sensorLog ORDER BY id ASC")
        rows = cursor_SQL.fetchall()
        for row in rows:
            print(row)

    except mysql.connector.Error as e:
        print(e)
    finally:
        cursor_SQL.close()
        connection_SQL.close()

非エンジニアとして、関数を跨ぐ変数の取り扱いが分からなかったからです。

はてな? はてな?
グローバルにすればいいのか?

合わせて、元のサンプルプログラムをなるべくそのまま使いたかったというのもあります。アレコレ変更すると、素人なので余計に分からなくなってハマりまることが多々あります。

基本としてはデータベースへ挿入する部分だけを追加したかったので、これでOKとしました。

最後に、関数を呼び出している部分がwhile文でエンドレスのため、指定回数計測させて抜けるようにしました。プログラムが分かる方は、While文の繰り返しを書き直してください。

        num = 0
        while ser.isOpen():
・・・
            if num >= 2:
              break
            else:
              print("num = " + str(num))
              num += 1
        print("End")

途中、わざとDBのデータをターミナルに表示しています。これも確認のためでした。コメントアウトするか削除してください。

        # 全てのデータを出力
        cursor_SQL.execute("SELECT * FROM sensorLog ORDER BY id ASC")
        rows = cursor_SQL.fetchall()
        for row in rows:
            print(row)

これでデータがデータベースに蓄積され、接続したMetabaseで確認できます。

計測した値をMetabaseで表示

追記して修正した部分は間違っている箇所もあると思われますが、結果はオーライでした。

実用させるには、もう少しプログラムでなんとかしたいところですが、データの計測をして閲覧するにはこれでも充分でした。

cronで1日1回の計測でもデータが溜まると興味深いですよね。

Rブラック Rブラック
毎日が暑い・・・。

記事の最下部に記載したWebサイトをリンクしておきました。一部分だけを参考にしましたが、どれも分かりやすかったです。

テーブルの作成で使ったCREATE文

今回の例で使うMariaDBのテーブル作成に使ったCREATE文です。変数の型は文字列で持っていた方が良さそうなので、varcharです。桁数は適当です。

create table sensorLog (id MEDIUMINT NOT NULL AUTO_INCREMENT, collect_datetime varchar(19),temp varchar(10),humid varchar(10),light varchar(10),press varchar(10),noise varchar(10),eTVOC varchar(10),eCO2 varchar(10),PRIMARY KEY(id));

INSERT INTO

テーブルとカラムを先に作ってから挿入したわけですが、ここのSQL文に変数を使うのが苦慮しました。

Pythonだと、%sがプレースホルダーになって、その後にタプル形式で変数を配置すればOKなことに気が付くのが時間かかりました。

cursor.execute('INSERT INTO テーブル名 (カラム名1,カラム名2,...)' VALUES (%s, %s, ...), (変数1, 変数2, ...)

他にも、テーブル作成時にidを自動採番したので、カラムや値にidを省略しても良かったのに、NULL値が入りエラーで難儀しました。

id MEDIUMINT NOT NULL AUTO_INCREMENTだったからNULLを入れてエラーになってた。

sql = "INSERT INTO sensorLog (collect_datetime, temp, humid, light, press, noise, eTVOC, eCO2) VALUES (%s, %s, %s, %s, %s, %s, %s, %s)"
cursor_SQL.execute(sql, (time_measured, temperature, relative_humidity, ambient_light, barometric_pressure, sound_noise, eTVOC, eCO2))

cursorやsqlの命名部分は読み替えてください。この例が一番分かりやすかったので使わせてもらいました。カラム名や変数名も参考サイトから拝借しています。まさに元気玉。

備忘録

非エンジニアとしてよく忘れるため、テーブル操作のSQL文を残しておきます。よく使ったのはこんなところでした。

use データベース名;

show full tables;

show columns from テーブル名;

select * from テーブル名;

テーブル削除
drop table テーブル名;

終了する
quit;

あと、cronで毎時取得してみた。実行権限を与えて、Python3のパスと共にcrontabへ記載します。

which python3
sudo chmod +x omron_get.py

crontab -e

#毎時で取得
0 * * * * /usr/bin/python3 /home/raspida/omron/omron_get.py

BLEは試していません

オムロンの環境センサは、BLEとして値を取得することもできます。

ただ、今回先に試したところ、何度か色んな場面でエラーになってうまく行かないことが続きました。

BLEで使うRaspberry Pi 4とbluepyで出るみたい。詳しくは分かりませんが、bullseyeに上がっていてもbulepyって古いからでしょうか。

次のサイトなどと同じでしたね。

Failed to connect to peripheral , addr type: random · Issue #495 · IanHarvey/bluepy · GitHub BluePy Frequent BLE Disconnects between Raspberry Pi4 and ESP32 - (Bluetooth) - Stack Overflow

前回もBLEを使って感じていましたけど、どうもBluetooth関連がRaspberry Pi 4になってから怪しい・・・気がします。

なので、今回はUSBシリアルで確実にデータを取得できました。

Python × MariaDB × Metabase

既存のファイルを使ってもいい

今回Pythonプログラムでデータベースを操り、得た値をMetabaseで綺麗に閲覧することができました。

今後はMetabaseの詳しい使い方を知りたいですね。Metabaseでクエリ文を書けば、思うようなグラフ表示もできるでしょう。マウス操作だけで、データを即座にフィルタリングできるのも面白い。

何よりもWebブラウザで綺麗に表示されることが嬉しい。あっ、無料のオープンソースなのも嬉しい。

一度連携してしまえば、CSVでも読込・書き出しができます。

データベースは覚えることも増えますが、自由度も上がるのでオススメです。

参考:

オムロン公式商品ページ 2JCIE-BU | 環境センサ(USB型) | オムロン電子部品サイト - Japan

基本的な動作で参考になりました。 オムロン環境センサ(2JCIE-BU)をラズパイで使ってみた。(1) - Qiita

正確性を重視するならこちらも。 オムロン環境センサUSB型&PCでデータを確実に取得する | デジカシ 環境センサ|センサからの室内環境データ収集

オムロン環境センサ入門 その1 | デジカシ

データベース関係で参考にしたサイト