//使用preferences作为数据持久化方案,类似于本项目的数据库 #include #include #include #include #include "DHT11control.h" #include "RTC_Module.h" #include #include "LunarCalendarAndHolidayJudge.h" #include "core.h" #include "ir_control.h" // WiFi热点配置 const char* ap_ssid = "ESP32_AirConditioner"; const char* ap_password = "12345678"; // Web服务器 WebServer server(80); // 全局变量 ThreeWire myWire(4,5,2); RtcDS1302 Rtc(myWire); Preferences prefs; bool isRecording = false; unsigned long lastJudgeTime = 0; const unsigned long judgeInterval = 30000; // 30秒执行一次判断 // 存储自定义按钮的红外信号 struct CustomButton { String name; String irData; bool isValid; }; // 最多支持10个自定义按钮 CustomButton customButtons[10]; int customButtonCount = 0; void setup() { // 初始化串口通信 Serial.begin(115200); // 打开preferences命名空间 prefs.begin("DACSC"); // 初始化红外控制 initIRControl(); // 启动DHT传感器 dht.begin(); // 启动RTC setupRTC(); // 初始化核心模块 Serial.println("正在初始化智能空调控制系统..."); if (initializeCore()) { Serial.println("系统初始化成功!"); } else { Serial.println("系统初始化失败!"); } // 加载自定义按钮 loadCustomButtons(); // 设置WiFi热点 setupWiFiAP(); // 设置Web服务器路由 setupWebServer(); // 启动Web服务器 server.begin(); Serial.println("Web服务器已启动"); Serial.print("请连接WiFi: "); Serial.println(ap_ssid); Serial.print("密码: "); Serial.println(ap_password); Serial.print("然后访问: http://"); Serial.println(WiFi.softAPIP()); } void loop() { // 处理Web服务器请求 server.handleClient(); // 定期执行核心判断逻辑 unsigned long currentTime = millis(); if (currentTime - lastJudgeTime >= judgeInterval) { lastJudgeTime = currentTime; executeJudgeLogic(); } delay(10); } // 设置WiFi热点 void setupWiFiAP() { Serial.println("正在设置WiFi热点..."); WiFi.mode(WIFI_AP); WiFi.softAP(ap_ssid, ap_password); IPAddress IP = WiFi.softAPIP(); Serial.print("热点IP地址: "); Serial.println(IP); Serial.println("WiFi热点设置完成"); } // 设置Web服务器路由 void setupWebServer() { // 主页 server.on("/", handleRoot); // API路由 server.on("/api/time", HTTP_GET, handleGetTime); server.on("/api/time", HTTP_POST, handleSetTime); server.on("/api/buttons", HTTP_GET, handleGetButtons); server.on("/api/buttons", HTTP_POST, handleAddButton); server.on("/api/buttons", HTTP_DELETE, handleDeleteButton); server.on("/api/record", HTTP_POST, handleStartRecord); server.on("/api/send", HTTP_POST, handleSendIR); server.on("/api/settings", HTTP_GET, handleGetSettings); server.on("/api/settings", HTTP_POST, handleSetSettings); // 404处理 server.onNotFound(handleNotFound); } // 执行核心判断逻辑 void executeJudgeLogic() { int judgeResult = judge(); switch (judgeResult) { case JUDGE_TURN_ON_COOLING: Serial.println("执行操作: 开启制冷"); sendStoredIRSignal("制冷"); break; case JUDGE_TURN_ON_HEATING: Serial.println("执行操作: 开启制暖"); sendStoredIRSignal("制热"); break; case JUDGE_TURN_OFF_AC: Serial.println("执行操作: 关闭空调"); sendStoredIRSignal("关机"); break; case JUDGE_ADJUST_TEMP: Serial.println("执行操作: 除湿"); sendStoredIRSignal("除湿"); break; case JUDGE_NO_ACTION: Serial.println("执行操作: 无需操作"); break; case JUDGE_ERROR: Serial.println("执行操作: 判断出错"); break; default: Serial.println("执行操作: 未知结果"); break; } } // 发送存储的红外信号 void sendStoredIRSignal(String buttonName) { String irDataStr = prefs.getString(("ir_" + buttonName).c_str(), ""); if (irDataStr.length() == 0) { Serial.println("未找到按钮 " + buttonName + " 的红外信号"); return; } // 解析红外信号数据 IRSignal signal = parseIRSignalFromString(irDataStr); if (signal.isValid) { Serial.println("发送红外信号: " + buttonName); sendIRSignal(signal); freeIRSignal(signal); } else { Serial.println("红外信号数据无效: " + buttonName); } } // 从字符串解析红外信号 IRSignal parseIRSignalFromString(String dataStr) { IRSignal signal; signal.markTimes = nullptr; signal.spaceTimes = nullptr; signal.markCount = 0; signal.spaceCount = 0; signal.carrierFreq = IR_CARRIER_FREQ; signal.isValid = false; if (dataStr.length() == 0) { return signal; } // 计算数据长度 int commaCount = 0; for (int i = 0; i < dataStr.length(); i++) { if (dataStr[i] == ',') commaCount++; } int totalLength = commaCount + 1; // 分配临时内存存储原始数据 unsigned int* tempData = (unsigned int*)malloc(totalLength * sizeof(unsigned int)); if (tempData == nullptr) { return signal; } // 解析数据 int index = 0; int startPos = 0; for (int i = 0; i <= dataStr.length(); i++) { if (i == dataStr.length() || dataStr[i] == ',') { String valueStr = dataStr.substring(startPos, i); tempData[index] = valueStr.toInt(); index++; startPos = i + 1; } } // 分离mark和space数据 signal.markCount = (totalLength + 1) / 2; signal.spaceCount = totalLength / 2; // 分配mark时间数组 if (signal.markCount > 0) { signal.markTimes = (unsigned int*)malloc(signal.markCount * sizeof(unsigned int)); if (signal.markTimes == nullptr) { free(tempData); return signal; } for (int i = 0; i < signal.markCount; i++) { signal.markTimes[i] = tempData[i * 2]; } } // 分配space时间数组 if (signal.spaceCount > 0) { signal.spaceTimes = (unsigned int*)malloc(signal.spaceCount * sizeof(unsigned int)); if (signal.spaceTimes == nullptr) { if (signal.markTimes != nullptr) { free(signal.markTimes); signal.markTimes = nullptr; signal.markCount = 0; } free(tempData); return signal; } for (int i = 0; i < signal.spaceCount; i++) { signal.spaceTimes[i] = tempData[i * 2 + 1]; } } free(tempData); signal.isValid = true; return signal; } // 将红外信号转换为字符串 String irSignalToString(const IRSignal& signal) { if (!signal.isValid || (signal.markTimes == nullptr && signal.spaceTimes == nullptr) || (signal.markCount == 0 && signal.spaceCount == 0)) { return ""; } String result = ""; int maxCount = max(signal.markCount, signal.spaceCount); // 重建原始时序数据:交替输出mark和space for (int i = 0; i < maxCount; i++) { // 添加mark时间 if (i < signal.markCount) { if (result.length() > 0) result += ","; result += String(signal.markTimes[i]); } // 添加space时间 if (i < signal.spaceCount) { if (result.length() > 0) result += ","; result += String(signal.spaceTimes[i]); } } return result; } // 加载自定义按钮 void loadCustomButtons() { customButtonCount = prefs.getInt("customBtnCount", 0); for (int i = 0; i < customButtonCount && i < 10; i++) { String keyName = "customBtn_" + String(i) + "_name"; String keyData = "customBtn_" + String(i) + "_data"; customButtons[i].name = prefs.getString(keyName.c_str(), ""); customButtons[i].irData = prefs.getString(keyData.c_str(), ""); customButtons[i].isValid = (customButtons[i].name.length() > 0 && customButtons[i].irData.length() > 0); } } // 保存自定义按钮 void saveCustomButtons() { prefs.putInt("customBtnCount", customButtonCount); for (int i = 0; i < customButtonCount && i < 10; i++) { String keyName = "customBtn_" + String(i) + "_name"; String keyData = "customBtn_" + String(i) + "_data"; prefs.putString(keyName.c_str(), customButtons[i].name); prefs.putString(keyData.c_str(), customButtons[i].irData); } } // Web服务器处理函数 // 主页处理 void handleRoot() { String html = ""; html += ""; html += ""; html += ""; html += ""; html += "宿舍空调智能控制器"; html += ""; html += ""; html += ""; html += "
"; html += "
"; html += "
--:--:--
"; html += "
----年--月--日
"; html += "
"; html += "
"; html += "
"; html += "
空调控制
"; html += "
"; html += "
"; html += ""; html += ""; html += "
"; html += "
"; html += ""; html += ""; html += ""; html += ""; html += "
"; html += "
"; html += "
"; html += "
自定义按钮
"; html += "
"; html += ""; html += ""; html += "
"; html += "
"; html += "
"; html += "
"; html += "
"; html += "
"; html += "
"; html += "×"; html += "

系统设置

"; html += "
"; html += "

时间设置

"; html += "
"; html += ""; html += ""; html += ""; html += "
"; html += "
"; html += ""; html += ""; html += ""; html += "
"; html += ""; html += "
"; html += "
"; html += "

温度设置

"; html += "
"; html += ""; html += ""; html += "
"; html += ""; html += "
"; html += "
"; html += "
"; html += ""; html += ""; html += ""; server.send(200, "text/html", html); } // 获取时间 void handleGetTime() { RtcDateTime currentTime = getRTCTime(); DynamicJsonDocument doc(200); doc["success"] = true; doc["year"] = currentTime.Year(); doc["month"] = currentTime.Month(); doc["day"] = currentTime.Day(); doc["hour"] = currentTime.Hour(); doc["minute"] = currentTime.Minute(); doc["second"] = currentTime.Second(); String response; serializeJson(doc, response); server.send(200, "application/json", response); } // 设置时间 void handleSetTime() { if (!server.hasArg("plain")) { server.send(400, "application/json", "{\"success\":false,\"message\":\"缺少请求数据\"}"); return; } DynamicJsonDocument doc(200); deserializeJson(doc, server.arg("plain")); int year = doc["year"]; int month = doc["month"]; int day = doc["day"]; int hour = doc["hour"]; int minute = doc["minute"]; int second = doc["second"]; setRTCTime(year, month, day, hour, minute, second); server.send(200, "application/json", "{\"success\":true}"); } // 获取按钮列表 void handleGetButtons() { DynamicJsonDocument doc(1024); doc["success"] = true; JsonArray buttons = doc.createNestedArray("buttons"); for (int i = 0; i < customButtonCount; i++) { if (customButtons[i].isValid) { JsonObject button = buttons.createNestedObject(); button["name"] = customButtons[i].name; } } String response; serializeJson(doc, response); server.send(200, "application/json", response); } // 添加按钮 void handleAddButton() { if (!server.hasArg("plain")) { server.send(400, "application/json", "{\"success\":false,\"message\":\"缺少请求数据\"}"); return; } DynamicJsonDocument doc(200); deserializeJson(doc, server.arg("plain")); String name = doc["name"]; if (name.length() == 0) { server.send(400, "application/json", "{\"success\":false,\"message\":\"按钮名称不能为空\"}"); return; } if (customButtonCount >= 10) { server.send(400, "application/json", "{\"success\":false,\"message\":\"自定义按钮数量已达上限\"}"); return; } // 检查是否已存在同名按钮 for (int i = 0; i < customButtonCount; i++) { if (customButtons[i].isValid && customButtons[i].name == name) { server.send(400, "application/json", "{\"success\":false,\"message\":\"按钮名称已存在\"}"); return; } } // 添加新按钮 customButtons[customButtonCount].name = name; customButtons[customButtonCount].irData = ""; customButtons[customButtonCount].isValid = true; customButtonCount++; saveCustomButtons(); server.send(200, "application/json", "{\"success\":true}"); } // 删除按钮 void handleDeleteButton() { String name = server.arg("name"); if (name.length() == 0) { server.send(400, "application/json", "{\"success\":false,\"message\":\"缺少按钮名称\"}"); return; } // 查找并删除按钮 bool found = false; for (int i = 0; i < customButtonCount; i++) { if (customButtons[i].isValid && customButtons[i].name == name) { // 删除对应的红外数据 prefs.remove(("ir_" + name).c_str()); // 移动数组元素 for (int j = i; j < customButtonCount - 1; j++) { customButtons[j] = customButtons[j + 1]; } customButtonCount--; found = true; break; } } if (found) { saveCustomButtons(); server.send(200, "application/json", "{\"success\":true}"); } else { server.send(404, "application/json", "{\"success\":false,\"message\":\"按钮不存在\"}"); } } // 开始录入红外信号 void handleStartRecord() { if (!server.hasArg("plain")) { server.send(400, "application/json", "{\"success\":false,\"message\":\"缺少请求数据\"}"); return; } DynamicJsonDocument doc(200); deserializeJson(doc, server.arg("plain")); String buttonName = doc["button"]; if (buttonName.length() == 0) { server.send(400, "application/json", "{\"success\":false,\"message\":\"按钮名称不能为空\"}"); return; } Serial.println("开始录入红外信号: " + buttonName); // 等待红外信号 unsigned long startTime = millis(); while (!checkIRSignalStart() && (millis() - startTime) < 10000) { delay(10); } if (millis() - startTime >= 10000) { server.send(400, "application/json", "{\"success\":false,\"message\":\"录入超时,未检测到红外信号\"}"); return; } // 接收红外信号 IRSignal signal = receiveIRSignal(); if (signal.isValid) { // 将信号转换为字符串并保存 String irDataStr = irSignalToString(signal); prefs.putString(("ir_" + buttonName).c_str(), irDataStr); Serial.println("红外信号录入成功: " + buttonName); Serial.println("mark数量: " + String(signal.markCount) + ", space数量: " + String(signal.spaceCount)); freeIRSignal(signal); server.send(200, "application/json", "{\"success\":true}"); } else { Serial.println("红外信号录入失败: " + buttonName); server.send(400, "application/json", "{\"success\":false,\"message\":\"红外信号接收失败\"}"); } } // 发送红外信号 void handleSendIR() { if (!server.hasArg("plain")) { server.send(400, "application/json", "{\"success\":false,\"message\":\"缺少请求数据\"}"); return; } DynamicJsonDocument doc(26700); deserializeJson(doc, server.arg("plain")); String buttonName = doc["button"]; if (buttonName.length() == 0) { server.send(400, "application/json", "{\"success\":false,\"message\":\"按钮名称不能为空\"}"); return; } sendStoredIRSignal(buttonName); server.send(200, "application/json", "{\"success\":true}"); } // 获取设置 void handleGetSettings() { float minTemp = prefs.getFloat("min_temp", 22.0); float maxTemp = prefs.getFloat("max_temp", 26.0); DynamicJsonDocument doc(200); doc["success"] = true; doc["minTemp"] = minTemp; doc["maxTemp"] = maxTemp; String response; serializeJson(doc, response); server.send(200, "application/json", response); } // 设置温度范围 void handleSetSettings() { if (!server.hasArg("plain")) { server.send(400, "application/json", "{\"success\":false,\"message\":\"缺少请求数据\"}"); return; } DynamicJsonDocument doc(200); deserializeJson(doc, server.arg("plain")); float minTemp = doc["minTemp"]; float maxTemp = doc["maxTemp"]; if (minTemp >= maxTemp) { server.send(400, "application/json", "{\"success\":false,\"message\":\"最低温度必须小于最高温度\"}"); return; } prefs.putFloat("min_temp", minTemp); prefs.putFloat("max_temp", maxTemp); server.send(200, "application/json", "{\"success\":true}"); } // 404处理 void handleNotFound() { server.send(404, "text/plain", "页面未找到"); }