ESP8266で電源電圧を自己測定し、Googleスプレッドシートに記録して推移を可視化する

# 目的
Arduinoで無線LANを利用したい場合、ESP8266やESP32を使用する場面は多いと思います。低消費電力モードを備え、乾電池により駆動することもできますが、実際、どのくらい動作し続けることができるのか検証してみようと思い立ち、定期的に電源電圧を取得するところから始めてみました。データはSDカード等のローカルに保存することもできますが、今回はGoogleスプレッドシートを活用し、クラウドに保存してみます。リアルタイムに確認できるので便利です。
![picture.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2951963/704d3c8c-df50-9b68-27c1-e284dee1ffbe.jpeg)
# 開発環境
特にライブラリのバージョンは注意してください。バージョンが異なると仕様も大きく異なるためです。
* Windows 11 Home 23H2
* Arduino IDE 2.3.0
* ArduinoJson 5.13.5
* cURL 8.4.0
# 概要
全体的な流れとしてはGASでWebアプリを作成し、ESP8266からデータを受け取る度にGoogleスプレッドシートの内容を更新する感じです。
## Google Apps Script
まず、Googleスプレッドシートを新規に作成します。拡張機能の中からGASの項目を選択すると別のタブが開き、GASのファイルが作成されます。
![screenshot-1.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2951963/f960e4bc-fe44-1c5d-a8c3-8c6637d6df08.png)
スクリプトの内容は以下の通りです。POSTリクエストで送信されたJSON形式のデータを受け取り、Googleスプレッドシートに追記します。なお、関数名は予め定義されているため、勝手に命名することはできないようです。
```javascript:main.gs
function doPost(e) {
  var sheet = SpreadsheetApp.getActiveSheet();
  
  var object = JSON.parse(e.postData.contents);
  sheet.appendRow([object.time, object.voltage]);
}
```
これを新規にデプロイし、実行用のURLを発行します。デプロイの種類としてはWebアプリを選択し、全員がアクセスできるように設定します。説明文の入力は任意です。
![screenshot-2.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2951963/98bac60f-c580-778d-9b93-ab3af786413c.png)
後は使用するGoogleアカウントを選択し、アクセスを承認していきます。初回は途中で警告が表示されますが、そのままアクセスを許可して構いません。最後に表示されたURLを後述するプログラムに貼り付けます。なお、デプロイを取り消すことはできませんが、不要なバージョンはアーカイブして無効化できます。また、スクリプトの内容を変更したら適用するためにバージョンは必ず更新する必要があります。ただし、新規にデプロイするとURLが変更されてしまうため、同じURLを使用したい場合はデプロイを編集し、バージョンを上げてから再度デプロイします。
![screenshot-3.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2951963/099af919-3f12-9fab-c9fa-8ee4f4590c34.png)
ここではcURLを使用し、正しく設定できたのか確認してみます。以下のコマンドをターミナルに貼り付けて実行します。JSON形式のデータに含まれる引用符はエスケープしておく必要がありました。
```
curl -X POST -H "Content-Type: application/json" -d "{\"time\":\"{time}\", \"voltage\":\"{voltage}\"}" "https://script.google.com/macros/s/{id}/exec"
```
少し遅延はありますが、Googleスプレッドシートの内容が更新されたら成功です。
## Arduino
ESP8266に書き込むプログラムの内容です。NTPサーバから取得した時間と電源電圧の測定値をJSON形式に変換し、5分毎にPOSTリクエストで送信します。[**こちら**](https://arduinojson.org/v5/example/generator/)が参考になりました。5分間の大半はスリープし、消費電力を抑えています。なお、スリープする時間はマイクロ秒単位で指定しますが、引数は32ビットの整数型なので最大72分くらいです。
```cpp:google_sheets_write.ino
#include <ESP8266WiFi.h>
#include <ESP8266HTTPClient.h>
#include <WiFiClientSecure.h>
#include <ArduinoJson.h>
#include <time.h>

const char *ssid = "{ssid}"; //put your Wi-Fi SSID
const char *password = "{password}"; //put your Wi-Fi password

const String url = "https://script.google.com/macros/s/{id}/exec";


ADC_MODE(ADC_VCC);

void setup() {
  WiFi.begin(ssid, password);

  Serial.begin(74880);
  Serial.print("Connecting...");
  while (WiFi.status() != WL_CONNECTED) {
    delay(500);
    Serial.print(".");
  }
  Serial.println();
  Serial.println("Connected to Wi-Fi");
  configTzTime("JST-9", "time.google.com");

  writeGoogleSheets();

  //go to sleep
  Serial.println("Start sleeping...");
  ESP.deepSleep(5 * 60 * 1000000); //5 minutes
  delay(1000);
}

void loop() {
}

void writeGoogleSheets() {
  HTTPClient http;
  WiFiClientSecure client;

  client.setInsecure();

  time_t now = time(NULL);
  struct tm *tm = localtime(&now);

  //create a time stamp
  char timeStamp[20];
  int year = tm->tm_year + 1900;
  int month = tm->tm_mon + 1;
  int day = tm->tm_mday;
  int hour = tm->tm_hour;
  int minute = tm->tm_min;
  int second = tm->tm_sec;
  sprintf(timeStamp, "%04d/%02d/%02d %02d:%02d:%02d", year, month, day, hour, minute, second);

  //generate a JSON value
  DynamicJsonBuffer buffer;
  JsonObject& object = buffer.createObject();
  object["time"] = timeStamp;
  object["voltage"] = ESP.getVcc() / 1000.0f; //read a battery voltage

  //convert the JSON value
  String query;
  object.printTo(query);

  //open a connection
  http.begin(client, url);
  http.addHeader("Content-Type", "application/json");

  //send a POST request
  http.POST(query);

  //receive a response
  String body = http.getString();
  Serial.println("Done");
  Serial.println(body);

  //close the connection
  http.end();
}
```
# 電源電圧を取得する
ESP8266の電源電圧を取得する方法に関して補足しておきます。結論としては専用の関数`getVcc`が用意されているので細かい設定は不要です。外付け部品も不要なので非常に簡単です。その反面、AD変換に使用できる唯一のピンTOUTが使用できなくなるため、注意してください。分解能は10ビットです。残念ながらESP32に同様の関数は用意されていないようです。
# まとめ
以上のようにArduinoと連携し、Googleスプレッドシートを簡易的なデータベースとして活用することができました。センサを追加し、簡易的なデータロガー等も作れそうです。