Software Codes

All the codes we used in our project.

Algae reactor

Turbidity & Voltage Measurement

Expandable Python Code Block

          /*
          * ESP32 水質監測系統 (改良版 - 含時間校準 + 類比濁度)
          */
         
         #include <OneWire.h>
         #include <DallasTemperature.h>
         #include <Wire.h>
         #include <SSD1306Wire.h>
         #include <RTClib.h>
         #include <BH1750.h>
         #include <WiFi.h>
         #include <HTTPClient.h>
         #include <SPI.h>
         #include <SD.h>
         #include <time.h>
         
         // 引腳定義
         #define TEMP_SENSOR_PIN 4
         #define TURBIDITY_ANALOG_PIN 34    // 🔥 改為類比引腳 (ADC1_CH6)
         #define I2C_SDA 21
         #define I2C_SCL 22
         #define SD_CS_PIN 5
         #define SD_MOSI_PIN 23
         #define SD_MISO_PIN 19
         #define SD_SCK_PIN 18
         
         // WiFi設置
         const char* ssid = "TP-Link_6D5A";
         const char* password = "40904453";
         
         // IFTTT設置
         const String iftttEvent = "water_quality_data";
         const String iftttKey = "ufMyFzDc5MsMCXlDDxISO";
         const String iftttUrl = "http://maker.ifttt.com/trigger/" + iftttEvent + "/with/key/" + iftttKey;
         
         // 時間間隔設置
         const long sdLogInterval = 180000;      // 3分鐘
         const long uploadInterval = 180000;     // 3分鐘
         const long sensorInterval = 2000;
         const long displayInterval = 3000;
         const long tempCheckInterval = 30000;
         const long ntpSyncInterval = 3600000;   // 1小時同步一次NTP
         
         unsigned long previousSDLogTime = 0;
         unsigned long previousUploadTime = 0;
         unsigned long previousSensorTime = 0;
         unsigned long previousDisplayTime = 0;
         unsigned long lastTempCheckTime = 0;
         unsigned long lastNTPSyncTime = 0;
         
         // 🔥 時間管理變數
         unsigned long bootTime = 0;            // 開機時的時間戳
         bool hasValidBootTime = false;          // 是否有有效的開機時間
         bool ntpSynced = false;                 // NTP是否已同步
         
         // 🔥 類比濁度感測器變數
         float latestTurbidityVoltage = 0.0;     // 電壓值
         float latestTurbidityNTU = 0.0;         // NTU值
         int turbidityRawValue = 0;              // ADC原始值
         const int turbidityReadings = 10;       // 平均讀取次數
         const float voltageReference = 3.3;     // ESP32參考電壓
         
         // 初始化物件
         SSD1306Wire display(0x3C, I2C_SDA, I2C_SCL);
         OneWire oneWire(TEMP_SENSOR_PIN);
         DallasTemperature tempSensors(&oneWire);
         RTC_DS3231 rtc;
         BH1750 lightMeter;
         
         // 狀態標誌
         bool rtcOK = false;
         bool tempSensorOK = false;
         bool turbidityOK = false;
         bool lightSensorOK = false;
         bool oledOK = false;
         bool wifiOK = false;
         bool sdCardOK = false;
         
         // 感測器數據
         float latestTemperature = -999;
         float latestLightLevel = 0;
         String latestDateString = "";
         String latestTimeString = "";
         String latestDateTime = "";
         
         int displayPage = 0;
         int uploadCounter = 0;
         int sdLogCounter = 0;
         char logFileName[20] = "";
         char daysOfTheWeek[7][12] = {"Sunday", "Monday", "Tuesday", "Wednesday", "Thursday", "Friday", "Saturday"};
         
         // 🔥 檢查RTC狀態
         void checkRTCStatus() {
           if (!rtc.begin()) {
               Serial.println("RTC 模組未找到");
               rtcOK = false;
               return;
           }
           
           if (rtc.lostPower()) {
               Serial.println("RTC 失去電源,需要重新設定時間");
               rtc.adjust(DateTime(F(__DATE__), F(__TIME__)));
               rtcOK = true;
           } else {
               rtcOK = true;
               // 記錄開機時間用於備用計算
               bootTime = rtc.now().unixtime();
               hasValidBootTime = true;
               Serial.println("RTC 時間有效,已記錄開機時間");
           }
         }
         
         // 🔥 NTP時間同步
         void syncTimeWithNTP() {
           if (WiFi.status() != WL_CONNECTED) {
               Serial.println("WiFi未連接,無法同步NTP");
               return;
           }
           
           Serial.println("開始NTP時間同步...");
           
           // 設定NTP伺服器 (台灣時區 GMT+8)
           configTime(8 * 3600, 0, "pool.ntp.org", "time.nist.gov", "time.google.com");
           
           struct tm timeinfo;
           int retries = 0;
           
           // 等待NTP同步,最多嘗試10次
           while (!getLocalTime(&timeinfo) && retries < 10) {
               delay(1000);
               retries++;
               Serial.print(".");
           }
           
           if (retries < 10) {
               // NTP同步成功
               if (rtcOK) {
                   rtc.adjust(DateTime(timeinfo.tm_year + 1900, 
                                      timeinfo.tm_mon + 1, 
                                      timeinfo.tm_mday,
                                      timeinfo.tm_hour, 
                                      timeinfo.tm_min, 
                                      timeinfo.tm_sec));
                   Serial.println("\nRTC時間已與NTP同步");
               }
               
               // 更新開機時間
               bootTime = mktime(&timeinfo) - (millis() / 1000);
               hasValidBootTime = true;
               ntpSynced = true;
               
               Serial.printf("同步時間: %04d/%02d/%02d %02d:%02d:%02d\n",
                            timeinfo.tm_year + 1900, timeinfo.tm_mon + 1, timeinfo.tm_mday,
                            timeinfo.tm_hour, timeinfo.tm_min, timeinfo.tm_sec);
           } else {
               Serial.println("\nNTP同步失敗");
           }
         }
         
         // 🔥 類比濁度讀取函數 (計算NTU和電壓)
         void readTurbidityAnalog() {
           long sum = 0;
           
           // 多次讀取求平均值,提高精度
           for (int i = 0; i < turbidityReadings; i++) {
               sum += analogRead(TURBIDITY_ANALOG_PIN);
               delay(10);
           }
           
           turbidityRawValue = sum / turbidityReadings;
           
           // 計算電壓值 (ESP32 ADC: 0-4095 對應 0-3.3V)
           latestTurbidityVoltage = (turbidityRawValue * voltageReference) / 4095.0;
           
           // 🔥 電壓轉NTU計算公式 (根據感測器規格調整)
           // 這是一個通用的轉換公式,可能需要根據實際感測器校準
           if (latestTurbidityVoltage > 2.5) {
               latestTurbidityNTU = 0.0;  // 非常清澈
           } else if (latestTurbidityVoltage > 2.0) {
               latestTurbidityNTU = (2.5 - latestTurbidityVoltage) * 100;  // 0-50 NTU
           } else if (latestTurbidityVoltage > 1.5) {
               latestTurbidityNTU = 50 + (2.0 - latestTurbidityVoltage) * 200;  // 50-150 NTU
           } else if (latestTurbidityVoltage > 1.0) {
               latestTurbidityNTU = 150 + (1.5 - latestTurbidityVoltage) * 400;  // 150-350 NTU
           } else {
               latestTurbidityNTU = 350 + (1.0 - latestTurbidityVoltage) * 650;  // 350-1000 NTU
           }
           
           // 確保NTU值在合理範圍內
           if (latestTurbidityNTU < 0) latestTurbidityNTU = 0;
           if (latestTurbidityNTU > 1000) latestTurbidityNTU = 1000;
         }
         
         // SD卡初始化
         bool initSDCard() {
           SPI.end();
           delay(100);
           SPI.begin(SD_SCK_PIN, SD_MISO_PIN, SD_MOSI_PIN);
           SPI.setFrequency(1000000);
           delay(200);
           
           pinMode(SD_CS_PIN, OUTPUT);
           digitalWrite(SD_CS_PIN, HIGH);
           delay(100);
           
           for (int retry = 0; retry < 3; retry++) {
               if (SD.begin(SD_CS_PIN, SPI, 1000000)) {
                   // 創建日誌文件
                   if (rtcOK) {
                       DateTime dt = rtc.now();
                       sprintf(logFileName, "/%04u%02u%02u.csv", dt.year(), dt.month(), dt.day());
                   } else {
                       sprintf(logFileName, "/data.csv");
                   }
                   
                   if (!SD.exists(logFileName)) {
                       File dataFile = SD.open(logFileName, FILE_WRITE);
                       if (dataFile) {
                           // 🔥 簡化SD卡記錄格式 - 只記錄濁度NTU和電壓
                           dataFile.println("Date(Time),Turbidity(NTU),Turbidity(Voltage)");
                           dataFile.close();
                       }
                   }
                   return true;
               }
               delay(500);
           }
           return false;
         }
         
         void setup() {
           Serial.begin(115200);
           delay(1000);
           
           Serial.println("=== ESP32 水質監測系統 (類比濁度版) ===");
           
           // 🔥 設置類比濁度感測器引腳
           pinMode(TURBIDITY_ANALOG_PIN, INPUT);
           
           // 初始化I2C
           Wire.begin(I2C_SDA, I2C_SCL);
           Wire.setClock(100000);
           delay(100);
           
           // 初始化OLED
           if (display.init()) {
               display.flipScreenVertically();
               display.setFont(ArialMT_Plain_10);
               oledOK = true;
               display.clear();
               display.drawString(0, 0, "System Reset...");
               display.display();
           }
           
           // 🔥 初始化RTC (改良版)
           checkRTCStatus();
           
           // 初始化光照感測器
           if (lightMeter.begin(BH1750::CONTINUOUS_HIGH_RES_MODE)) {
               lightSensorOK = true;
               delay(200);
           }
           
           // 初始化溫度感測器
           tempSensors.begin();
           delay(500);
           tempSensors.requestTemperatures();
           delay(200);
           float tempC = tempSensors.getTempCByIndex(0);
           if (tempC != -127.00 && tempC != 85.00) {
               tempSensorOK = true;
           }
           
           // 🔥 檢查類比濁度感測器
           readTurbidityAnalog();
           turbidityOK = true; // 類比感測器通常都能正常工作
           
           // 初始化SD卡
           sdCardOK = initSDCard();
           
           // 連接WiFi
           WiFi.begin(ssid, password);
           int wifiRetries = 0;
           while (WiFi.status() != WL_CONNECTED && wifiRetries < 20) {
               delay(500);
               wifiRetries++;
           }
           if (WiFi.status() == WL_CONNECTED) {
               wifiOK = true;
               Serial.println("WiFi連接成功");
               
               // 🔥 首次NTP同步
               syncTimeWithNTP();
               lastNTPSyncTime = millis();
           }
           
           // 顯示系統狀態
           Serial.println("=== 系統狀態 ===");
           Serial.println("OLED: " + String(oledOK ? "OK" : "ERR"));
           Serial.println("溫度: " + String(tempSensorOK ? "OK" : "ERR"));
           Serial.println("濁度(類比): " + String(turbidityOK ? "OK" : "ERR"));
           Serial.println("光照: " + String(lightSensorOK ? "OK" : "ERR"));
           Serial.println("RTC: " + String(rtcOK ? "OK" : "ERR"));
           Serial.println("SD卡: " + String(sdCardOK ? "OK" : "ERR"));
           Serial.println("WiFi: " + String(wifiOK ? "OK" : "ERR"));
           Serial.println("NTP同步: " + String(ntpSynced ? "OK" : "ERR"));
           
           if (oledOK) {
               display.clear();
               display.drawString(0, 0, "System Ready!");
               display.drawString(0, 15, "SD:" + String(sdCardOK ? "OK" : "ERR") + " WiFi:" + String(wifiOK ? "OK" : "ERR"));
               display.drawString(0, 25, "Temp:" + String(tempSensorOK ? "OK" : "ERR") + "Turbidity(A):" + String(turbidityOK ? "OK" : "ERR"));
               display.drawString(0, 35, "Lux:" + String(lightSensorOK ? "OK" : "ERR"));
               display.drawString(0, 45, "RTC:" + String(rtcOK ? "OK" : "ERR"));
             
               display.display();
               delay(3000);
           }
           
           Serial.println("系統初始化完成");
           Serial.println("當前濁度: " + String(latestTurbidityNTU, 1) + " NTU, " + String(latestTurbidityVoltage, 3) + "V");
         }
         
         // 🔥 更新感測器數據 (改良版時間處理)
         void updateSensorData() {
           // 獲取時間 - 改良版邏輯
           if (rtcOK) {
               DateTime now = rtc.now();
               latestDateString = getDateString(now);
               latestTimeString = getTimeString(now);
               latestDateTime = latestDateString + " " + latestTimeString;
           } else if (hasValidBootTime) {
               // 使用開機時間 + 運行時間估算
               unsigned long currentTime = bootTime + (millis() / 1000);
               DateTime estimatedTime = DateTime(currentTime);
               latestDateString = getDateString(estimatedTime);
               latestTimeString = getTimeString(estimatedTime);
               latestDateTime = latestDateString + " " + latestTimeString + " (估計)";
           } else {
               // 純粹的運行時間
               unsigned long seconds = millis() / 1000;
               unsigned long minutes = seconds / 60;
               unsigned long hours = minutes / 60;
               unsigned long days = hours / 24;
               
               char timeStr[30];
               if (days > 0) {
                   sprintf(timeStr, "%lu天 %02lu:%02lu:%02lu", days, hours % 24, minutes % 60, seconds % 60);
               } else {
                   sprintf(timeStr, "%02lu:%02lu:%02lu", hours % 24, minutes % 60, seconds % 60);
               }
               latestTimeString = String(timeStr);
               latestDateTime = "運行時間 " + latestTimeString;
               latestDateString = "----/--/--";
           }
         
           // 讀取溫度
           if (tempSensorOK) {
               tempSensors.requestTemperatures();
               delay(200);
               latestTemperature = tempSensors.getTempCByIndex(0);
               if (latestTemperature == -127.00 || latestTemperature == 85.00) {
                   tempSensorOK = false;
                   latestTemperature = -999;
               }
           }
           
           // 🔥 讀取類比濁度
           readTurbidityAnalog();
         
           // 讀取光照
           if (lightSensorOK) {
               latestLightLevel = lightMeter.readLightLevel();
               if (latestLightLevel < 0) {
                   lightSensorOK = false;
                   latestLightLevel = 0;
               }
           }
         }
         
         // 🔥 SD卡記錄 (簡化版 - 只記錄濁度NTU和電壓)
         void logDataToSD() {
           if (!sdCardOK) return;
           
           sdLogCounter++;
           File dataFile = SD.open(logFileName, FILE_APPEND);
           if (dataFile) {
               String logEntry = latestDateTime + "," + 
                                String(latestTurbidityNTU, 2) + "," +
                                String(latestTurbidityVoltage, 3);
               
               dataFile.println(logEntry);
               dataFile.close();
               Serial.println("SD記錄 #" + String(sdLogCounter) + ": " + logEntry);
           }
         }
         
         // 上傳數據到IFTTT
         void uploadDataToIFTTT() {
           if (WiFi.status() != WL_CONNECTED) return;
           
           uploadCounter++;
           HTTPClient http;
           
           String allData = "時間: " + latestDateTime +
                           " | 溫度: " + String(latestTemperature, 2) + "°C" +
                           " | 濁度: " + String(latestTurbidityNTU, 1) + "NTU" +
                           " | 電壓: " + String(latestTurbidityVoltage, 3) + "V" +
                           " | 光照: " + String(latestLightLevel, 1) + "lx";
           
           String postData = "value1=" + urlEncode(allData) + "&value2=&value3=";
           
           http.begin(iftttUrl);
           http.addHeader("Content-Type", "application/x-www-form-urlencoded");
           
           int httpResponseCode = http.POST(postData);
           
           if (httpResponseCode == 200) {
               Serial.println("IFTTT上傳成功 #" + String(uploadCounter));
           } else {
               Serial.println("IFTTT上傳失敗: " + String(httpResponseCode));
           }
           
           http.end();
         }
         
         void loop() {
           unsigned long currentTime = millis();
           
           // 🔥 定期NTP同步
           if (currentTime - lastNTPSyncTime >= ntpSyncInterval && wifiOK) {
               lastNTPSyncTime = currentTime;
               syncTimeWithNTP();
           }
           
           // 定期檢查溫度感測器
           if (currentTime - lastTempCheckTime >= tempCheckInterval && !tempSensorOK) {
               lastTempCheckTime = currentTime;
               tempSensors.begin();
               delay(500);
               tempSensors.requestTemperatures();
               delay(200);
               float tempC = tempSensors.getTempCByIndex(0);
               if (tempC != -127.00 && tempC != 85.00) {
                   tempSensorOK = true;
               }
           }
           
           // 更新感測器數據
           if (currentTime - previousSensorTime >= sensorInterval) {
               previousSensorTime = currentTime;
               updateSensorData();
           }
           
           // 更新OLED顯示
           if (currentTime - previousDisplayTime >= displayInterval) {
               previousDisplayTime = currentTime;
               displayPage = (displayPage + 1) % 4;
               updateOLEDDisplay();
           }
           
           // 上傳數據到IFTTT
           if (currentTime - previousUploadTime >= uploadInterval && wifiOK) {
               previousUploadTime = currentTime;
               uploadDataToIFTTT();
           }
           
           // 記錄數據到SD卡
           if (currentTime - previousSDLogTime >= sdLogInterval && sdCardOK) {
               previousSDLogTime = currentTime;
               logDataToSD();
           }
           
           // 處理串列命令
           if (Serial.available()) {
               String command = Serial.readString();
               command.trim();
               command.toLowerCase();
               
               if (command == "turbidity" || command == "ntu") {
                   Serial.println("=== 濁度測試 (類比模式) ===");
                   readTurbidityAnalog();
                   Serial.println("ADC原始值: " + String(turbidityRawValue));
                   Serial.println("電壓值: " + String(latestTurbidityVoltage, 3) + "V");
                   Serial.println("NTU值: " + String(latestTurbidityNTU, 2) + " NTU");
               }
               else if (command == "sd") {
                   if (sdCardOK) {
                       logDataToSD();
                   } else {
                       sdCardOK = initSDCard();
                   }
               }
               else if (command == "sync" || command == "ntp") {
                   Serial.println("手動NTP同步...");
                   syncTimeWithNTP();
               }
               else if (command == "rtc") {
                   Serial.println("檢查RTC狀態...");
                   checkRTCStatus();
               }
               else if (command == "status") {
                   Serial.println("=== 系統狀態 ===");
                   Serial.println("OLED: " + String(oledOK ? "OK" : "ERR"));
                   Serial.println("溫度: " + String(tempSensorOK ? "OK" : "ERR"));
                   Serial.println("濁度(類比): " + String(turbidityOK ? "OK" : "ERR"));
                   Serial.println("光照: " + String(lightSensorOK ? "OK" : "ERR"));
                   Serial.println("RTC: " + String(rtcOK ? "OK" : "ERR"));
                   Serial.println("SD卡: " + String(sdCardOK ? "OK" : "ERR"));
                   Serial.println("WiFi: " + String(wifiOK ? "OK" : "ERR"));
                   Serial.println("NTP同步: " + String(ntpSynced ? "OK" : "ERR"));
                   Serial.println("當前時間: " + latestDateTime);
                   Serial.println("當前濁度: " + String(latestTurbidityNTU, 2) + " NTU");
                   Serial.println("當前電壓: " + String(latestTurbidityVoltage, 3) + "V");
               }
               else if (command == "help") {
                   Serial.println("=== 可用命令 ===");
                   Serial.println("turbidity/ntu - 測試濁度(類比)");
                   Serial.println("sd - 測試SD卡");
                   Serial.println("sync/ntp - 手動NTP同步");
                   Serial.println("rtc - 檢查RTC狀態");
                   Serial.println("status - 系統狀態");
                   Serial.println("help - 顯示幫助");
               }
           }
           
           // 檢查WiFi狀態
           if (WiFi.status() != WL_CONNECTED && wifiOK) {
               wifiOK = false;
               WiFi.reconnect();
           } else if (WiFi.status() == WL_CONNECTED && !wifiOK) {
               wifiOK = true;
           }
           
           delay(100);
         }
         
         // 🔥 OLED顯示更新 (濁度頁面顯示NTU值和電壓)
         ;void updateOLEDDisplay() {
             if (!oledOK) return;
             
             display.clear();
             
             switch (displayPage) {
                 case 0:  // 濁度頁面 (改為顯示NTU值和電壓)
                     display.setFont(ArialMT_Plain_10);
                     display.setTextAlignment(TEXT_ALIGN_CENTER);
                     display.drawString(64, 0, "=== TURBIDITY ===");  
                     display.drawString(64, 12, latestTimeString);
                     
                     if (turbidityOK) {
                         display.setFont(ArialMT_Plain_16);
                         display.drawString(64, 24, String(latestTurbidityNTU, 1) + " NTU");  // NTU值
                         display.setFont(ArialMT_Plain_10);
                         display.drawString(64, 40, String(latestTurbidityVoltage, 3) + "V");  // 電壓值
                         display.drawString(64, 52, "ADC: " + String(turbidityRawValue));      // ADC原始值
                     } else {
                         display.setFont(ArialMT_Plain_24);
                         display.drawString(64, 35, "ERROR");
                     }
                     break;
                     
                 case 1:  // 時間頁面
                     display.setFont(ArialMT_Plain_10);
                     display.setTextAlignment(TEXT_ALIGN_CENTER);
                     display.drawString(64, 0, "=== TIME ===");  
                     
                     if (rtcOK) {
                         DateTime now = rtc.now();
                         display.setFont(ArialMT_Plain_10);
                         display.drawString(64, 15, latestDateString);
                         display.setFont(ArialMT_Plain_16);
                         display.drawString(64, 32, latestTimeString);
                         display.setFont(ArialMT_Plain_10);
                         display.drawString(64, 50, daysOfTheWeek[now.dayOfTheWeek()]);
                     } else {
                         display.setFont(ArialMT_Plain_16);
                         display.drawString(64, 20, latestDateString);
                         display.setFont(ArialMT_Plain_16);
                         display.drawString(64, 40, latestTimeString);
                     }
                     break;
                     
                 case 2:  // 光照頁面
                     display.setFont(ArialMT_Plain_10);
                     display.setTextAlignment(TEXT_ALIGN_CENTER);
                     display.drawString(64, 0, "=== LIGHT ===");  
                     display.drawString(64, 14, latestTimeString);
                     
                     if (lightSensorOK) {
                         display.setFont(ArialMT_Plain_24);
                         display.drawString(64, 28, String(latestLightLevel, 0) + " lux");
                     } else {
                         display.setFont(ArialMT_Plain_24);
                         display.drawString(64, 35, "ERROR");
                     }
                     break;
                     
                 case 3:  // 溫度頁面
                     display.setFont(ArialMT_Plain_10);
                     display.setTextAlignment(TEXT_ALIGN_CENTER);
                     display.drawString(64, 0, "=== TEMP ===");  
                     display.drawString(64, 14, latestTimeString);
                     
                     if (latestTemperature == -999) {
                         display.setFont(ArialMT_Plain_24);
                         display.drawString(64, 35, "ERROR");
                     } else {
                         display.setFont(ArialMT_Plain_24);
                         display.drawString(64, 28, String(latestTemperature, 1));
                         display.setFont(ArialMT_Plain_16);
                         display.drawString(64, 50, "°C");
                     }
                     break;
             }
             
             display.display();
         }
         
         
         // 輔助函數
         String getDateString(const DateTime& dt) {
           char dateStr[12];
           sprintf(dateStr, "%04u/%02u/%02u", dt.year(), dt.month(), dt.day());
           return String(dateStr);
         }
         
         String getTimeString(const DateTime& dt) {
           char timeStr[10];
           sprintf(timeStr, "%02u:%02u:%02u", dt.hour(), dt.minute(), dt.second());
           return String(timeStr);
         }
         
         String urlEncode(String str) {
             String encodedString = "";
             char c;
             
             for (int i = 0; i < str.length(); i++) {
                 c = str.charAt(i);
                 if (c == ' ') {
                     encodedString += '+';
                 } else if (isalnum(c)) {
                     encodedString += c;
                 } else {
                     char code0, code1;
                     code1 = (c & 0xf) + '0';
                     if ((c & 0xf) > 9) code1 = (c & 0xf) - 10 + 'A';
                     c = (c >> 4) & 0xf;
                     code0 = c + '0';
                     if (c > 9) code0 = c - 10 + 'A';
                     encodedString += '%';
                     encodedString += code0;
                     encodedString += code1;
                 }
             }
             return encodedString;
         }
        
Show more

Power Supply and Data Uploading

Expandable Python Code Block

            #include "Adafruit_INA3221.h"
            #include <Wire.h>
            #include <Adafruit_GFX.h>
            #include <Adafruit_SSD1306.h>
            #include <WiFi.h>
            #include <HTTPClient.h>

            // WiFi設置
            const char* ssid = "TP-Link_6D5A";
            const char* password = "40904453";

            // IFTTT設置 - 修改為使用HTTP而非HTTPS
            const String iftttEvent = "water_quality_data";          // 您的IFTTT事件名稱
            const String iftttKey = "ufMyFzDc5MsMCXlDDxISO";         // 您的IFTTT Webhook密鑰
            const String iftttUrl = "http://maker.ifttt.com/trigger/" + iftttEvent + "/with/key/" + iftttKey;  // 使用HTTP

            // OLED 設定
            #define SCREEN_WIDTH 128
            #define SCREEN_HEIGHT 64
            #define OLED_RESET -1
            #define SCREEN_ADDRESS 0x3C

            // 創建物件
            Adafruit_INA3221 ina3221;
            Adafruit_SSD1306 display(SCREEN_WIDTH, SCREEN_HEIGHT, &Wire, OLED_RESET);

            // 顯示模式
            int displayMode = 0;
            const int maxDisplayModes = 4;
            unsigned long lastModeChange = 0;
            const unsigned long modeInterval = 2000; // 2 秒切換一次

            // 時間控制變數
            unsigned long previousSensorTime = 0;
            const long sensorInterval = 1000;    // 感測器更新間隔為1秒

            unsigned long previousUploadTime = 0;
            const long uploadInterval = 180000;   // 上傳到IFTTT的間隔為30秒

            // 設備狀態標誌
            bool ina3221OK = false;
            bool oledOK = false;
            bool wifiOK = false;

            // 最新的感測器數據
            float latestVoltage[3] = {0, 0, 0};
            float latestCurrent[3] = {0, 0, 0};
            float latestPower[3] = {0, 0, 0};
            float latestTotalPower = 0;

            // 上傳計數器和狀態
            int uploadCounter = 0;
            bool uploadSuccess = false;
            unsigned long uploadStatusTime = 0;

            void setup() {
              Serial.begin(115200);
              delay(1000);
              
              Serial.println("\n\n=== ESP32 INA3221 電流電壓監控程式 (IFTTT雲端版) ===");
              Serial.println("-----------------------------------");
              
              // 初始化OLED顯示器
              Serial.print("初始化OLED顯示器...");
              if(!display.begin(SSD1306_SWITCHCAPVCC, SCREEN_ADDRESS)) {
                Serial.println("失敗!");
              } else {
                Serial.println("成功!");
                oledOK = true;
                display.clearDisplay();
                display.setTextSize(1);
                display.setTextColor(1); // 使用數字1代替WHITE
                display.setCursor(0,0);
                display.println("系統初始化中...");
                display.display();
              }
              
              // 初始化 INA3221
              Serial.print("初始化INA3221電流感測器...");
              if (!ina3221.begin(0x40, &Wire)) {
                Serial.println("失敗! 無法找到 INA3221!");
                if (oledOK) {
                  display.setCursor(0, 20);
                  display.println("INA3221: 失敗");
                  display.display();
                }
              } else {
                Serial.println("成功!");
                ina3221OK = true;
                if (oledOK) {
                  display.setCursor(0, 30);
                  display.println("INA3221: OK");
                  display.display();
                }
                
                // 設定 INA3221 (通道 0, 1, 2)
                ina3221.setAveragingMode(INA3221_AVG_16_SAMPLES);
                ina3221.setShuntResistance(0, 0.1); // CH0 - 0.1Ω 分流電阻
                ina3221.setShuntResistance(1, 0.1); // CH1 - 0.1Ω 分流電阻
                ina3221.setShuntResistance(2, 0.1); // CH2 - 0.1Ω 分流電阻 (主要通道)
                
                Serial.println("INA3221 設定完成:");
                Serial.println("• CH1: 0.1Ω 分流電阻");
                Serial.println("• CH2: 0.1Ω 分流電阻");
                Serial.println("• CH3: 0.1Ω 分流電阻");
              }
              
              // 連接WiFi
              Serial.print("連接WiFi...");
              if (oledOK) {
                display.setCursor(0, 20);
                display.println("WiFi: Connect...?");
                display.display();
              }
              
              WiFi.begin(ssid, password);
              
              int wifiRetries = 0;
              while (WiFi.status() != WL_CONNECTED && wifiRetries < 20) {
                delay(500);
                Serial.print(".");
                wifiRetries++;
              }
              
              if (WiFi.status() == WL_CONNECTED) {
                Serial.println("\nWiFi連接成功!");
                Serial.print("IP地址: ");
                Serial.println(WiFi.localIP());
                Serial.print("信號強度: ");
                Serial.print(WiFi.RSSI());
                Serial.println(" dBm");
                wifiOK = true;
                if (oledOK) {
                  display.setCursor(0, 20);
                  display.println("WiFi: Connect OK");
                  display.setCursor(0, 30);
                  display.print("IP: ");
                  display.println(WiFi.localIP());
                  display.display();
                }
              } else {
                Serial.println("\nWiFi連接失敗!");
                if (oledOK) {
                  display.setCursor(0, 20);
                  display.println("WiFi: 失敗");
                  display.display();
                }
              }
              
              // 顯示系統狀態摘要
              Serial.println("\n=== 系統狀態摘要 ===");
              Serial.println("OLED顯示器: " + String(oledOK ? "正常" : "失敗"));
              Serial.println("INA3221感測器: " + String(ina3221OK ? "正常" : "失敗"));
              Serial.println("WiFi連接: " + String(wifiOK ? "正常" : "失敗"));
              Serial.println("-----------------------------------");
              
              // 在OLED上顯示系統狀態摘要
              if (oledOK) {
                display.clearDisplay();
                display.setCursor(0, 0);
                display.println("System Status:");
                display.print("INA3221: ");
                display.println(ina3221OK ? "OK" : "ERR");
                display.print("WiFi: ");
                display.println(wifiOK ? "OK" : "ERR");
                display.println("");
                display.println("CH1: Clear");
                display.println("CH2: Clear");
                display.println("CH3: Clear");
                display.display();
                delay(3000);
              }
              
              // 顯示IFTTT設置信息
              Serial.println("\n=== IFTTT雲端設置 ===");
              Serial.println("事件名稱: " + iftttEvent);
              Serial.println("Webhook密鑰: " + iftttKey);
              Serial.println("上傳間隔: 3分鐘");
              Serial.println("-----------------------------------");
              
              Serial.println("\n系統初始化完成!開始監控...");
              Serial.println("可用指令:");
              Serial.println("• 輸入 'upload' 手動觸發上傳");
              Serial.println("• 輸入 'status' 查看系統狀態");
            }

            void loop() {
              unsigned long currentTime = millis();
              
              // 檢查串口輸入
              if (Serial.available() > 0) {
                String command = Serial.readStringUntil('\n');
                command.trim();
                
                if (command == "upload") {
                  Serial.println("手動觸發上傳...");
                  if (wifiOK && ina3221OK) {
                    uploadDataToIFTTT();
                  } else {
                    Serial.println("系統未就緒,無法上傳 (WiFi: " + String(wifiOK) + ", INA3221: " + String(ina3221OK) + ")");
                  }
                } 
                else if (command == "status") {
                  printSystemStatus();
                }
              }
              
              // 定期更新感測器數據
              if (currentTime - previousSensorTime >= sensorInterval) {
                previousSensorTime = currentTime;
                if (ina3221OK) {
                  updateSensorData();
                }
              }
              
              // 自動切換顯示模式
              if (currentTime - lastModeChange > modeInterval) {
                displayMode = (displayMode + 1) % maxDisplayModes;
                lastModeChange = currentTime;
              }
              
              // 更新OLED顯示
              if (oledOK) {
                updateOLEDDisplay();
              }
              
              // 定期上傳數據到IFTTT
              if (currentTime - previousUploadTime >= uploadInterval) {
                previousUploadTime = currentTime;
                if (wifiOK && ina3221OK) {
                  uploadDataToIFTTT();
                }
              }
              
              // 檢查WiFi連接狀態
              if (WiFi.status() != WL_CONNECTED && wifiOK) {
                Serial.println("WiFi連接已斷開,嘗試重新連接...");
                wifiOK = false;
                WiFi.reconnect();
              } else if (WiFi.status() == WL_CONNECTED && !wifiOK) {
                Serial.println("WiFi已重新連接");
                wifiOK = true;
              }
              
              delay(100); // 短暫延遲避免過度佔用CPU
            }

            // 更新所有感測器數據
            void updateSensorData() {
              // 讀取所有通道數據
              latestTotalPower = 0;
              
              for (int ch = 0; ch < 3; ch++) {
                latestVoltage[ch] = ina3221.getBusVoltage(ch);
                latestCurrent[ch] = ina3221.getCurrentAmps(ch) * 1000; // 轉換為 mA
                latestPower[ch] = latestVoltage[ch] * latestCurrent[ch]; // mW
                latestTotalPower += latestPower[ch];
              }
              
              // 每5秒輸出一次詳細數據到串列監視器
              static unsigned long lastDetailedOutput = 0;
              if (millis() - lastDetailedOutput > 5000) {
                lastDetailedOutput = millis();
                printDetailedData();
              }
            }

            // 輸出詳細數據到串列監視器 - 修正為顯示 CH1、CH2、CH3
            void printDetailedData() {
              Serial.println("\n=== INA3221 三通道監控數據 ===");
              Serial.println("CH | 電壓(V) | 電流(mA) | 功率(mW) | 狀態");
              Serial.println("---|---------|----------|----------|--------");
              
              for (int ch = 0; ch < 3; ch++) {
                Serial.print(ch + 1);  // 顯示為 CH1、CH2、CH3
                Serial.print("  | ");
                Serial.print(latestVoltage[ch], 3);
                Serial.print("     | ");
                Serial.print(latestCurrent[ch], 1);
                Serial.print("      | ");
                Serial.print(latestPower[ch], 1);
                Serial.print("      | ");
                
                if (abs(latestCurrent[ch]) < 0.1) {
                  Serial.println("關閉");
                } else if (latestCurrent[ch] < 10) {
                  Serial.println("睡眠");
                } else if (latestCurrent[ch] < 50) {
                  Serial.println("低功耗");
                } else if (latestCurrent[ch] < 100) {
                  Serial.println("正常");
                } else {
                  Serial.println("高負載");
                }
              }
              
              Serial.print("總功耗: ");
              Serial.print(latestTotalPower, 1);
              Serial.println(" mW");
              Serial.print("運行時間: ");
              Serial.print(millis() / 1000);
              Serial.println(" 秒");
              Serial.println("================================");
            }

            // 更新OLED顯示
            void updateOLEDDisplay() {
              display.clearDisplay();
              display.setTextSize(1);
              display.setTextColor(1); // 使用數字1代替WHITE
              
              switch (displayMode) {
                case 0:
                  displayOverview();
                  break;
                case 1:
                  displayChannel(0);
                  break;
                case 2:
                  displayChannel(1);
                  break;
                case 3:
                  displayChannel(2);
                  break;
              }
              
              display.display();
            }

            // 顯示總覽模式 - 修正為顯示 CH1、CH2、CH3
            void displayOverview() {
              display.setCursor(0, 0);
              display.println("=== ALL CHANNELS ===");
              
              for (int ch = 0; ch < 3; ch++) {
                display.setCursor(0, 16 + ch * 10);
                display.print("CH");
                display.print(ch + 1);  // 顯示為 CH1、CH2、CH3
                display.print(": ");
                
                if (abs(latestCurrent[ch]) < 0.1) {
                  display.println("OFF");
                } else {
                  display.print((int)latestCurrent[ch]);
                  display.print("mA ");
                  display.print(latestVoltage[ch], 1);
                  display.println("V");
                }
              }
              
              // 顯示總功耗
              display.setCursor(0, 46);
              display.print("Total: ");
              display.print((int)latestTotalPower);
              display.println("mW");
              
              // 顯示運行時間和狀態
              display.setCursor(0, 57);
              display.print("Time:");
              display.print(millis() / 1000);
              display.print("s U:");
              display.print(uploadCounter);
              
              // 顯示系統狀態和上傳狀態
              display.setCursor(100, 0);
              display.print(ina3221OK ? "I" : "-");
              display.print(wifiOK ? "W" : "-");
              
              // 顯示上傳狀態 (成功顯示 Update OK,失敗顯示 Update ER,5秒後消失)
              if (millis() - uploadStatusTime < 5000) {
                display.setCursor(65, 64);
                if (uploadSuccess) {
                  display.print("UD OK");
                } else {
                  display.print("UD ER");
                }
              }
            }

            // 顯示單一通道詳細資訊 - 修正為顯示 CH1、CH2、CH3
            void displayChannel(int channelNum) {
              display.setCursor(0, 0);
              display.setTextSize(2);
              display.print("CH");
              display.print(channelNum + 1);  // 顯示為 CH1、CH2、CH3
              
              display.setTextSize(1);
              
              // 電壓顯示
              display.setCursor(0, 20);
              display.print("V: ");
              display.print(latestVoltage[channelNum], 3);
              display.println(" V");
              
              // 電流顯示
              display.setCursor(0, 30);
              display.print("I: ");
              display.print(latestCurrent[channelNum], 1);
              display.println(" mA");
              
              // 功率顯示
              display.setCursor(0, 40);
              display.print("P: ");
              display.print(latestPower[channelNum], 1);
              display.println(" mW");
              
              // 狀態指示
              display.setCursor(0, 50);
              if (abs(latestCurrent[channelNum]) < 0.1) {
                display.println("Status: OFF");
              } else if (latestCurrent[channelNum] < 10) {
                display.println("Status: NORMAL");
              } else if (latestCurrent[channelNum] < 50) {
                display.println("Status: LOW");
              } else if (latestCurrent[channelNum] < 100) {
                display.println("Status: NORMAL");
              } else if (latestCurrent[channelNum] < 200) {
                display.println("Status: HIGH");
              } else {
                display.println("Status: PEAK");
              }
            
              // 顯示系統狀態
              display.setCursor(100, 0);
              display.print(ina3221OK ? "I" : "-");
              display.print(wifiOK ? "W" : "-");
              
              // 顯示上傳狀態 (Update OK/ER)
              if (millis() - uploadStatusTime < 5000) {
                display.setCursor(60, 50);
                if (uploadSuccess) {
                  display.print("Update OK");
                } else {
                  display.print("Update ER");
                }
              }
            }

            // 上傳數據到IFTTT - 修正 value1 和 value2 為空,通道改為 CH1、CH2、CH3
            void uploadDataToIFTTT() {
              if (WiFi.status() != WL_CONNECTED) {
                Serial.println("無法上傳數據:WiFi未連接");
                uploadSuccess = false;
                uploadStatusTime = millis();
                return;
              }
              
              uploadCounter++;  // 增加上傳計數
              
              Serial.println("\n=== 開始IFTTT上傳 #" + String(uploadCounter) + " ===");
              Serial.println("WiFi狀態: " + String(WiFi.status()));
              Serial.println("IP地址: " + WiFi.localIP().toString());
              Serial.println("信號強度: " + String(WiFi.RSSI()) + " dBm");
              
              HTTPClient http;
              
              // 建立包含所有資料的 JSON 字串,全部放在 value3,通道改為 CH1、CH2、CH3
              String jsonData = "{";
              jsonData += "\"CH1_V\":" + String(latestVoltage[0], 2) + ",";
              jsonData += "\"CH1_mA\":" + String(latestCurrent[0], 1) + ",";
              jsonData += "\"CH1_mW\":" + String(latestPower[0], 1) + ",";
              jsonData += "\"CH2_V\":" + String(latestVoltage[1], 2) + ",";
              jsonData += "\"CH2_mA\":" + String(latestCurrent[1], 1) + ",";
              jsonData += "\"CH2_mW\":" + String(latestPower[1], 1) + ",";
              jsonData += "\"CH3_V\":" + String(latestVoltage[2], 2) + ",";
              jsonData += "\"CH3_mA\":" + String(latestCurrent[2], 1) + ",";
              jsonData += "\"CH3_mW\":" + String(latestPower[2], 1) + ",";
              jsonData += "\"Total_mW\":" + String(latestTotalPower, 1) + ",";
              jsonData += "\"Uptime\":" + String(millis() / 1000) + ",";
              jsonData += "\"WiFi_RSSI\":" + String(WiFi.RSSI());
              jsonData += "}";
              
              // 構建GET請求URL - value2 和 value3 設為空字串
              String url = iftttUrl;
              url += "?value1=" + urlEncode(jsonData); 
              url += "&value2=";  // 空白
              url += "&value3="; // 空白
              
              Serial.println("上傳URL: " + url);
              
              // 開始HTTP連接
              http.begin(url);
              http.setTimeout(5000);  // 設置5秒超時
              
              // 發送GET請求
              unsigned long startTime = millis();
              int httpResponseCode = http.GET();
              unsigned long endTime = millis();
              
              Serial.println("請求耗時: " + String(endTime - startTime) + " ms");
              
              if (httpResponseCode > 0) {
                String response = http.getString();
                Serial.println("IFTTT上傳成功,響應碼: " + String(httpResponseCode));
                Serial.println("響應內容: " + response);
                uploadSuccess = true;
              } else {
                Serial.println("IFTTT上傳失敗,錯誤碼: " + String(httpResponseCode));
                uploadSuccess = false;
              }
              
              // 記錄上傳狀態時間,用於OLED顯示
              uploadStatusTime = millis();
              
              http.end();
              Serial.println("=== IFTTT上傳結束 ===\n");
            }

            // 輸出系統狀態
            void printSystemStatus() {
              Serial.println("\n=== 系統狀態報告 ===");
              Serial.println("INA3221感測器: " + String(ina3221OK ? "正常" : "失敗"));
              Serial.println("OLED顯示器: " + String(oledOK ? "正常" : "失敗"));
              Serial.println("WiFi連接: " + String(wifiOK ? "正常" : "失敗"));
              if (wifiOK) {
                Serial.println("IP地址: " + WiFi.localIP().toString());
                Serial.println("信號強度: " + String(WiFi.RSSI()) + " dBm");
              }
              Serial.println("運行時間: " + String(millis() / 1000) + " 秒");
              Serial.println("上傳次數: " + String(uploadCounter));
              Serial.println("最後上傳: " + String(uploadSuccess ? "成功" : "失敗"));
              Serial.println("顯示模式: " + String(displayMode));
              Serial.println("===================\n");
            }

            // URL編碼函數 - 處理特殊字符
            String urlEncode(String str) {
              String encodedString = "";
              char c;
              char code0;
              char code1;
              for (int i = 0; i < str.length(); i++) {
                c = str.charAt(i);
                if (c == ' ') {
                  encodedString += '+';
                } else if (isAlphaNumeric(c)) {
                  encodedString += c;
                } else {
                  code1 = (c & 0xf) + '0';
                  if ((c & 0xf) > 9) {
                    code1 = (c & 0xf) - 10 + 'A';
                  }
                  c = (c >> 4) & 0xf;
                  code0 = c + '0';
                  if (c > 9) {
                    code0 = c - 10 + 'A';
                  }
                  encodedString += '%';
                  encodedString += code0;
                  encodedString += code1;
                }
              }
              return encodedString;
            }
        
Show more

WIFI and OLED Display

Expandable Python Code Block

          /*
          * ESP32 完整音響系統 + IFTTT整合 + MAX9814音量檢測 + OLED輪播顯示
          * 功能: OLED分頁顯示 + DFPlayer自動輪播 + MAX9814音量檢測 + IFTTT通知
          * 
          * 硬體接線:
          * ESP32 → DFPlayer Mini (外部5V供電):
          * - GPIO16 → DFPlayer RX
          * - GPIO17 → DFPlayer TX  
          * - GND → DFPlayer GND (共地)
          * - 外部5V → DFPlayer VCC
          * 
          * ESP32 → OLED (I2C):
          * - GPIO21 (SDA) → OLED SDA
          * - GPIO22 (SCL) → OLED SCL
          * - 3.3V → OLED VCC, GND → OLED GND
          * 
          * ESP32 → MAX9814麥克風:
          * - GPIO34 (ADC) → MAX9814 OUT
          * - 3.3V → MAX9814 VCC, GND → MAX9814 GND
          * - AR引腳可接GPIO32調整增益(可選)
          */
        
        // ==================== 庫引用 ====================
        #include <WiFi.h>
        #include <HTTPClient.h>
        #include <Wire.h>
        #include <Adafruit_GFX.h>
        #include <Adafruit_SSD1306.h>
        #include <DFRobotDFPlayerMini.h>
        
        // ==================== 常數定義 ====================
        // OLED顯示器設定
        #define SCREEN_WIDTH 128
        #define SCREEN_HEIGHT 64
        #define OLED_RESET -1
        #define SCREEN_ADDRESS 0x3C
        
        // 如果SSD1306常數未定義,手動定義
        #ifndef SSD1306_WHITE
        #define SSD1306_WHITE 1
        #endif
        #ifndef SSD1306_BLACK
        #define SSD1306_BLACK 0
        #endif
        #ifndef SSD1306_SWITCHCAPVCC
        #define SSD1306_SWITCHCAPVCC 0x02
        #endif
        
        // 引腳定義
        #define MIC_PIN 34          // MAX9814 OUT
        #define MIC_GAIN_PIN 32     // MAX9814 AR (增益控制, 可選)
        #define LED_PIN 2
        
        // MAX9814 設定
        #define SAMPLE_WINDOW 50    // 採樣窗口 (毫秒)
        #define SAMPLE_RATE 1000    // 採樣率 (Hz)
        #define VOLUME_SAMPLES 20   // 音量平滑樣本數
        
        // OLED 分頁設定
        #define TOTAL_PAGES 4       // 總頁數 (新增第四頁)
        #define PAGE_DURATION 5000  // 每頁顯示時間 (毫秒) - 改為5秒
        #define IFTTT_PAGE_DURATION 2000  // IFTTT頁面顯示時間 (毫秒) - 2秒
        
        // ==================== 網路設定 ====================
        // WiFi設置
        const char* ssid = "TP-Link_6D5A";
        const char* password = "40904453";
        const String iftttEvent = "water_quality_data";    
        const String iftttKey = "ufMyFzDc5MsMCXlDDxISO";    
        const String iftttUrl = "http://maker.ifttt.com/trigger/" + iftttEvent + "/with/key/" + iftttKey;  // 使用HTTP
        
        // ==================== 物件初始化 ====================
        HardwareSerial myHardwareSerial(1);
        DFRobotDFPlayerMini myDFPlayer;
        Adafruit_SSD1306 display(SCREEN_WIDTH, SCREEN_HEIGHT, &Wire, OLED_RESET);
        
        // ==================== 全域變數 ====================
        // 自動輪播設定
        int currentTrack = 1;
        const int totalTracks = 3;
        unsigned long lastPlayTime = 0;
        const unsigned long trackDuration = 30000;  // 30秒切換
        bool isPlaying = false;
        bool systemReady = false;
        
        // 音量控制
        int currentVolume = 20;
        
        // MAX9814 音量檢測
        float currentSoundLevel = 0.0;      // 當前音量 (dB)
        float peakSoundLevel = 0.0;         // 峰值音量
        float avgSoundLevel = 0.0;          // 平均音量
        int rawADCValue = 0;                // 原始ADC值
        float volumeBuffer[VOLUME_SAMPLES]; // 音量緩衝區
        int bufferIndex = 0;                // 緩衝區索引
        bool volumeBufferFull = false;      // 緩衝區是否已滿
        unsigned long lastVolumeUpdate = 0;
        unsigned long lastVolumeSerial = 0; // 新增:序列埠音量顯示時間
        
        // 噪音檢測 (基於MAX9814)
        int noiseThreshold = 45;            // dB閾值
        bool autoMode = false;
        unsigned long lastNoiseCheck = 0;
        
        // WiFi和IFTTT
        bool wifiConnected = false;
        unsigned long lastIFTTT = 0;
        bool lastIFTTTSuccess = false;
        String lastIFTTTEvent = "";
        String lastIFTTTTime = "";          // 新增:最後IFTTT時間
        int iftttSuccessCount = 0;          // 新增:成功次數
        int iftttFailCount = 0;             // 新增:失敗次數
        
        // OLED 分頁顯示
        int currentPage = 0;
        unsigned long lastPageChange = 0;
        bool manualPageMode = false;        // 手動翻頁模式
        
        // 顯示控制
        unsigned long lastDisplay = 0;
        String systemStatus = "初始化...";
        
        // 錯誤處理
        int sdErrorCount = 0;
        const int maxSDErrors = 3;
        bool dfPlayerOnline = false;
        bool oledOnline = false;
        bool max9814Online = false;
        
        void setup() {
        Serial.begin(115200);
        Serial.println("🎵 ESP32 完整音響+MAX9814+IFTTT系統");
        Serial.println("=====================================");
        
        pinMode(LED_PIN, OUTPUT);
        pinMode(MIC_PIN, INPUT);
        pinMode(MIC_GAIN_PIN, OUTPUT);  // MAX9814增益控制
        
        // 設定MAX9814增益 (HIGH=60dB, LOW=50dB, 浮空=40dB)
        digitalWrite(MIC_GAIN_PIN, LOW);  // 50dB增益
        
        // 初始化音量緩衝區
        initVolumeBuffer();
        
        // 初始化OLED
        initOLED();
        
        // 連接WiFi (非阻塞)
        startWiFiConnection();
        
        // 初始化DFPlayer
        initDFPlayerStable();
        
        // 測試MAX9814
        testMAX9814();
        
        Serial.println("✅ 系統初始化完成!");
        printHelp();
        printSystemStatus();
        }
        
        void loop() {
        // 自動輪播控制
        if (isPlaying && systemReady) {
          if (millis() - lastPlayTime > trackDuration) {
            nextTrack();
          }
        }
        
        // 更新MAX9814音量檢測
        if (millis() - lastVolumeUpdate > 50) {  // 20Hz更新率
          updateSoundLevel();
          lastVolumeUpdate = millis();
        }
        
        // MAX9814數值序列埠監控顯示 (每2秒顯示一次)
        if (millis() - lastVolumeSerial > 2000) {
          printVolumeMonitor();
          lastVolumeSerial = millis();
        }
        
        // 處理DFPlayer狀態
        handleDFPlayerStatus();
        
        // 處理指令
        handleCommands();
        
        // 檢查WiFi (非阻塞)
        checkWiFi();
        
        // 噪音檢測 (基於MAX9814)
        if (millis() - lastNoiseCheck > 1000) {  // 每秒檢測
          handleAutoPlayWithMAX9814();
          lastNoiseCheck = millis();
        }
        
        // OLED分頁輪播 (根據頁面調整停留時間)
        if (!manualPageMode) {
          unsigned long pageDuration = (currentPage == 3) ? IFTTT_PAGE_DURATION : PAGE_DURATION;
          if (millis() - lastPageChange > pageDuration) {
            currentPage = (currentPage + 1) % TOTAL_PAGES;
            lastPageChange = millis();
          }
        }
        
        // 更新顯示
        if (millis() - lastDisplay > 200) {  // 5Hz更新率
          updateDisplayPages();
          lastDisplay = millis();
        }
        
        // 狀態LED
        digitalWrite(LED_PIN, (millis() / (isPlaying ? 250 : 1000)) % 2);
        
        delay(50);
        }
        
        // ==================== MAX9814 音量檢測 ====================
        void initVolumeBuffer() {
        for (int i = 0; i < VOLUME_SAMPLES; i++) {
          volumeBuffer[i] = 0.0;
        }
        Serial.println("📊 MAX9814音量緩衝區初始化完成");
        }
        
        void testMAX9814() {
        Serial.println("🎤 測試MAX9814麥克風...");
        systemStatus = "測試麥克風";
        
        // 測試讀取
        int testReadings = 0;
        for (int i = 0; i < 10; i++) {
          int reading = analogRead(MIC_PIN);
          if (reading > 0 && reading < 4095) {
            testReadings++;
          }
          delay(100);
        }
        
        if (testReadings >= 5) {
          max9814Online = true;
          Serial.println("✅ MAX9814 檢測正常");
        } else {
          max9814Online = false;
          Serial.println("⚠️ MAX9814 可能未正確連接");
        }
        }
        
        void updateSoundLevel() {
          long sum = 0;
          int maxVal = 0;
          int minVal = 4095;
          
          // 採樣100次 (參考你的方法)
          for(int i = 0; i < 100; i++) {
            int reading = analogRead(MIC_PIN);
            sum += reading;
            
            if(reading > maxVal) maxVal = reading;
            if(reading < minVal) minVal = reading;
            
            delayMicroseconds(100);
          }
          
          int average = sum / 100;
          int amplitude = maxVal - minVal;  // 真正的振幅
          rawADCValue = average;            // 儲存平均值
          
          // 轉換為音量等級 (0-80 對應你的 0-1000 振幅範圍)
          float soundLevel = map(amplitude, 0, 1000, 0, 80);
          if (soundLevel < 0) soundLevel = 0;
          if (soundLevel > 80) soundLevel = 80;
          
          // 加入音量緩衝區進行平滑
          volumeBuffer[bufferIndex] = soundLevel;
          bufferIndex = (bufferIndex + 1) % VOLUME_SAMPLES;
          if (bufferIndex == 0) volumeBufferFull = true;
          
          // 計算平均和峰值
          calculateVolumeStats();
          
          currentSoundLevel = soundLevel;
        }
        
        
        void calculateVolumeStats() {
        float sum = 0.0;
        float maxVal = 0.0;
        int samples = volumeBufferFull ? VOLUME_SAMPLES : bufferIndex;
        
        for (int i = 0; i < samples; i++) {
          sum += volumeBuffer[i];
          if (volumeBuffer[i] > maxVal) {
            maxVal = volumeBuffer[i];
          }
        }
        
        if (samples > 0) {
          avgSoundLevel = sum / samples;
          peakSoundLevel = maxVal;
        }
        }
        
        // 新增:MAX9814數值序列埠監控顯示
        void printVolumeMonitor() {
          if (max9814Online) {
            Serial.println("🎤 ===== MAX9814 即時監控 =====");
            Serial.printf("   平均ADC: %4d/4095 (%.1f%%)\n", rawADCValue, (rawADCValue/4095.0)*100);
            Serial.printf("   振幅值:  %4d (動態範圍)\n", (int)currentSoundLevel * 12.5); // 反推振幅
            Serial.printf("   當前音量: %5.1f/80\n", currentSoundLevel);
            Serial.printf("   平均音量: %5.1f/80\n", avgSoundLevel);
            Serial.printf("   峰值音量: %5.1f/80\n", peakSoundLevel);
            Serial.printf("   閾值設定: %5d %s\n", noiseThreshold, (avgSoundLevel > noiseThreshold) ? "🔊 超過" : "🔇 未達");
            Serial.printf("   自動模式: %s\n", autoMode ? "✅ 開啟" : "❌ 關閉");
            
            // 音量條顯示 (參考你的方法)
            int volume = map(currentSoundLevel, 0, 80, 0, 15);
            Serial.print("   音量條: ");
            for(int i = 0; i < volume; i++) {
              Serial.print("█");
            }
            Serial.println();
            Serial.println("=============================");
          } else {
            Serial.println("🎤 MAX9814 離線中...");
          }
        }
        
        void handleAutoPlayWithMAX9814() {
        static int quietCount = 0;
        static int loudCount = 0;
        
        if (autoMode && max9814Online) {
          if (avgSoundLevel > noiseThreshold) {
            loudCount++;
            quietCount = 0;
            
            if (loudCount >= 2 && !isPlaying && systemReady) {  // 連續2秒檢測到聲音
              Serial.printf("🎵 MAX9814檢測到聲音(%.1fdB),啟動播放\n", avgSoundLevel);
              startAutoRotation();
              sendIFTTT("auto_play", "聲音檢測啟動播放", "音量: " + String(avgSoundLevel, 1) + "dB");
              loudCount = 0;
            }
          } else {
            quietCount++;
            loudCount = 0;
            
            if (quietCount >= 10 && isPlaying) {  // 安靜10秒後暫停
              Serial.printf("🤫 環境安靜(%.1fdB),暫停播放\n", avgSoundLevel);
              myDFPlayer.pause();
              isPlaying = false;
              systemStatus = "自動暫停";
              sendIFTTT("auto_pause", "環境安靜自動暫停", "音量: " + String(avgSoundLevel, 1) + "dB");
              quietCount = 0;
            }
          }
        }
        }
        
        // ==================== OLED 分頁顯示 ====================
        void updateDisplayPages() {
        display.clearDisplay();
        
        switch (currentPage) {
          case 0:
            drawPage1_MusicInfo();
            break;
          case 1:
            drawPage2_VolumeInfo();
            break;
          case 2:
            drawPage3_SystemStatus();
            break;
          case 3:
            drawPage4_IFTTTStatus();  // 新增第四頁
            break;
        }
        
        // 頁面指示器
        drawPageIndicator();
        
        display.display();
        }
        
        void drawPage1_MusicInfo() {
        // 頁面標題
        display.setTextSize(1);
        display.setTextColor(SSD1306_WHITE);
        display.setCursor(0, 0);
        display.println("♪ MUSIC PLAYER");
        
        // WiFi狀態
        display.setCursor(100, 0);
        display.println(wifiConnected ? "WiFi" : "----");
        
        // 分隔線
        display.drawLine(0, 10, 128, 10, SSD1306_WHITE);
        
        // 當前歌曲 (大字體)
        display.setTextSize(3);
        display.setCursor(35, 18);
        display.printf("%02d", currentTrack);
        
        // 播放狀態圖示
        display.setTextSize(2);
        display.setCursor(5, 22);
        if (isPlaying) {
          display.println(">");
        } else {
          display.println("||");
        }
        
        // 進度條 (模擬)
        int progress = ((millis() - lastPlayTime) * 100) / trackDuration;
        if (progress > 100) progress = 100;
        display.drawRect(5, 45, 118, 8, SSD1306_WHITE);
        display.fillRect(6, 46, (progress * 116) / 100, 6, SSD1306_WHITE);
        
        // 狀態文字
        display.setTextSize(1);
        display.setCursor(0, 55);
        display.println(systemStatus);
        }
        
        void drawPage2_VolumeInfo() {
        // 頁面標題
        display.setTextSize(1);
        display.setTextColor(SSD1306_WHITE);
        display.setCursor(0, 0);
        display.println("🎤 VOLUME METER");
        
        // MAX9814狀態
        display.setCursor(100, 0);
        display.println(max9814Online ? "MIC" : "---");
        
        // 分隔線
        display.drawLine(0, 10, 128, 10, SSD1306_WHITE);
        
        // 當前音量數值
        display.setTextSize(1);
        display.setCursor(0, 15);
        display.printf("Live: %.1f dB", currentSoundLevel);
        
        display.setCursor(0, 25);
        display.printf("Avg:  %.1f dB", avgSoundLevel);
        
        display.setCursor(0, 35);
        display.printf("Peak: %.1f dB", peakSoundLevel);
        
        // 圖形化音量條
        // 當前音量條
        int currentBar = (currentSoundLevel * 100) / 80;  // 0-80dB 對應 0-100
        if (currentBar > 100) currentBar = 100;
        display.drawRect(0, 47, 128, 6, SSD1306_WHITE);
        display.fillRect(1, 48, (currentBar * 126) / 100, 4, SSD1306_WHITE);
        
        // 平均音量條
        int avgBar = (avgSoundLevel * 100) / 80;
        if (avgBar > 100) avgBar = 100;
        display.drawRect(0, 55, 128, 6, SSD1306_WHITE);
        for (int i = 0; i < (avgBar * 126) / 100; i += 2) {
          display.drawPixel(1 + i, 56, SSD1306_WHITE);
          display.drawPixel(1 + i, 58, SSD1306_WHITE);
        }
        
        // 閾值線
        int thresholdPos = (noiseThreshold * 126) / 80;
        display.drawLine(thresholdPos, 47, thresholdPos, 61, SSD1306_WHITE);
        }
        
        void drawPage3_SystemStatus() {
        // 頁面標題
        display.setTextSize(1);
        display.setTextColor(SSD1306_WHITE);
        display.setCursor(0, 0);
        display.println("⚙ SYSTEM STATUS");
        
        // IFTTT狀態
        display.setCursor(100, 0);
        display.println(lastIFTTTSuccess ? "IFTTT" : "----");
        
        // 分隔線
        display.drawLine(0, 10, 128, 10, SSD1306_WHITE);
        
        // 模組狀態
        display.setCursor(0, 15);
        display.printf("DFPlayer: %s", dfPlayerOnline ? "OK" : "ERR");
        
        display.setCursor(0, 25);
        display.printf("OLED:     %s", oledOnline ? "OK" : "ERR");
        
        display.setCursor(0, 35);
        display.printf("MAX9814:  %s", max9814Online ? "OK" : "ERR");
        
        display.setCursor(0, 45);
        display.printf("WiFi:     %s", wifiConnected ? "OK" : "ERR");
        
        // 最後IFTTT事件
        display.setCursor(0, 55);
        if (lastIFTTTEvent.length() > 0) {
          String shortEvent = lastIFTTTEvent;
          if (shortEvent.length() > 16) {
            shortEvent = shortEvent.substring(0, 13) + "...";
          }
          display.printf("Last: %s", shortEvent.c_str());
        } else {
          display.println("Last: None");
        }
        }
        
        // 新增:第四頁 IFTTT狀態詳細顯示
        void drawPage4_IFTTTStatus() {
        // 頁面標題
        display.setTextSize(1);
        display.setTextColor(SSD1306_WHITE);
        display.setCursor(0, 0);
        display.println("📤 IFTTT STATUS");
        
        // 連線狀態指示
        display.setCursor(100, 0);
        display.println(wifiConnected ? (lastIFTTTSuccess ? "OK" : "ERR") : "----");
        
        // 分隔線
        display.drawLine(0, 10, 128, 10, SSD1306_WHITE);
        
        // IFTTT統計
        display.setTextSize(1);
        display.setCursor(0, 15);
        display.printf("Success: %d", iftttSuccessCount);
        
        display.setCursor(0, 25);
        display.printf("Failed:  %d", iftttFailCount);
        
        display.setCursor(0, 35);
        display.printf("Total:   %d", iftttSuccessCount + iftttFailCount);
        
        // 最後事件詳細資訊
        display.setCursor(0, 45);
        if (lastIFTTTEvent.length() > 0) {
          String shortEvent = lastIFTTTEvent;
          if (shortEvent.length() > 10) {
            shortEvent = shortEvent.substring(0, 10);
          }
          display.printf("Event: %s", shortEvent.c_str());
        } else {
          display.println("Event: None");
        }
        
        // 最後時間
        display.setCursor(0, 55);
        if (lastIFTTTTime.length() > 0) {
          display.printf("Time: %s", lastIFTTTTime.c_str());
        } else {
          display.println("Time: --:--");
        }
        }
        
        void drawPageIndicator() {
        // 頁面指示點
        int dotY = 62;
        int startX = 48;  // 調整起始位置以容納4個點
        
        for (int i = 0; i < TOTAL_PAGES; i++) {
          int x = startX + i * 8;
          if (i == currentPage) {
            display.fillCircle(x, dotY, 2, SSD1306_WHITE);
          } else {
            display.drawCircle(x, dotY, 2, SSD1306_WHITE);
          }
        }
        }
        
        // ==================== DFPlayer控制 ====================
        void initDFPlayerStable() {
        Serial.println("🎵 初始化DFPlayer...");
        systemStatus = "初始化音樂";
        
        myHardwareSerial.begin(9600, SERIAL_8N1, 16, 17);
        delay(3000);
        
        bool initSuccess = false;
        for (int attempt = 0; attempt < 3; attempt++) {
          if (myDFPlayer.begin(myHardwareSerial)) {
            Serial.println("✅ DFPlayer 初始化成功!");
            dfPlayerOnline = true;
            initSuccess = true;
            break;
          } else {
            Serial.printf("⚠️ DFPlayer初始化嘗試 %d/3 失敗\n", attempt + 1);
            delay(2000);
          }
        }
        
        if (!initSuccess) {
          Serial.println("❌ DFPlayer 初始化失敗");
          dfPlayerOnline = false;
          return;
        }
        
        delay(1000);
        myDFPlayer.volume(currentVolume);
        delay(500);
        myDFPlayer.outputDevice(DFPLAYER_DEVICE_SD);
        delay(1500);
        
        startAutoRotation();
        sendIFTTT("system_start", "ESP32音響系統啟動", "DFPlayer已就緒");
        }
        
        void startAutoRotation() {
        if (!dfPlayerOnline) return;
        
        Serial.println("🚀 啟動自動輪播...");
        currentTrack = 1;
        playCurrentTrack();
        isPlaying = true;
        systemReady = true;
        lastPlayTime = millis();
        systemStatus = "輪播中";
        }
        
        void playCurrentTrack() {
        if (!dfPlayerOnline) return;
        
        Serial.printf("🎵 播放: Track %d\n", currentTrack);
        myDFPlayer.play(currentTrack);
        lastPlayTime = millis();
        
        if (millis() - lastIFTTT > 15000) {  // 限制IFTTT頻率
          sendIFTTT("music_play", "播放音樂", "Track " + String(currentTrack));
        }
        }
        
        void nextTrack() {
        currentTrack++;
        if (currentTrack > totalTracks) {
          currentTrack = 1;
          Serial.println("🔄 輪播循環");
        }
        Serial.printf("⏭️ 切換到: Track %d\n", currentTrack);
        playCurrentTrack();
        }
        
        void handleDFPlayerStatus() {
        if (!dfPlayerOnline || !myDFPlayer.available()) return;
        
        uint8_t type = myDFPlayer.readType();
        int value = myDFPlayer.read();
        
        switch (type) {
          case DFPlayerPlayFinished:
            Serial.println("✅ 歌曲完成,自動下一首");
            delay(500);
            nextTrack();
            break;
            
          case DFPlayerCardRemoved:
            sdErrorCount++;
            Serial.printf("⚠️ SD卡錯誤 #%d\n", sdErrorCount);
            if (sdErrorCount >= maxSDErrors) {
              recoverSystem();
              sdErrorCount = 0;
            }
            break;
            
          case DFPlayerCardOnline:
            Serial.println("✅ SD卡恢復");
            sdErrorCount = 0;
            if (!isPlaying && systemReady) {
              delay(1000);
              playCurrentTrack();
              isPlaying = true;
            }
            break;
        }
        }
        
        void recoverSystem() {
        Serial.println("🔧 系統恢復中...");
        isPlaying = false;
        systemStatus = "恢復中";
        delay(1000);
        
        if (dfPlayerOnline) {
          myDFPlayer.outputDevice(DFPLAYER_DEVICE_SD);
          delay(2000);
          startAutoRotation();
        }
        
        Serial.println("✅ 系統恢復完成");
        }
        
        // ==================== WiFi和IFTTT ====================
        void startWiFiConnection() {
        Serial.println("📶 開始連接WiFi...");
        systemStatus = "連接WiFi";
        WiFi.begin(ssid, password);
        }
        
        void checkWiFi() {
        static unsigned long lastCheck = 0;
        static int wifiAttempts = 0;
        
        if (millis() - lastCheck > 5000) {
          if (WiFi.status() == WL_CONNECTED && !wifiConnected) {
            wifiConnected = true;
            Serial.println("✅ WiFi連接成功!");
            Serial.printf("   IP: %s\n", WiFi.localIP().toString().c_str());
            wifiAttempts = 0;
          } else if (WiFi.status() != WL_CONNECTED && wifiConnected) {
            wifiConnected = false;
            Serial.println("📶 WiFi連線中斷");
          } else if (WiFi.status() != WL_CONNECTED && !wifiConnected && wifiAttempts < 3) {
            wifiAttempts++;
            Serial.printf("📶 WiFi重試 %d/3\n", wifiAttempts);
            WiFi.reconnect();
          }
          lastCheck = millis();
        }
        }
        
        // ==================== WiFi和IFTTT ====================
        void sendIFTTT(String event, String value1, String value2) {
          if (!wifiConnected || millis() - lastIFTTT < 180000) return;
          
          HTTPClient http;
          String url = iftttUrl;
          http.begin(url);
          http.addHeader("Content-Type", "application/json");
          
        // 🔧 修改:將所有數據集中到value1,value2和value3留空
        String concentratedData = value1;
        if (value2.length() > 0) {
          concentratedData += "|" + value2;
        }
          
          // 加入MAX9814監控數據
          concentratedData += "|當前音量:" + String(currentSoundLevel, 1) + "dB";
          concentratedData += "|平均音量:" + String(avgSoundLevel, 1) + "dB";
          concentratedData += "|峰值音量:" + String(peakSoundLevel, 1) + "dB";
          concentratedData += "|ADC值:" + String(rawADCValue);
          concentratedData += "|閾值:" + String(noiseThreshold) + "dB";
          concentratedData += "|自動模式:" + String(autoMode ? "開啟" : "關閉");
          concentratedData += "|系統音量:" + String(currentVolume);
          concentratedData += "|當前歌曲:" + String(currentTrack);
          concentratedData += "|播放狀態:" + String(isPlaying ? "播放中" : "暫停");
          concentratedData += "|運行時間:" + String(millis()/1000) + "秒";
          
        // 🔧 修改:將所有資料放到value1,value2和value3留空
        String jsonData = "{\"value1\":\"" + concentratedData + "\",\"value2\":\"\",\"value3\":\"\"}";
        
        int httpCode = http.POST(jsonData);
          
        // 更新IFTTT時間戳記
        unsigned long currentTime = millis() / 1000;
        int hours = (currentTime / 3600) % 24;
        int minutes = (currentTime / 60) % 60;
        lastIFTTTTime = String(hours) + ":" + (minutes < 10 ? "0" : "") + String(minutes);
          
        if (httpCode > 0) {
          lastIFTTTSuccess = true;
          lastIFTTTEvent = event;
          iftttSuccessCount++;
          Serial.printf("📤 IFTTT成功: %s (Code: %d)\n", event.c_str(), httpCode);
          Serial.printf("   數據: %s\n", concentratedData.c_str());
        } else {
          lastIFTTTSuccess = false;
          iftttFailCount++;
          Serial.printf("❌ IFTTT失敗: %s (Code: %d)\n", event.c_str(), httpCode);
        }
          
          http.end();
          lastIFTTT = millis();
        }
        
        
        // ==================== OLED初始化 ====================
        void initOLED() {
          Serial.println("📱 初始化OLED...");
          
          if(!display.begin(SSD1306_SWITCHCAPVCC, SCREEN_ADDRESS)) {
            Serial.println("❌ OLED初始化失敗");
            oledOnline = false;
            return;
          }
          
          oledOnline = true;
          display.clearDisplay();
          display.setTextColor(SSD1306_WHITE);
          display.setTextSize(1);
          display.setCursor(0, 0);
          display.println("ESP32 Audio System");
          display.setCursor(0, 20);
          display.println("MAX9814 + IFTTT");
          display.setCursor(0, 40);
          display.println("Initializing...");
          display.display();
          
          Serial.println("✅ OLED初始化成功");
        }
        
        // ==================== 指令處理 ====================
        void handleCommands() {
          if (!Serial.available()) return;
          
          char cmd = Serial.read();
          
          switch(cmd) {
            case '1': case '2': case '3':
              currentTrack = cmd - '0';
              playCurrentTrack();
              isPlaying = true;
              break;
              
            case '+':
              if (currentVolume < 30) {
                currentVolume++;
                if (dfPlayerOnline) myDFPlayer.volume(currentVolume);
                Serial.printf("🔊 音量: %d/30\n", currentVolume);
              }
              break;
              
            case '-':
              if (currentVolume > 0) {
                currentVolume--;
                if (dfPlayerOnline) myDFPlayer.volume(currentVolume);
                Serial.printf("🔉 音量: %d/30\n", currentVolume);
              }
              break;
              
            case 'p':
              if (dfPlayerOnline) {
                if (isPlaying) {
                  myDFPlayer.pause();
                  isPlaying = false;
                  systemStatus = "暫停";
                  Serial.println("⏸️ 暫停");
                } else {
                  myDFPlayer.start();
                  isPlaying = true;
                  systemStatus = "播放中";
                  Serial.println("▶️ 繼續");
                }
              }
              break;
              
            case 's':
              startAutoRotation();
              Serial.println("🚀 重啟輪播");
              break;
              
            case 'n':
              nextTrack();
              break;
              
            case 'a':
              autoMode = !autoMode;
              Serial.printf("🤖 自動模式: %s (閾值: %ddB)\n", autoMode ? "開啟" : "關閉", noiseThreshold);
              sendIFTTT("auto_mode", autoMode ? "開啟" : "關閉", "閾值: " + String(noiseThreshold) + "dB");
              break;
              
            case 't':
              sendIFTTT("test", "手動測試", "音量: " + String(currentSoundLevel, 1) + "dB");
              Serial.println("📤 測試IFTTT");
              break;
              
            case 'v':
              printVolumeInfo();
              break;
              
            case 'i':
              printSystemStatus();
              break;
              
            case 'r':
              recoverSystem();
              Serial.println("🔧 手動重置系統");
              break;
              
            case 'g':
              // 切換MAX9814增益
              static bool highGain = false;
              highGain = !highGain;
              digitalWrite(MIC_GAIN_PIN, highGain ? HIGH : LOW);
              Serial.printf("🎚️ MAX9814增益: %s (%ddB)\n", 
                            highGain ? "高" : "中", 
                            highGain ? 60 : 50);
              break;
              
            case 'h':
              printHelp();
              break;
              
            case 'm':
              // 手動翻頁模式切換
              manualPageMode = !manualPageMode;
              Serial.printf("📱 OLED翻頁: %s\n", manualPageMode ? "手動" : "自動");
              break;
              
            case '<':
              // 上一頁
              if (manualPageMode) {
                currentPage = (currentPage - 1 + TOTAL_PAGES) % TOTAL_PAGES;
                Serial.printf("📱 切換到頁面: %d\n", currentPage + 1);
              }
              break;
              
            case '>':
              // 下一頁
              if (manualPageMode) {
                currentPage = (currentPage + 1) % TOTAL_PAGES;
                Serial.printf("📱 切換到頁面: %d\n", currentPage + 1);
              }
              break;
              
            case 'u':
              // 調高噪音閾值
              if (noiseThreshold < 70) {
                noiseThreshold += 5;
                Serial.printf("🔊 噪音閾值: %ddB\n", noiseThreshold);
              }
              break;
              
            case 'd':
              // 調低噪音閾值
              if (noiseThreshold > 20) {
                noiseThreshold -= 5;
                Serial.printf("🔉 噪音閾值: %ddB\n", noiseThreshold);
              }
              break;
              
            case 'c':
              // 校準MAX9814
              calibrateMAX9814();
              break;
              
            case 'z':
              // 重置音量統計
              resetVolumeStats();
              break;
          }
        }
        
        // ==================== 資訊顯示函數 ====================
        void printHelp() {
          Serial.println("\n📋 指令說明:");
          Serial.println("=====================================");
          Serial.println("🎵 音樂控制:");
          Serial.println("  1/2/3  - 播放指定歌曲");
          Serial.println("  p      - 播放/暫停");
          Serial.println("  s      - 重啟自動輪播");
          Serial.println("  n      - 下一首");
          Serial.println("  +/-    - 音量調整");
          Serial.println("");
          Serial.println("🎤 音量檢測:");
          Serial.println("  a      - 切換自動播放模式");
          Serial.println("  g      - 切換MAX9814增益");
          Serial.println("  u/d    - 調整噪音閾值");
          Serial.println("  v      - 顯示音量資訊");
          Serial.println("  c      - 校準MAX9814");
          Serial.println("  z      - 重置音量統計");
          Serial.println("");
          Serial.println("📱 顯示控制:");
          Serial.println("  m      - 切換OLED翻頁模式");
          Serial.println("      - 手動翻頁 (需開啟手動模式)");
          Serial.println("");
          Serial.println("🌐 系統控制:");
          Serial.println("  t      - 測試IFTTT");
          Serial.println("  r      - 重置系統");
          Serial.println("  i      - 系統狀態");
          Serial.println("  h      - 顯示此說明");
          Serial.println("=====================================");
        }
        
        void printSystemStatus() {
          Serial.println("\n📊 系統狀態:");
          Serial.println("=====================================");
          Serial.printf("🎵 音樂: Track %d/%d (%s)\n", 
                        currentTrack, totalTracks, 
                        isPlaying ? "播放中" : "暫停");
          Serial.printf("🔊 音量: %d/30\n", currentVolume);
          Serial.printf("⏱️ 運行時間: %lu 秒\n", millis() / 1000);
          Serial.println("");
          
          Serial.println("🔌 硬體狀態:");
          Serial.printf("  DFPlayer: %s\n", dfPlayerOnline ? "✅ 正常" : "❌ 離線");
          Serial.printf("  OLED:     %s\n", oledOnline ? "✅ 正常" : "❌ 離線");
          Serial.printf("  MAX9814:  %s\n", max9814Online ? "✅ 正常" : "❌ 離線");
          Serial.printf("  WiFi:     %s", wifiConnected ? "✅ 已連接" : "❌ 未連接");
          if (wifiConnected) {
            Serial.printf(" (%s)", WiFi.localIP().toString().c_str());
          }
          Serial.println("");
          Serial.println("");
          
          Serial.println("🎤 音量檢測:");
          Serial.printf("  當前音量: %.1f dB\n", currentSoundLevel);
          Serial.printf("  平均音量: %.1f dB\n", avgSoundLevel);
          Serial.printf("  峰值音量: %.1f dB\n", peakSoundLevel);
          Serial.printf("  原始ADC:  %d/4095\n", rawADCValue);
          Serial.printf("  自動模式: %s (閾值: %ddB)\n", 
                        autoMode ? "開啟" : "關閉", noiseThreshold);
          Serial.println("");
          
          Serial.println("📱 OLED顯示:");
          Serial.printf("  當前頁面: %d/%d\n", currentPage + 1, TOTAL_PAGES);
          Serial.printf("  翻頁模式: %s\n", manualPageMode ? "手動" : "自動");
          Serial.println("");
          
          Serial.println("🌐 IFTTT狀態:");
          Serial.printf("  最後事件: %s\n", 
                        lastIFTTTEvent.length() > 0 ? lastIFTTTEvent.c_str() : "無");
          Serial.printf("  狀態: %s\n", lastIFTTTSuccess ? "成功" : "失敗");
          Serial.printf("  成功次數: %d\n", iftttSuccessCount);
          Serial.printf("  失敗次數: %d\n", iftttFailCount);
          Serial.printf("  最後時間: %s\n", lastIFTTTTime.length() > 0 ? lastIFTTTTime.c_str() : "--:--");
          Serial.println("");
          
          Serial.printf("💾 記憶體: %d bytes 可用\n", ESP.getFreeHeap());
          Serial.println("=====================================");
        }
        
        void printVolumeInfo() {
          Serial.println("\n🎤 詳細音量資訊:");
          Serial.println("=====================================");
          Serial.printf("原始ADC值: %d (0-4095)\n", rawADCValue);
          Serial.printf("電壓值: %.3f V\n", (rawADCValue / 4095.0) * 3.3);
          Serial.printf("當前音量: %.1f dB\n", currentSoundLevel);
          Serial.printf("平均音量: %.1f dB (過去%d個樣本)\n", avgSoundLevel, VOLUME_SAMPLES);
          Serial.printf("峰值音量: %.1f dB\n", peakSoundLevel);
          Serial.printf("噪音閾值: %d dB\n", noiseThreshold);
          Serial.printf("緩衝區狀態: %s (%d/%d)\n", 
                        volumeBufferFull ? "已滿" : "填充中", 
                        bufferIndex, VOLUME_SAMPLES);
          
          // 顯示最近的音量歷史
          Serial.println("\n📈 最近音量歷史 (最新10個樣本):");
          int startIdx = volumeBufferFull ? bufferIndex : 0;
          int count = volumeBufferFull ? VOLUME_SAMPLES : bufferIndex;
          int showCount = min(10, count);
          
          for (int i = 0; i < showCount; i++) {
            int idx = (startIdx - showCount + i + VOLUME_SAMPLES) % VOLUME_SAMPLES;
            Serial.printf("  [%d] %.1f dB\n", i + 1, volumeBuffer[idx]);
          }
          Serial.println("=====================================");
        }
        
        // ==================== 額外功能函數 ====================
        void resetVolumeStats() {
          // 重置音量統計
          for (int i = 0; i < VOLUME_SAMPLES; i++) {
            volumeBuffer[i] = 0.0;
          }
          bufferIndex = 0;
          volumeBufferFull = false;
          currentSoundLevel = 0.0;
          avgSoundLevel = 0.0;
          peakSoundLevel = 0.0;
          Serial.println("🔄 音量統計已重置");
        }
        
        void calibrateMAX9814() {
          // MAX9814校準函數
          Serial.println("🎚️ 開始MAX9814校準...");
          Serial.println("   請保持環境安靜 5 秒...");
          
          float quietSum = 0.0;
          int quietSamples = 0;
          
          unsigned long startTime = millis();
          while (millis() - startTime < 5000) {
            int reading = analogRead(MIC_PIN);
            float voltage = (reading / 4095.0) * 3.3;
            float amplitude = abs(voltage - 1.65);
            
            if (amplitude > 0.001) {
              float dbValue = 20 * log10(amplitude / 0.001) + 20;
              if (dbValue >= 0 && dbValue <= 80) {
                quietSum += dbValue;
                quietSamples++;
              }
            }
            delay(100);
          }
          
          if (quietSamples > 0) {
            float quietLevel = quietSum / quietSamples;
            noiseThreshold = quietLevel + 10;  // 安靜基準 + 10dB
            Serial.printf("✅ 校準完成!\n");
            Serial.printf("   安靜基準: %.1f dB\n", quietLevel);
            Serial.printf("   新閾值: %d dB\n", noiseThreshold);
            sendIFTTT("calibration", "MAX9814校準完成", "新閾值: " + String(noiseThreshold) + "dB");
          } else {
            Serial.println("❌ 校準失敗,請檢查MAX9814連接");
          }
        }
        
Show more

Expected Results

Experiment Expected Outcome
Plasmid Transformation Colonies showing antibiotic resistance
Algae Culture Increased cell density (OD measurement)
Molecular Cloning Successful insert confirmation by PCR