From 46ca6884346d6bce23a915162c1c95d665e02dcb Mon Sep 17 00:00:00 2001 From: spdis Date: Sat, 20 Sep 2025 18:30:26 +0800 Subject: [PATCH] =?UTF-8?q?=E4=BF=AE=E5=A4=8D=E5=86=9C=E5=8E=86=E8=BD=AC?= =?UTF-8?q?=E6=8D=A2=E7=9A=84bug?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- Dorm-Air-Conditioner-Smart-Controller.ino | 839 ++++++++++++++++++++-- 1 file changed, 794 insertions(+), 45 deletions(-) diff --git a/Dorm-Air-Conditioner-Smart-Controller.ino b/Dorm-Air-Conditioner-Smart-Controller.ino index 028d101..2f61844 100644 --- a/Dorm-Air-Conditioner-Smart-Controller.ino +++ b/Dorm-Air-Conditioner-Smart-Controller.ino @@ -1,33 +1,57 @@ //使用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; // 声明Preferences对象 - - //打开preferences命名空间:DACSC + + // 打开preferences命名空间 prefs.begin("DACSC"); - + + // 初始化红外控制 + initIRControl(); + // 启动DHT传感器 dht.begin(); - // 启动ds1302 + + // 启动RTC setupRTC(); - + // 初始化核心模块 Serial.println("正在初始化智能空调控制系统..."); if (initializeCore()) { @@ -35,44 +59,769 @@ void setup() { } 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() { - // 调用核心判断函数 - int decision = judge(); + // 处理Web服务器请求 + server.handleClient(); - // 打印决策结果 - Serial.println("========================================"); - Serial.print("智能决策结果: "); - - switch(decision) { - case JUDGE_NO_ACTION: - Serial.println("无需操作 - 当前环境正常"); - break; - case JUDGE_TURN_ON_COOLING: - Serial.println("开启制冷模式 - 检测到需要降温"); - break; - case JUDGE_TURN_ON_HEATING: - Serial.println("开启制暖模式 - 检测到需要升温"); - break; - case JUDGE_TURN_OFF_AC: - Serial.println("关闭空调 - 检测到无人或离开"); - break; - case JUDGE_ADJUST_TEMP: - Serial.println("打开除湿 - 优化舒适度"); - break; - case JUDGE_ERROR: - Serial.println("系统错误 - 请检查传感器连接"); - break; - default: - Serial.println("未知状态"); - break; + // 定期执行核心判断逻辑 + unsigned long currentTime = millis(); + if (currentTime - lastJudgeTime >= judgeInterval) { + lastJudgeTime = currentTime; + executeJudgeLogic(); } - Serial.println("========================================"); - Serial.println(); + delay(10); +} + +// 设置WiFi热点 +void setupWiFiAP() { + Serial.println("正在设置WiFi热点..."); - // 等待30秒后再次执行判断 - delay(5000); + 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.data = nullptr; + signal.length = 0; + signal.isValid = false; + + if (dataStr.length() == 0) { + return signal; + } + + // 计算数据长度 + int commaCount = 0; + for (int i = 0; i < dataStr.length(); i++) { + if (dataStr[i] == ',') commaCount++; + } + signal.length = commaCount + 1; + + // 分配内存 + signal.data = (unsigned int*)malloc(signal.length * sizeof(unsigned int)); + if (signal.data == 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); + signal.data[index] = valueStr.toInt(); + index++; + startPos = i + 1; + } + } + + signal.isValid = true; + return signal; +} + +// 将红外信号转换为字符串 +String irSignalToString(const IRSignal& signal) { + if (!signal.isValid || signal.data == nullptr || signal.length == 0) { + return ""; + } + + String result = ""; + for (int i = 0; i < signal.length; i++) { + if (i > 0) result += ","; + result += String(signal.data[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("信号长度: " + String(signal.length)); + + 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(200); + 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", "页面未找到"); } \ No newline at end of file