ラズパイで取得した温湿度気圧（BME680）データをInfluxDBへAPI経由で格納

# 概要

RaspberryPi4と温湿度気圧計（BME680）をつなぎ、データを取得します。同ラズパイ上にInfluxDBを起動し、API経由で、取得したデータを格納します。

## BME680をつなぐ

[BME680をRaspberry Pi 4で使ってみる #RaspberryPi - Qiita](https://qiita.com/rmatttu/items/6a1d12b922eac150c9ff) 前の記事を参考に接続し、BME680から値を取得できることを確認します。

## InfluxDBを起動

dockerで起動します。

### 起動&接続確認

docker-composeファイルを作成します

```yml:compose.yaml
services:
  influxdb:
    image: influxdb:2.7.6
    container_name: influxdb-local
    volumes:
      - ./docker/influxdb2/data/:/var/lib/influxdb2/
      - ./docker/influxdb2/config/:/etc/influxdb2/
    ports:
      - 8086:8086
    environment:
      - DOCKER_INFLUXDB_INIT_MODE=setup
      - DOCKER_INFLUXDB_INIT_USERNAME=admin
      - DOCKER_INFLUXDB_INIT_PASSWORD=my_secret_password
      - DOCKER_INFLUXDB_INIT_ORG=MyOrganization
      - DOCKER_INFLUXDB_INIT_BUCKET=MyBucket
```

```bash
docker compose up -d
```

起動できたらInfluxDBのWebクライアントに接続します [http://localhost:8086/](http://localhost:8086/) 。初期アカウントは`compose.yaml`のenvに指定している`admin`/`my_secret_password`となります。接続できることを確認します。

## APIを使ってみる

### API Token発行

APIで格納するためにAPI認証するためのTokenを発行します。

- 左メニューの「Load Data」 > API TOKENS > 「GENERATE API TOKEN」を押下 > とりあえず「Custom Access API Token」を作成
  - Descriptionに適当にわかり易い名前をつける
  - Resources > Buckets > とりあえず「All Buckets」の「Read」と「Write」にチェックを入れる
  - 「GENERATE」押下で作成

### BME680のデータを投入

pythonであれば、ライブラリ`influxdb_client`があるので、pythonを使いました。温湿度気圧を取得し、APIに格納します。

```py:post_sample.py
import datetime
from dataclasses import dataclass

from influxdb_client import InfluxDBClient, Point
from influxdb_client.client.write_api import SYNCHRONOUS

TOKEN = "xxxxxxxxxx-xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx...xxx==" # 先程生成したTOKENを設定
ORGANIZATION = "MyOrganization"
BUCKET = "MyBucket"
URL = f"http://localhost:8086"
MEASUREMENT = "sample"

BME680_ADDRESS = "0077"
BME680_DIR = f"/sys/bus/i2c/devices/1-{BME680_ADDRESS}/iio:device0"


@dataclass
class Bme680Data:
    """Bme680から取得した温湿度気圧データ"""

    humidity: float
    """相対湿度[%]"""

    pressure: float
    """気圧[hPa]"""

    resistance: int
    """抵抗値"""

    milli_temperature: int
    """気温 [ミリ℃]"""

    def get_temperature(self) -> float:
        """単位はミリ℃で格納されているようなので1000で割る（例: 27820℃ -> 27.820℃）"""
        return self.milli_temperature / 1000.0


class Bme680Reader(object):
    """Bme680のデータを取得します"""

    def __init__(self, bme680_dir: str):
        super().__init__()
        self.bme680_dir = bme680_dir

    def fetch(self):
        """Bme680から温湿度気圧データを取得します"""

        with open(
            f"{BME680_DIR}/in_humidityrelative_input", "r", encoding="utf-8"
        ) as humidity_reader, open(
            f"{BME680_DIR}/in_pressure_input", "r", encoding="utf-8"
        ) as pressure_reader, open(
            f"{BME680_DIR}/in_resistance_input", "r", encoding="utf-8"
        ) as resistance_reader, open(
            f"{BME680_DIR}/in_temp_input", "r", encoding="utf-8"
        ) as temp_reader:
            # blackにフォーマット任せたらこうなったけど、なんか奇妙↑

            humidity = float(humidity_reader.readline())
            pressure = float(pressure_reader.readline())
            resistance = int(resistance_reader.readline())
            milli_temp = int(temp_reader.readline())
            humidity_reader.close()
            pressure_reader.close()
            resistance_reader.close()
            temp_reader.close()
            return Bme680Data(humidity, pressure, resistance, milli_temp)

    def __str__(self):
        return self.bme680_dir


def main():
    """ラズパイ上でBME680の温湿度気圧データを取得してInfluxDBへ投入します"""

    write_client = InfluxDBClient(url=URL, token=TOKEN, org=ORGANIZATION)
    write_api = write_client.write_api(write_options=SYNCHRONOUS)

    bme680_reader = Bme680Reader(BME680_DIR)
    bme680_data = bme680_reader.fetch()

    # timezoneをしっかり設定しないと投入される時刻がおかしくなる。
    # JST環境では+9時間後の時刻で投入された・・・
    localTimeZone = datetime.datetime.now().astimezone().tzinfo
    timestamp = datetime.datetime.now(localTimeZone)

    point = (
        Point(MEASUREMENT)
        .tag("sensor", "bme680")  # tagは追加できます
        .field("temperature", bme680_data.get_temperature())
        .field("humidity", bme680_data.humidity)
        .field("pressure", bme680_data.pressure)
        .field("resistance", bme680_data.resistance)
        .time(timestamp)
    )
    write_api.write(bucket=BUCKET, org=ORGANIZATION, record=point)
    print(f"Point: {str(point)}")


if __name__ == "__main__":
    main()
```

下記のようにスクリプトを起動しデータを投入します。

```bash
# InfluxDBのライブラリをインストール
pip3 install influxdb-client --break-system-packages

# 実行
python3 post_sample.py
```

`break-system-packages`したくない場合は、下記のように仮想環境を作ってから実行します。

```bash
# 新しい仮想環境を作成
python3 -m venv .venv
source .venv/bin/activate

pip3 install influxdb-client
python3 post_sample.py

# 仮想環境終了
deactivate
```

## データの確認

- InfluxDBのWebクライアントに接続します [http://localhost:8086/](http://localhost:8086/)
- 左メニュー「Data Explorer」 > 「FROM」にバケット一覧が表示されているので「MyBucket」を選択 > Filter/`_measurement`で「sample」を選択 > Filter/`_field`で「temperature」等を選択 > 「SUBMIT」ボタンを押下
- グラフが表示されることを確認
  - 表示されない場合はグラフの表示期間（「SUBMIT」ボタンの2つほど左のプルダウン）で表示を広げてみる。

## cronに指定

毎分実行するようにします。

```bash
crontab -e
```

下記のようなcron設定を記述します。

```cron
LANG=ja_JP.UTF-8
SHELL=/bin/bash

* * * * * python3 path/to/post_sample.py &>>cronrun.log

# 仮想環境の場合
# * * * * * cd "path/to/post_sample_dir" && source .venv/bin/activate && python3 post_sample.py &>>cronrun.log
```

## おまけ

### curlでデータ投入するサンプル

サクッとInfluxDBへ投入したいときはこちらが使えます。

```bash
TOKEN="xxxxx"
ORGANIZATION="MyOrganization"
BUCKET="MyBucket"
API_URL="http://localhost:8086/api/v2/write?org=${ORGANIZATION}&bucket=${BUCKET}&precision=ns"
MEASUREMENT="sample"

curl -fv \
  -H "Authorization: Token $TOKEN" \
  -H "Content-Type: text/plain; charset=utf-8" \
  -H "Accept: application/json" \
  -d @- \
  -X POST "$API_URL" <<EOS
{{{ここにLineProtocolフォーマットで、改行区切りでバルクインサートできるっぽいがうまくいかなかった}}}
EOS
```

下記に具体例を示します。

```bash
curl -fv \
  -H "Authorization: Token $TOKEN" \
  -H "Content-Type: text/plain; charset=utf-8" \
  -H "Accept: application/json" \
  -d @- \
  -X POST "$API_URL" <<EOS
${MEASUREMENT},sensor=bme680 humidity=61.613,pressure=1006.88,resistance=16422i,temperature=31.59 1719757981665187000
EOS
```

### エポック秒・ミリ秒・マイクロ秒・ナノ秒 早見表

|日時|エポック|単位|桁|指数表記|
|---|:---|---|---|---|
|2024年6月30日 23:16:11|1719756971|秒|10||
|2024年6月30日 23:16:11.629|1719756971629|ミリ秒|13|10^-3|
|2024年6月30日 23:16:11.629074|1719756971629074|マイクロ秒|16|10^-6|
|2024年6月30日 23:16:11.629074000|1719756971629074000|ナノ秒|19|10^-9|