Now Playing

Software

Music Player
Algae Dancing

Software and Control

The software layer integrated ESP32 firmware with Arduino-based control routines, enabling automated device operation, real-time monitoring, and cloud-based data logging. OLED displays served as local user interfaces for each subsystem.

Control Parameters:

  • Audio: Frequency and volume tunable within 200 Hz – 2200 Hz.
  • Light: Automated LED control at 520 nm, 550 nm, and 600 nm wavelengths.
  • Turbidity: Optical density measured every 6 hours to monitor biomass accumulation.
  • Temperature: Logged every 5 minutes with automatic thermal regulation.
  • pH: Recorded daily for stability of the saltwater medium.
  • Power Consumption: Logged every 5 minutes for high-consumption devices.

This implementation enabled autonomous operation of the platform, ensuring precise environmental control, continuous data acquisition, and seamless synchronization between local storage and cloud infrastructure.

ESP32 Integrated Code Viewer

ESP32_0812_NTU_TEM.ino

/*
 * ESP32 (translated) ((translated) - (translated) + (translated))
 */

#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>

// Pin definitions
#define TEMP_SENSOR_PIN 4
#define TURBIDITY_ANALOG_PIN 34    // 🔥 (translated)pin (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 setup
const char* ssid = "TP-Link_6D5A";
const char* password = "********";

// IFTTT setup
const String iftttEvent = "water_quality_data";
const String iftttKey = "****************";
const String iftttUrl = "http://maker.ifttt.com/trigger/" + iftttEvent + "/with/key/" + iftttKey;

// Interval settings
const long sdLogInterval = 180000;      // 3 minutes
const long uploadInterval = 180000;     // 3 minutes
const long sensorInterval = 2000;
const long displayInterval = 3000;
const long tempCheckInterval = 30000;
const long ntpSyncInterval = 3600000;   // 1 hourssync(translated)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;

// 🔥 Time management variables
unsigned long bootTime = 0;            // Boot timestamp
bool hasValidBootTime = false;          // (translated)
bool ntpSynced = false;                 // NTP(translated)sync

// 🔥 (translated)sensorvariable
float latestTurbidityVoltage = 0.0;     // voltage(translated)
float latestTurbidityNTU = 0.0;         // NTU(translated)
int turbidityRawValue = 0;              // ADC(translated)
const int turbidityReadings = 10;       // averageread(translated)
const float voltageReference = 3.3;     // ESP32(translated)voltage

// initialize(translated)
SSD1306Wire display(0x3C, I2C_SDA, I2C_SCL);
OneWire oneWire(TEMP_SENSOR_PIN);
DallasTemperature tempSensors(&oneWire);
RTC_DS3231 rtc;
BH1750 lightMeter;

// status(translated)
bool rtcOK = false;
bool tempSensorOK = false;
bool turbidityOK = false;
bool lightSensorOK = false;
bool oledOK = false;
bool wifiOK = false;
bool sdCardOK = false;

// sensor(translated)
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"};

// 🔥 (translated)RTCstatus
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;
      // record(translated)
      bootTime = rtc.now().unixtime();
      hasValidBootTime = true;
      Serial.println("RTC 時間有效,已記錄開機時間");
  }
}

// 🔥 NTP(translated)sync
void syncTimeWithNTP() {
  if (WiFi.status() != WL_CONNECTED) {
      Serial.println("WiFi未連接,無法同步NTP");
      return;
  }
  
  Serial.println("開始NTP時間同步...");
  
  // settingsNTPserver ((translated) GMT+8)
  configTime(8 * 3600, 0, "pool.ntp.org", "time.nist.gov", "time.google.com");
  
  struct tm timeinfo;
  int retries = 0;
  
  // (translated)NTPsync, (translated)10(translated)
  while (!getLocalTime(&timeinfo) && retries < 10) {
      delay(1000);
      retries++;
      Serial.print(".");
  }
  
  if (retries < 10) {
      // NTPsyncsuccess
      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同步");
      }
      
      // update(translated)
      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同步失敗");
  }
}

// 🔥 (translated)read(translated) ((translated)NTU(translated)voltage)
void readTurbidityAnalog() {
  long sum = 0;
  
  // (translated)read(translated)average(translated), (translated)
  for (int i = 0; i < turbidityReadings; i++) {
      sum += analogRead(TURBIDITY_ANALOG_PIN);
      delay(10);
  }
  
  turbidityRawValue = sum / turbidityReadings;
  
  // (translated)voltage(translated) (ESP32 ADC: 0-4095 (translated) 0-3.3V)
  latestTurbidityVoltage = (turbidityRawValue * voltageReference) / 4095.0;
  
  // 🔥 voltage(translated)NTU(translated) ((translated)sensor(translated))
  // (translated)convert(translated), (translated)sensor(translated)
  if (latestTurbidityVoltage > 2.5) {
      latestTurbidityNTU = 0.0;  // (translated)
  } 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
  }
  
  // (translated)NTU(translated)range(translated)
  if (latestTurbidityNTU < 0) latestTurbidityNTU = 0;
  if (latestTurbidityNTU > 1000) latestTurbidityNTU = 1000;
}

// SD(translated)initialize
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)) {
          // (translated)log(translated)
          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) {
                  // 🔥 (translated)SD(translated)recordformat - (translated)record(translated)NTU(translated)voltage
                  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 水質監測系統 (類比濁度版) ===");
  
  // 🔥 setup(translated)sensorpin
  pinMode(TURBIDITY_ANALOG_PIN, INPUT);
  
  // initializeI2C
  Wire.begin(I2C_SDA, I2C_SCL);
  Wire.setClock(100000);
  delay(100);
  
  // initializeOLED
  if (display.init()) {
      display.flipScreenVertically();
      display.setFont(ArialMT_Plain_10);
      oledOK = true;
      display.clear();
      display.drawString(0, 0, "System Reset...");
      display.display();
  }
  
  // 🔥 initializeRTC ((translated))
  checkRTCStatus();
  
  // initialize(translated)sensor
  if (lightMeter.begin(BH1750::CONTINUOUS_HIGH_RES_MODE)) {
      lightSensorOK = true;
      delay(200);
  }
  
  // initializetemperaturesensor
  tempSensors.begin();
  delay(500);
  tempSensors.requestTemperatures();
  delay(200);
  float tempC = tempSensors.getTempCByIndex(0);
  if (tempC != -127.00 && tempC != 85.00) {
      tempSensorOK = true;
  }
  
  // 🔥 (translated)sensor
  readTurbidityAnalog();
  turbidityOK = true; // (translated)sensor(translated)
  
  // initializeSD(translated)
  sdCardOK = initSDCard();
  
  // connectWiFi
  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連接成功");
      
      // 🔥 (translated)NTPsync
      syncTimeWithNTP();
      lastNTPSyncTime = millis();
  }
  
  // display(translated)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"));
  
  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");
}

// 🔥 updatesensor(translated) ((translated))
void updateSensorData() {
  // (translated) - (translated)
  if (rtcOK) {
      DateTime now = rtc.now();
      latestDateString = getDateString(now);
      latestTimeString = getTimeString(now);
      latestDateTime = latestDateString + " " + latestTimeString;
  } else if (hasValidBootTime) {
      // (translated) + (translated)
      unsigned long currentTime = bootTime + (millis() / 1000);
      DateTime estimatedTime = DateTime(currentTime);
      latestDateString = getDateString(estimatedTime);
      latestTimeString = getTimeString(estimatedTime);
      latestDateTime = latestDateString + " " + latestTimeString + " (估計)";
  } else {
      // (translated)
      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 = "----/--/--";
  }

  // readtemperature
  if (tempSensorOK) {
      tempSensors.requestTemperatures();
      delay(200);
      latestTemperature = tempSensors.getTempCByIndex(0);
      if (latestTemperature == -127.00 || latestTemperature == 85.00) {
          tempSensorOK = false;
          latestTemperature = -999;
      }
  }
  
  // 🔥 read(translated)
  readTurbidityAnalog();

  // read(translated)
  if (lightSensorOK) {
      latestLightLevel = lightMeter.readLightLevel();
      if (latestLightLevel < 0) {
          lightSensorOK = false;
          latestLightLevel = 0;
      }
  }
}

// 🔥 SD(translated)record ((translated) - (translated)record(translated)NTU(translated)voltage)
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);
  }
}

// upload(translated)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();
  
  // 🔥 (translated)NTPsync
  if (currentTime - lastNTPSyncTime >= ntpSyncInterval && wifiOK) {
      lastNTPSyncTime = currentTime;
      syncTimeWithNTP();
  }
  
  // (translated)temperaturesensor
  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;
      }
  }
  
  // updatesensor(translated)
  if (currentTime - previousSensorTime >= sensorInterval) {
      previousSensorTime = currentTime;
      updateSensorData();
  }
  
  // updateOLEDdisplay
  if (currentTime - previousDisplayTime >= displayInterval) {
      previousDisplayTime = currentTime;
      displayPage = (displayPage + 1) % 4;
      updateOLEDDisplay();
  }
  
  // upload(translated)IFTTT
  if (currentTime - previousUploadTime >= uploadInterval && wifiOK) {
      previousUploadTime = currentTime;
      uploadDataToIFTTT();
  }
  
  // record(translated)SD(translated)
  if (currentTime - previousSDLogTime >= sdLogInterval && sdCardOK) {
      previousSDLogTime = currentTime;
      logDataToSD();
  }
  
  // (translated)
  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 - 顯示幫助");
      }
  }
  
  // (translated)WiFistatus
  if (WiFi.status() != WL_CONNECTED && wifiOK) {
      wifiOK = false;
      WiFi.reconnect();
  } else if (WiFi.status() == WL_CONNECTED && !wifiOK) {
      wifiOK = true;
  }
  
  delay(100);
}

// 🔥 OLEDdisplayupdate ((translated)displayNTU(translated)voltage)
void updateOLEDDisplay() {
    if (!oledOK) return;
    
    display.clear();
    
    switch (displayPage) {
        case 0:  // (translated) ((translated)displayNTU(translated)voltage)
            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(translated)
                display.setFont(ArialMT_Plain_10);
                display.drawString(64, 40, String(latestTurbidityVoltage, 3) + "V");  // voltage(translated)
                display.drawString(64, 52, "ADC: " + String(turbidityRawValue));      // ADC(translated)
            } else {
                display.setFont(ArialMT_Plain_24);
                display.drawString(64, 35, "ERROR");
            }
            break;
            
        case 1:  // (translated)
            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:  // (translated)
            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:  // temperature(translated)
            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();
}


// (translated)
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;
}

/*
 * === (translated) ((translated)) ===
 * 
 * 🔥 (translated):
 * 1. (translated)sensor(translated) (ADCread)
 * 2. OLED(translated)display:
 *    - NTU(translated) ((translated)voltage(translated))
 *    - voltage(translated) (V)
 *    - ADC(translated)
 * 
 * 🔥 (translated)sensor(translated):
 * - (translated) analogRead() read 0-4095 ADC(translated)
 * - convert(translated) 0-3.3V voltage(translated)
 * - (translated)voltage(translated) NTU (translated)
 * - (translated)readaverage, (translated)
 * 
 * 🔥 (translated)SD(translated)record:
 * - (translated)record(translated), NTU(translated), voltage(translated)
 * - format:Date(Time),Turbidity(NTU),Turbidity(Voltage)
 * 
 * 🔥 NTU(translated):
 * - voltage > 2.5V: 0 NTU ((translated))
 * - voltage 2.0-2.5V: 0-50 NTU
 * - voltage 1.5-2.0V: 50-150 NTU  
 * - voltage 1.0-1.5V: 150-350 NTU
 * - voltage < 1.0V: 350-1000 NTU
 * 
 * ✅ (translated):
 * - OLED(translated)display
 * - IFTTT(translated)upload ((translated)NTU(translated)voltage(translated))
 * - NTP(translated)sync
 * - (translated)
 * - (translated)sensor(translated)
 * 
 * 📊 OLEDdisplay(translated):
 * Page 0: (translated) - NTU(translated)/voltage(translated)/ADC(translated)
 * Page 1: (translated) - (translated)/(translated)/(translated)
 * Page 2: (translated) - brightness(translated)
 * Page 3: temperature - temperature(translated)
 * 
 * 💾 SD(translated)recordformat:
 * Date(Time),Turbidity(NTU),Turbidity(Voltage)
 * 
 * 📡 IFTTTupload(translated):
 * (translated) | temperature | (translated)(NTU) | voltage | (translated)
 * 
 * 🔧 (translated):
 * - turbidity/ntu: test(translated)sensor (displayADC/voltage/NTU)
 * - sd: testSD(translated)record
 * - sync/ntp: (translated)NTPsync
 * - rtc: (translated)RTCstatus
 * - status: display(translated)status
 * - help: display(translated)
 */

ESP32_INA3221_X6_F1.ino

/*
 * ESP32 INA3221 currentvoltage(translated) ((translated)OLEDdisplay(translated)IFTTT(translated)upload)
 * hardware:ESP32-WROOM-32, INA3221currentsensor, SSD1306 OLEDdisplay(translated)
 * (translated):(translated)currentvoltage(translated), OLED(translated)display, IFTTT(translated)upload
 * version:(translated) IFTTT format(translated)display
 */

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

// WiFi setup
const char* ssid = "TP-Link_6D5A";
const char* password = "********";

// IFTTT setup - (translated)HTTP(translated)HTTPS
const String iftttEvent = "water_quality_data";          // (translated)IFTTT(translated)
const String iftttKey = "**************";         // (translated)IFTTT Webhook(translated)
const String iftttUrl = "http://maker.ifttt.com/trigger/" + iftttEvent + "/with/key/" + iftttKey;  // (translated)HTTP

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

// (translated)
Adafruit_INA3221 ina3221;
Adafruit_SSD1306 display(SCREEN_WIDTH, SCREEN_HEIGHT, &Wire, OLED_RESET);

// display(translated)
int displayMode = 0;
const int maxDisplayModes = 4;
unsigned long lastModeChange = 0;
const unsigned long modeInterval = 2000; // 2 seconds(translated)

// (translated)variable
unsigned long previousSensorTime = 0;
const long sensorInterval = 1000;    // sensorupdate(translated)1 seconds

unsigned long previousUploadTime = 0;
const long uploadInterval = 180000;   // upload(translated)IFTTT(translated)30 seconds

// (translated)status(translated)
bool ina3221OK = false;
bool oledOK = false;
bool wifiOK = false;

// (translated)sensor(translated)
float latestVoltage[3] = {0, 0, 0};
float latestCurrent[3] = {0, 0, 0};
float latestPower[3] = {0, 0, 0};
float latestTotalPower = 0;

// upload(translated)status
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("-----------------------------------");
  
  // initializeOLEDdisplay(translated)
  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); // (translated)1(translated)WHITE
    display.setCursor(0,0);
    display.println("系統初始化中...");
    display.display();
  }
  
  // initialize 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();
    }
    
    // settings INA3221 ((translated) 0, 1, 2)
    ina3221.setAveragingMode(INA3221_AVG_16_SAMPLES);
    ina3221.setShuntResistance(0, 0.1); // CH0 - 0.1Ω (translated)
    ina3221.setShuntResistance(1, 0.1); // CH1 - 0.1Ω (translated)
    ina3221.setShuntResistance(2, 0.1); // CH2 - 0.1Ω (translated) ((translated))
    
    Serial.println("INA3221 設定完成:");
    Serial.println("• CH1: 0.1Ω 分流電阻");
    Serial.println("• CH2: 0.1Ω 分流電阻");
    Serial.println("• CH3: 0.1Ω 分流電阻");
  }
  
  // connectWiFi
  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();
    }
  }
  
  // display(translated)status(translated)
  Serial.println("\n=== 系統狀態摘要 ===");
  Serial.println("OLED顯示器: " + String(oledOK ? "正常" : "失敗"));
  Serial.println("INA3221感測器: " + String(ina3221OK ? "正常" : "失敗"));
  Serial.println("WiFi連接: " + String(wifiOK ? "正常" : "失敗"));
  Serial.println("-----------------------------------");
  
  // (translated)OLED(translated)display(translated)status(translated)
  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);
  }
  
  // displayIFTTT setup(translated)
  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();
  
  // (translated)input
  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();
    }
  }
  
  // (translated)updatesensor(translated)
  if (currentTime - previousSensorTime >= sensorInterval) {
    previousSensorTime = currentTime;
    if (ina3221OK) {
      updateSensorData();
    }
  }
  
  // (translated)display(translated)
  if (currentTime - lastModeChange > modeInterval) {
    displayMode = (displayMode + 1) % maxDisplayModes;
    lastModeChange = currentTime;
  }
  
  // updateOLEDdisplay
  if (oledOK) {
    updateOLEDDisplay();
  }
  
  // (translated)upload(translated)IFTTT
  if (currentTime - previousUploadTime >= uploadInterval) {
    previousUploadTime = currentTime;
    if (wifiOK && ina3221OK) {
      uploadDataToIFTTT();
    }
  }
  
  // (translated)WiFiconnectstatus
  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); // (translated)delayavoid(translated)CPU
}

// update(translated)sensor(translated)
void updateSensorData() {
  // read(translated)
  latestTotalPower = 0;
  
  for (int ch = 0; ch < 3; ch++) {
    latestVoltage[ch] = ina3221.getBusVoltage(ch);
    latestCurrent[ch] = ina3221.getCurrentAmps(ch) * 1000; // convert(translated) mA
    latestPower[ch] = latestVoltage[ch] * latestCurrent[ch]; // mW
    latestTotalPower += latestPower[ch];
  }
  
  // (translated)5 secondsoutput(translated)
  static unsigned long lastDetailedOutput = 0;
  if (millis() - lastDetailedOutput > 5000) {
    lastDetailedOutput = millis();
    printDetailedData();
  }
}

// output(translated) - (translated)display 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);  // display(translated) 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("================================");
}

// updateOLEDdisplay
void updateOLEDDisplay() {
  display.clearDisplay();
  display.setTextSize(1);
  display.setTextColor(1); // (translated)1(translated)WHITE
  
  switch (displayMode) {
    case 0:
      displayOverview();
      break;
    case 1:
      displayChannel(0);
      break;
    case 2:
      displayChannel(1);
      break;
    case 3:
      displayChannel(2);
      break;
  }
  
  display.display();
}

// display(translated) - (translated)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);  // display(translated) 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(translated)
  display.setCursor(0, 46);
  display.print("Total: ");
  display.print((int)latestTotalPower);
  display.println("mW");
  
  // display(translated)status
  display.setCursor(0, 57);
  display.print("Time:");
  display.print(millis() / 1000);
  display.print("s U:");
  display.print(uploadCounter);
  
  // display(translated)status(translated)uploadstatus
  display.setCursor(100, 0);
  display.print(ina3221OK ? "I" : "-");
  display.print(wifiOK ? "W" : "-");
  
  // displayuploadstatus (successdisplay Update OK, (translated)display Update ER, 5 seconds(translated))
  if (millis() - uploadStatusTime < 5000) {
    display.setCursor(65, 64);
    if (uploadSuccess) {
      display.print("UD OK");
    } else {
      display.print("UD ER");
    }
  }
}

// display(translated) - (translated)display CH1, CH2, CH3
void displayChannel(int channelNum) {
  display.setCursor(0, 0);
  display.setTextSize(2);
  display.print("CH");
  display.print(channelNum + 1);  // display(translated) CH1, CH2, CH3
  
  display.setTextSize(1);
  
  // voltagedisplay
  display.setCursor(0, 20);
  display.print("V: ");
  display.print(latestVoltage[channelNum], 3);
  display.println(" V");
  
  // currentdisplay
  display.setCursor(0, 30);
  display.print("I: ");
  display.print(latestCurrent[channelNum], 1);
  display.println(" mA");
  
  // powerdisplay
  display.setCursor(0, 40);
  display.print("P: ");
  display.print(latestPower[channelNum], 1);
  display.println(" mW");
  
  // status(translated)
  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(translated)status
  display.setCursor(100, 0);
  display.print(ina3221OK ? "I" : "-");
  display.print(wifiOK ? "W" : "-");
  
  // displayuploadstatus (Update OK/ER)
  if (millis() - uploadStatusTime < 5000) {
    display.setCursor(60, 50);
    if (uploadSuccess) {
      display.print("Update OK");
    } else {
      display.print("Update ER");
    }
  }
}

// upload(translated)IFTTT - (translated) value1 (translated) value2 (translated), (translated) CH1, CH2, CH3
void uploadDataToIFTTT() {
  if (WiFi.status() != WL_CONNECTED) {
    Serial.println("無法上傳數據:WiFi未連接");
    uploadSuccess = false;
    uploadStatusTime = millis();
    return;
  }
  
  uploadCounter++;  // (translated)upload(translated)
  
  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;
  
  // (translated)data(translated) JSON string, (translated) value3, (translated) 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 += "}";
  
  // (translated)GET(translated)URL - value2 (translated) value3 (translated)string
  String url = iftttUrl;
  url += "?value1=" + urlEncode(jsonData); 
  url += "&value2=";  // (translated)
  url += "&value3="; // (translated)
  
  Serial.println("上傳URL: " + url);
  
  // (translated)HTTPconnect
  http.begin(url);
  http.setTimeout(5000);  // setup5 seconds(translated)
  
  // (translated)GET(translated)
  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;
  }
  
  // recorduploadstatus(translated), (translated)OLEDdisplay
  uploadStatusTime = millis();
  
  http.end();
  Serial.println("=== IFTTT上傳結束 ===\n");
}

// output(translated)status
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(translated) - (translated)
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;
}

ESP32_MP3_PAM8403_WIFI_OLED_MIC_T1.ino

/*
 * ESP32 (translated) + IFTTT(translated) + MAX9814volume(translated) + OLED(translated)display
 * (translated): OLED(translated)display + DFPlayer(translated) + MAX9814volume(translated) + IFTTT(translated)
 * 
 * hardware(translated):
 * ESP32 → DFPlayer Mini ((translated)5V(translated)):
 * - GPIO16 → DFPlayer RX
 * - GPIO17 → DFPlayer TX  
 * - GND → DFPlayer GND ((translated))
 * - (translated)5V → DFPlayer VCC
 * 
 * ESP32 → OLED (I2C):
 * - GPIO21 (SDA) → OLED SDA
 * - GPIO22 (SCL) → OLED SCL
 * - 3.3V → OLED VCC, GND → OLED GND
 * 
 * ESP32 → MAX9814microphone:
 * - GPIO34 (ADC) → MAX9814 OUT
 * - 3.3V → MAX9814 VCC, GND → MAX9814 GND
 * - ARpin(translated)GPIO32(translated)((translated))
 */

// ==================== (translated) ====================
#include <WiFi.h>
#include <HTTPClient.h>
#include <Wire.h>
#include <Adafruit_GFX.h>
#include <Adafruit_SSD1306.h>
#include "DFRobotDFPlayerMini.h"

// ==================== (translated) ====================
// OLEDdisplay(translated)settings
#define SCREEN_WIDTH 128
#define SCREEN_HEIGHT 64
#define OLED_RESET -1
#define SCREEN_ADDRESS 0x3C

// (translated)SSD1306(translated), (translated)
#ifndef SSD1306_WHITE
#define SSD1306_WHITE 1
#endif
#ifndef SSD1306_BLACK
#define SSD1306_BLACK 0
#endif
#ifndef SSD1306_SWITCHCAPVCC
#define SSD1306_SWITCHCAPVCC 0x02
#endif

// Pin definitions
#define MIC_PIN 34          // MAX9814 OUT
#define MIC_GAIN_PIN 32     // MAX9814 AR ((translated), (translated))
#define LED_PIN 2

// MAX9814 settings
#define SAMPLE_WINDOW 50    // (translated) ((translated)seconds)
#define SAMPLE_RATE 1000    // (translated) (Hz)
#define VOLUME_SAMPLES 20   // volume(translated)

// OLED (translated)settings
#define TOTAL_PAGES 4       // (translated) ((translated))
#define PAGE_DURATION 5000  // (translated)display(translated) ((translated)seconds) - (translated)5 seconds
#define IFTTT_PAGE_DURATION 2000  // IFTTT(translated)display(translated) ((translated)seconds) - 2 seconds

// ==================== networksettings ====================
// WiFi setup
const char* ssid = "TP-Link_6D5A";
const char* password = "********";
const String iftttEvent = "water_quality_data";    
const String iftttKey = "****************";    
const String iftttUrl = "http://maker.ifttt.com/trigger/" + iftttEvent + "/with/key/" + iftttKey;  // (translated)HTTP

// ==================== (translated)initialize ====================
HardwareSerial myHardwareSerial(1);
DFRobotDFPlayerMini myDFPlayer;
Adafruit_SSD1306 display(SCREEN_WIDTH, SCREEN_HEIGHT, &Wire, OLED_RESET);

// ==================== (translated)variable ====================
// (translated)settings
int currentTrack = 1;
const int totalTracks = 3;
unsigned long lastPlayTime = 0;
const unsigned long trackDuration = 30000;  // 30 seconds(translated)
bool isPlaying = false;
bool systemReady = false;

// volume(translated)
int currentVolume = 20;

// MAX9814 volume(translated)
float currentSoundLevel = 0.0;      // (translated)volume (dB)
float peakSoundLevel = 0.0;         // (translated)volume
float avgSoundLevel = 0.0;          // averagevolume
int rawADCValue = 0;                // (translated)ADC(translated)
float volumeBuffer[VOLUME_SAMPLES]; // volume(translated)
int bufferIndex = 0;                // (translated)
bool volumeBufferFull = false;      // (translated)
unsigned long lastVolumeUpdate = 0;
unsigned long lastVolumeSerial = 0; // (translated):serial portvolumedisplay(translated)

// (translated) ((translated)MAX9814)
int noiseThreshold = 45;            // dBthreshold
bool autoMode = false;
unsigned long lastNoiseCheck = 0;

// WiFi(translated)IFTTT
bool wifiConnected = false;
unsigned long lastIFTTT = 0;
bool lastIFTTTSuccess = false;
String lastIFTTTEvent = "";
String lastIFTTTTime = "";          // (translated):(translated)IFTTT(translated)
int iftttSuccessCount = 0;          // (translated):success(translated)
int iftttFailCount = 0;             // (translated):(translated)

// OLED (translated)display
int currentPage = 0;
unsigned long lastPageChange = 0;
bool manualPageMode = false;        // (translated)

// display(translated)
unsigned long lastDisplay = 0;
String systemStatus = "初始化...";

// error(translated)
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(translated)

// settingsMAX9814(translated) (HIGH=60dB, LOW=50dB, (translated)=40dB)
digitalWrite(MIC_GAIN_PIN, LOW);  // 50dB(translated)

// initializevolume(translated)
initVolumeBuffer();

// initializeOLED
initOLED();

// connectWiFi ((translated))
startWiFiConnection();

// initializeDFPlayer
initDFPlayerStable();

// testMAX9814
testMAX9814();

Serial.println("✅ 系統初始化完成!");
printHelp();
printSystemStatus();
}

void loop() {
// (translated)
if (isPlaying && systemReady) {
  if (millis() - lastPlayTime > trackDuration) {
    nextTrack();
  }
}

// updateMAX9814volume(translated)
if (millis() - lastVolumeUpdate > 50) {  // 20Hzupdate(translated)
  updateSoundLevel();
  lastVolumeUpdate = millis();
}

// MAX9814valueserial port(translated)display ((translated)2 secondsdisplay(translated))
if (millis() - lastVolumeSerial > 2000) {
  printVolumeMonitor();
  lastVolumeSerial = millis();
}

// (translated)DFPlayerstatus
handleDFPlayerStatus();

// (translated)
handleCommands();

// (translated)WiFi ((translated))
checkWiFi();

// (translated) ((translated)MAX9814)
if (millis() - lastNoiseCheck > 1000) {  // (translated)seconds(translated)
  handleAutoPlayWithMAX9814();
  lastNoiseCheck = millis();
}

// OLED(translated) ((translated))
if (!manualPageMode) {
  unsigned long pageDuration = (currentPage == 3) ? IFTTT_PAGE_DURATION : PAGE_DURATION;
  if (millis() - lastPageChange > pageDuration) {
    currentPage = (currentPage + 1) % TOTAL_PAGES;
    lastPageChange = millis();
  }
}

// updatedisplay
if (millis() - lastDisplay > 200) {  // 5Hzupdate(translated)
  updateDisplayPages();
  lastDisplay = millis();
}

// statusLED
digitalWrite(LED_PIN, (millis() / (isPlaying ? 250 : 1000)) % 2);

delay(50);
}

// ==================== MAX9814 volume(translated) ====================
void initVolumeBuffer() {
for (int i = 0; i < VOLUME_SAMPLES; i++) {
  volumeBuffer[i] = 0.0;
}
Serial.println("📊 MAX9814音量緩衝區初始化完成");
}

void testMAX9814() {
Serial.println("🎤 測試MAX9814麥克風...");
systemStatus = "測試麥克風";

// testread
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;
  
  // (translated)100(translated) ((translated))
  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;  // (translated)
  rawADCValue = average;            // saveaverage(translated)
  
  // convert(translated)volume(translated) (0-80 (translated) 0-1000 (translated)range)
  float soundLevel = map(amplitude, 0, 1000, 0, 80);
  if (soundLevel < 0) soundLevel = 0;
  if (soundLevel > 80) soundLevel = 80;
  
  // (translated)volume(translated)
  volumeBuffer[bufferIndex] = soundLevel;
  bufferIndex = (bufferIndex + 1) % VOLUME_SAMPLES;
  if (bufferIndex == 0) volumeBufferFull = true;
  
  // (translated)average(translated)
  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;
}
}

// (translated):MAX9814valueserial port(translated)display
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); // (translated)
    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 ? "✅ 開啟" : "❌ 關閉");
    
    // volume(translated)display ((translated))
    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) {  // (translated)2 seconds(translated)
      Serial.printf("🎵 MAX9814檢測到聲音(%.1fdB),啟動播放\n", avgSoundLevel);
      startAutoRotation();
      sendIFTTT("auto_play", "聲音檢測啟動播放", "音量: " + String(avgSoundLevel, 1) + "dB");
      loudCount = 0;
    }
  } else {
    quietCount++;
    loudCount = 0;
    
    if (quietCount >= 10 && isPlaying) {  // (translated)10 seconds(translated)
      Serial.printf("🤫 環境安靜(%.1fdB),暫停播放\n", avgSoundLevel);
      myDFPlayer.pause();
      isPlaying = false;
      systemStatus = "自動暫停";
      sendIFTTT("auto_pause", "環境安靜自動暫停", "音量: " + String(avgSoundLevel, 1) + "dB");
      quietCount = 0;
    }
  }
}
}

// ==================== OLED (translated)display ====================
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();  // (translated)
    break;
}

// (translated)
drawPageIndicator();

display.display();
}

void drawPage1_MusicInfo() {
// (translated)
display.setTextSize(1);
display.setTextColor(SSD1306_WHITE);
display.setCursor(0, 0);
display.println("♪ MUSIC PLAYER");

// WiFistatus
display.setCursor(100, 0);
display.println(wifiConnected ? "WiFi" : "----");

// (translated)
display.drawLine(0, 10, 128, 10, SSD1306_WHITE);

// (translated) ((translated))
display.setTextSize(3);
display.setCursor(35, 18);
display.printf("%02d", currentTrack);

// playstatus(translated)
display.setTextSize(2);
display.setCursor(5, 22);
if (isPlaying) {
  display.println(">");
} else {
  display.println("||");
}

// (translated) ((translated))
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);

// status(translated)
display.setTextSize(1);
display.setCursor(0, 55);
display.println(systemStatus);
}

void drawPage2_VolumeInfo() {
// (translated)
display.setTextSize(1);
display.setTextColor(SSD1306_WHITE);
display.setCursor(0, 0);
display.println("🎤 VOLUME METER");

// MAX9814status
display.setCursor(100, 0);
display.println(max9814Online ? "MIC" : "---");

// (translated)
display.drawLine(0, 10, 128, 10, SSD1306_WHITE);

// (translated)volumevalue
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);

// (translated)volume(translated)
// (translated)volume(translated)
int currentBar = (currentSoundLevel * 100) / 80;  // 0-80dB (translated) 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);

// averagevolume(translated)
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);
}

// threshold(translated)
int thresholdPos = (noiseThreshold * 126) / 80;
display.drawLine(thresholdPos, 47, thresholdPos, 61, SSD1306_WHITE);
}

void drawPage3_SystemStatus() {
// (translated)
display.setTextSize(1);
display.setTextColor(SSD1306_WHITE);
display.setCursor(0, 0);
display.println("⚙ SYSTEM STATUS");

// IFTTTstatus
display.setCursor(100, 0);
display.println(lastIFTTTSuccess ? "IFTTT" : "----");

// (translated)
display.drawLine(0, 10, 128, 10, SSD1306_WHITE);

// modulestatus
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");

// (translated)IFTTT(translated)
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");
}
}

// (translated):(translated) IFTTTstatus(translated)display
void drawPage4_IFTTTStatus() {
// (translated)
display.setTextSize(1);
display.setTextColor(SSD1306_WHITE);
display.setCursor(0, 0);
display.println("📤 IFTTT STATUS");

// connectionstatus(translated)
display.setCursor(100, 0);
display.println(wifiConnected ? (lastIFTTTSuccess ? "OK" : "ERR") : "----");

// (translated)
display.drawLine(0, 10, 128, 10, SSD1306_WHITE);

// IFTTT(translated)
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);

// (translated)
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");
}

// (translated)
display.setCursor(0, 55);
if (lastIFTTTTime.length() > 0) {
  display.printf("Time: %s", lastIFTTTTime.c_str());
} else {
  display.println("Time: --:--");
}
}

void drawPageIndicator() {
// (translated)
int dotY = 62;
int startX = 48;  // (translated)4(translated)

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(translated) ====================
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) {  // (translated)IFTTTfrequency
  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(translated)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(translated)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");
  
// 🔧 (translated):(translated)value1, value2(translated)value3(translated)
String concentratedData = value1;
if (value2.length() > 0) {
  concentratedData += "|" + value2;
}
  
  // (translated)MAX9814(translated)
  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) + "秒";
  
// 🔧 (translated):(translated)data(translated)value1, value2(translated)value3(translated)
String jsonData = "{\"value1\":\"" + concentratedData + "\",\"value2\":\"\",\"value3\":\"\"}";

int httpCode = http.POST(jsonData);
  
// updateIFTTT(translated)
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();
}


// ==================== OLEDinitialize ====================
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初始化成功");
}

// ==================== (translated) ====================
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':
      // (translated)MAX9814(translated)
      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':
      // (translated)
      manualPageMode = !manualPageMode;
      Serial.printf("📱 OLED翻頁: %s\n", manualPageMode ? "手動" : "自動");
      break;
      
    case '<':
      // (translated)
      if (manualPageMode) {
        currentPage = (currentPage - 1 + TOTAL_PAGES) % TOTAL_PAGES;
        Serial.printf("📱 切換到頁面: %d\n", currentPage + 1);
      }
      break;
      
    case '>':
      // (translated)
      if (manualPageMode) {
        currentPage = (currentPage + 1) % TOTAL_PAGES;
        Serial.printf("📱 切換到頁面: %d\n", currentPage + 1);
      }
      break;
      
    case 'u':
      // (translated)threshold
      if (noiseThreshold < 70) {
        noiseThreshold += 5;
        Serial.printf("🔊 噪音閾值: %ddB\n", noiseThreshold);
      }
      break;
      
    case 'd':
      // (translated)threshold
      if (noiseThreshold > 20) {
        noiseThreshold -= 5;
        Serial.printf("🔉 噪音閾值: %ddB\n", noiseThreshold);
      }
      break;
      
    case 'c':
      // (translated)MAX9814
      calibrateMAX9814();
      break;
      
    case 'z':
      // resetvolume(translated)
      resetVolumeStats();
      break;
  }
}

// ==================== (translated)display(translated) ====================
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);
  
  // display(translated)volumehistory
  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("=====================================");
}

// ==================== (translated) ====================
void resetVolumeStats() {
  // resetvolume(translated)
  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(translated)
  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;  // (translated) + 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連接");
  }
}

Python Code Demonstration to convert data from ESP32 to CLOUD System


  import pandas as pd
  import os
  import re
  import json
  
  input_files = [
      "water_quality_data (48).xlsx",
      "water_quality_data (49).xlsx",
      "water_quality_data (50).xlsx"
  ]
  
  output_columns = [
      '時間', '溫度', '濁度', '電壓', '光照度', '播放音樂', '當前音量', '平均音量', '峰值音量', 'ADC值',
      'CH1_V', 'CH1_mA', 'CH1_mW', 'CH2_V', 'CH2_mA', 'CH2_mW', 'CH3_V', 'CH3_mA', 'CH3_mW',
      'Total_mW', 'Uptime', 'WiFi_RSSI'
  ]
  
  def identify_data_type(text):
      if isinstance(text, str):
          if text.startswith("時間:"):
              return "environment"
          elif text.startswith("播放音樂"):
              return "music"
          elif text.startswith("{"):
              return "power"
      return "unknown"
  
  def parse_environment(text):
      match = re.search(r"時間: ([\d/ :]+) \| 溫度: ([\d.]+)°C \| 濁度: ([\d.]+)NTU \| 電壓: ([\d.]+)V \| 光照: ([\d.]+)lx", text)
      if match:
          return list(match.groups())
      return [None]*5
  
  def parse_music(text):
      match = re.search(r"播放音樂\|(.+?)\|當前音量:([\d.]+)dB\|平均音量:([\d.]+)dB\|峰值音量:([\d.]+)dB\|ADC值:(\d+)", text)
      if match:
          return list(match.groups())
      return [None]*5
  
  def parse_power(text):
      try:
          data = json.loads(text)
          return [
              data.get("CH1_V"), data.get("CH1_mA"), data.get("CH1_mW"),
              data.get("CH2_V"), data.get("CH2_mA"), data.get("CH2_mW"),
              data.get("CH3_V"), data.get("CH3_mA"), data.get("CH3_mW"),
              data.get("Total_mW"), data.get("Uptime"), data.get("WiFi_RSSI")
          ]
      except:
          return [None]*12
  
  summary_data = []
  for file in input_files:
      df = pd.read_excel(file, engine='openpyxl', header=None)
      grouped_data = []
      buffer = {"environment": None, "music": None, "power": None}
  
      for _, row in df.iterrows():
          text = row[2]
          dtype = identify_data_type(text)
          if dtype in buffer and buffer[dtype] is None:
              buffer[dtype] = text
          if all(buffer.values()):
              env_data = parse_environment(buffer["environment"])
              music_data = parse_music(buffer["music"])
              power_data = parse_power(buffer["power"])
              grouped_data.append(env_data + music_data + power_data)
              buffer = {"environment": None, "music": None, "power": None}
  
      output_df = pd.DataFrame(grouped_data, columns=output_columns)
      output_filename = f"iGem2025-{file}"
      output_df.to_excel(output_filename, index=False)
      summary_data.append(output_df)
  
  summary_df = pd.concat(summary_data, ignore_index=True)
  summary_df.to_excel("iGem2025-summary.xlsx", index=False)
  

System Demonstration and Pictures

Drylab Experiment Image

Figure: Cloud Data Demostration 1

Drylab Experiment Image

Figure: Cloud Data Demostration 2

Drylab Experiment Image

Figure: Cloud Data Demostration 3

Drylab Experiment Image

Figure: Cloud Data Demostration 4