こんにちは、ほたかっち です。今回は前回の続きをやっていこうと思います。
前回の振り返り
前回は実際にパケットを送信してPCを起動してみました。今回は外部からでもPCを起動できるようにしたいと思います。
作成手順
- Google Sheetsを作成
- Google App Scriptsを作成
- ESP32のプログラムを作成
Google Sheetsを作成
Google Sheetsのセルの値でパケットを送信するかを判断します。
まずは,Chromeを開いてGoogleアカウントにログインします。Google Driveにアクセスして,新しいGoogle Sheetsファイルを作成します。今後も使う可能性を考慮して,ファイル名を「IoTプロジェクト」にしておきました。このシートでは,PCのステータスを管理するためのセルを一つ用意します。
今回は,ステータス列のB2セルにPCがオンかオフかを示す値を設定します。ここではTRUEとFALSEを使って表記しますが,値をいちいち入力するのは面倒なので,B2セルを選択した状態で「挿入」から「プルダウン」を選択します。すると,右側にデータの入力規則が表示されるので,オプション 1をFALSE(色は赤),オプション 2をTRUE(色は青)に変更して「完了」をクリック。これでステータスを簡単に切り替えられるようになりました。

Google App Scriptsを作成
GAS(Google Apps Script)は,Google Sheetsの拡張機能として簡単に連携できます。まず,Google Sheetsの「拡張機能」タブから「Apps Script」をクリックします。これで,Google Sheets専用のプロジェクトがGoogle Apps Scriptに自動的に作成されます。プロジェクト名は,わかりやすく「IoTプロジェクト」に変更しておきましょう。
Google Apps ScriptはJavaScriptをベースにしているので,JavaScriptの知識があればすぐに始められます。ただし,ファイルの拡張子は.jsではなく.gsになります。また,通常のJavaScriptとは少し異なり,Google独自の専用関数も用意されているので,それを使うことでGoogle Sheetsや他のGoogleサービスと簡単に連携できます。
まずは,Google SheetsとESP32がやりとりできるようにGASで関数を作成します。関数は,標準のJavaScriptを使って書くことができますが,Google Apps Scriptの特性を活かして作ると,さらに便利なツールになります。
その関数で,Google SheetsとESP32がやりとりできるAPIを作成します。
API(Application Programming Interface)とは,アプリケーション間でデータや機能をやりとりするためのインターフェースです。APIといっても広義的な意味があり,今回の場合はWeb APIと呼ばれるものになります。Web APIは,HTTP通信を使ってデータを送受信することができ,通常はJSON(JavaScript Object Notation)というデータフォーマットでデータが返ってきます。JSONは,データ交換に広く使われる軽量なフォーマットです。
以下のコードは,Google Sheetsのセルから値を取得し,その値をJSON形式で返すように設定したものです。このコードをGoogle Apps Scriptのコード.gsに貼り付けます。
const SECRET = "password"; //パスワード
const SHEET_NAME = "シート1"; //シート名
const CELL = "B2"; // フラグを置いているセル
function doGet(e) {
if (e.parameter.key !== SECRET) {
return ContentService.createTextOutput(JSON.stringify({ok: false, error: "forbidden"}))
.setMimeType(ContentService.MimeType.JSON);
}
const sheet = SpreadsheetApp.getActiveSpreadsheet().getSheetByName(SHEET_NAME);
if (e.parameter.action === "set") {
sheet.getRange(CELL).setValue(e.parameter.value);
return ContentService.createTextOutput(JSON.stringify({ok: true, value: e.parameter.value}))
.setMimeType(ContentService.MimeType.JSON);
}
const value = sheet.getRange(CELL).getValue();
return ContentService.createTextOutput(JSON.stringify({ok: true, value: String(value)}))
.setMimeType(ContentService.MimeType.JSON);
}
このdoGet関数は,HTTP GETリクエストが来たときに呼び出されます。Google Sheetsの「シート1」シートのB2セルの値を取得し,それをJSON形式で返します。これにより,ESP32からこのAPIを呼び出すことで,Google Sheetsのセルの値を簡単に取得することができます。
プログラムを貼り付けたら、デプロイを押して新しいデプロイを作成します。

アクセスできるユーザーを必ず全員にしてデプロイをします。ウェブアプリのURL(https://script.google.com/macros/s/~~~/exec)は後ほど使うので保存しておいてください。
(追記)保存したURLは一度ログインした方がいいかも。
GASの作成は以上です。
ESP32のプログラムを作成
完成したプログラムはこちらです。
#include <WiFi.h>
#include <HTTPClient.h>
#include <WiFiClientSecure.h>
#include <ArduinoJson.h>
#include <WakeOnLan.h>
#include <ESPping.h>
bool getGASValue(bool &outValue);
bool setGASValue(const String& status);
bool checkPcStatus();
void handleHttpError(int httpResponseCode, HTTPClient& http);
void ledOn();
void ledOff();
void ledBlink();
void ledFastBlink();
WiFiUDP UDP;
WiFiClient client;
WakeOnLan WOL(UDP);
const char* ssid = "wifi";
const char* password = "password";
// GAS Webアプリ設定(OAuth不要)
const char* gasBaseUrl = "https://script.google.com/macros/s/~~~/exec";
const char* gasSecret = "password"; //gasで設定したパスワード
const int LED_PIN = 2;
const char* pcMacAddress = "AA:BB:CC:DD:EE:FF";
const IPAddress pcIP(128, 0, 0, 1);
RTC_DATA_ATTR bool previousValue = true;
const uint64_t sleepIntervalOff = 1 * 60 * 1000000; // 1分
void setup() {
pinMode(LED_PIN, OUTPUT);
ledOff();
Serial.begin(115200);
WiFi.begin(ssid, password);
while (WiFi.status() != WL_CONNECTED) {
delay(500);
ledBlink();
Serial.print(".");
}
Serial.println("\nWiFiに接続しました:");
Serial.print("IPアドレス: ");
Serial.println(WiFi.localIP());
bool sheetValue = false;
if (getGASValue(sheetValue)) {
if (sheetValue) {
Serial.println("Google Sheets が TRUE → WOL を送信します");
if (setGASValue("FALSE")) {
ledOn();
WOL.sendMagicPacket(pcMacAddress);
delay(1000);
previousValue = false;
ledOff();
} else {
Serial.println("FALSE に戻せなかったため WOL を中止します");
ledFastBlink();
}
} else {
Serial.println("Google Sheets は FALSE です");
previousValue = false;
}
} else {
Serial.println("シートの取得に失敗しました");
ledFastBlink();
}
WiFi.disconnect();
Serial.println("Wi-Fi接続を解除しました");
Serial.println("1分間スリープします...");
esp_sleep_enable_timer_wakeup(sleepIntervalOff);
esp_deep_sleep_start();
}
void loop() {
// setup()内でディープスリープするため未使用
}
bool checkPcStatus() {
Serial.print("Pingを送信中...");
while (WiFi.status() != WL_CONNECTED) {
delay(100);
}
bool isOnline = client.connect(pcIP, 3389);
Serial.println(isOnline ? "PCはオンラインです。" : "PCはオフラインまたは到達不能です。");
return isOnline;
}
bool getGASValue(bool &outValue) {
WiFiClientSecure secureClient;
secureClient.setInsecure();
HTTPClient http;
String url = String(gasBaseUrl) + "?key=" + gasSecret;
http.begin(secureClient, url);
http.setFollowRedirects(HTTPC_STRICT_FOLLOW_REDIRECTS);
int code = http.GET();
if (code == HTTP_CODE_OK) {
String response = http.getString();
DynamicJsonDocument doc(512);
if (deserializeJson(doc, response) || doc["ok"] != true) {
Serial.println("応答の解析に失敗: " + response);
http.end();
return false;
}
String v = doc["value"].as<String>();
v.trim();
v.toLowerCase();
outValue = (v == "true" || v == "1");
http.end();
return true;
}
handleHttpError(code, http);
http.end();
return false;
}
bool setGASValue(const String& status) {
WiFiClientSecure secureClient;
secureClient.setInsecure();
HTTPClient http;
String url = String(gasBaseUrl) + "?key=" + gasSecret + "&action=set&value=" + status;
http.begin(secureClient, url);
http.setFollowRedirects(HTTPC_STRICT_FOLLOW_REDIRECTS);
int code = http.GET();
bool ok = (code == HTTP_CODE_OK);
if (ok) {
Serial.println("Google Sheetsの更新結果: " + http.getString());
} else {
handleHttpError(code, http);
}
http.end();
return ok;
}
void handleHttpError(int httpResponseCode, HTTPClient& http) {
Serial.print("HTTPリクエストでエラーが発生しました: HTTP/1.1 ");
Serial.println(httpResponseCode);
String errorResponse = http.getString();
if (errorResponse.length() > 0) {
Serial.println("エラーレスポンス: ");
Serial.println(errorResponse);
}
}
void ledOn() { digitalWrite(LED_PIN, HIGH); Serial.println("LED: 点灯"); }
void ledOff() { digitalWrite(LED_PIN, LOW); Serial.println("LED: 消灯"); }
void ledBlink() {
digitalWrite(LED_PIN, HIGH); delayMicroseconds(500);
digitalWrite(LED_PIN, LOW); delayMicroseconds(500);
}
void ledFastBlink() {
for (int i = 0; i < 10; i++) {
digitalWrite(LED_PIN, HIGH); delay(100);
digitalWrite(LED_PIN, LOW); delay(100);
}
Serial.println("LED: エラー点滅完了");
}
Wi-FiのSSIDとパスワードと、先ほどのウェブアプリのURLとパスワードと、PCのMACアドレスとIPアドレスを書き換えてください。
この状態だとセキュリティ面でいろいろと心配ですが……セルの値を変えてPCが起動すると完成です。
最後まで見ていただきありがとうございました。