~ビオトープの冬超し~ IoT的モニタリングしてみた

# 岩手アドベントカレンダーに参加します

今年はあまり記事を書けなかったけど、これは参加したい。

https://qiita.com/advent-calendar/2024/ipu

# はじめに

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/342722/e87f6ca6-0be5-cd52-819e-f260a1eb6576.png)

ビオトープでミナミヌマエビを飼育しています。私の住む岩手では豪雪&-20℃近くまで冷え込んでしまうので何らか冬対策する必要がある。


そこでエビだけ救出して、半屋外へ移動した。
その場所もまぁまぁ寒いので、せっかくなのでIoT的にモニタリングしてみた。(外よりは大丈夫だろうけど、、)

あとはLookerStudioも使ってみたかったという動機。

### 引っ越し先&設置イメージ
外よりはいいけど、それなりに寒い。
今回はモバイルバッテリーで電源確保。どのくらい持つか？？
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/342722/16e8f9d8-2196-0406-231a-564f7b3e1297.png)


## やったこと
1. ESP32で温度・湿度計を実装
1. スプレッドシートへ送信(GAS)
1. LookerStudioでグラフ化


# ESP32 温度・湿度計

自分の記事を参考にサクッと。

https://qiita.com/taiyyytai/items/7f3f8001a08f53508665

:::note info
ポイント
今回モバイルバッテリーを使用する。
稼働時間を増やすためDeepSleepも入れてみた(効果はいかほど？)

(追記)
1週間程度稼働させましたが、無事稼働していました！(3200mAh)
:::

全体のコード
```cpp
#include <Arduino.h>
#include <DHT.h>
#include <WiFi.h>
#include <HTTPClient.h>

// DHT11のピン
const int PIN_DHT = 4;
DHT dht(PIN_DHT, DHT11);

// Wi-Fi接続情報
const char *ssid = "hogehoge";    // wifiのssid
const char *password = "fugafuga"; // wifiのパスワード
const char *scriptURL = "https://script.google.com_hogehoge";

// Deep Sleep間隔（3時間）
const uint64_t SLEEP_INTERVAL = 3 * 60 * 60 * 1000000ULL;

//---------------------------
//  Wi-Fi接続関数
//---------------------------
void connectWiFi()
{
  Serial.println("Connecting to Wi-Fi...");
  WiFi.mode(WIFI_STA);
  WiFi.begin(ssid, password);

  while (WiFi.status() != WL_CONNECTED)
  {
    delay(500);
    Serial.print(".");
  }

  Serial.println("\nWi-Fi connected");
}

//---------------------------
// データ送信関数
//---------------------------
void sendData(float temperature, float humidity)
{
  if (WiFi.status() != WL_CONNECTED)
  {
    Serial.println("Wi-Fi not connected. Reconnecting...");
    connectWiFi();
  }

  HTTPClient http;
  http.begin(scriptURL);
  http.addHeader("Content-Type", "application/json");

  String jsonData = "{\"val1\":" + String(temperature) + ", \"val2\":" + String(humidity) + "}";
  int httpResponseCode = http.POST(jsonData);

  if (httpResponseCode > 0)
  {
    String response = http.getString();
    Serial.println("Response: " + response);
  }
  else
  {
    Serial.println("Error on sending POST: " + String(httpResponseCode));
  }

  http.end();
}

//---------------------------
//  セットアップ&ループ
//---------------------------
void setup()
{
  Serial.begin(9600);
  dht.begin();
  connectWiFi();
}

void loop()
{
  float temperature = dht.readTemperature();
  float humidity = dht.readHumidity();

  Serial.printf("Temperature: %.2f°C  Humidity: %.2f%%\n", temperature, humidity);

  sendData(temperature, humidity);

  // Deep Sleepに入る
  Serial.println("Entering deep sleep...");
  esp_deep_sleep(SLEEP_INTERVAL);
}

```

# GAS(スプレッドシート)
こっちはPOSTを受けられるように

```javascript
function doPost(e) {
  try {
    // POSTデータの取得と解析
    const string = e.postData.getDataAsString();
    const JsonData = JSON.parse(string);

    const val1 = JsonData.val1;
    const val2 = JsonData.val2;

    // スプレッドシートを開く
    const sheet = SpreadsheetApp.openById("sheetID");
    if (!sheet) {
      throw new Error("Spreadsheet not found. Check the spreadsheet ID.");
    }

    // シートを取得
    const sheetName = "Sheet1";
    const sheetObj = SpreadsheetApp.getActiveSpreadsheet().getActiveSheet(); // アクティブなシートを取得

    if (!sheetObj) {
      throw new Error(`Sheet "${sheetName}" not found. Check the sheet name.`);
    }

    // データを追加
    const row = [new Date(), val1, val2];
    sheetObj.appendRow(row);

    // レスポンスを返す
    const data = { message: "Data added successfully", val1: val1, val2: val2 };
    const response = ContentService.createTextOutput();
    response.setMimeType(ContentService.MimeType.JSON);
    response.setContent(JSON.stringify(data));
    return response;

  } catch (error) {
    Logger.log("Error: " + error.message);
    const errorMessage = { error: error.message };
    const response = ContentService.createTextOutput();
    response.setMimeType(ContentService.MimeType.JSON);
    response.setContent(JSON.stringify(errorMessage));
    return response;
  }
  ```

こんな感じで受信

  ![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/342722/3f2c61d1-560f-7c2a-3b62-5c8e152c7a21.png)


# LookerStudio

とりあえずグラフだけ。LookerStudio便利ですね～
リビング温度や外気温との比較もあったらよかったかも。(APIでとるか、別に設置するか...)

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/342722/50998d59-ec77-6da6-58a9-41c7fb45b12b.png)

### 表示Tips: データの並び替えと範囲指定
Google Apps Script (GAS)でデータを挿入する際、データはスプレッドシートの一番下に追加されます。そのまま日付順にすると表示が乱れる可能性があるため、以下の手順で調整するのがおすすめです。

#### 並び順を降順に設定
最新データが上に表示されるように日付を降順にソートします。
さらにX軸を逆方向にするといい感じに降順で表示できるっぽい。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/342722/8c3a07e2-3465-7546-abf7-b16c2809d868.png)


#### 表示範囲を制限
表示ポイントを「最新の20～30件」に絞り込むことで、適切なデータを表示します。

## スマホでも見られるように
リンク共有で手軽に見れる！全然これでいい。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/342722/a6191073-c72d-ae5f-96ad-5211c5130830.png)


今度は水温を年中モニタリングできたらいいかも。