提示詞-1 (接線指導)
提供嵌入式系統物聯網開發的接線指導
#知識專長
-嵌入式系統與物聯網(IoT)硬體開發專家
#嵌入式系統硬體
-NodeMCU-32S 開發板
-Keyes ESP-32 專用 GPIO 擴充板
#外接元件
##光敏電阻模組(LDR Sensor Module / Photoresistor Module)
-數量:1
-接腳:AO, DO, VCC, GND
##DHT11 溫濕度感測器模組
-數量:1
-接腳:DATA, VCC, GND
#接線需求
-讀取光敏電阻模組 DHT11的感測資料,進行 WiFi 連線 上傳至物聯網平台
-提供外接元件與 Keyes ESP-32 專用 GPIO 擴充板的接線
-提醒接線須特別注意事項
提示詞-2 (資料導入 GSheet 諮詢)
根據需求,提供你的規劃給我確認。
#知識專長
-嵌入式系統與物聯網(IoT)硬體開發專家
#嵌入式系統硬體
##開發板
-NodeMCU-32S 開發板
-Keyes ESP-32 專用 GPIO 擴充板
##外接元件
-1個光敏電阻模組
##接線
-AO 接 擴充板 GPIO34(S)
-DO G 接 擴充板 GPIO18(S)
-GND 接 擴充板 GND
-VCC 接 擴充板 3.3V
-1個DHT11模組
##接線
-DATA 接 擴充板 GPIO4(S)
-GND 接 擴充板 GND
-VCC 接 擴充板 3.3V
#專題需求
-將光敏電阻、DHT11的感測讀值儲存至雲端 Google Sheet中
##Google Sheet
-建立一工作表 Data
-工作表 第一列標提列分別是 日期(A1),時間(B1),光敏AO(C1),光敏DO(D1),溫度(E1),濕度(F1)
-將光敏電阻、DHT11的感測讀值分別儲存於(C~F欄)
-A~B欄為寫入資料時的日期與時間
##評估可行的解決方案,提供你的規劃給我
提示詞-3 (GAS 方案逐步教學)
我們一步一步來,先來解決 Google Sheet 與 Google Apps Script (GAS) 設定,我需要你一步一步詳細完整說明如何操作
步驟 1:開啟 Google 雲端硬碟,點擊左上角 「+ 新增」> 「Google 試算表」。
將試算表標題(左上角)改為:ESP32 Sensor Logger(或您喜歡的名稱)。
看向畫面左下角的工作表頁籤,預設名稱為 工作表1:
點擊兩下 或 右鍵點選「重新命名」,將其精確改為:Data(大小寫需完全一致)。
設定第一列的標題(Header):
A1 儲存格 輸入:日期
B1 儲存格 輸入:時間
C1 儲存格 輸入:光敏AO
D1 儲存格 輸入:光敏DO
E1 儲存格 輸入:溫度
F1 儲存格 輸入:濕度
步驟 2:開啟與編寫 Google Apps Script (GAS)
在試算表上方選單列,點選 「擴充功能」 > 「Apps Script」。
系統會打開一個新的 程式碼編輯器 標籤頁。
將編輯器內原有的 function myFunction() { ... } 預設程式碼全部刪除。
將以下完整程式碼複製,並貼入編輯器中
GAS Code
function doPost(e) {
try {
// 1. 開啟當前連結的 Google 試算表與 Data 工作表
var sheet = SpreadsheetApp.getActiveSpreadsheet().getSheetByName("Data");
// 如果找不到 Data 工作表,回傳錯誤訊息
if (!sheet) {
return ContentService.createTextOutput("Error: Sheet 'Data' not found.");
}
// 2. 解析 ESP32 傳入的 POST 參數
var aoValue = e.parameter.ao; // 光敏電阻 AO
var doValue = e.parameter.do; // 光敏電阻 DO
var tempValue = e.parameter.temp; // 溫度
var humiValue = e.parameter.humi; // 濕度
// 3. 取得當前伺服器時間(設定為台灣台北時區 GMT+8)
var now = new Date();
var dateStr = Utilities.formatDate(now, "GMT+8", "yyyy-MM-dd");
var timeStr = Utilities.formatDate(now, "GMT+8", "HH:mm:ss");
// 4. 將資料組成一列,寫入試算表最後一行 (Append Row)
// 欄位順序:[日期, 時間, 光敏AO, 光敏DO, 溫度, 濕度]
sheet.appendRow([dateStr, timeStr, aoValue, doValue, tempValue, humiValue]);
// 5. 回傳成功訊息給 ESP32
return ContentService.createTextOutput("Success: Data logged successfully.");
} catch (error) {
// 若發生異常,回傳錯誤原因
return ContentService.createTextOutput("Error: " + error.toString());
}
}
// 提供 doGet 以防使用 GET 方式測試
function doGet(e) {
return ContentService.createTextOutput("GAS Web App is running! Use POST to send sensor data.");
}
步驟 3:部署 Web 應用程式 (Web App)
這是最關鍵的一步,權限設定正確 ESP32 才能順利寫入:
點擊右上角的藍色按鈕 「部署」 > 「新增部署」。
在彈出的視窗中:
點擊左上角齒輪圖示 ⚙️(選擇類型),選擇 「Web 應用程式」。
填寫部署設定:
說明: 輸入 ESP32 Sensor API v1(可自由填寫)。
執行身份: 選擇 「我」(Your account)。
誰設有存取權: 務必選擇「所有人」(Anyone)。(此設定不需要使用者登入即可接收數據)。
點擊右下角的 「部署」 按鈕。
步驟 4:授權專案權限
首次部署時,Google 會要求授權此腳本讀寫您的 Google Sheet:
畫面上出現「需要存取權」提示,點擊 「授權存取權」。
選擇您的 Google 帳戶。
如果出現 「Google 未驗證此應用程式」 的警告畫面:
請點選左下角的 「進階」(Advanced)。
點選最下方的 「前往『未命名專案』(不安全)」(Go to Untrusted App)。
滾動到最下方,點擊 「允許」(Allow)。
步驟 5:複製並保存網址 (Webhook URL)
授權完成後,畫面會顯示「已更新部署」。
找到 「Web 應用程式」 下方的 「網址」(格式會像:[https://script.google.com/macros/s/AKfycbx.../exec](https://script.google.com/macros/s/AKfycbx.../exec))。
點擊 「複製」,並將這串網址保存到記事本中。這就是我們接下來要在 ESP32 程式碼中填入的網址!
提示詞-4 (GAS GSheet 連動測試)
我需要在這裡增加一個測試 GAS 與 GoogleSheet 連動是否正常的操作,給我逐步指導
直接使用 HTML 網頁測試(零第三方工具,100% 成功)
如果您不想使用任何第三方測試網站,可以直接在電腦建立一個簡單的測試網頁,利用瀏覽器原生的表單發送資料給 GAS。
操作步驟:
在電腦桌面上新建一個文字文件,命名為 test.html。
用「記事本」開啟該檔案,將以下程式碼貼入(請將網址替換為您的 GAS Web App URL)
雙擊開啟 test.html(會自動用瀏覽器打開)。
點擊畫面上的 「發送資料至 Google Sheet」 按鈕。
檢查結果: 瀏覽器會開啟新分頁並顯示 Success: Data logged successfully.,回到 Google 試算表即可看到測試資料!
測試 HTML
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>GAS 連動測試</title>
</head>
<body>
<h2>ESP32 資料模擬發送測試</h2>
<!-- 請將 action 替換為您的 GAS Web App URL -->
<form action="https://script.google.com/macros/s/AKfycbxd_2kefYMIVpRvR3AWWLnZFpUIuIFlCCD8UqkcJseKVgYjppCwwotazXcDWqhvvO3x/exec
" method="POST" target="_blank">
<label>光敏 AO: <input type="text" name="ao" value="2500"></label><br><br>
<label>光敏 DO: <input type="text" name="do" value="1"></label><br><br>
<label>溫度 Temp: <input type="text" name="temp" value="26.5"></label><br><br>
<label>濕度 Humi: <input type="text" name="humi" value="65.0"></label><br><br>
<button type="submit">發送資料至 Google Sheet</button>
</form>
</body>
</html>
提示詞
GAS與Gsheet端建立測試成功
補充下列需求,進行ESP32 的 Arduino 程式碼編寫。開始寫程式前,提供你的規劃給我確認。
#知識專長
-嵌入式系統與物聯網(IoT)硬體開發專家
#嵌入式系統硬體
##開發板
-NodeMCU-32S 開發板
-Keyes ESP-32 專用 GPIO 擴充板
#開發環境
-Arduino IDE 1.8.19
-ESP32 核心版本 3.1.1
#建立 Station mode wifi 連網
-SSID:chuangHome
-PASSWORD:0988087024
-連網成功後 會檢查網路連線,當網路連線失敗,重新自動建立連線
#感測器讀值
-以非阻斷式每 2 秒讀取光敏AO DO與DHT 溫度 濕度 數值
-將感測器讀值資料記錄至 GoogleSheet 中
-讀值開始開啟內建LED燈,傳送資料完畢關LED燈
#GAS Web App URL
-https://script.google.com/macros/s/AKfycbxd_2kefYMIVpRvR3AWWLnZFpUIuIFlCCD8UqkcJseKVgYjppCwwotazXcDWqhvvO3x/exec
#程式運作將必要的互動追蹤訊息 完整輸出至序列埠
-速率 115200
-序列埠輸出訊息,需簡潔明確,方便開發者追蹤除錯
#程式碼逐行以繁體中文註解
#程式安裝指導說明
#程式若使用第三方函式庫,須提供函式庫下載與安裝的詳細操作
#提供程式燒錄使用的詳細操作
Code
// 引入 ESP32 基礎與網路相關函式庫
#include <WiFi.h> // ESP32 WiFi 連線函式庫
#include <HTTPClient.h> // ESP32 HTTP/HTTPS 傳輸函式庫
#include "DHT.h" // Adafruit DHT 溫濕度感測器函式庫
// ------------------- 網路與雲端設定 -------------------
// WiFi 基地台 SSID 與密碼
const char* ssid = "chuangHome";
const char* password = "0988087024";
// Google Apps Script (GAS) Web App 部署網址
const char* gasUrl = "https://script.google.com/macros/s/AKfycbxd_2kefYMIVpRvR3AWWLnZFpUIuIFlCCD8UqkcJseKVgYjppCwwotazXcDWqhvvO3x/exec";
// ------------------- 硬體腳位與元件設定 -------------------
#define LDR_AO_PIN 34 // 光敏電阻類比輸出 (AO) 連接至 GPIO 34 (ADC1 通道)
#define LDR_DO_PIN 18 // 光敏電阻數位輸出 (DO) 連接至 GPIO 18
#define DHTPIN 4 // DHT11 資料腳位 (DATA) 連接至 GPIO 4
#define DHTTYPE DHT11 // 指定感測器類型為 DHT11
#define LED_PIN 2 // 板載內建 LED 腳位 (NodeMCU-32S 通常為 GPIO 2)
// 初始化 DHT 物件
DHT dht(DHTPIN, DHTTYPE);
// ------------------- 非阻斷式計時器變數 -------------------
unsigned long previousMillis = 0; // 記錄上一次執行的毫秒數
const long interval = 2000; // 設定執行週期時間間隔 (2000 毫秒 = 2 秒)
// ------------------- WiFi 連線檢查與自動重連函式 -------------------
void checkWiFiConnection() {
// 檢查當前 WiFi 連線狀態
if (WiFi.status() != WL_CONNECTED) {
Serial.println("[WiFi] 警告:網絡連線已斷開!嘗試重新連線中...");
WiFi.disconnect(); // 中斷現有連線
WiFi.reconnect(); // 嘗試重新連線
int retryCount = 0;
// 等待連線,最多嘗試 10 次 (約 5 秒)
while (WiFi.status() != WL_CONNECTED && retryCount < 10) {
delay(500);
Serial.print(".");
retryCount++;
}
if (WiFi.status() == WL_CONNECTED) {
Serial.println("\n[WiFi] 自動重新連線成功!");
Serial.print("[WiFi] 目前 IP 位址: ");
Serial.println(WiFi.localIP());
} else {
Serial.println("\n[WiFi] 錯誤:重新連線失敗,將於下個週期再次嘗試。");
}
}
}
// ------------------- 初始化設定 (setup) -------------------
void setup() {
// 初始化序列埠通訊,傳輸速率設定為 115200
Serial.begin(115200);
delay(1000); // 稍微等待序列埠穩定
Serial.println("\n==========================================");
Serial.println(" ESP32 IoT Google Sheet Logger 啟動中");
Serial.println("==========================================");
// 初始化腳位模式
pinMode(LDR_AO_PIN, INPUT); // 設定光敏電阻 AO 為輸入
pinMode(LDR_DO_PIN, INPUT); // 設定光敏電阻 DO 為輸入
pinMode(LED_PIN, OUTPUT); // 設定內建 LED 為輸出
digitalWrite(LED_PIN, LOW); // 預設關閉 LED
// 初始化 DHT11 感測器
dht.begin();
Serial.println("[Sensor] DHT11 感測器初始化完成。");
// 連接 WiFi 網絡
Serial.print("[WiFi] 正在連線至 SSID: ");
Serial.println(ssid);
WiFi.mode(WIFI_STA); // 設定為 Station (STA) 終端模式
WiFi.begin(ssid, password);
// 等待 WiFi 連線
while (WiFi.status() != WL_CONNECTED) {
delay(500);
Serial.print(".");
}
// 顯示連線成功資訊
Serial.println("\n[WiFi] 連線成功!");
Serial.print("[WiFi] 取得 IP 位址: ");
Serial.println(WiFi.localIP());
Serial.println("==========================================\n");
}
// ------------------- 主程式迴圈 (loop) -------------------
void loop() {
// 取得系統當前運行毫秒數
unsigned long currentMillis = millis();
// 非阻斷式檢查:判斷是否已經過了 2 秒間隔
if (currentMillis - previousMillis >= interval) {
previousMillis = currentMillis; // 更新上一次執行時間點
// 1. 檢查 WiFi 狀態
checkWiFiConnection();
// 若網路未連線則跳過本次傳送
if (WiFi.status() != WL_CONNECTED) {
return;
}
// 2. 啟動資料處理流程:點亮內建 LED 指示燈
digitalWrite(LED_PIN, HIGH);
Serial.println("[Status] 開始讀取感測器與資料傳送 (LED 亮起)");
// 3. 讀取光敏電阻與 DHT11 感測器數值
int ldrAO = analogRead(LDR_AO_PIN); // 讀取光敏電阻類比值 (0~4095)
int ldrDO = digitalRead(LDR_DO_PIN); // 讀取光敏電阻數位觸發狀態 (0 或 1)
float temp = dht.readTemperature(); // 讀取攝氏溫度
float humi = dht.readHumidity(); // 讀取相對濕度 (%)
// 檢查 DHT11 是否讀取失敗 (NaN 判斷)
if (isnan(temp) || isnan(humi)) {
Serial.println("[Sensor] 錯誤:無法從 DHT11 感測器讀取數據!");
digitalWrite(LED_PIN, LOW); // 關閉 LED
return;
}
// 輸出讀取到的數據至序列埠
Serial.print("[Sensor] 讀值 -> 光敏AO: ");
Serial.print(ldrAO);
Serial.print(" | 光敏DO: ");
Serial.print(ldrDO);
Serial.print(" | 溫度: ");
Serial.print(temp, 1);
Serial.print(" C | 濕度: ");
Serial.print(humi, 1);
Serial.println(" %");
// 4. 組裝 HTTP POST 數據內容 (URL-Encoded 格式)
String postData = "ao=" + String(ldrAO) +
"&do=" + String(ldrDO) +
"&temp=" + String(temp, 1) +
"&humi=" + String(humi, 1);
// 5. 建立 HTTPClient 物件並發送資料給 Google Apps Script
HTTPClient http;
Serial.println("[HTTP] 連線至 Google Apps Script Web App...");
// 開始 HTTP 連線
http.begin(gasUrl);
// 設定 HTTP 標頭為 Standard Form URL-Encoded
http.addHeader("Content-Type", "application/x-www-form-urlencoded");
// 設定自動追蹤 Google 的 302 重新導向 (Redirect)
http.setFollowRedirects(HTTPC_STRICT_FOLLOW_REDIRECTS);
// 發送 HTTP POST 請求
int httpCode = http.POST(postData);
// 6. 處理伺服器回傳結果
if (httpCode > 0) {
Serial.print("[HTTP] POST 成功,回應代碼: ");
Serial.println(httpCode);
String response = http.getString(); // 取得 GAS 回傳文字
Serial.print("[HTTP] 雲端回應內容: ");
Serial.println(response);
} else {
Serial.print("[HTTP] POST 失敗,錯誤原因: ");
Serial.println(http.errorToString(httpCode).c_str());
}
// 關閉 HTTP 連線
http.end();
// 7. 資料傳送完畢:關閉內建 LED 指示燈
digitalWrite(LED_PIN, LOW);
Serial.println("[Status] 資料處理完成 (LED 關閉)");
Serial.println("------------------------------------------");
}
}
提示詞
程式運作正常,資料也記錄至 GoogleSheet 中
序列埠出現訊息如下,分析問題的原因,提出解決的規劃給我
----------------------------------------------------------------------------
21:06:16.508 -> [HTTP] 連線至 Google Apps Script Web App...
21:06:20.001 -> [HTTP] POST 成功,回應代碼: 400
21:06:20.001 -> [HTTP] 雲端回應內容: <!DOCTYPE html>
21:06:20.001 -> <html lang=en>
21:06:20.048 -> <meta charset=utf-8>
21:06:20.048 -> <meta name=viewport content="initial-scale=1, minimum-scale=1, width=device-width">
21:06:20.048 -> <title>Error 400 (Bad Request)!!1</title>
21:06:20.048 -> <style>
21:06:20.048 -> *{margin:0;padding:0}html,code{font:15px/22px arial,sans-serif}html{background:#fff;color:#222;padding:15px}body{margin:7% auto 0;max-width:390px;min-height:180px;padding:30px 0 15px}* > body{background:url(//www.google.com/images/errors/robot.png) 100% 5px no-repeat;padding-right:205px}p{margin:11px 0 22px;overflow:hidden}ins{color:#777;text-decoration:none}a img{border:0}@media screen and (max-width:772px){body{background:none;margin-top:0;max-width:none;padding-right:0}}#logo{background:url(//www.google.com/images/branding/googlelogo/1x/googlelogo_color_150x54dp.png) no-repeat;margin-left:-5px}@media only screen and (min-resolution:192dpi){#logo{background:url(//www.google.com/images/branding/googlelogo/2x/googlelogo_color_150x54dp.png) no-repeat 0% 0%/100% 100%;-moz-border-image:url(//www.google.com/images/branding/googlelogo/2x/googlelogo_color_150x54dp.png) 0}}@media only screen and (-webkit-min-device-pixel-ratio:2){#logo{background:url(//www.google.com/images/branding/googlelogo/2x/googlelogo_color_150x54dp.png) no-repeat;-webkit-background-size:100% 100%}}#logo{display:inline-block;height:54px;width:150px}
21:06:20.142 -> </style>
21:06:20.142 -> <a href=//www.google.com/><span id=logo aria-label=Google></span></a>
21:06:20.142 -> <p><b>400.</b> <ins>That’s an error.</ins>
21:06:20.142 -> <p>Your client has issued a malformed or illegal request. <ins>That’s all we know.</ins>
-----------------------------------------------------------------------------------
Code
function doGet(e) {
try {
// 1. 開啟名稱為 Data 的工作表
var sheet = SpreadsheetApp.getActiveSpreadsheet().getSheetByName("Data");
if (!sheet) {
return ContentService.createTextOutput("Error: Sheet 'Data' not found.");
}
// 2. 從 GET 網址參數 (Query String) 讀取數據
var aoValue = e.parameter.ao; // 光敏電阻 AO
var doValue = e.parameter.do; // 光敏電阻 DO
var tempValue = e.parameter.temp; // 溫度
var humiValue = e.parameter.humi; // 濕度
// 3. 取得當前伺服器時間(設定為台灣台北時區 GMT+8)
var now = new Date();
var dateStr = Utilities.formatDate(now, "GMT+8", "yyyy-MM-dd");
var timeStr = Utilities.formatDate(now, "GMT+8", "HH:mm:ss");
// 4. 新增資料列至工作表
sheet.appendRow([dateStr, timeStr, aoValue, doValue, tempValue, humiValue]);
// 5. 回傳成功訊息
return ContentService.createTextOutput("Success: Data logged successfully via GET.");
} catch (error) {
return ContentService.createTextOutput("Error: " + error.toString());
}
}
// 保留 doPost 相容性
function doPost(e) {
return doGet(e);
}
提示詞
提出如何驗證 GAS 與 GoogleSheet 成功正常連動的測試
方法一:透過瀏覽器網址列測試(最直覺、最推薦)
由於改用了 GET 帶參數傳輸,瀏覽器網址列本身就是最完美的測試工具。
操作步驟:
開啟新的瀏覽器分頁。
將您的 GAS Web App URL 後面加上測試參數:
https://script.google.com/macros/s/AKfycbxd_2kefYMIVpRvR3AWWLnZFpUIuIFlCCD8UqkcJseKVgYjppCwwotazXcDWqhvvO3x/exec?ao=3000&do=0&temp=28.2&humi=55.0
方法二:透過 HTTPie 線上工具測試(模擬完整的物聯網 API 請求)
若想驗證 HTTP 協定層級與回應碼(HTTP 200):
操作步驟:
開啟 HTTPie Web 網頁版。
左上角選單選擇 GET。
按下右側藍色 「Send」 按鈕
Code
// 引入 ESP32 基礎與網路相關函式庫
#include <WiFi.h> // ESP32 WiFi 連線函式庫
#include <HTTPClient.h> // ESP32 HTTP/HTTPS 傳輸函式庫
#include "DHT.h" // Adafruit DHT 溫濕度感測器函式庫
// ------------------- 網路與雲端設定 -------------------
// WiFi 基地台 SSID 與密碼
const char* ssid = "chuangHome";
const char* password = "0988087024";
// Google Apps Script (GAS) Web App 部署網址
const char* gasUrl = "https://script.google.com/macros/s/AKfycbxd_2kefYMIVpRvR3AWWLnZFpUIuIFlCCD8UqkcJseKVgYjppCwwotazXcDWqhvvO3x/exec";
// ------------------- 硬體腳位與元件設定 -------------------
#define LDR_AO_PIN 34 // 光敏電阻類比輸出 (AO) 連接至 GPIO 34 (ADC1 通道)
#define LDR_DO_PIN 18 // 光敏電阻數位輸出 (DO) 連接至 GPIO 18
#define DHTPIN 4 // DHT11 資料腳位 (DATA) 連接至 GPIO 4
#define DHTTYPE DHT11 // 指定感測器類型為 DHT11
#define LED_PIN 2 // 板載內建 LED 腳位 (NodeMCU-32S 通常為 GPIO 2)
// 初始化 DHT 物件
DHT dht(DHTPIN, DHTTYPE);
// ------------------- 非阻斷式計時器變數 -------------------
unsigned long previousMillis = 0; // 記錄上一次執行的毫秒數
const long interval = 2000; // 設定執行週期時間間隔 (2000 毫秒 = 2 秒)
// ------------------- WiFi 連線檢查與自動重連函式 -------------------
void checkWiFiConnection() {
if (WiFi.status() != WL_CONNECTED) {
Serial.println("[WiFi] 警告:網絡連線已斷開!嘗試重新連線中...");
WiFi.disconnect(); // 中斷現有連線
WiFi.reconnect(); // 嘗試重新連線
int retryCount = 0;
while (WiFi.status() != WL_CONNECTED && retryCount < 10) {
delay(500);
Serial.print(".");
retryCount++;
}
if (WiFi.status() == WL_CONNECTED) {
Serial.println("\n[WiFi] 自動重新連線成功!");
Serial.print("[WiFi] 目前 IP 位址: ");
Serial.println(WiFi.localIP());
} else {
Serial.println("\n[WiFi] 錯誤:重新連線失敗,將於下個週期再次嘗試。");
}
}
}
// ------------------- 初始化設定 (setup) -------------------
void setup() {
Serial.begin(115200);
delay(1000); // 等待序列埠穩定
Serial.println("\n==========================================");
Serial.println(" ESP32 IoT Google Sheet Logger (GET Mode)");
Serial.println("==========================================");
// 初始化腳位模式
pinMode(LDR_AO_PIN, INPUT); // 設定光敏電阻 AO 為輸入
pinMode(LDR_DO_PIN, INPUT); // 設定光敏電阻 DO 為輸入
pinMode(LED_PIN, OUTPUT); // 設定內建 LED 為輸出
digitalWrite(LED_PIN, LOW); // 預設關閉 LED
// 初始化 DHT11 感測器
dht.begin();
Serial.println("[Sensor] DHT11 感測器初始化完成。");
// 連接 WiFi 網絡
Serial.print("[WiFi] 正在連線至 SSID: ");
Serial.println(ssid);
WiFi.mode(WIFI_STA); // 設定為 Station (STA) 模式
WiFi.begin(ssid, password);
// 等待 WiFi 連線
while (WiFi.status() != WL_CONNECTED) {
delay(500);
Serial.print(".");
}
Serial.println("\n[WiFi] 連線成功!");
Serial.print("[WiFi] 取得 IP 位址: ");
Serial.println(WiFi.localIP());
Serial.println("==========================================\n");
}
// ------------------- 主程式迴圈 (loop) -------------------
void loop() {
unsigned long currentMillis = millis();
// 非阻斷式檢查:判斷是否過了 2 秒間隔
if (currentMillis - previousMillis >= interval) {
previousMillis = currentMillis; // 更新執行時間
// 1. 檢查 WiFi 狀態
checkWiFiConnection();
if (WiFi.status() != WL_CONNECTED) {
return;
}
// 2. 啟動資料處理流程:點亮內建 LED
digitalWrite(LED_PIN, HIGH);
Serial.println("[Status] 開始讀取感測器與資料傳送 (LED 亮起)");
// 3. 讀取感測器數值
int ldrAO = analogRead(LDR_AO_PIN); // 光敏電阻類比值 (0~4095)
int ldrDO = digitalRead(LDR_DO_PIN); // 光敏電阻數位狀態 (0 或 1)
float temp = dht.readTemperature(); // 攝氏溫度
float humi = dht.readHumidity(); // 相對濕度 (%)
// 檢查 DHT11 是否讀取失敗
if (isnan(temp) || isnan(humi)) {
Serial.println("[Sensor] 錯誤:無法從 DHT11 感測器讀取數據!");
digitalWrite(LED_PIN, LOW); // 關閉 LED
return;
}
// 序列埠印出感測數據
Serial.print("[Sensor] 讀值 -> 光敏AO: ");
Serial.print(ldrAO);
Serial.print(" | 光敏DO: ");
Serial.print(ldrDO);
Serial.print(" | 溫度: ");
Serial.print(temp, 1);
Serial.print(" C | 濕度: ");
Serial.print(humi, 1);
Serial.println(" %");
// 4. 組裝 HTTP GET 網址與查詢參數 (Query String)
String fullUrl = String(gasUrl) + "?ao=" + String(ldrAO) +
"&do=" + String(ldrDO) +
"&temp=" + String(temp, 1) +
"&humi=" + String(humi, 1);
// 5. 建立 HTTPClient 物件發送 GET 請求
HTTPClient http;
Serial.println("[HTTP] 發送 GET 請求至 Google Apps Script...");
// 開始 HTTP 連線
http.begin(fullUrl);
// 設定自動跟隨 Google 的 302 重新導向 (Redirect)
http.setFollowRedirects(HTTPC_STRICT_FOLLOW_REDIRECTS);
// 發送 HTTP GET 請求
int httpCode = http.GET();
// 6. 處理伺服器回傳結果
if (httpCode > 0) {
Serial.print("[HTTP] 發送成功,回應代碼: ");
Serial.println(httpCode);
String response = http.getString(); // 取得 GAS 回傳內容
Serial.print("[HTTP] 雲端回應內容: ");
Serial.println(response);
} else {
Serial.print("[HTTP] GET 失敗,錯誤原因: ");
Serial.println(http.errorToString(httpCode).c_str());
}
// 關閉 HTTP 連線
http.end();
// 7. 資料傳送完畢:關閉內建 LED
digitalWrite(LED_PIN, LOW);
Serial.println("[Status] 資料處理完成 (LED 關閉)");
Serial.println("------------------------------------------");
}
}
提示詞-1 (不走 GAS,改走 Google Sheets API 規劃)
將目前專案的資料紀錄 更改成不透過 GAS 以ESP32 直連 Sheets API 的方式將感測資料儲存於試算表,提出規劃給我
提示詞-2 (逐步教學)
是的,帶我逐步完整說明實作
這個步驟的目的,是向 Google 申請一個代表 ESP32 的「機器人帳號」,並取得對應的專屬金鑰。
開啟 Google Cloud Console 控制台 並登入您的 Google 帳戶。
點選頂部列的專案選擇下拉選單,點選 「新建專案」(New Project)。
專案名稱輸入 ymiot1151-ESP32GsheetsApi,點選 「建立」。
建立完成後,確保頂部列目前選取的專案是剛建立的 ymiot1151-ESP32GsheetsApi
點選左側選單的 「API 和服務」 > 「程式庫」(Library)。
在搜尋欄輸入 Google Sheets API,找到後點進去,點擊 「啟用」(Enable)。
啟用 API 後,點選左側選單的 「憑證」(Credentials)。
點擊頂部的 「+ 建立憑證」 > 選擇 「服務帳號」(Service Account)。
填寫服務帳號資訊:
服務帳號名稱: 輸入 esp32-logger
點選 「建立並繼續」 > 第二步權限選單可直接略過,點擊 「完成」。
在憑證頁面的「服務帳號」列表中,找到剛建立的帳號,點擊該帳號的 Email 連結(格式類似 esp32-logger@esp32-sheets-direct.iam.gserviceaccount.com)。
切換至上方的 「金鑰」(KEYS)分頁。
點選 「新增金鑰」 > 「建立新金鑰」。
選擇 JSON 格式,點擊 「建立」。
電腦會自動下載一個 .json 檔案。請用記事本開啟這個檔案,我們稍後需要裡面的 client_email 與 private_key!
json
{
"type": "service_account",
"project_id": "ymiot1151-esp32gsheetapi",
"private_key_id": "3d42f7f6c82b7b46a21dc4bd62010d747c995a85",
"private_key": "-----BEGIN PRIVATE KEY-----\nMIIEvwIBADANBgkqhkiG9w0BAQEFAASCBKkwggSlAgEAAoIBAQDY9wOjNadKqMxP\n2InFSt/qRhcfp5xDGde1s39nc3JiSsfwYqJV9xlWdQFmrYfPoS0D34JJUF1/7/tf\njZuoZ9ogU8u3d9rT5B0nRN51UeHD4mm8VLF6TLK/KJjY3TAEWKQS7Yea4IyUCf4P\n9jocNLPr+g7CFzHk4rJ6nMegYatfLhuVQQi0rFEO48e2dK6oV65G0JUtYqdI1P9x\nXsX6WtvmPnKMjTv/5qZFzgCD5ZgxE3YTFWgY2BZeaPuZkEUwxjfuDktsgY3ksQVq\nw2WuRORpNakQ6HbXNv+D1Dga7ztUSga91gEisFvcWgaWd3CoNLyjiAzQR6tcg7nS\nonG3nbYLAgMBAAECggEAarrAqFXpcLmuoAymYU5e7hECqQi7vuqEiZa+MrbA6wdc\naa0rrFw8s6IlSUxOBd6ElWYq4kPNavUz1CKGEwbbb5GpIhysohWSHdIsGMxp4gGM\nACAtmjsGpzCiV/Qe/dRZChKc5ZxWqzBrMZDvrxZh99Bv2Byu56h5IgXj0cqaEdxl\nkAzVKpld0ipl41D2izkSvnoXvSDDckwd8ArxqrY9TrSaqk2lzRooR2bSyUamOq29\nA1V/DnCpAhSQZgFd0Kt7vOVMR+U0mwKNaTHIHc/+VD7UnLBpQKApXxpBl6cEGrnM\ny5wMvOcRbN9LbTjySOdmOmfz5JDT2y6j/4qFhCFz0QKBgQD2KIxekKNao92oWb9J\nx2iLRcyceYT6I3bQleN5e62OwTF9JSa07CytBDO1O9b81FjAyWupsf+alBs7tzte\nGNUTgqFdJ+LDaC2dX3X/Be7v4010fkX97SFXV1AKda6UCP2oNNBur9/3lGVrJYzS\nS2ztba3+qsfSzw5OD6lhbWzFmQKBgQDho6sF3Dsk+FnC6zq0GfmkfQzpN2xfvVch\nPOALcIDpp/5MEcH0uWCpYmOW0gwFq2v+mHx8lPOY81Yvy7PZ2ylZ57aEBynUGdEy\ns1mGIz+2UFR2QxOgrlv+4E/8jo+oSezi4SggkD5oTK6z8SPLpj4Ll8p1FePlMa8j\n8c2QZldXQwKBgQDLeZcWcB7nxL5r88vAU9oiDc9lpLl7/yauosfG2qoLb7zVwojt\nUrBXUDrh25YJpgm7SoP5YM51W2nrgbnMPWCcYQW7uEWk2w+hn5As/x2UI+MZoWt/\ny60cwmrCSqbFbW0nxohohm7u+zaeTArMplZjKJ+D/G1y337mneGYTFh2OQKBgQDX\nqWY6GK/S+yhX/BrErxsDU552fcmwjDjuBNAjPumH6lBNBp/6NwCjhO2o5qQk+x4i\nXj6FmJxnsRTMZAdHj4irbKPu9yXMmsxviCpl4eTwbUD9M8lbx6sYJe9tdhjqgZXP\nl+KRlSyA5eIJDM7ow8EDtZYFaOHEW0OJuICApsCOHwKBgQDguqd1dHOei1tlxRmQ\nW/PaPzyAq7QhqrNN9f2MwW11WJ3oLo7KRf7bV24lSGyGc22CXapOAJzkNsiaBS+C\nqvAJeKlahDChxe93Mojmoeit0ckcIEBEcwvQFN0mTAND5K6yLTaEHkYRXV1GsaKp\nRcV7oI3CgHIA22uo0lfmRIeuww==\n-----END PRIVATE KEY-----\n",
"client_email": "ymiot1151-esp32gsheetapi@ymiot1151-esp32gsheetapi.iam.gserviceaccount.com",
"client_id": "104184552373839353332",
"auth_uri": "https://accounts.google.com/o/oauth2/auth",
"token_uri": "https://oauth2.googleapis.com/token",
"auth_provider_x509_cert_url": "https://www.googleapis.com/oauth2/v1/certs",
"client_x509_cert_url": "https://www.googleapis.com/robot/v1/metadata/x509/ymiot1151-esp32gsheetapi%40ymiot1151-esp32gsheetapi.iam.gserviceaccount.com",
"universe_domain": "googleapis.com"
}
把試算表的存取權限交給剛建立的服務帳號
開啟您的 Google 試算表
點選右上角的藍色 「共用」 按鈕。
在新增人員欄位中,貼上您剛剛服務帳號的 Email(從 JSON 檔裡複製 client_email 的值,ymiot1151-esp32gsheetapi@ymiot1151-esp32gsheetapi.iam.gserviceaccount.com)。
角色權限確保為 「編輯者」(Editor),取消勾選「通知人員」,點選 「共用」(Share)。
複製試算表 ID:
觀察您試算表網址的結構:https://docs.google.com/spreadsheets/d/【這一段字串就是_SPREADSHEET_ID】/edit
將這串 ID 複製並保存至記事本(例如 1A2b3C4d5E6f7G8h9I...)。
https://docs.google.com/spreadsheets/d/1KVEmb6wBXfO2WwKws5OqbLWhuajBstaC11Tua0JpIMQ/edit?gid=0#gid=0
SPREADSHEET_ID:
1KVEmb6wBXfO2WwKws5OqbLWhuajBstaC11Tua0JpIMQ
private_key:
-----BEGIN PRIVATE KEY-----\nMIIEvwIBADANBgkqhkiG9w0BAQEFAASCBKkwggSlAgEAAoIBAQDY9wOjNadKqMxP\n2InFSt/qRhcfp5xDGde1s39nc3JiSsfwYqJV9xlWdQFmrYfPoS0D34JJUF1/7/tf\njZuoZ9ogU8u3d9rT5B0nRN51UeHD4mm8VLF6TLK/KJjY3TAEWKQS7Yea4IyUCf4P\n9jocNLPr+g7CFzHk4rJ6nMegYatfLhuVQQi0rFEO48e2dK6oV65G0JUtYqdI1P9x\nXsX6WtvmPnKMjTv/5qZFzgCD5ZgxE3YTFWgY2BZeaPuZkEUwxjfuDktsgY3ksQVq\nw2WuRORpNakQ6HbXNv+D1Dga7ztUSga91gEisFvcWgaWd3CoNLyjiAzQR6tcg7nS\nonG3nbYLAgMBAAECggEAarrAqFXpcLmuoAymYU5e7hECqQi7vuqEiZa+MrbA6wdc\naa0rrFw8s6IlSUxOBd6ElWYq4kPNavUz1CKGEwbbb5GpIhysohWSHdIsGMxp4gGM\nACAtmjsGpzCiV/Qe/dRZChKc5ZxWqzBrMZDvrxZh99Bv2Byu56h5IgXj0cqaEdxl\nkAzVKpld0ipl41D2izkSvnoXvSDDckwd8ArxqrY9TrSaqk2lzRooR2bSyUamOq29\nA1V/DnCpAhSQZgFd0Kt7vOVMR+U0mwKNaTHIHc/+VD7UnLBpQKApXxpBl6cEGrnM\ny5wMvOcRbN9LbTjySOdmOmfz5JDT2y6j/4qFhCFz0QKBgQD2KIxekKNao92oWb9J\nx2iLRcyceYT6I3bQleN5e62OwTF9JSa07CytBDO1O9b81FjAyWupsf+alBs7tzte\nGNUTgqFdJ+LDaC2dX3X/Be7v4010fkX97SFXV1AKda6UCP2oNNBur9/3lGVrJYzS\nS2ztba3+qsfSzw5OD6lhbWzFmQKBgQDho6sF3Dsk+FnC6zq0GfmkfQzpN2xfvVch\nPOALcIDpp/5MEcH0uWCpYmOW0gwFq2v+mHx8lPOY81Yvy7PZ2ylZ57aEBynUGdEy\ns1mGIz+2UFR2QxOgrlv+4E/8jo+oSezi4SggkD5oTK6z8SPLpj4Ll8p1FePlMa8j\n8c2QZldXQwKBgQDLeZcWcB7nxL5r88vAU9oiDc9lpLl7/yauosfG2qoLb7zVwojt\nUrBXUDrh25YJpgm7SoP5YM51W2nrgbnMPWCcYQW7uEWk2w+hn5As/x2UI+MZoWt/\ny60cwmrCSqbFbW0nxohohm7u+zaeTArMplZjKJ+D/G1y337mneGYTFh2OQKBgQDX\nqWY6GK/S+yhX/BrErxsDU552fcmwjDjuBNAjPumH6lBNBp/6NwCjhO2o5qQk+x4i\nXj6FmJxnsRTMZAdHj4irbKPu9yXMmsxviCpl4eTwbUD9M8lbx6sYJe9tdhjqgZXP\nl+KRlSyA5eIJDM7ow8EDtZYFaOHEW0OJuICApsCOHwKBgQDguqd1dHOei1tlxRmQ\nW/PaPzyAq7QhqrNN9f2MwW11WJ3oLo7KRf7bV24lSGyGc22CXapOAJzkNsiaBS+C\nqvAJeKlahDChxe93Mojmoeit0ckcIEBEcwvQFN0mTAND5K6yLTaEHkYRXV1GsaKp\nRcV7oI3CgHIA22uo0lfmRIeuww==\n-----END PRIVATE KEY-----\n
client_email
ymiot1151-esp32gsheetapi@ymiot1151-esp32gsheetapi.iam.gserviceaccount.com
Code
// 引入 ESP32 基礎、WiFi、NTP 校時與 Google Sheets API 相關函式库
#include <Arduino.h>
#include <WiFi.h>
#include <ESP_Google_Sheet_Client.h> // Mobizt 的 Google Sheets API 核心庫
#include <time.h> // C 語言標準時間庫 (用於 NTP 校時)
#include "DHT.h" // Adafruit DHT 溫濕度感測器庫
// ------------------- 1. 網路與 GCP 帳號憑證設定 -------------------
// WiFi 基地台 SSID 與密碼
const char* ssid = "chuangHome";
const char* password = "0988087024";
// Google 試算表 ID (從網址 https://docs.google.com/spreadsheets/d/【ID】/edit 取得)
#define SPREADSHEET_ID "1KVEmb6wBXfO2WwKws5OqbLWhuajBstaC11Tua0JpIMQ"
// 服務帳號 Email (從 JSON 檔中的 client_email 複製)
#define PROJECT_CLIENT_EMAIL "ymiot1151-esp32gsheetapi@ymiot1151-esp32gsheetapi.iam.gserviceaccount.com"
// 服務帳號私鑰 Private Key (從 JSON 檔中的 private_key 完整複製,包含 \n 與註解)
// 注意:請保留格式,不要刪除 -----BEGIN PRIVATE KEY----- 等文字
const char PRIVATE_KEY[] PROGMEM = "-----BEGIN PRIVATE KEY-----\nMIIEvwIBADANBgkqhkiG9w0BAQEFAASCBKkwggSlAgEAAoIBAQDY9wOjNadKqMxP\n2InFSt/qRhcfp5xDGde1s39nc3JiSsfwYqJV9xlWdQFmrYfPoS0D34JJUF1/7/tf\njZuoZ9ogU8u3d9rT5B0nRN51UeHD4mm8VLF6TLK/KJjY3TAEWKQS7Yea4IyUCf4P\n9jocNLPr+g7CFzHk4rJ6nMegYatfLhuVQQi0rFEO48e2dK6oV65G0JUtYqdI1P9x\nXsX6WtvmPnKMjTv/5qZFzgCD5ZgxE3YTFWgY2BZeaPuZkEUwxjfuDktsgY3ksQVq\nw2WuRORpNakQ6HbXNv+D1Dga7ztUSga91gEisFvcWgaWd3CoNLyjiAzQR6tcg7nS\nonG3nbYLAgMBAAECggEAarrAqFXpcLmuoAymYU5e7hECqQi7vuqEiZa+MrbA6wdc\naa0rrFw8s6IlSUxOBd6ElWYq4kPNavUz1CKGEwbbb5GpIhysohWSHdIsGMxp4gGM\nACAtmjsGpzCiV/Qe/dRZChKc5ZxWqzBrMZDvrxZh99Bv2Byu56h5IgXj0cqaEdxl\nkAzVKpld0ipl41D2izkSvnoXvSDDckwd8ArxqrY9TrSaqk2lzRooR2bSyUamOq29\nA1V/DnCpAhSQZgFd0Kt7vOVMR+U0mwKNaTHIHc/+VD7UnLBpQKApXxpBl6cEGrnM\ny5wMvOcRbN9LbTjySOdmOmfz5JDT2y6j/4qFhCFz0QKBgQD2KIxekKNao92oWb9J\nx2iLRcyceYT6I3bQleN5e62OwTF9JSa07CytBDO1O9b81FjAyWupsf+alBs7tzte\nGNUTgqFdJ+LDaC2dX3X/Be7v4010fkX97SFXV1AKda6UCP2oNNBur9/3lGVrJYzS\nS2ztba3+qsfSzw5OD6lhbWzFmQKBgQDho6sF3Dsk+FnC6zq0GfmkfQzpN2xfvVch\nPOALcIDpp/5MEcH0uWCpYmOW0gwFq2v+mHx8lPOY81Yvy7PZ2ylZ57aEBynUGdEy\ns1mGIz+2UFR2QxOgrlv+4E/8jo+oSezi4SggkD5oTK6z8SPLpj4Ll8p1FePlMa8j\n8c2QZldXQwKBgQDLeZcWcB7nxL5r88vAU9oiDc9lpLl7/yauosfG2qoLb7zVwojt\nUrBXUDrh25YJpgm7SoP5YM51W2nrgbnMPWCcYQW7uEWk2w+hn5As/x2UI+MZoWt/\ny60cwmrCSqbFbW0nxohohm7u+zaeTArMplZjKJ+D/G1y337mneGYTFh2OQKBgQDX\nqWY6GK/S+yhX/BrErxsDU552fcmwjDjuBNAjPumH6lBNBp/6NwCjhO2o5qQk+x4i\nXj6FmJxnsRTMZAdHj4irbKPu9yXMmsxviCpl4eTwbUD9M8lbx6sYJe9tdhjqgZXP\nl+KRlSyA5eIJDM7ow8EDtZYFaOHEW0OJuICApsCOHwKBgQDguqd1dHOei1tlxRmQ\nW/PaPzyAq7QhqrNN9f2MwW11WJ3oLo7KRf7bV24lSGyGc22CXapOAJzkNsiaBS+C\nqvAJeKlahDChxe93Mojmoeit0ckcIEBEcwvQFN0mTAND5K6yLTaEHkYRXV1GsaKp\nRcV7oI3CgHIA22uo0lfmRIeuww==\n-----END PRIVATE KEY-----\n";
// ------------------- 2. 硬體腳位與元件設定 -------------------
#define LDR_AO_PIN 34 // 光敏電阻類比輸出 (AO) -> GPIO 34 (ADC1 通道)
#define LDR_DO_PIN 18 // 光敏電阻數位輸出 (DO) -> GPIO 18
#define DHTPIN 4 // DHT11 資料腳位 (DATA) -> GPIO 4
#define DHTTYPE DHT11 // 指定感測器類型為 DHT11
#define LED_PIN 2 // 板載內建 LED -> GPIO 2
// 初始化 DHT 物件
DHT dht(DHTPIN, DHTTYPE);
// ------------------- 3. 全局變數與非阻斷式計時器 -------------------
unsigned long previousMillis = 0; // 記錄上一次執行的毫秒數
const long interval = 2000; // 執行週期時間間隔 (2000 毫秒 = 2 秒)
// ------------------- 4. NTP 網路校時函式 -------------------
// 直連 API 無法依賴 GAS 補時間,需由 ESP32 連接 NTP 伺服器校準台北時間 (UTC+8)
void configNTPTime() {
Serial.println("[NTP] 正在連線至 NTP 伺服器進行時間校準...");
// 設定時區:台灣為 GMT+8 (8 * 3600 秒 = 28800 秒),日光節約時間為 0
configTime(28800, 0, "pool.ntp.org", "time.nist.gov");
struct tm timeinfo;
int retry = 0;
// 等待取得時間,最多嘗試 10 次
while (!getLocalTime(&timeinfo) && retry < 10) {
delay(500);
Serial.print(".");
retry++;
}
if (retry < 10) {
Serial.println("\n[NTP] 時間校準成功!");
} else {
Serial.println("\n[NTP] 警告:無法取得 NTP 時間,時間戳記可能不準確。");
}
}
// ------------------- 5. 取得當前日期與時間字串 -------------------
void getDateTime(String &dateStr, String &timeStr) {
struct tm timeinfo;
if (getLocalTime(&timeinfo)) {
char dBuf[16], tBuf[16];
strftime(dBuf, sizeof(dBuf), "%Y-%m-%d", &timeinfo);
strftime(tBuf, sizeof(tBuf), "%H:%M:%S", &timeinfo);
dateStr = String(dBuf);
timeStr = String(tBuf);
} else {
dateStr = "N/A";
timeStr = "N/A";
}
}
// ------------------- 6. Google API 驗證狀態回呼函式 -------------------
void tokenStatusCallback(TokenInfo info) {
if (info.status == token_status_ready) {
// JWT 驗證成功,Token 已就緒
} else {
Serial.printf("[Google API Token] Status: %s, Msg: %s\n", GSheet.getTokenStatus(info).c_str(), info.msg.c_str());
}
}
// ------------------- 7. 初始化設定 (setup) -------------------
void setup() {
Serial.begin(115200);
delay(1000);
Serial.println("\n==============================================");
Serial.println(" ESP32 Direct Google Sheets API v4 Logger");
Serial.println("==============================================");
// 初始化腳位模式
pinMode(LDR_AO_PIN, INPUT);
pinMode(LDR_DO_PIN, INPUT);
pinMode(LED_PIN, OUTPUT);
digitalWrite(LED_PIN, LOW);
// 初始化 DHT11
dht.begin();
Serial.println("[Sensor] DHT11 初始化完成。");
// 連接 WiFi 網絡
Serial.print("[WiFi] 正在連線至 SSID: ");
Serial.println(ssid);
WiFi.mode(WIFI_STA);
WiFi.begin(ssid, password);
while (WiFi.status() != WL_CONNECTED) {
delay(500);
Serial.print(".");
}
Serial.println("\n[WiFi] 連線成功!IP: " + WiFi.localIP().toString());
// 連線成功後校準 NTP 時間
configNTPTime();
// 設定 Google API Client 憑證與認證
Serial.println("[GSheet] 初始化 Google Client 憑證與簽署 JWT Token...");
GSheet.setTokenCallback(tokenStatusCallback);
GSheet.begin(PROJECT_CLIENT_EMAIL, PRIVATE_KEY);
Serial.println("==============================================\n");
}
// ------------------- 8. 主程式迴圈 (loop) -------------------
void loop() {
// 保持 Google API Token 定期刷新運作
GSheet.ready();
unsigned long currentMillis = millis();
// 非阻斷式檢查:判斷是否過了 2 秒間隔
if (currentMillis - previousMillis >= interval) {
previousMillis = currentMillis;
// 檢查 WiFi 狀態
if (WiFi.status() != WL_CONNECTED) {
Serial.println("[WiFi] 斷線,嘗試重連中...");
WiFi.reconnect();
return;
}
// 檢查 Google API 是否已完成 JWT 授權準備
if (!GSheet.ready()) {
Serial.println("[GSheet] 等待 Google API Token 準備完畢...");
return;
}
// 1. 點亮內建 LED
digitalWrite(LED_PIN, HIGH);
Serial.println("[Status] 開始讀取感測器並呼叫 Sheets API (LED 亮起)");
// 2. 讀取感測器數值
int ldrAO = analogRead(LDR_AO_PIN);
int ldrDO = digitalRead(LDR_DO_PIN);
float temp = dht.readTemperature();
float humi = dht.readHumidity();
if (isnan(temp) || isnan(humi)) {
Serial.println("[Sensor] 錯誤:無法從 DHT11 讀取數據!");
digitalWrite(LED_PIN, LOW);
return;
}
// 3. 取得目前日期與時間
String dateStr, timeStr;
getDateTime(dateStr, timeStr);
Serial.print("[Sensor] 時間: " + dateStr + " " + timeStr);
Serial.print(" | AO: " + String(ldrAO) + " | DO: " + String(ldrDO));
Serial.println(" | 溫度: " + String(temp, 1) + " C | 濕度: " + String(humi, 1) + " %");
// 4. 組裝 Google Sheets API v4 的 JSON Array 物件
FirebaseJson response;
FirebaseJson valueRange;
// 將資料排成一列 [日期, 時間, 光敏AO, 光敏DO, 溫度, 濕度]
valueRange.add("values/0/[0]", dateStr);
valueRange.add("values/0/[1]", timeStr);
valueRange.add("values/0/[2]", ldrAO);
valueRange.add("values/0/[3]", ldrDO);
valueRange.add("values/0/[4]", temp);
valueRange.add("values/0/[5]", humi);
// 5. 直連發送 API 追加請求 (Append Values 至 Data 工作表)
Serial.println("[GSheet] 發送 HTTPS 追加資料至 Google Sheets API v4...");
// 呼叫 Direct Append 方法寫入 Data 工作表的 A 至 F 欄
bool success = GSheet.values.append(&response, SPREADSHEET_ID, "Data!A:F", &valueRange);
// 6. 處理 API 回應結果
if (success) {
Serial.println("[GSheet] 數據成功直連寫入 Google Sheet!");
} else {
Serial.print("[GSheet] API 寫入失敗,原因: ");
Serial.println(GSheet.errorReason().c_str());
}
// 7. 完成傳輸:關閉內建 LED
digitalWrite(LED_PIN, LOW);
Serial.println("[Status] 資料處理完成 (LED 關閉)");
Serial.println("----------------------------------------------");
}
}
編譯超久,失敗
提示詞 - 1 (編譯錯誤)
編譯錯誤,訊息如下,分析並找出問題並提供解決方案,修改程式前,先提出規畫給我
-----------------------------------------------------------------------------------
C:\Users\USER\AppData\Local\Temp\arduino_modified_sketch_905823\sketch_oct06a.ino: In function 'void tokenStatusCallback(TokenInfo)':
sketch_oct06a:80:105: error: 'TokenInfo' {aka 'struct gauth_token_info_t'} has no member named 'msg'
80 | Serial.printf("[Google API Token] Status: %s, Msg: %s\n", GSheet.getTokenStatus(info).c_str(), info.msg.c_str());
| ^~~
C:\Users\USER\AppData\Local\Temp\arduino_modified_sketch_905823\sketch_oct06a.ino: In function 'void setup()':
sketch_oct06a:122:15: error: no matching function for call to 'ESP_Google_Sheet_Client::begin(const char [74], const char [1709])'
122 | GSheet.begin(PROJECT_CLIENT_EMAIL, PRIVATE_KEY);
| ~~~~~~~~~~~~^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
In file included from C:\Users\USER\AppData\Local\Temp\arduino_modified_sketch_905823\sketch_oct06a.ino:4:
F:\pArduinoV1\myProject\libraries\ESP-Google-Sheet-Client\src/ESP_Google_Sheet_Client.h:1377:10: note: candidate: 'template<class T1, class T2, class T3> void ESP_Google_Sheet_Client::begin(T1, T2, T3, esp_google_sheet_no_eth_module_t*)'
1377 | void begin(T1 client_email, T2 project_id, T3 private_key, ESP8266_SPI_ETH_MODULE *eth = nullptr)
| ^~~~~
F:\pArduinoV1\myProject\libraries\ESP-Google-Sheet-Client\src/ESP_Google_Sheet_Client.h:1377:10: note: candidate expects 3 arguments, 2 provided
F:\pArduinoV1\myProject\libraries\ESP-Google-Sheet-Client\src/ESP_Google_Sheet_Client.h:1393:10: note: candidate: 'template<class T1> void ESP_Google_Sheet_Client::begin(T1, esp_google_sheet_file_storage_type, esp_google_sheet_no_eth_module_t*)'
1393 | void begin(T1 service_account_file, esp_google_sheet_file_storage_type storage_type, ESP8266_SPI_ETH_MODULE *eth = nullptr)
| ^~~~~
F:\pArduinoV1\myProject\libraries\ESP-Google-Sheet-Client\src/ESP_Google_Sheet_Client.h:1393:10: note: template argument deduction/substitution failed:
C:\Users\USER\AppData\Local\Temp\arduino_modified_sketch_905823\sketch_oct06a.ino:122:38: note: cannot convert 'PRIVATE_KEY' (type 'const char [1709]') to type 'esp_google_sheet_file_storage_type'
122 | GSheet.begin(PROJECT_CLIENT_EMAIL, PRIVATE_KEY);
| ^~~~~~~~~~~
-------------------------------------------------------------------------------------
提示詞 - 2
依規劃修正
-輸出修正後的完整 Arduino 程式碼
-詳細逐步指導我完成修正操作
Code
// 引入 ESP32 基礎、WiFi、NTP 校時與 Google Sheets API 相關函式庫
#include <Arduino.h>
#include <WiFi.h>
#include <ESP_Google_Sheet_Client.h> // Mobizt 的 Google Sheets API 核心庫
#include <time.h> // C 語言標準時間庫 (用於 NTP 校時)
#include "DHT.h" // Adafruit DHT 溫濕度感測器庫
// ------------------- 1. 網路與 GCP 帳號憑證設定 -------------------
// WiFi 基地台 SSID 與密碼
const char* ssid = "chuangHome";
const char* password = "0988087024";
// 1-1. GCP 專案 ID (於 GCP 控制台頂部專案選單中可查看到,例如 esp32-sheets-direct-412345)
#define PROJECT_ID "貼上您的_GCP_PROJECT_ID"
// 1-2. Google 試算表 ID (從網址 https://docs.google.com/spreadsheets/d/【ID】/edit 取得)
#define SPREADSHEET_ID "貼上您的_SPREADSHEET_ID"
// 1-3. 服務帳號 Email (從 JSON 檔中的 client_email 複製)
#define PROJECT_CLIENT_EMAIL "esp32-logger@esp32-sheets-direct.iam.gserviceaccount.com"
// 1-4. 服務帳號私鑰 Private Key
// 使用 R"KEY(...)KEY" Raw String 語法,可直接將 JSON 檔內的 private_key 多行內容完整貼入
const char PRIVATE_KEY[] PROGMEM = R"KEY(
-----BEGIN PRIVATE KEY-----
貼上您的 PRIVATE KEY 完整內容 (包含多行換行與等號)
-----END PRIVATE KEY-----
)KEY";
// ------------------- 2. 硬體腳位與元件設定 -------------------
#define LDR_AO_PIN 34 // 光敏電阻類比輸出 (AO) -> GPIO 34 (ADC1 通道)
#define LDR_DO_PIN 18 // 光敏電阻數位輸出 (DO) -> GPIO 18
#define DHTPIN 4 // DHT11 資料腳位 (DATA) -> GPIO 4
#define DHTTYPE DHT11 // 指定感測器類型為 DHT11
#define LED_PIN 2 // 板載內建 LED -> GPIO 2
// 初始化 DHT 物件
DHT dht(DHTPIN, DHTTYPE);
// ------------------- 3. 全局變數與非阻斷式計時器 -------------------
unsigned long previousMillis = 0; // 記錄上一次執行的毫秒數
const long interval = 2000; // 執行週期時間間隔 (2000 毫秒 = 2 秒)
// ------------------- 4. NTP 網路校時函式 -------------------
void configNTPTime() {
Serial.println("[NTP] 正在連線至 NTP 伺服器進行時間校準...");
// 設定時區:台灣為 GMT+8 (8 * 3600 秒 = 28800 秒),日光節約時間為 0
configTime(28800, 0, "pool.ntp.org", "time.nist.gov");
struct tm timeinfo;
int retry = 0;
while (!getLocalTime(&timeinfo) && retry < 10) {
delay(500);
Serial.print(".");
retry++;
}
if (retry < 10) {
Serial.println("\n[NTP] 時間校準成功!");
} else {
Serial.println("\n[NTP] 警告:無法取得 NTP 時間,時間戳記可能不準確。");
}
}
// ------------------- 5. 取得當前日期與時間字串 -------------------
void getDateTime(String &dateStr, String &timeStr) {
struct tm timeinfo;
if (getLocalTime(&timeinfo)) {
char dBuf[16], tBuf[16];
strftime(dBuf, sizeof(dBuf), "%Y-%m-%d", &timeinfo);
strftime(tBuf, sizeof(tBuf), "%H:%M:%S", &timeinfo);
dateStr = String(dBuf);
timeStr = String(tBuf);
} else {
dateStr = "N/A";
timeStr = "N/A";
}
}
// ------------------- 6. Google API 驗證狀態回呼函式 -------------------
void tokenStatusCallback(TokenInfo info) {
if (info.status == token_status_ready) {
// Token 已成功驗證與準備完畢
} else {
// 【修正點】:將原 info.msg 修改為新版 SDK 相容的 info.status_desc
Serial.printf("[Google API Token] Status: %s, Desc: %s\n", GSheet.getTokenStatus(info).c_str(), info.status_desc.c_str());
}
}
// ------------------- 7. 初始化設定 (setup) -------------------
void setup() {
Serial.begin(115200);
delay(1000);
Serial.println("\n==============================================");
Serial.println(" ESP32 Direct Google Sheets API v4 Logger");
Serial.println("==============================================");
// 初始化腳位模式
pinMode(LDR_AO_PIN, INPUT);
pinMode(LDR_DO_PIN, INPUT);
pinMode(LED_PIN, OUTPUT);
digitalWrite(LED_PIN, LOW);
// 初始化 DHT11
dht.begin();
Serial.println("[Sensor] DHT11 初始化完成。");
// 連接 WiFi 網絡
Serial.print("[WiFi] 正在連線至 SSID: ");
Serial.println(ssid);
WiFi.mode(WIFI_STA);
WiFi.begin(ssid, password);
while (WiFi.status() != WL_CONNECTED) {
delay(500);
Serial.print(".");
}
Serial.println("\n[WiFi] 連線成功!IP: " + WiFi.localIP().toString());
// 連線成功後校準 NTP 時間
configNTPTime();
// 設定 Google API Client 憑證與認證
Serial.println("[GSheet] 初始化 Google Client 憑證與簽署 JWT Token...");
GSheet.setTokenCallback(tokenStatusCallback);
// 【修正點】:傳入 3 個參數 (client_email, project_id, private_key)
GSheet.begin(PROJECT_CLIENT_EMAIL, PROJECT_ID, PRIVATE_KEY);
Serial.println("==============================================\n");
}
// ------------------- 8. 主程式迴圈 (loop) -------------------
void loop() {
// 保持 Google API Token 定期刷新運作
GSheet.ready();
unsigned long currentMillis = millis();
// 非阻斷式檢查:判斷是否過了 2 秒間隔
if (currentMillis - previousMillis >= interval) {
previousMillis = currentMillis;
// 檢查 WiFi 狀態
if (WiFi.status() != WL_CONNECTED) {
Serial.println("[WiFi] 斷線,嘗試重連中...");
WiFi.reconnect();
return;
}
// 檢查 Google API 是否已完成 JWT 授權準備
if (!GSheet.ready()) {
Serial.println("[GSheet] 等待 Google API Token 準備完畢...");
return;
}
// 1. 點亮內建 LED
digitalWrite(LED_PIN, HIGH);
Serial.println("[Status] 開始讀取感測器並呼叫 Sheets API (LED 亮起)");
// 2. 讀取感測器數值
int ldrAO = analogRead(LDR_AO_PIN);
int ldrDO = digitalRead(LDR_DO_PIN);
float temp = dht.readTemperature();
float humi = dht.readHumidity();
if (isnan(temp) || isnan(humi)) {
Serial.println("[Sensor] 錯誤:無法從 DHT11 讀取數據!");
digitalWrite(LED_PIN, LOW);
return;
}
// 3. 取得目前日期與時間
String dateStr, timeStr;
getDateTime(dateStr, timeStr);
Serial.print("[Sensor] 時間: " + dateStr + " " + timeStr);
Serial.print(" | AO: " + String(ldrAO) + " | DO: " + String(ldrDO));
Serial.println(" | 溫度: " + String(temp, 1) + " C | 濕度: " + String(humi, 1) + " %");
// 4. 組裝 Google Sheets API v4 的 JSON Array 物件
FirebaseJson response;
FirebaseJson valueRange;
// 將資料排成一列 [日期, 時間, 光敏AO, 光敏DO, 溫度, 濕度]
valueRange.add("values/0/[0]", dateStr);
valueRange.add("values/0/[1]", timeStr);
valueRange.add("values/0/[2]", ldrAO);
valueRange.add("values/0/[3]", ldrDO);
valueRange.add("values/0/[4]", temp);
valueRange.add("values/0/[5]", humi);
// 5. 直連發送 API 追加請求 (Append Values 至 Data 工作表)
Serial.println("[GSheet] 發送 HTTPS 追加資料至 Google Sheets API v4...");
// 呼叫 Direct Append 方法寫入 Data 工作表的 A 至 F 欄
bool success = GSheet.values.append(&response, SPREADSHEET_ID, "Data!A:F", &valueRange);
// 6. 處理 API 回應結果
if (success) {
Serial.println("[GSheet] 數據成功直連寫入 Google Sheet!");
} else {
Serial.print("[GSheet] API 寫入失敗,原因: ");
Serial.println(GSheet.errorReason().c_str());
}
// 7. 完成傳輸:關閉內建 LED
digitalWrite(LED_PIN, LOW);
Serial.println("[Status] 資料處理完成 (LED 關閉)");
Serial.println("----------------------------------------------");
}
}
一、取得專案 ID (Project ID)
點擊畫面最上方藍色頂列的 「專案選擇下拉選單」。
在彈出的視窗中,找到您建立的專案名稱(ymiot1151-ESP32GsheetApi),觀察名稱右側或下方顯示的 「ID」 欄位(ymiot1151-esp32gsheetapi)。
將該字串複製,貼至程式碼第 13 行的 #define PROJECT_ID "您的_GCP_PROJECT_ID" 中。
Project ID
ymiot1151-esp32gsheetapi
二、貼上私鑰 (Private Key)
開啟之前下載的 JSON 金鑰檔案,找到 "private_key" 的內容:
將從 -----BEGIN PRIVATE KEY----- 到 -----END PRIVATE KEY-----\n 的所有文字複製。
直接貼到程式碼中的 const char PRIVATE_KEY[] PROGMEM = R"KEY( 與 )KEY"; 之間。
使用 R"KEY(...)KEY" 語法時,無需手動加入 \n,直接維持原 JSON 內的換行複製即可。
提示詞 (編譯錯誤)
貼上程式碼,修改參數,編譯錯誤,請分析找出問題並提供解決方案,提出規劃給我確認
-------------------------------------------------------------------------------------
C:\Users\User\AppData\Local\Temp\arduino_modified_sketch_491882\sketch_oct06a.ino: In function 'void tokenStatusCallback(TokenInfo)':
sketch_oct06a:86:106: error: 'TokenInfo' {aka 'struct gauth_token_info_t'} has no member named 'status_desc'
86 | Serial.printf("[Google API Token] Status: %s, Desc: %s\n", GSheet.getTokenStatus(info).c_str(), info.status_desc.c_str());
| ^~~~~~~~~~~
exit status 1
Exception in thread "Thread-25" java.lang.NullPointerException
at org.fife.ui.rsyntaxtextarea.TokenMakerBase.addToken(TokenMakerBase.java:136)
at processing.app.syntax.SketchTokenMaker.addToken(SketchTokenMaker.java:73)
at org.fife.ui.rsyntaxtextarea.TokenMakerBase.addToken(TokenMakerBase.java:110)
at org.fife.ui.rsyntaxtextarea.modes.CPlusPlusTokenMaker.addToken(CPlusPlusTokenMaker.java:1868)
at org.fife.ui.rsyntaxtextarea.modes.CPlusPlusTokenMaker.addToken(CPlusPlusTokenMaker.java:1852)
at org.fife.ui.rsyntaxtextarea.modes.CPlusPlusTokenMaker.addToken(CPlusPlusTokenMaker.java:1841)
at org.fife.ui.rsyntaxtextarea.modes.CPlusPlusTokenMaker.yylex(CPlusPlusTokenMaker.java:2287)
at org.fife.ui.rsyntaxtextarea.modes.CPlusPlusTokenMaker.getTokenList(CPlusPlusTokenMaker.java:1919)
at org.fife.ui.rsyntaxtextarea.RSyntaxDocument.getTokenListForLine(RSyntaxDocument.java:431)
at org.fife.ui.rsyntaxtextarea.SyntaxView.getLineWidth(SyntaxView.java:306)
at org.fife.ui.rsyntaxtextarea.SyntaxView.calculateLongestLine(SyntaxView.java:110)
at org.fife.ui.rsyntaxtextarea.SyntaxView.updateMetrics(SyntaxView.java:903)
at org.fife.ui.rsyntaxtextarea.SyntaxView.setSize(SyntaxView.java:811)
at javax.swing.plaf.basic.BasicTextUI$RootView.setSize(BasicTextUI.java:1722)
at javax.swing.plaf.basic.BasicTextUI.modelToView(BasicTextUI.java:1046)
at javax.swing.plaf.basic.BasicTextUI.modelToView(BasicTextUI.java:1022)
at javax.swing.text.JTextComponent.modelToView(JTextComponent.java:1377)
at org.fife.ui.rtextarea.RTextAreaBase.possiblyUpdateCurrentLineHighlightLocation(RTextAreaBase.java:778)
at org.fife.ui.rtextarea.RTextArea.fireCaretUpdate(RTextArea.java:614)
at org.fife.ui.rsyntaxtextarea.RSyntaxTextArea.fireCaretUpdate(RSyntaxTextArea.java:835)
at javax.swing.text.JTextComponent$MutableCaretEvent.fire(JTextComponent.java:4394)
at javax.swing.text.JTextComponent$MutableCaretEvent.stateChanged(JTextComponent.java:4416)
at javax.swing.text.DefaultCaret.fireStateChanged(DefaultCaret.java:802)
at javax.swing.text.DefaultCaret.changeCaretPosition(DefaultCaret.java:1274)
at javax.swing.text.DefaultCaret.handleSetDot(DefaultCaret.java:1173)
at javax.swing.text.DefaultCaret$DefaultFilterBypass.setDot(DefaultCaret.java:1913)
at javax.swing.text.NavigationFilter.setDot(NavigationFilter.java:64)
at org.fife.ui.rtextarea.ConfigurableCaret$FoldAwareNavigationFilter.setDot(ConfigurableCaret.java:724)
at javax.swing.text.DefaultCaret.setDot(DefaultCaret.java:1151)
at javax.swing.text.DefaultCaret.setDot(DefaultCaret.java:1051)
at javax.swing.text.JTextComponent.setCaretPosition(JTextComponent.java:1631)
at processing.app.Editor.addLineHighlight(Editor.java:1671)
at processing.app.Editor.statusError(Editor.java:2552)
at processing.app.Editor$BuildHandler.run(Editor.java:1646)
at java.lang.Thread.run(Thread.java:748)
-------------------------------------------------------------------------------------
Code
// 引入 ESP32 基礎、WiFi、NTP 校時與 Google Sheets API 相關函式庫
#include <Arduino.h>
#include <WiFi.h>
#include <ESP_Google_Sheet_Client.h> // Mobizt 的 Google Sheets API 核心庫
#include <time.h> // C 語言標準時間庫 (用於 NTP 校時)
#include "DHT.h" // Adafruit DHT 溫濕度感測器庫
// ------------------- 1. 網路與 GCP 帳號憑證設定 -------------------
// WiFi 基地台 SSID 與密碼
const char* ssid = "chuangHome";
const char* password = "0988087024";
// 1-1. GCP 專案 ID
#define PROJECT_ID "ymiot1151-esp32gsheetapi"
// 1-2. Google 試算表 ID
#define SPREADSHEET_ID "1KVEmb6wBXfO2WwKws5OqbLWhuajBstaC11Tua0JpIMQ"
// 1-3. 服務帳號 Email
#define PROJECT_CLIENT_EMAIL "ymiot1151-esp32gsheetapi@ymiot1151-esp32gsheetapi.iam.gserviceaccount.com"
// 1-4. 服務帳號私鑰 Private Key (改用雙引號轉義格式,防範 Arduino IDE Java 語法高亮崩潰)
const char PRIVATE_KEY[] PROGMEM = "-----BEGIN PRIVATE KEY-----\nMIIEvwIBADANBgkqhkiG9w0BAQEFAASCBKkwggSlAgEAAoIBAQDY9wOjNadKqMxP\n2InFSt/qRhcfp5xDGde1s39nc3JiSsfwYqJV9xlWdQFmrYfPoS0D34JJUF1/7/tf\njZuoZ9ogU8u3d9rT5B0nRN51UeHD4mm8VLF6TLK/KJjY3TAEWKQS7Yea4IyUCf4P\n9jocNLPr+g7CFzHk4rJ6nMegYatfLhuVQQi0rFEO48e2dK6oV65G0JUtYqdI1P9x\nXsX6WtvmPnKMjTv/5qZFzgCD5ZgxE3YTFWgY2BZeaPuZkEUwxjfuDktsgY3ksQVq\nw2WuRORpNakQ6HbXNv+D1Dga7ztUSga91gEisFvcWgaWd3CoNLyjiAzQR6tcg7nS\nonG3nbYLAgMBAAECggEAarrAqFXpcLmuoAymYU5e7hECqQi7vuqEiZa+MrbA6wdc\naa0rrFw8s6IlSUxOBd6ElWYq4kPNavUz1CKGEwbbb5GpIhysohWSHdIsGMxp4gGM\nACAtmjsGpzCiV/Qe/dRZChKc5ZxWqzBrMZDvrxZh99Bv2Byu56h5IgXj0cqaEdxl\nkAzVKpld0ipl41D2izkSvnoXvSDDckwd8ArxqrY9TrSaqk2lzRooR2bSyUamOq29\nA1V/DnCpAhSQZgFd0Kt7vOVMR+U0mwKNaTHIHc/+VD7UnLBpQKApXxpBl6cEGrnM\ny5wMvOcRbN9LbTjySOdmOmfz5JDT2y6j/4qFhCFz0QKBgQD2KIxekKNao92oWb9J\nx2iLRcyceYT6I3bQleN5e62OwTF9JSa07CytBDO1O9b81FjAyWupsf+alBs7tzte\nGNUTgqFdJ+LDaC2dX3X/Be7v4010fkX97SFXV1AKda6UCP2oNNBur9/3lGVrJYzS\nS2ztba3+qsfSzw5OD6lhbWzFmQKBgQDho6sF3Dsk+FnC6zq0GfmkfQzpN2xfvVch\nPOALcIDpp/5MEcH0uWCpYmOW0gwFq2v+mHx8lPOY81Yvy7PZ2ylZ57aEBynUGdEy\ns1mGIz+2UFR2QxOgrlv+4E/8jo+oSezi4SggkD5oTK6z8SPLpj4Ll8p1FePlMa8j\n8c2QZldXQwKBgQDLeZcWcB7nxL5r88vAU9oiDc9lpLl7/yauosfG2qoLb7zVwojt\nUrBXUDrh25YJpgm7SoP5YM51W2nrgbnMPWCcYQW7uEWk2w+hn5As/x2UI+MZoWt/\ny60cwmrCSqbFbW0nxohohm7u+zaeTArMplZjKJ+D/G1y337mneGYTFh2OQKBgQDX\nqWY6GK/S+yhX/BrErxsDU552fcmwjDjuBNAjPumH6lBNBp/6NwCjhO2o5qQk+x4i\nXj6FmJxnsRTMZAdHj4irbKPu9yXMmsxviCpl4eTwbUD9M8lbx6sYJe9tdhjqgZXP\nl+KRlSyA5eIJDM7ow8EDtZYFaOHEW0OJuICApsCOHwKBgQDguqd1dHOei1tlxRmQ\nW/PaPzyAq7QhqrNN9f2MwW11WJ3oLo7KRf7bV24lSGyGc22CXapOAJzkNsiaBS+C\nqvAJeKlahDChxe93Mojmoeit0ckcIEBEcwvQFN0mTAND5K6yLTaEHkYRXV1GsaKp\nRcV7oI3CgHIA22uo0lfmRIeuww==\n-----END PRIVATE KEY-----\n";
// ------------------- 2. 硬體腳位與元件設定 -------------------
#define LDR_AO_PIN 34 // 光敏電阻類比輸出 (AO) -> GPIO 34 (ADC1 通道)
#define LDR_DO_PIN 18 // 光敏電阻數位輸出 (DO) -> GPIO 18
#define DHTPIN 4 // DHT11 資料腳位 (DATA) -> GPIO 4
#define DHTTYPE DHT11 // 指定感測器類型為 DHT11
#define LED_PIN 2 // 板載內建 LED -> GPIO 2
// 初始化 DHT 物件
DHT dht(DHTPIN, DHTTYPE);
// ------------------- 3. 全局變數與非阻斷式計時器 -------------------
unsigned long previousMillis = 0; // 記錄上一次執行的毫秒數
const long interval = 2000; // 執行週期時間間隔 (2000 毫秒 = 2 秒)
// ------------------- 4. NTP 網路校時函式 -------------------
void configNTPTime() {
Serial.println("[NTP] 正在連線至 NTP 伺服器進行時間校準...");
configTime(28800, 0, "pool.ntp.org", "time.nist.gov");
struct tm timeinfo;
int retry = 0;
while (!getLocalTime(&timeinfo) && retry < 10) {
delay(500);
Serial.print(".");
retry++;
}
if (retry < 10) {
Serial.println("\n[NTP] 時間校準成功!");
} else {
Serial.println("\n[NTP] 警告:無法取得 NTP 時間,時間戳記可能不準確。");
}
}
// ------------------- 5. 取得當前日期與時間字串 -------------------
void getDateTime(String &dateStr, String &timeStr) {
struct tm timeinfo;
if (getLocalTime(&timeinfo)) {
char dBuf[16], tBuf[16];
strftime(dBuf, sizeof(dBuf), "%Y-%m-%d", &timeinfo);
strftime(tBuf, sizeof(tBuf), "%H:%M:%S", &timeinfo);
dateStr = String(dBuf);
timeStr = String(tBuf);
} else {
dateStr = "N/A";
timeStr = "N/A";
}
}
// ------------------- 6. Google API 驗證狀態回呼函式 -------------------
void tokenStatusCallback(TokenInfo info) {
if (info.status == token_status_ready) {
// Token 已成功驗證與準備完畢
} else {
// 【修復點】:僅使用庫官方標準提供的 getTokenStatus() 回傳字串,不再存取結構體成員
Serial.printf("[Google API Token] Status: %s\n", GSheet.getTokenStatus(info).c_str());
}
}
// ------------------- 7. 初始化設定 (setup) -------------------
void setup() {
Serial.begin(115200);
delay(1000);
Serial.println("\n==============================================");
Serial.println(" ESP32 Direct Google Sheets API v4 Logger");
Serial.println("==============================================");
// 初始化腳位模式
pinMode(LDR_AO_PIN, INPUT);
pinMode(LDR_DO_PIN, INPUT);
pinMode(LED_PIN, OUTPUT);
digitalWrite(LED_PIN, LOW);
// 初始化 DHT11
dht.begin();
Serial.println("[Sensor] DHT11 初始化完成。");
// 連接 WiFi 網絡
Serial.print("[WiFi] 正在連線至 SSID: ");
Serial.println(ssid);
WiFi.mode(WIFI_STA);
WiFi.begin(ssid, password);
while (WiFi.status() != WL_CONNECTED) {
delay(500);
Serial.print(".");
}
Serial.println("\n[WiFi] 連線成功!IP: " + WiFi.localIP().toString());
// 連線成功後校準 NTP 時間
configNTPTime();
// 設定 Google API Client 憑證與認證
Serial.println("[GSheet] 初始化 Google Client 憑證與簽署 JWT Token...");
GSheet.setTokenCallback(tokenStatusCallback);
// 傳入 3 個參數:client_email, project_id, private_key
GSheet.begin(PROJECT_CLIENT_EMAIL, PROJECT_ID, PRIVATE_KEY);
Serial.println("==============================================\n");
}
// ------------------- 8. 主程式迴圈 (loop) -------------------
void loop() {
// 保持 Google API Token 定期刷新運作
GSheet.ready();
unsigned long currentMillis = millis();
// 非阻斷式檢查:判斷是否過了 2 秒間隔
if (currentMillis - previousMillis >= interval) {
previousMillis = currentMillis;
// 檢查 WiFi 狀態
if (WiFi.status() != WL_CONNECTED) {
Serial.println("[WiFi] 斷線,嘗試重連中...");
WiFi.reconnect();
return;
}
// 檢查 Google API 是否已完成 JWT 授權準備
if (!GSheet.ready()) {
Serial.println("[GSheet] 等待 Google API Token 準備完畢...");
return;
}
// 1. 點亮內建 LED
digitalWrite(LED_PIN, HIGH);
Serial.println("[Status] 開始讀取感測器並呼叫 Sheets API (LED 亮起)");
// 2. 讀取感測器數值
int ldrAO = analogRead(LDR_AO_PIN);
int ldrDO = digitalRead(LDR_DO_PIN);
float temp = dht.readTemperature();
float humi = dht.readHumidity();
if (isnan(temp) || isnan(humi)) {
Serial.println("[Sensor] 錯誤:無法從 DHT11 讀取數據!");
digitalWrite(LED_PIN, LOW);
return;
}
// 3. 取得目前日期與時間
String dateStr, timeStr;
getDateTime(dateStr, timeStr);
Serial.print("[Sensor] 時間: " + dateStr + " " + timeStr);
Serial.print(" | AO: " + String(ldrAO) + " | DO: " + String(ldrDO));
Serial.println(" | 溫度: " + String(temp, 1) + " C | 濕度: " + String(humi, 1) + " %");
// 4. 組裝 Google Sheets API v4 的 JSON Array 物件
FirebaseJson response;
FirebaseJson valueRange;
// 將資料排成一列 [日期, 時間, 光敏AO, 光敏DO, 溫度, 濕度]
valueRange.add("values/0/[0]", dateStr);
valueRange.add("values/0/[1]", timeStr);
valueRange.add("values/0/[2]", ldrAO);
valueRange.add("values/0/[3]", ldrDO);
valueRange.add("values/0/[4]", temp);
valueRange.add("values/0/[5]", humi);
// 5. 直連發送 API 追加請求 (Append Values 至 Data 工作表)
Serial.println("[GSheet] 發送 HTTPS 追加資料至 Google Sheets API v4...");
// 呼叫 Direct Append 方法寫入 Data 工作表的 A 至 F 欄
bool success = GSheet.values.append(&response, SPREADSHEET_ID, "Data!A:F", &valueRange);
// 6. 處理 API 回應結果
if (success) {
Serial.println("[GSheet] 數據成功直連寫入 Google Sheet!");
} else {
Serial.print("[GSheet] API 寫入失敗,原因: ");
Serial.println(GSheet.errorReason().c_str());
}
// 7. 完成傳輸:關閉內建 LED
digitalWrite(LED_PIN, LOW);
Serial.println("[Status] 資料處理完成 (LED 關閉)");
Serial.println("----------------------------------------------");
}
}
編譯超久,成功
提示詞 (執行錯誤)
燒錄上傳成功,執行出現錯誤,訊息如下。
-分析問題並提出解決方法
-先提規劃給我確認
---------------------------------------------------------------------------
[Status] 開始讀取感測器並呼叫 Sheets API (LED 亮起)
[Sensor] 時間: 2026-10-06 20:23:29 | AO: 1643 | DO: 0 | 溫度: 28.3 C | 濕度: 64.0 %
[GSheet] 發送 HTTPS 追加資料至 Google Sheets API v4...
[GSheet] API 寫入失敗,原因: Invalid JSON payload received. Unknown name \"values/0/[0]\" at 'data': Cannot find field.\nInvalid JSON payload received. Unknown name \"values/0/[1]\" at 'data': Cannot find field.\nInvalid JSON payload received. Unknown name \"values/0/[2]\" at 'data': Cannot find field.\nInvalid JSON payload received. Unknown name \"values/0/[3]\" at 'data': Cannot find field.\nInvalid JSON payload received. Unknown name \"values/0/[4]\" at 'data': Cannot find field.\nInvalid JSON payload received. Unknown name \"values/0/[5]\" at 'data': Cannot find field.
[Status] 資料處理完成 (LED 關閉)
---------------------------------------------------------------------------------
Code
// 引入 ESP32 基礎、WiFi、NTP 校時與 Google Sheets API 相關函式庫
#include <Arduino.h>
#include <WiFi.h>
#include <ESP_Google_Sheet_Client.h> // Mobizt 的 Google Sheets API 核心庫
#include <time.h> // C 語言標準時間庫 (用於 NTP 校時)
#include "DHT.h" // Adafruit DHT 溫濕度感測器庫
// ------------------- 1. 網路與 GCP 帳號憑證設定 -------------------
// WiFi 基地台 SSID 與密碼
const char* ssid = "chuangHome";
const char* password = "0988087024";
// 1-1. GCP 專案 ID
#define PROJECT_ID "ymiot1151-esp32gsheetapi"
// 1-2. Google 試算表 ID
#define SPREADSHEET_ID "1KVEmb6wBXfO2WwKws5OqbLWhuajBstaC11Tua0JpIMQ"
// 1-3. 服務帳號 Email
#define PROJECT_CLIENT_EMAIL "ymiot1151-esp32gsheetapi@ymiot1151-esp32gsheetapi.iam.gserviceaccount.com"
// 1-4. 服務帳號私鑰 Private Key
const char PRIVATE_KEY[] PROGMEM = "-----BEGIN PRIVATE KEY-----\nMIIEvwIBADANBgkqhkiG9w0BAQEFAASCBKkwggSlAgEAAoIBAQDY9wOjNadKqMxP\n2InFSt/qRhcfp5xDGde1s39nc3JiSsfwYqJV9xlWdQFmrYfPoS0D34JJUF1/7/tf\njZuoZ9ogU8u3d9rT5B0nRN51UeHD4mm8VLF6TLK/KJjY3TAEWKQS7Yea4IyUCf4P\n9jocNLPr+g7CFzHk4rJ6nMegYatfLhuVQQi0rFEO48e2dK6oV65G0JUtYqdI1P9x\nXsX6WtvmPnKMjTv/5qZFzgCD5ZgxE3YTFWgY2BZeaPuZkEUwxjfuDktsgY3ksQVq\nw2WuRORpNakQ6HbXNv+D1Dga7ztUSga91gEisFvcWgaWd3CoNLyjiAzQR6tcg7nS\nonG3nbYLAgMBAAECggEAarrAqFXpcLmuoAymYU5e7hECqQi7vuqEiZa+MrbA6wdc\naa0rrFw8s6IlSUxOBd6ElWYq4kPNavUz1CKGEwbbb5GpIhysohWSHdIsGMxp4gGM\nACAtmjsGpzCiV/Qe/dRZChKc5ZxWqzBrMZDvrxZh99Bv2Byu56h5IgXj0cqaEdxl\nkAzVKpld0ipl41D2izkSvnoXvSDDckwd8ArxqrY9TrSaqk2lzRooR2bSyUamOq29\nA1V/DnCpAhSQZgFd0Kt7vOVMR+U0mwKNaTHIHc/+VD7UnLBpQKApXxpBl6cEGrnM\ny5wMvOcRbN9LbTjySOdmOmfz5JDT2y6j/4qFhCFz0QKBgQD2KIxekKNao92oWb9J\nx2iLRcyceYT6I3bQleN5e62OwTF9JSa07CytBDO1O9b81FjAyWupsf+alBs7tzte\nGNUTgqFdJ+LDaC2dX3X/Be7v4010fkX97SFXV1AKda6UCP2oNNBur9/3lGVrJYzS\nS2ztba3+qsfSzw5OD6lhbWzFmQKBgQDho6sF3Dsk+FnC6zq0GfmkfQzpN2xfvVch\nPOALcIDpp/5MEcH0uWCpYmOW0gwFq2v+mHx8lPOY81Yvy7PZ2ylZ57aEBynUGdEy\ns1mGIz+2UFR2QxOgrlv+4E/8jo+oSezi4SggkD5oTK6z8SPLpj4Ll8p1FePlMa8j\n8c2QZldXQwKBgQDLeZcWcB7nxL5r88vAU9oiDc9lpLl7/yauosfG2qoLb7zVwojt\nUrBXUDrh25YJpgm7SoP5YM51W2nrgbnMPWCcYQW7uEWk2w+hn5As/x2UI+MZoWt/\ny60cwmrCSqbFbW0nxohohm7u+zaeTArMplZjKJ+D/G1y337mneGYTFh2OQKBgQDX\nqWY6GK/S+yhX/BrErxsDU552fcmwjDjuBNAjPumH6lBNBp/6NwCjhO2o5qQk+x4i\nXj6FmJxnsRTMZAdHj4irbKPu9yXMmsxviCpl4eTwbUD9M8lbx6sYJe9tdhjqgZXP\nl+KRlSyA5eIJDM7ow8EDtZYFaOHEW0OJuICApsCOHwKBgQDguqd1dHOei1tlxRmQ\nW/PaPzyAq7QhqrNN9f2MwW11WJ3oLo7KRf7bV24lSGyGc22CXapOAJzkNsiaBS+C\nqvAJeKlahDChxe93Mojmoeit0ckcIEBEcwvQFN0mTAND5K6yLTaEHkYRXV1GsaKp\nRcV7oI3CgHIA22uo0lfmRIeuww==\n-----END PRIVATE KEY-----\n";
// ------------------- 2. 硬體腳位與元件設定 -------------------
#define LDR_AO_PIN 34 // 光敏電阻類比輸出 (AO) -> GPIO 34 (ADC1 通道)
#define LDR_DO_PIN 18 // 光敏電阻數位輸出 (DO) -> GPIO 18
#define DHTPIN 4 // DHT11 資料腳位 (DATA) -> GPIO 4
#define DHTTYPE DHT11 // 指定感測器類型為 DHT11
#define LED_PIN 2 // 板載內建 LED -> GPIO 2
// 初始化 DHT 物件
DHT dht(DHTPIN, DHTTYPE);
// ------------------- 3. 全局變數與非阻斷式計時器 -------------------
unsigned long previousMillis = 0; // 記錄上一次執行的毫秒數
const long interval = 2000; // 執行週期時間間隔 (2000 毫秒 = 2 秒)
// ------------------- 4. NTP 網路校時函式 -------------------
void configNTPTime() {
Serial.println("[NTP] 正在連線至 NTP 伺服器進行時間校準...");
configTime(28800, 0, "pool.ntp.org", "time.nist.gov");
struct tm timeinfo;
int retry = 0;
while (!getLocalTime(&timeinfo) && retry < 10) {
delay(500);
Serial.print(".");
retry++;
}
if (retry < 10) {
Serial.println("\n[NTP] 時間校準成功!");
} else {
Serial.println("\n[NTP] 警告:無法取得 NTP 時間,時間戳記可能不準確。");
}
}
// ------------------- 5. 取得當前日期與時間字串 -------------------
void getDateTime(String &dateStr, String &timeStr) {
struct tm timeinfo;
if (getLocalTime(&timeinfo)) {
char dBuf[16], tBuf[16];
strftime(dBuf, sizeof(dBuf), "%Y-%m-%d", &timeinfo);
strftime(tBuf, sizeof(tBuf), "%H:%M:%S", &timeinfo);
dateStr = String(dBuf);
timeStr = String(tBuf);
} else {
dateStr = "N/A";
timeStr = "N/A";
}
}
// ------------------- 6. Google API 驗證狀態回呼函式 -------------------
void tokenStatusCallback(TokenInfo info) {
if (info.status == token_status_ready) {
// Token 已成功驗證與準備完畢
} else {
Serial.printf("[Google API Token] Status: %s\n", GSheet.getTokenStatus(info).c_str());
}
}
// ------------------- 7. 初始化設定 (setup) -------------------
void setup() {
Serial.begin(115200);
delay(1000);
Serial.println("\n==============================================");
Serial.println(" ESP32 Direct Google Sheets API v4 Logger");
Serial.println("==============================================");
// 初始化腳位模式
pinMode(LDR_AO_PIN, INPUT);
pinMode(LDR_DO_PIN, INPUT);
pinMode(LED_PIN, OUTPUT);
digitalWrite(LED_PIN, LOW);
// 初始化 DHT11
dht.begin();
Serial.println("[Sensor] DHT11 初始化完成。");
// 連接 WiFi 網絡
Serial.print("[WiFi] 正在連線至 SSID: ");
Serial.println(ssid);
WiFi.mode(WIFI_STA);
WiFi.begin(ssid, password);
while (WiFi.status() != WL_CONNECTED) {
delay(500);
Serial.print(".");
}
Serial.println("\n[WiFi] 連線成功!IP: " + WiFi.localIP().toString());
// 連線成功後校準 NTP 時間
configNTPTime();
// 設定 Google API Client 憑證與認證
Serial.println("[GSheet] 初始化 Google Client 憑證與簽署 JWT Token...");
GSheet.setTokenCallback(tokenStatusCallback);
// 傳入 3 個參數:client_email, project_id, private_key
GSheet.begin(PROJECT_CLIENT_EMAIL, PROJECT_ID, PRIVATE_KEY);
Serial.println("==============================================\n");
}
// ------------------- 8. 主程式迴圈 (loop) -------------------
void loop() {
// 保持 Google API Token 定期刷新運作
GSheet.ready();
unsigned long currentMillis = millis();
// 非阻斷式檢查:判斷是否過了 2 秒間隔
if (currentMillis - previousMillis >= interval) {
previousMillis = currentMillis;
// 檢查 WiFi 狀態
if (WiFi.status() != WL_CONNECTED) {
Serial.println("[WiFi] 斷線,嘗試重連中...");
WiFi.reconnect();
return;
}
// 檢查 Google API 是否已完成 JWT 授權準備
if (!GSheet.ready()) {
Serial.println("[GSheet] 等待 Google API Token 準備完畢...");
return;
}
// 1. 點亮內建 LED
digitalWrite(LED_PIN, HIGH);
Serial.println("[Status] 開始讀取感測器並呼叫 Sheets API (LED 亮起)");
// 2. 讀取感測器數值
int ldrAO = analogRead(LDR_AO_PIN);
int ldrDO = digitalRead(LDR_DO_PIN);
float temp = dht.readTemperature();
float humi = dht.readHumidity();
if (isnan(temp) || isnan(humi)) {
Serial.println("[Sensor] 錯誤:無法從 DHT11 讀取數據!");
digitalWrite(LED_PIN, LOW);
return;
}
// 3. 取得目前日期與時間
String dateStr, timeStr;
getDateTime(dateStr, timeStr);
Serial.print("[Sensor] 時間: " + dateStr + " " + timeStr);
Serial.print(" | AO: " + String(ldrAO) + " | DO: " + String(ldrDO));
Serial.println(" | 溫度: " + String(temp, 1) + " C | 濕度: " + String(humi, 1) + " %");
// 4. 【核心修復】:建立標準的 Google Sheets 2維陣列 JSON 結構
FirebaseJsonArray row;
row.add(dateStr);
row.add(timeStr);
row.add(ldrAO);
row.add(ldrDO);
row.add(temp);
row.add(humi);
FirebaseJsonArray rows;
rows.add(row);
FirebaseJson valueRange;
valueRange.add("values", rows);
// 5. 直連發送 API 追加請求 (Append Values 至 Data 工作表)
FirebaseJson response;
Serial.println("[GSheet] 發送 HTTPS 追加資料至 Google Sheets API v4...");
// 呼叫 Direct Append 方法寫入 Data 工作表的 A 至 F 欄
bool success = GSheet.values.append(&response, SPREADSHEET_ID, "Data!A:F", &valueRange);
// 6. 處理 API 回應結果
if (success) {
Serial.println("[GSheet] 數據成功直連寫入 Google Sheet!");
} else {
Serial.print("[GSheet] API 寫入失敗,原因: ");
Serial.println(GSheet.errorReason().c_str());
}
// 7. 完成傳輸:關閉內建 LED
digitalWrite(LED_PIN, LOW);
Serial.println("[Status] 資料處理完成 (LED 關閉)");
Serial.println("----------------------------------------------");
}
}
反應快,約 2 秒。by GAS 4 秒
穩定運作 1.0 hrs
提示詞 (LINE 雙向)
程式運作正常,謝謝
提示詞
我要進一步延伸這專案的應用,我的需求如下,提供規劃給我
#當感測到數值達警戒值(如溫度80度),傳送警戒訊息與感測資料到LINE
#從LINE接收指令來控制ESP GPIO
提示詞
會使用GAS當中繼,那我們先回到前面 [ ESP32 ] ──> [ GAS Web App ]──>[GoogleSheet]的版本中。確認可以從那版本繼續往下延伸,請回答"OK"
提示詞
那開始繼續延伸,我的需求如下。請先提供規劃給我
#當感測到數值達警戒值(如溫度80度),除繼續將資料紀錄於Google試算表外,傳送警戒訊息與感測資料到LINE
#從LINE接收指令來控制ESP GPIO
提示詞
開始第一階段「LINE 憑證取得」,逐步完整說明帶我實作
這個階段我們要申請一個免費的 LINE 官方帳號 (Messaging API Channel),並取得 兩個關鍵憑證:
Channel Access Token(讓 GAS 代表官方帳號發送訊息給您)
Your User ID(讓 GAS 知道要將警報訊息推播給「誰」)
1 開啟 LINE Developers Console 控制台 並以您的個人 LINE 帳號登入。
2 建立 Provider(提供者):
若您是第一次使用,點擊頁面上的 「Create a new provider」。
輸入名稱(例如:My-IoT-Projects),點擊 「Create」。
3 建立LINE官方帳號(相當於 channel):
在剛建立的 Provider 頁面中,點選 「Create a Messaging API channel」。
點擊 Create a LINE Official Account (會進入 LINE 官方帳號管理頁面)
建立 LINE官方帳號
帳號名稱: 1151進擊創客的~
電子郵件: 輸入您的 Email
公司/業種(Category)
確認
啟用 Messaging API:
點擊右上角的 「設定 (Settings)」 > 在左側選單點選 「Messaging API」 > 點擊中間的 「啟用 Messaging API (Enable Messaging API)」 按鈕。
選擇 Provider 完成連結
一路點擊 「確定」。
回到 LINE Developers Console,選 Provider(進擊創客-1151),您就會在 Provider 下看到該 Channel 了!
點進剛建立好的 Channel,切換到上方的 「Messaging API」 分頁。
頁面拉到最底部,找到 「Channel access token (long-lived)」 區塊。
點擊右側的 「Issue」 按鈕。
系統會產生一長串字元,請複製並儲存到記事本(這就是 LINE_CHANNEL_ACCESS_TOKEN)。
LINE Channel access token
UaL8dezgmgE+xIfF+yuQXxhdwAGgeXq2NoBHprpVoBy9uEquUS4+a2W8Nd6cVj9OEZK3BXiwCcVCUvHsKp2HRC9bcv+n16E0RpHYMw9n1zzmXL2snCcmA4bSfMvMr5Lxvv7RRLFNnzfmjV63rpFNtQdB04t89/1O/w1cDnyilFU=
在同一個 「Messaging API」 分頁中,向上滾動找到 「Your user ID」 區塊。
在 「Basic settings」 分頁中,向下滾動找到 「Your user ID」 區塊
會看到一串以 U 開頭的 33 字元號碼(例如 U1234567890abcdef1234567890abcdef)。
請複製並儲存到記事本(這就是 LINE_USER_ID)。
加入好友: 在該頁面上方「Messaging API」 分頁中會看到一個 QR Code,請拿起手機打開 LINE 掃描此 QR Code,將您的官方帳號加為好友(這樣它才有權限推播訊息給您)。
前面 啟用 Messaging API 時,就自動加入好友了
LINE user ID
Ua5f57ba22485a004149bf883fc0c4e4b