～「SpeeDBee Hive」でBluetooth対応のセンサーコレクタを開発する(INKBIRD温湿度計)　～

# 概要
- この記事では、「SpeeDBee Hive」を使用して、
INKBIRD社の温湿度計(IBS-TH2)からBluetooth通信を用いた温湿度のデータ収集（センサーコレクタ開発）・可視化（グラフツール利用）の方法について解説します。
- 「SpeeDBee Hive」のPythonカスタムコレクタを用いたデータ収集方法の解説となり、他のBluetooth通信対応のセンサーなどを利用したデータ収集プログラム開発の参考になります。
- [前回の記事"「SpeeDBee Hive」でSwitchBot温湿度計からデータを取得する(Bluetooth通信)"](https://qiita.com/ss_nagahari/items/d26ad5e20a0fcd38a49a)のサンプルプログラムを修正し、INKBIRD社の温湿度計よりデータ収集を行います。


# はじめに
「SpeeDBee Hive」は多様なデータを取り込み、分析、制御、可視化をGUIで簡単に実現する実用的IoTミドルウェア、兼アプリケーションプラットフォームです。
[ソルティスターのHP](https://www.saltyster.com/speedbee-hive-trial/)から評価版をダウンロードすることが可能です。

本記事では[前回の記事"「SpeeDBee Hive」でSwitchBot温湿度計からデータを取得する(Bluetooth通信)"](https://qiita.com/ss_nagahari/items/d26ad5e20a0fcd38a49a)のサンプルプログラムを応用して、INKBIRD社の温湿度計からデータ収集を行う「SpeeDBee Hive」のカスタムコレクタを作成します。

主にサンプルプログラムの修正箇所について解説します。

センサーやSpeeDBee Hive、カスタムコレクタ等の各関係は下図のような構成となっています。
![関係図3.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2627884/4d1e9fdd-0b69-5891-16a1-19e01d468063.png)

「SpeeDBee Hive」のインストールやカスタムコレクタ、SwitchBot 温湿度計用のサンプルプログラムの解説については[前回の記事](https://qiita.com/ss_nagahari/items/d26ad5e20a0fcd38a49a)をご参照ください

INKBIRD社の温湿度計からのテータ収集に対応したサンプルプログラムを本記事の最後、[まとめ](#まとめ)に貼り付けますので、ご活用ください。

# 環境
- Raspberry Pi 3 Model B
  OS：Raspbian GNU/Linux 11 (bullseye)
- INKBIRD 温湿度計(IBS-TH2)
  https://inkbird.com/products/hygrometer-ibs-th2
- SpeeDBee Hive 
  バージョン：3.7.2
- Grafana
  バージョン：8.3.3

# SwitchBot 温湿度計用サンプルプログラムの修正点
SwitchBot 温湿度計用サンプルプログラムからの修正は以下の2点となります。
- ScanDelegate.handleDiscoveryメソッド内の温湿度データの判定を行う説明(desc)の文字列
- SwitchbotCollecter.insertメソッドでのデータ解析

修正点について解説していきます。

### ScanDelegateクラス
Bluetoothセンサーのスキャンでコールバックされるdelegate関数となるScanDelegateクラスです。
クラスの解説については[前回の記事](https://qiita.com/ss_nagahari/items/d26ad5e20a0fcd38a49a#scandelegateクラス)をご参照ください。
スキャンにより取得されたデータが温湿度データであるかを判定するための説明(desc)の文字列がSwitchBot 温湿度計では"16b Service Data"となっていましたが、INKBIRD 温湿度計(IBS-TH2)では"Manufacturer"となります。
判定に用いている文字列を修正します。

```Python:ble_collector_inkbird.py
class ScanDelegate(btle.DefaultDelegate):
    :
    def handleDiscovery(self, dev, isNewDev, isNewData):
        if isNewData:
            for (adtype, desc, value) in dev.getScanData():
                # 説明(desc)の文字列を"Manufacturer"に修正
                if desc == 'Manufacturer' and dev.addr in self.collectors:
                    self.collectors[dev.addr].insert(value)
```

### SwitchbotCollecterクラス
SwitchbotCollecterクラスは"SwitchBot 温湿度計"からのデータの解析や、SpeeDBee Hiveへの登録などの処理を定義したクラスとなっていました。
クラスの解説については[前回の記事](https://qiita.com/ss_nagahari/items/d26ad5e20a0fcd38a49a#switchbotcollecterクラス)をご参照ください。
まず、SwitchBot 温湿度計用の処理を行うクラスであったのでクラス名にSwitchBotと入っていましたが、INKBIRD 温湿度計用に修正を行うのでクラス名をInkbirdCollecterクラスと改めています。

insert()メソッド内のデータ解析処理の修正が必要となります。
その他のメソッドに修正は必要ありません。

```Python:ble_collector_inkbird.py
class InkbirdCollecter():
    :
    def insert(self, value):
        # INKBIRD 温湿度計(IBS-TH2)のため温湿度のデータ解析を修正
        # 温度を数値データに変換
        temp = int(value[2:4]+value[:2], 16)
        temperature_bits = 16
        if temp & (1 << (temperature_bits-1)):
            temp -= 1 << temperature_bits
        temp = (temp / 100)
        
        # 湿度を数値データに変換
        humi = (int(value[6:8]+value[4:6], 16)/100)
        
        # Hiveカラムへデータのインサート処理
        self._columns['temp'].insert(temp)
        self._columns['humi'].insert(humi)
```

上記の2点の修正ができましたら、修正したPythonプログラムをカスタムコレクタとして追加と設定を行ってください。

# カスタムコレクタ追加と設定
修正したカスタムコレクタのSpeeDBee Hiveへの追加、設定などは[前回の記事](https://qiita.com/ss_nagahari/items/d26ad5e20a0fcd38a49a#speedbee-hive-カスタムコレクタ)と同様ですので、ご参照ください。

設定時に必要となるパラメータについても変更はありません。
パラメータの例を以下に記載します。

```
※見やすいように改行とインデントを入れています。
{
  "devices":[
    {
      "name":"sensor1",
      "address":"aa:bb:cc:dd:ee:ff"
    },
    {
      "name":"sensor2",
      "address":"11:22:33:44:55:66"
    }
  ],
  "interval":5
}
```
パラメータの各要素は以下のようになっています。
| プロパティ |説明  |
|---|---
|devices  | センサーの情報<br>nameとaddressをデータ取得したいセンサ個数分設定 |
|name|センサーの名前(任意の名前)  |
|address|センサーのアドレス|
|interval|データのインサート間隔(秒)|

# INKBIRD 温湿度計(IBS-TH2)のアドレスの確認
[INKBIRD の公式アプリ](https://play.google.com/store/apps/details?id=com.inkbird.engbird&hl=ja)でセンサーのアドレスを確認をします。

1. 使用するINKBIRD 温湿度計(IBS-TH2)をINKBIRD の公式アプリでお手持ちのスマートフォンに追加してください。
追加方法に関してはINKBIRD の公式アプリでご確認ください。

2. アプリのHome画面に追加したセンサーが表示されます。
画像の赤枠内の値がセンサーのアドレスとなり、パラメータの"address"に入力する情報となります。
<img width="600" alt="inkbird.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2627884/3f7bea58-dfa3-4e90-68b3-28569581ce5f.png">

# Grafanaによる温湿度データの可視化
サンプルプログラムを修正し、INKBIRD 温湿度計からのデータ収集が動作すると可視化ツールである「Grafana」で閲覧可能となります。

Grafanaでの可視化方法について詳しくはSpeeDBee Hive マニュアルの[Grafana連携](https://www.saltyster.com/hive-docs/user_guide/grafana_collaboration.html)をご参照ください。

下の例はINKBIRD 温湿度計より収集した、現在時刻から過去15分間の温度データの推移を折れ線グラフで表示したものとなります。
温度データは10秒間隔で収集しています。
![grafana.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2627884/44308b96-9cc6-934d-2a1b-a4ac57c701fb.png)


# まとめ
今回は["「SpeeDBee Hive」でSwitchBot温湿度計からデータを取得する(Bluetooth通信)"](https://qiita.com/ss_nagahari/items/d26ad5e20a0fcd38a49a)のサンプルプログラムを応用して、INKBIRD社の温湿度計からデータ収集を行う「SpeeDBee Hive」のカスタムコレクタを作成しました。

その他のBluetooth通信可能な温湿度計でも今回記載しました修正箇所に変更を加えていただければデータ収集が可能になると思われます。

お手元に環境をご用意できる方は是非、サンプルプログラムを応用していただき、データ収集と可視化をお試しください。

SpeeDBee Hiveの関連記事：
・[AE2100でソルティスターの「SpeeDBee Hive」を使用してみよう(1) ―SmartHopセンサーデータの収集・可視化編―](https://qiita.com/shimatak/items/14d2001b04620a25a242)
・[AE2100でソルティスターの「SpeeDBee Hive」を使用してみよう(2) ―パトランプ制御編―](https://qiita.com/shimatak/items/1dafcc8bb79574ab6052)
・[AE2100でソルティスターの「SpeeDBee Hive」を使用してみよう(3) ―AWS連携編―](https://qiita.com/shimatak/items/87a1226e176ff60a6609)

最後に本記事で修正しました、INKBIRD社の温湿度計に対応したサンプルプログラムを以下に貼りますのでご活用ください。

```Python:ble_collector_inkbird.py
from hive_collector import HiveCollectorBase, HiveColumn
from bluepy import btle
import json

class HiveCollector(HiveCollectorBase):
    def __init__(self, param):
        self.logger.info("init")
        # JSON形式のパラメータの読込
        param_dict = json.loads(param)

        # パラメータを解析し、InkbirdCollecterインスタンス作成やデータ収集間隔を保持
        self.collectors = {}
        device_list = param_dict['devices']
        for device_info in device_list:
            device_info['address'] = str(device_info['address']).lower()
            self.collectors[device_info['address']] = InkbirdCollecter(self, device_info['name'])
        
        self.interval = param_dict['interval']

    def mainloop(self):
        self.logger.debug("main loop execute")
        # ScanDelegateインスタンス作成
        self.delegate = ScanDelegate(self.collectors)
        # delegate関数として指定
        self.scanner = btle.Scanner().withDelegate(self.delegate)
        # データ収集、登録
        self.intervalCall(self.interval*1000*1000, self.proc)
        
    def proc(self, ts, skip):
        try:
            self.scanner.scan(self.interval - 0.1)
        except btle.BTLEManagementError as e:
            self.logger.error(e)

class ScanDelegate(btle.DefaultDelegate):
    def __init__(self, collectors):
        super().__init__()
        self.collectors = collectors

    def handleDiscovery(self, dev, isNewDev, isNewData):
        if isNewData:
            for (adtype, desc, value) in dev.getScanData():
                # 説明(desc)の文字列を"Manufacturer"に修正
                if desc == 'Manufacturer' and dev.addr in self.collectors:
                    self.collectors[dev.addr].insert(value)

class InkbirdCollecter():
    def __init__(self, hivecollector, device_name):
        # カスタムコレクタインスタンス
        self._hivecollector = hivecollector
        # センサー名
        self._device_name = device_name
        # Hiveカラム
        self._columns = {}
        
        # Hiveカラムの作成
        self._cleate_column()
        
    def _cleate_column(self):
        self._columns['temp'] = self._hivecollector.makeOutputColumn(self._device_name + "_temp",  HiveColumn.TypeFloat)
        self._columns['humi'] = self._hivecollector.makeOutputColumn(self._device_name + "_humi",  HiveColumn.TypeFloat)

    def insert(self, value):
        # INKBIRD 温湿度計(IBS-TH2)のため温湿度のデータ解析を修正
        # 温度を数値データに変換
        temp = int(value[2:4]+value[:2], 16)
        temperature_bits = 16
        if temp & (1 << (temperature_bits-1)):
            temp -= 1 << temperature_bits
        temp = (temp / 100)
        
        # 湿度を数値データに変換
        humi = (int(value[6:8]+value[4:6], 16)/100)
        
        # Hiveカラムへデータのインサート処理
        self._columns['temp'].insert(temp)
        self._columns['humi'].insert(humi)
```