ArduinoとGCPで部屋の環境をモニタリングしてみた

# 概要
センサーとArduinoを使って、温度・湿度などをモニタリングしてみました
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/628295/0c35d4a4-1cb1-d256-72fb-7372820fdeff.png)

# 使ったもの
- 計測
  - Arduino Yun Mini https://docs.arduino.cc/retired/boards/arduino-yun-mini
  - AE-BME680 https://akizukidenshi.com/catalog/g/gK-14469/
    - BME680 https://github.com/adafruit/Adafruit_BME680
- データ保存、表示
  - Google Cloud Functions https://cloud.google.com/functions
    - Python 3.10
  - Google Cloud BigQuery https://cloud.google.com/bigquery
  - Google データポータル https://support.google.com/datastudio/answer/6283323

# 準備編
## Arduino Yun MiniをWiFiに接続する
こちらの記事を参考に接続しました。

https://t.co/pG5cWdVqv5


## Arduino Yun MiniとI2C接続する
Arduino Yun MiniでのSDA・SCLのアドレスを確認します。
公式ドキュメントの「SCHEMATICS IN .PDF」が回路図で、3Pを見ると次のようになります。
- SDA: D2
- SCL: D3

Arduino IDEでスケッチ例「i2c_scanner」を使うことで、接続を確認できます。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/628295/5779592e-040d-3bdc-eeb7-70a56536d987.png)

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/628295/4d9eb15f-1a82-ff13-19b8-646b878af4e4.png)

参考
https://www.denshi.club/cookbook/stem/maker-uno-stem-7-accuracy.html

## BME680とArduino Yun Miniを接続する
秋月電子の資料に書いてある手順を参考に、ArduinoIDE﻿にBME680用ライブラリを導入します。
Adafruit 社の"Adafruit﻿BME680﻿Library﻿"のv2.0.2をインストールしました。

Arduino Yun Mini | BME680
---- | ----
3.3V | 1. VIN
D3(SCL) | 2. SCL
D2(SDA) | 3. SDA
GND | 4. GND

![DSC_0001.JPG](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/628295/45aff8ca-2739-7272-90c7-e926ef54eb8b.jpeg)

サンプルソースが無事動きました🙌
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/628295/6b3f2cae-4ca7-4c50-760e-bdca10c87c10.png)

```
Temperature = 29.03 *C
Pressure = 1006.25 hPa
Humidity = 64.55 %
Gas = 11.43 KOhms
Approx. Altitude = 16.16 m
```

# 実装編
## データの保存先をつくる
今回もGCPを使います。
Firestoreだとデータポータルと連動させにくいので、BigQueryにデータを保存します。

1. データセットを作成
1. テーブルを作成
    1. ソース: テーブルの作成元「空のテーブル」
    1. スキーマ![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/628295/5dc43325-fc11-b96c-8f04-61cb55a0ca3d.png)
    1. パーティショニングなし、クラスタリングの順序は`createdAt`

※フィールド名にキャメルケースとスネークケース混ざってしまったのは失敗

## データを受け付けるWEB APIを用意する
Functionsを使います。
第2世代が出ていたのでついそちらを使いました。

Arduinoでうまく認証させられる気がしなかったので、「未認証の呼び出しを許可」で作成しています。
また第2世代なのでHTTP**S**です。

```main.py
import datetime
import os

import functions_framework
from google.cloud import bigquery


@functions_framework.http
def register_monitoring_data(request):
    request_json = request.get_json(silent=True)
    dt_now_iso = datetime.datetime.now(datetime.timezone(datetime.timedelta(hours=9))).isoformat()
    print(request_json, dt_now_iso)

    rows_to_insert = [{
        'createdAt': dt_now_iso,
        'temperature': request_json['temperature'],  # 温度(℃)
        'pressure': request_json['pressure'],  # 気圧(hPa)
        'humidity': request_json['humidity'],  # 湿度(%)
        'gas_resistance': request_json['gas_resistance'],  # 気体抵抗(KOhms)
        'elevation': request_json['elevation']  # 標高(m)
    }]

    table_id = os.environ.get('BIGQUERY_TABLE_ID', 'Specified environment variable is not set.')

    client = bigquery.Client()
    table = client.get_table(table_id)
    errors = client.insert_rows(table, rows_to_insert)
    if errors:
        print("Encountered errors while inserting rows: {}".format(errors))
        return 'NG {}'.format(dt_now_iso)

    return 'OK {}'.format(dt_now_iso)

```
ランタイム環境変数に`BIGQUERY_TABLE_ID`で登録先のBigQueryテーブルIDを設定します

## ArduinoからJSONでデータを送る
サンプルソースをもとに、API通信を追加しました。
送信先APIがHTTPSなのですが、証明書登録が容量不足でできなかったため、証明書チェックを`client.noCheckSSL()`で無視しています。

```c
// 計測まわり
#include <Wire.h>
#include <SPI.h>
#include <Adafruit_Sensor.h>
#include "Adafruit_BME680.h"
// データ送信まわり
#include <Bridge.h>
#include <HttpClient.h>
#include <Arduino_JSON.h>

#define SEALEVELPRESSURE_HPA (1008.2)
#define HTTP_URL "your url"

Adafruit_BME680 bme; // I2C
const int STATUS_LED = 13;

void setup() {
  pinMode(STATUS_LED, OUTPUT);
  digitalWrite(STATUS_LED, LOW);
  Bridge.begin();
  digitalWrite(STATUS_LED, HIGH);
  delay(3000);

  if (!bme.begin()) {
    digitalWrite(STATUS_LED, LOW);
    Serial.println("Could not find a valid BME680 sensor, check wiring!");
    while (1);
  }

  // Set up oversampling and filter initialization
  bme.setTemperatureOversampling(BME680_OS_8X);
  bme.setHumidityOversampling(BME680_OS_2X);
  bme.setPressureOversampling(BME680_OS_4X);
  bme.setIIRFilterSize(BME680_FILTER_SIZE_3);
  bme.setGasHeater(320, 150); // 320*C for 150 ms
}

void exec() {
  if (! bme.performReading()) {
    Serial.println("Failed to perform reading :(");
    for(int i = 0; i < 10; i++) {
       digitalWrite(STATUS_LED, HIGH);
       delay(1000);
       digitalWrite(STATUS_LED, LOW);
       delay(1000);
    }
    return;
  }

  // データ作成
  JSONVar json;
  json["temperature"] = bme.temperature;
  json["pressure"] = bme.pressure / 100.0;
  json["humidity"] = bme.humidity;
  json["gas_resistance"] = bme.gas_resistance / 1000.0;
  json["elevation"] = bme.readAltitude(SEALEVELPRESSURE_HPA);

  String jsonStr = JSON.stringify(json);
  int str_len = jsonStr.length() + 1; 
  char char_array[str_len];
  jsonStr.toCharArray(char_array, str_len); 

  // HTTP通信
  HttpClient client;
  client.noCheckSSL();
  client.setHeader("Content-Type: application/json");

  client.post(HTTP_URL, char_array);
  String payload = client.readString();
  Serial.println(payload);

  client.close();
}

void loop() {
  exec();
  delay(5 * 60 * 1000UL); // 5分(ms)
}
```
`#define HTTP_URL "your url"`にさきほど作ったFuctionsのトリガーURLを設定します。
このコードだと容量ぎりぎり98%になってしまったので、他の処理を入れるならJSONをライブラリ無で生成した方がいいかもしれません。

## データを表示する
データポータル(旧Data Studio)で、BigQueryのデータを参照してグラフを表示します。
スマホで確認したかったので縦長に配置しました。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/628295/9b443562-524e-feb1-9a7f-40de335001fa.png)
コントロール部品を設定したので、デフォルト今日のデータが表示されます。

# まとめ
思っていたよりは手間がかからずモニタリングができました🤗

ソースコードはGitHubに置いてあります。
https://github.com/k-akie/room-monitor

- これからやりたいこと
  - 値によってSlack通知する(アラート機能)
  - センサー以外に天気などの情報も一緒に表示する
  - 気体抵抗をCO2濃度に換算する