はじめに
Inkbirdの温湿度計IBS-TH1やIBS-TH2は、専用のスマホアプリを使ってBluetooth経由で取得したデータを参照することができます。
スマホアプリだとグラフの読み込みに時間がかかることやアプリをインストールした端末からしかデータを参照できない不便さがあるので、PrometheusとGrafanaを使ってWebブラウザで参照できるように設定してみました。
参考情報
複数のIBS-THシリーズからBluetooth経由でデータを取得するコードは、@c60evaporatorさんの記事 家の中のセンサデータをRaspberryPiで取得しまくり、スーパーIoTハウスを実現 をもとにしています。元記事にはない IBS-TH2やPrometheus exporterの処理を追加し、今回使用しないセンサー向けの処理は省きました。
- 家の中のセンサデータをRaspberryPiで取得しまくり、スーパーIoTハウスを実現
- インフラ・サービス監視ツールの新顔「Prometheus」入門
- PrometheusとGrafanaを組み合わせて監視用ダッシュボードを作る
- 独自メトリクス取るなら Textfile Collector 独自メトリクス取るなら Textfile Collector
- Inkbird IBS-TH1 のログをbluetoothで取得してAmbientに送信し可視化する
- Inkbird IBS-TH1の値をRaspberryPiでロギング
- PythonでバイナリをあつかうためのTips
- Prometheusのストレージのドキュメントをさらっと読んでみて、retentionの設定もしてみる
- Prometheus -> InfluxDB -> Grafana
- 【Prometheus】弱点を克服。Prometheusのデータを長期保管出来るようにする方法
- Raspberry Piでセンサー情報グラフ化~その1 InfluxDBとGrafanaをインストール
準備するもの
センサーはIBS-TH1かIBS-TH2のいずれかがあれば今回の記事の内容を試すことができます。同じセンサーが複数個あっても設定可能です。
- Raspberry Pi4 Model B
-
Inkbird IBS-TH2 Bluetooth温度計 湿度計
- 同じ型番で温度計のみのものがあります。購入間違いに注意。(私は間違って温度計のみのものを購入したため、改めて購入しなおしました。)
- Inkbird IBS-TH1 温湿度計 外部プローブ付き
ゴール
スマホアプリで見ている情報を、Grafanaで見られるようにします。
3台のセンサーをそれぞれ以下の場所に設置し、データを取得しています。
IBS-TH1 : 外部プローブをワインセラー庫内に入れて温湿度を計測
IBS-TH2 (温湿度計) : ワインセラーを置いている収納スペースの温湿度を計測
IBS-TH2 (温度計のみ) : 書斎の室温を計測
環境構築
前提条件
Raspbian OSを使用。
PRETTY_NAME="Raspbian GNU/Linux 10 (buster)"
NAME="Raspbian GNU/Linux"
VERSION_ID="10"
VERSION="10 (buster)"
VERSION_CODENAME=buster
ID=raspbian
ID_LIKE=debian
HOME_URL="http://www.raspbian.org/"
SUPPORT_URL="http://www.raspbian.org/RaspbianForums"
BUG_REPORT_URL="http://www.raspbian.org/RaspbianBugs"
Pythonのコードを実行するディレクトリは、/home/pi/raspberry-inkbird_ibsth/
(別のディレクトリにコードや設定ファイルを配置する場合は、後述のcronや設定内のパスも変更が必要です。)
最終的にはこのようなディレクトリ構成となります。
Sensorディレクトリ以下のファイルやディレクトリは、Pythonスクリプトにより自動生成されます。
$ tree /home/pi/raspberry-inkbird_ibsth/
/home/pi/raspberry-inkbird_ibsth/
├── config.ini
├── Data
│ ├── inkbird-ibs-th.prom
│ └── Sensor
│ ├── Inkbird_IBSTH1_1
│ │ └── 2021
│ │ └── Inkbird_IBSTH1_1_202105.csv
│ ├── Inkbird_IBSTH2_1
│ │ └── 2021
│ │ └── Inkbird_IBSTH2_1_202105.csv
│ └── Inkbird_IBSTH2_2
│ └── 2021
│ └── Inkbird_IBSTH2_2_202105.csv
├── DeviceList.csv
├── inkbird_ibsth1.py
├── Log
│ ├── inkbird-ibs-th1.prom
│ └── Sensor
│ ├── sensorlog_210501.log
│ ├── sensorlog_210502.log
│ └── sensorlog_210503.log
└── sensors_to_prometheus.py
Python3や必要なライブラリのインストール
$ sudo apt-get update && sudo apt-get upgrade
$ sudo apt-get install python3-dev python3-pip libglib2.0-dev bluez libatlas-base-dev
$ pip3 install bluepy pandas prometheus-client
crontabの有効化
Raspberry Pi 4ではcrontabがデフォルトでは有効になっていないため、設定を変更します。
- #cron.* /var/log/cron.log
+ cron.* /var/log/cron.log
設定を変更後、rsyslogを再起動します。
$ sudo /etc/init.d/rsyslog restart
- #EXTRA_OPTS=''
+ EXTRA_OPTS='-L 15'
設定を変更後、cronを再起動します。
$ sudo /etc/init.d/cron restart
$ sudo /bin/systemctl enable cron.service
Pythonスクリプトの設置
- @c60evaporatorさんの記事 家の中のセンサデータをRaspberryPiで取得しまくり、スーパーIoTハウスを実現 をもとに、IBS-TH2の処理やPrometheus exporterの処理を追加しました。
ディレクトリの作成
$ cd /home/pi
$ mkdir -p ./raspberry-inkbird_ibsth/{Data/Sensor,Log/Sensor}
設定ファイル
- DeviceList.csv
センサーごとの情報を記載します。ここでは3台のセンサーを設定しています。
DeviceName,SensorType,MacAddress,Timeout,Retry,Offset_Temp,Offset_Humid,API_URL,Token
Inkbird_IBSTH1_1,Inkbird_IBSTH1,10:08:xx:xx:xx:xx,0,2,0,0,,
Inkbird_IBSTH2_1,Inkbird_IBSTH2,49:42:xx:xx:xx:xx,0,2,0,0,,
Inkbird_IBSTH2_2,Inkbird_IBSTH2,49:42:xx:xx:xx:xx,0,2,0,0,,
カラムの仕様は、家の中のセンサデータをRaspberryPiで取得しまくり、スーパーIoTハウスを実現 の 設定ファイルの項目を参照。
DeviceName:デバイス名を管理、同種類のセンサが複数あるときの識別用
SensorType:センサの種類。この値に応じ実行するセンサデータ取得クラスを分ける
MacAddress:センサのMACアドレス
Timeout:スキャン時のタイムアウト値(ブロードキャストモードのセンサのみ、推奨値5前後)
Retry:最大再実行回数(詳細、推奨値2~3)
Offset_Temp:温度オフセット値(現状未使用)
Offset_Humid:湿度オフセット値(現状未使用)
API_URL:APIのURL(Nature Remoのみ使用)
Token:アクセストークン(Nature Remoのみ使用)
センサーのMACアドレスは、Inkbird IBS-TH1の値をRaspberryPiでロギングの解説もしくは、Inkbirdのスマホアプリから確認できます。
- config.ini
ログやCSVの出力先を記載します。CSVデータは今回の記事内では使用しませんが、取得したデータのログとして残しています。
[Path]
CSVOutput = /home/pi/raspberry-inkbird_ibsth/Data/Sensor
LogOutput = /home/pi/raspberry-inkbird_ibsth/Log/Sensor
PromOutPut = /home/pi/raspberry-inkbird_ibsth/Data/inkbird-ibs-th.prom
CSVOutput : センサーごとに取得したデータをCSVで記録したもの
LogOutput : Pythonスクリプトの動作ログ
PromOutPut : Prometheus node exporterの出力先
Pythonスクリプト本体
- characteristicの1~2byteが温度、3~4byteが湿度、それ以降が不明。(バッテリー残量が取得できると思われるが、それらしき値がとれず。AWS Amplify で自宅の温度や湿度を可視化する IoT アプリ開発にチャレンジしてみよう ! の記事ではnode.jsでバッテリー残量が取得できているのでPythonでなにか方法はありそうな気がします。)
# coding=UTF-8
from bluepy import btle
import struct
#Inkbird IBS-TH1データ取得クラス
class GetIBSTH1Data():
def get_ibsth1_data(self, macaddr, sensortype):
#デバイスに接続
peripheral = btle.Peripheral(macaddr)
#IBS-TH2のとき
if sensortype == 'Inkbird_IBSTH2':
characteristic = peripheral.readCharacteristic(0x24)
return self._decodeSensorData_th2(characteristic)
#IBS-TH1のとき
elif sensortype == 'Inkbird_IBSTH1':
characteristic = peripheral.readCharacteristic(0x28)
return self._decodeSensorData_th1(characteristic)
else:
return None
#IBS-TH2
def _decodeSensorData_th2(self, valueBinary):
(temp, humid, unknown1, unknown2, unknown3) = struct.unpack('<hhBBB', valueBinary)
sensorValue = {
'SensorType': 'Inkbird_IBSTH2',
'Temperature': float(temp) / 100,
'Humidity': float(humid) / 100,
'unknown1': unknown1,
'unknown2': unknown2,
'unknown3': unknown3,
}
return sensorValue
#IBS-TH1
def _decodeSensorData_th1(self, valueBinary):
(temp, humid, unknown1, unknown2, unknown3) = struct.unpack('<hhBBB', valueBinary)
sensorValue = {
'SensorType': 'Inkbird_IBSTH1',
'Temperature': temp / 100,
'Humidity': humid / 100,
'unknown1': unknown1,
'unknown2': unknown2,
'unknown3': unknown3,
}
return sensorValue
- sensors_to_prometheus.py
#!/usr/bin/env python3
# -*- coding: UTF-8 -*-
from bluepy import btle
from inkbird_ibsth1 import GetIBSTH1Data
from datetime import datetime, timedelta
from prometheus_client import Counter, Gauge, write_to_textfile, REGISTRY
import os
import csv
import configparser
import pandas as pd
import requests
import logging
import subprocess
import time
#グローバル変数
global masterdate
######Inkbird IBS-TH1のデータ取得######
def getdata_ibsth1(device):
#値が得られないとき、最大device.Retry回スキャンを繰り返す
for i in range(device.Retry):
try:
sensorValue = GetIBSTH1Data().get_ibsth1_data(device.MacAddress, device.SensorType)
#エラー出たらログ出力
except:
logging.warning(f'retry to get data [loop{str(i)}, date{str(masterdate)}, device{device.DeviceName}]')
sensorValue = None
continue
else:
break
if sensorValue is not None:
#POSTするデータ
data = {
'DeviceName': device.DeviceName,
'Date_Master': str(masterdate),
'Date': str(datetime.today()),
'Temperature': str(sensorValue['Temperature']),
'Humidity': str(sensorValue['Humidity']),
}
return data
#値取得できていなかったら、ログ出力してBluetoothアダプタ再起動
else:
logging.error(f'cannot get data [loop{str(device.Retry)}, date{str(masterdate)}, device{device.DeviceName}]')
restart_hci0(device.DeviceName)
return None
######データのCSV出力######
def output_csv(data, csvpath):
dvname = data['DeviceName']
monthstr = masterdate.strftime('%Y%m')
#出力先フォルダ名
outdir = f'{csvpath}/{dvname}/{masterdate.year}'
#出力先フォルダが存在しないとき、新規作成
os.makedirs(outdir, exist_ok=True)
#出力ファイルのパス
outpath = f'{outdir}/{dvname}_{monthstr}.csv'
if not os.path.exists(outpath):
with open(outpath, 'w') as f:
writer = csv.DictWriter(f, data.keys())
writer.writeheader()
writer.writerow(data)
#出力ファイル存在するとき、1行追加
else:
with open(outpath, 'a') as f:
writer = csv.DictWriter(f, data.keys())
writer.writerow(data)
######温度と湿度データを.promファイルに出力######
def output_prometheus_collector(data):
# メトリック名を指定
g_temp = Gauge(data['DeviceName'] + '_temp', 'Gauge')
g_humid = Gauge(data['DeviceName'] + '_humid','Gauge')
while True:
g_temp.set(data['Temperature'])
g_humid.set(data['Humidity'])
# promファイルに出力
write_to_textfile(cfg['Path']['PromOutPut'], REGISTRY)
break
######Bluetoothアダプタ再起動######
def restart_hci0(devicename):
passwd = 'RaspberryPiパスワードを入力'
subprocess.run(('sudo','-S','hciconfig','hci0','down'), input=passwd, check=True)
subprocess.run(('sudo','-S','hciconfig','hci0','up'), input=passwd, check=True)
logging.error(f'restart bluetooth adapter [date{str(masterdate)}, device{devicename}]')
######メイン######
if __name__ == '__main__':
#開始時刻を取得
startdate = datetime.today()
#開始時刻を分単位で丸める
masterdate = startdate.replace(second=0, microsecond=0)
if startdate.second >= 30:
masterdate += timedelta(minutes=1)
#設定ファイルとデバイスリスト読込
cfg = configparser.ConfigParser()
cfg.read('./config.ini', encoding='utf-8')
df_devicelist = pd.read_csv('./DeviceList.csv')
#全センサ数とデータ取得成功数
sensor_num = len(df_devicelist)
success_num = 0
#ログの初期化
logname = f"/sensorlog_{str(masterdate.strftime('%y%m%d'))}.log"
logging.basicConfig(filename=cfg['Path']['LogOutput'] + logname, level=logging.INFO)
######デバイスごとにデータ取得######
for device in df_devicelist.itertuples():
#Inkbird IBS-TH1
if device.SensorType in ['Inkbird_IBSTH2','Inkbird_IBSTH1']:
data = getdata_ibsth1(device)
#上記以外
else:
data = None
#データが存在するとき、CSV、Prometheusに出力
if data is not None:
# Inkbird
if device.SensorType in ['Inkbird_IBSTH2','Inkbird_IBSTH1']:
# Prometheus textfile collector出力
output_prometheus_collector(data)
#CSV出力
output_csv(data, cfg['Path']['CSVOutput'])
#成功数プラス
success_num+=1
#処理終了をログ出力
logging.info(f'[masterdate{str(masterdate)} startdate{str(startdate)} enddate{str(datetime.today())} success{str(success_num)}/{str(sensor_num)}]')
crontabの設定
piユーザーのcrontabに以下の設定を追加します。
毎分スクリプトを実行し、config.iniに指定したファイルにログなどを出力します。
正常に動作しない場合、/var/log/syslogを確認します。
* * * * * cd /home/pi/raspberry-inkbird_ibsth; /usr/bin/python3 /home/pi/raspberry-inkbird_ibsth/sensors_to_prometheus.py >/dev/null 2>&1
Prometheusのインストールと設定
PrometheusはOSのパッケージマネージャ経由でインストールします。
node_exporterのインストールと設定
Node ExporterにTextfile Collectorを設定し、指定したディレクトリにある.promファイル内の値をPrometheusが取得できるようにします。
prometheus-node-exporterをインストール。
$ sudo apt-get install prometheus-node-exporter
node_exporterを実行するprometheusユーザーを追加。
$ sudo useradd -U -s /sbin/nologin -M -d / prometheus
prometheus-node-exporterの起動を確認。
$ systemctl status prometheus-node-exporter
● prometheus-node-exporter.service - node_exporter for Prometheus
Loaded: loaded (/etc/systemd/system/prometheus-node-exporter.service; enabled; vendor preset: enabled)
Active: active (running) since Sat 2021-04-03 22:55:06 JST; 4 weeks 2 days ago
Main PID: 5305 (prometheus-node)
Tasks: 16 (limit: 4915)
CGroup: /system.slice/prometheus-node-exporter.service
systemd経由でnode_exporterを管理できるよう、/etc/systemd/system/prometheus-node-exporter.service
として以下の内容のファイルを作成します。ここで、prometheus-node-exporterの起動オプションとして --collector.textfile.directory の値に前述の前提条件に記載した実行ディレクトリを指定しています。
[Unit]
Description=node_exporter for Prometheus
[Service]
Restart=always
User=prometheus
ExecStart=/usr/bin/prometheus-node-exporter --collector.textfile.directory /home/pi/raspberry-inkbird_ibsth/Data
ExecReload=/bin/kill -HUP $MAINPID
TimeoutStopSec=20s
SendSIGKILL=no
[Install]
WantedBy=multi-user.target
systemdの設定を再読み込みする。
$ sudo systemctl daemon-reload
Prometheus Serverのインストールと設定
Prometheusをインストールします。
$ sudo apt-get install prometheus
node_exporter同様にsystemdでprometheusを管理できるよう/etc/systemd/system/prometheus.service
として以下の内容のファイルを作成します。
--storage.tsdb.retention.time=1y
取得したデータの保持期間を設定しています。デフォルトは15d(15日間)です。データ容量とディスクサイズ、必要な保持期間とのバランスで設定します。
後述の時系列データベース InfluxDB にデータを保存するため、ここでは15日に設定します。
[Unit]
Description=Prometheus - Monitoring system and time series database
[Service]
Restart=always
User=prometheus
ExecStart=/usr/bin/prometheus --config.file=/etc/prometheus/prometheus.yml --storage.tsdb.path=/var/lib/prometheus/metrics --storage.tsdb.retention.time=15d
ExecReload=/bin/kill -HUP $MAINPID
TimeoutStopSec=20s
SendSIGKILL=no
[Install]
WantedBy=multi-user.target
収集したデータを格納するディレクトリを--storage.tsdb.pathで指定しています。このディレクトリを作成します。
$ sudo mkdir -p /var/lib/prometheus
$ sudo chown prometheus:prometheus /var/lib/prometheus
systemdの設定を再読み込みする。
$ sudo systemctl daemon-reload
Prometheus Serverの設定はprometheus.ymlに定義します。ここでは、Prometheusがexporterに対しデータを取りに行く間隔scrape_interval
を1m(1分)に設定しています。
global:
scrape_interval: 1m
evaluation_interval: 1m
# Alertmanager configuration
alerting:
alertmanagers:
- static_configs:
- targets: ['localhost:9093']
scrape_configs:
- job_name: 'prometheus'
static_configs:
- targets: ['localhost:9090']
- job_name: node
static_configs:
- targets: ['localhost:9100']
OS起動時にPrometheus Serverが自動起動するようにします。
sudo systemctl enable prometheus.service
Prometheus Serverを起動します。
sudo systemctl start prometheus.service
WebブラウザからPrometheusコンソールにアクセスする
Prometheus Serverを起動後、Webブラウザで http://(Raspberry Pi4のIPアドレス):9090/ にアクセスすると、コンソールが表示されます。
inkbird-ibs-th.promにPrometheus exporterのデータが正しく出力されていれば、-insert metric at cursor-
のプルダウンメニューにDeviceList.csvで設定したデバイス名が表示されます。
どれかひとつを選択しExecute
をクリック->Graph
タブをクリックすると、センサーから取得した値をもとにグラフが描画されます。
Grafanaのインストールと設定
Grafanaのインストール
Grafanaは、公式サイトのInstall on Debian or Ubuntuに従ってインストールします。
$ curl https://packages.grafana.com/gpg.key | sudo apt-key add -
$ echo "deb https://packages.grafana.com/oss/deb stable main" | sudo tee /etc/apt/sources.list.d/grafana.list
$ sudo apt-get install apt-transport-https
$ sudo apt-get update
$ sudo apt-get install grafana
systemdの設定を再読み込みする。
$ sudo /bin/systemctl daemon-reload
OS起動時にGrafanaが自動起動するようにします。
$ sudo /bin/systemctl enable grafana-server
$ Grafanaを起動します。
$ sudo /bin/systemctl start grafana-server
WebブラウザからGrafanaコンソールにアクセスする
Grafanaを起動後、Webブラウザで http://(Raspberry Pi4のIPアドレス):3000/ にアクセスすると、コンソールが表示されます。
初回は、Username, Passwordともadmin
でログインできます。ログイン後、パスワードを変更します。
Grafanaの設定
データソースを指定
PrometheusのデータをGrafanaで読み込むためのデータソースを指定します。
左サイドバーの歯車アイコンからData Sources
をクリックします。
Data Sourcesのタブ内にあるAdd data source
をクリックし、表示された時系列データベース一覧からPrometheus
をクリックします。
Nameに適当なデータソース名を入力します。URLは、PrometheusのWebコンソールにアクセスした際のURLを入力します。その他の項目はデフォルトのままとし、画面下部のSave & Test
をクリックします。Data source is working
と表示されれば成功です。
ダッシュボードを作成
様々なグラフを管理するダッシュボードを追加します。
左サイドバーの+アイコンからDashboard
をクリックします。
QueryタブのMetrics
プルダウンからInkbird
を選択し、展開された一覧からパネルに表示するデータを選択します。
グラフの見た目や追加表示する項目、凡例などは右側のPanel
タブ内の項目で設定可能です。
今回は、温度と湿度のデータを取得したので、グラフのY軸に設定する単位はAxes
のLeft Y
にあるUnit
で設定します。
それぞれのグラフをダッシュボードに追加すると、以下のようになります。
グラフの設定
Panel
タブ内の設定です。
InfluxDBのインストールと設定
センサーから取得したデータを長期保存するためデータをリモートストレージへ転送し、Grafanaはリモートストレージをデータストアとして使用するように設定を変更します。
InfluxDBのインストール
InfluxDBのリポジトリを登録します。
$ wget -qO- https://repos.influxdata.com/influxdb.key | sudo apt-key add -
$ echo "deb https://repos.influxdata.com/debian $(lsb_release -cs) stable" | sudo tee /etc/apt/sources.list.d/influxdb.list
InfluxDBをインストールします。
$ sudo apt update && sudo apt install -y influxdb
OS起動時にサービスが自動起動するように設定します。
$ sudo systemctl unmask influxdb.service
$ sudo systemctl start influxdb
$ sudo systemctl enable influxdb.service
IncludDBの設定
センサーのデータを投入するデータベースを作成します。
$ influx
Connected to http://localhost:8086 version 1.8.6
InfluxDB shell version: 1.8.6
> CREATE DATABASE prometheus
データベースが作成されたことが確認できます。
> show databases;
name: databases
name
----
_internal
prometheus
prometheus.ymlをに remote_read
とremote_write
を追記します。
global:
scrape_interval: 1m
evaluation_interval: 1m
# Alertmanager configuration
alerting:
alertmanagers:
- static_configs:
- targets: ['localhost:9093']
scrape_configs:
- job_name: 'prometheus'
static_configs:
- targets: ['localhost:9090']
- job_name: node
static_configs:
- targets: ['localhost:9100']
remote_write:
- url: "http://localhost:8086/api/v1/prom/write?db=prometheus"
remote_read:
- url: "http://localhost:8086/api/v1/prom/read?db=prometheus"
サービスを再起動します。
$ sudo systemctl daemon-reload
$ sudo systemctl reload prometheus.service
データベースを確認
$ influx
Connected to http://localhost:8086 version 1.8.6
InfluxDB shell version: 1.8.6
> use prometheus
Using database prometheus
> show MEASUREMENTS
name: measurements
name
----
Inkbird_IBSTH1_1_humid
Inkbird_IBSTH1_1_temp
Inkbird_IBSTH2_1_humid
Inkbird_IBSTH2_1_temp
Inkbird_IBSTH2_2_humid
Inkbird_IBSTH2_2_temp
(後略)
毎分データが登録されているので、Prometheusを再起動した数分後にはこのようなデータが確認できます。
> select * from Inkbird_IBSTH1_1_humid limit 5;
name: Inkbird_IBSTH1_1_humid
time __name__ instance job value
---- -------- -------- --- -----
1622648491459000000 Inkbird_IBSTH1_1_humid localhost:9100 node 62.87
1622648551459000000 Inkbird_IBSTH1_1_humid localhost:9100 node 62.68
1622648611459000000 Inkbird_IBSTH1_1_humid localhost:9100 node 62.28
1622648671459000000 Inkbird_IBSTH1_1_humid localhost:9100 node 61.98
1622648731459000000 Inkbird_IBSTH1_1_humid localhost:9100 node 61.58
Grafanaの設定
データソースを指定
GrafanaのデータソースにInfluxDBを追加します。
Nameに適当なデータソース名を入力します。 URLはhttp://localhost:8086
を指定します。
Databaseは、先ほど作成した prometheus
を指定します。 画面下部のSave & Testをクリックします。Data source is workingと表示されれば成功です。
新たにダッシュボードを作成しパネルを追加、もしくは、Prometheusをデータソースとして作成したダッシュボードのパネルを編集します。
QueryタブのデーターソースプルダウンからInfluxDBを選択し、FROMプルダウンからパネルに表示させるデータを選択します。1分毎のデータなので、GROUP BYにはtime(1m)を選択します。