Turbidity & Voltage Measurement
/*
* 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;
}
Power Supply and Data Uploading
#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;
}
WIFI and OLED Display
/*
* 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連接");
}
}
Records

Experiment 1

Experiment 2

Experiment 3
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 |