🍣

Raspberry Pi + 2JCIE-BU01 + Grafanaでデータ取得・可視化してみる

2023/06/25に公開

今回使用したもの

・Raspberry Pi4 ModelB 4GB
ラズパイ本体。

・OMRON 2JCIE-BU01
今回使用したセンサー。
https://www.fa.omron.co.jp/products/family/3724/lineup.html

取得できるのは、温度、湿度、照度、気圧、騒音、3軸加速度、eTVOC、不快指数、熱中症警戒度、振動情報、と多くのデータが取得可能です(少し値段が張りますが)。

ラズパイのセンサーは、ググれば、基盤に接続するものがよく見つかります。これはUSB接続で使用できるので、お手軽に作れるのが良いところです。

・Grafana
OSSの可視化ツールです。
Grafana自体は、Web UIでメトリックス情報を可視化するだけの機能になります(だけど、見た目は良さそう)。

https://grafana.com/oss/grafana/

・InfluxDB
時系列データの蓄積、分析に特化された、OSSのDBです。今回はラズパイ1台だけで、それほど大量のデータは想定してませんが、IoTからのデータの扱いには向くものだそうで、試しに使用してみます。

https://www.influxdata.com/products/influxdb/

今回の環境と詳しいバージョンは、以下の通りです。

  • Raspberry Pi4 ModelB 4GB (Raspberry Pi OS bullseye)
  • OMRON 2JCIE-BU01(センサー)
  • Grafana v10.0.1
  • InfluxDB v2.7.1
  • Python 3.9.2

本当はGrafana、InfluxDBは、クラウドなどの安定した環境のサーバーに立て、ラズパイ自体はエッジの機能として使用するのが良いですが、今回は機材の関係でラズパイに全部入れてしまいます。

構築

ラズパイのセットアップ(NW周りの設定とか)は完了している前提とします。

2JCIE-BU01接続

公式のGitHubに、Pythonのサンプルあり、その説明書を参考にします
動作確認も同時にしてみます。

適当なディレクトリを作成し、git clone

$ mkdir omron_sensor
$ cd omron_sensor
$ git clone https://github.com/omron-devhub/2jciebu-usb-raspberrypi.git

これで、センサーが認識され、

$ sudo modprobe ftdi_sio
$ sudo chmod 777 /sys/bus/usb-serial/drivers/ftdi_sio/new_id
$ sudo echo 0590 00d4 > /sys/bus/usb-serial/drivers/ftdi_sio/new_id

root権限で実行してみます(昇格が必要)。

$ sudo python3 sample_2jciebu.py

できました。

Time measured:2023/06/25 10:57:18
Temperature:26.15
(略)

なお、ラズパイ本体が熱を持つため、センサーとの接続にはUSB延長ケーブルも使いました。壁面に、テープで固定しています。

InfluxDB構築

公式ドキュメントがちょっとややこしく、以下のページに、セットアップスクリプトが掲載されています。「Get InfluxDB構築」をクリックし、

https://www.influxdata.com/products/influxdb/

バージョンと適切なプラットフォームを選択すると、

スクリプトが表示されるので、後はその通りに実行。

自動起動させたいので、

sudo systemctl enable influxdb
sudo systemctl start influxdb

とし、サービスは立ち上げておきます。

ブラウザでポート8086にアクセスし、Web UIが表示されたらOK。
http://[ip address]:8086/

初回のみユーザー情報とUser Name、パスワード、Organization Name、Bucketの入力が促されますので、適切に入力しておきます。
BucketはInfluxDB独自の仕組みというか概念だと思いますが、おそらく、(Oracleで言えば)Databaseに近い概念だと思います。

Grafana構築

公式ドキュメントの通りに実行するだけです。
https://grafana.com/tutorials/install-grafana-on-raspberry-pi/#install-grafana

インストール。

wget -q -O - https://packages.grafana.com/gpg.key | sudo apt-key add -
echo "deb https://packages.grafana.com/oss/deb stable main" | sudo tee -a /etc/apt/sources.list.d/grafana.list

sudo apt-get update
sudo apt-get install -y grafana

InfluxDBと同じように、自動起動、サービス立ち上げ。

sudo systemctl enable grafana-server
sudo systemctl start grafana-server

デフォルトポートが3000で、以下にアクセスすると、ログイン画面がでてきます。
http://[ip address]:3000

admin/adminでログインし、初回のみパスワード変更が求められます。
GrafanaとInfluxDBの連携設定は、データを書き込むPythonコードができた後にするとします。

Pythonコード

get_dataディレクトリに以下のPythonコードを作成します。

  • get_2jciebu_data.py(センサーからデータを取得する関数)
  • write_sensor_data.py(本体:IndluxDBにデータを書込み)

前者のコードはOMRON公式サンプルを、今回用に、ライブラリ(のようなもの)として改変したものです。なお、公式リポジトリは、以下。

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

・get_2jciebu_data.py
センサーからデータを取得し、ただ返すだけの役割で、後述するwrite_sensor_data.pyから呼び出されます。
改変点としては、LEDを光らせるコードを削除し(必要ないし)、データを数値型にし、また、辞書型にして返すものにしています。
また、日時情報をretval["time_measured"] = datetime.utcnow()に変えた理由は、InfluxDBにはUTCの日時情報を渡す必要があるためです。
後は、ほぼそのまま流用。
データ取得の間隔は、とりあえず、10秒毎にしています。

なお、事前にinfluxdb-clientを入れておきます(pip install influxdb-client)。

get_2jciebu_data.py
import serial
import time
from datetime import datetime

"""
以下のOMRON公式コードを改変
https://github.com/omron-devhub/2jciebu-usb-raspberrypi
"""

# 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 get_data() -> dict:
    """
    return measured latest value.
    """
    ser = serial.Serial("/dev/ttyUSB0", 115200, serial.EIGHTBITS, serial.PARITY_NONE)
    
    # 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())

    retval = {}
    retval["time_measured"] = datetime.utcnow()
    retval["temperature"] = ( s16(int(hex(data[9]) + '{:02x}'.format(data[8], 'x'), 16)) / 100)
    retval["relative_humidity"] = (int(hex(data[11]) + '{:02x}'.format(data[10], 'x'), 16) / 100)
    retval["ambient_light"] = (int(hex(data[13]) + '{:02x}'.format(data[12], 'x'), 16))
    retval["barometric_pressure"] = (int(hex(data[17]) + '{:02x}'.format(data[16], 'x')
                                  + '{:02x}'.format(data[15], 'x') + '{:02x}'.format(data[14], 'x'), 16) / 1000)
    retval["sound_noise"] = (int(hex(data[19]) + '{:02x}'.format(data[18], 'x'), 16) / 100)
    retval["eTVOC"] = (int(hex(data[21]) + '{:02x}'.format(data[20], 'x'), 16))
    retval["eCO2"] = (int(hex(data[23]) + '{:02x}'.format(data[22], 'x'), 16))
    retval["discomfort_index"] = (int(hex(data[25]) + '{:02x}'.format(data[24], 'x'), 16) / 100)
    retval["heat_stroke"] = (s16(int(hex(data[27]) + '{:02x}'.format(data[26], 'x'), 16)) / 100)
    retval["vibration_information"] = (int(hex(data[28]), 16))
    retval["si_value"] = (int(hex(data[30]) + '{:02x}'.format(data[29], 'x'), 16) / 10)
    retval["pga"] = (int(hex(data[32]) + '{:02x}'.format(data[31], 'x'), 16) / 10)
    retval["seismic_intensity"] = (int(hex(data[34]) + '{:02x}'.format(data[33], 'x'), 16) / 1000)
    
    return retval

・write_sensor_data.py
本体部分。ループさせて、強制的に終了されるまで実行し続けます。
トークンは、InfluxDBのWeb UI画面から取得可能です。

write_sensor_data.py
import get_2jciebu_data
import influxdb_client, time, sys
from influxdb_client import Point
from influxdb_client.client.write_api import SYNCHRONOUS

token = "[トークン]"
org = "[Organization Name]"
url = "http://localhost:8086"
bucket="[bucket name]"


if __name__ == '__main__':
  write_client = influxdb_client.InfluxDBClient(url=url, token=token, org=org)

  write_api = write_client.write_api(write_options=SYNCHRONOUS)

  while True:
    data = get_2jciebu_data.get_data()

    point = (
      Point("2jciebu")
      .tag("location", "home")
      .time(time=data["time_measured"])
      .field("temperature", data["temperature"])
      .field("relative_humidity", data["relative_humidity"])
      .field("ambient_light", data["ambient_light"])
      .field("barometric_pressure", data["barometric_pressure"])
      .field("sound_noise", data["sound_noise"])
      .field("eTVOC", data["eTVOC"])
      .field("eCO2", data["eCO2"])
      .field("discomfort_index", data["discomfort_index"])
      .field("heat_stroke", data["heat_stroke"])
      .field("vibration_information", data["vibration_information"])
      .field("si_value", data["si_value"])
      .field("pga", data["pga"])
      .field("seismic_intensity", data["seismic_intensity"])
    )
  
    write_api.write(bucket=bucket, org="Home", record=point)
    time.sleep(10) # separate points by 10 secon

バックグラウンドで実行しておきたいので、以下のコマンドで実行。

sudo nohup python write_sensor_data.py > nohup.log & 

InfluxDBのWeb UI画面に入り、Data Explorerから、データを確認できます。
Data Explorer

InfluxDBのWeb画面でもデータは確認できそうですが、現状、ログインしないと確認できないのと(Grafanaだとログインなしの閲覧が可能)、ダッシュボードの機能としてはGrafanaのほうが良さそうかもです。

Grafanaに連携

Grafanaにログインし、Add your first data sourceから、

InfluxDBを選択

まだ、ベータ機能ということですが、Query Languageで「Flux」を選択

あとは、必要な情報を入力し、「Save & Test」。

Grafanaで表示

ダッシュボードにグラフを追加する時のクエリは、
InfluxDBのData Explorerの、Script Editorから取得できます。

ダッシュボードに追加すると、こんな感じ。

とりあえず、基本的な部分は一通りはできたでしょうか。
さらに、tailscaleも使って、遠隔の場所をモニタリングすることもできそうです。

以上。

Discussion