基本完成
This commit is contained in:
		@@ -1,33 +1,57 @@
 | 
			
		||||
//使用preferences作为数据持久化方案,类似于本项目的数据库
 | 
			
		||||
#include <Preferences.h>
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
#include <WiFi.h>
 | 
			
		||||
#include <WebServer.h>
 | 
			
		||||
#include <ArduinoJson.h>
 | 
			
		||||
#include "DHT11control.h"
 | 
			
		||||
 | 
			
		||||
#include "RTC_Module.h"
 | 
			
		||||
#include <RtcDS1302.h>
 | 
			
		||||
 | 
			
		||||
#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<ThreeWire> 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 = "<!DOCTYPE html>";
 | 
			
		||||
  html += "<html lang=\"zh-CN\">";
 | 
			
		||||
  html += "<head>";
 | 
			
		||||
  html += "<meta charset=\"UTF-8\">";
 | 
			
		||||
  html += "<meta name=\"viewport\" content=\"width=device-width, initial-scale=1.0\">";
 | 
			
		||||
  html += "<title>宿舍空调智能控制器</title>";
 | 
			
		||||
  html += "<style>";
 | 
			
		||||
  html += "* { margin: 0; padding: 0; box-sizing: border-box; }";
 | 
			
		||||
  html += "body { font-family: 'Microsoft YaHei', Arial, sans-serif; background: linear-gradient(135deg, #667eea 0%, #764ba2 100%); min-height: 100vh; }";
 | 
			
		||||
  html += ".container { max-width: 800px; margin: 0 auto; padding: 20px; }";
 | 
			
		||||
  html += ".header { background: rgba(255,255,255,0.95); border-radius: 15px; padding: 20px; margin-bottom: 20px; text-align: center; box-shadow: 0 8px 32px rgba(0,0,0,0.1); }";
 | 
			
		||||
  html += ".time-display { font-size: 24px; font-weight: bold; color: #333; margin-bottom: 10px; }";
 | 
			
		||||
  html += ".date-display { font-size: 16px; color: #666; }";
 | 
			
		||||
  html += ".main-content { background: rgba(255,255,255,0.95); border-radius: 15px; padding: 30px; box-shadow: 0 8px 32px rgba(0,0,0,0.1); }";
 | 
			
		||||
  html += ".section { margin-bottom: 30px; }";
 | 
			
		||||
  html += ".section-title { font-size: 20px; font-weight: bold; color: #333; margin-bottom: 15px; border-bottom: 2px solid #667eea; padding-bottom: 5px; }";
 | 
			
		||||
  html += ".button-grid { display: grid; grid-template-columns: repeat(auto-fit, minmax(150px, 1fr)); gap: 15px; margin-bottom: 20px; }";
 | 
			
		||||
  html += ".btn { padding: 15px 20px; border: none; border-radius: 10px; font-size: 16px; font-weight: bold; cursor: pointer; transition: all 0.3s ease; text-align: center; }";
 | 
			
		||||
  html += ".btn-primary { background: linear-gradient(45deg, #667eea, #764ba2); color: white; }";
 | 
			
		||||
  html += ".btn-primary:hover { transform: translateY(-2px); box-shadow: 0 5px 15px rgba(102,126,234,0.4); }";
 | 
			
		||||
  html += ".btn-success { background: linear-gradient(45deg, #56ab2f, #a8e6cf); color: white; }";
 | 
			
		||||
  html += ".btn-success:hover { transform: translateY(-2px); box-shadow: 0 5px 15px rgba(86,171,47,0.4); }";
 | 
			
		||||
  html += ".btn-danger { background: linear-gradient(45deg, #ff416c, #ff4b2b); color: white; }";
 | 
			
		||||
  html += ".btn-danger:hover { transform: translateY(-2px); box-shadow: 0 5px 15px rgba(255,65,108,0.4); }";
 | 
			
		||||
  html += ".btn-warning { background: linear-gradient(45deg, #f7971e, #ffd200); color: white; }";
 | 
			
		||||
  html += ".btn-warning:hover { transform: translateY(-2px); box-shadow: 0 5px 15px rgba(247,151,30,0.4); }";
 | 
			
		||||
  html += ".btn-secondary { background: linear-gradient(45deg, #bdc3c7, #2c3e50); color: white; }";
 | 
			
		||||
  html += ".btn-secondary:hover { transform: translateY(-2px); box-shadow: 0 5px 15px rgba(189,195,199,0.4); }";
 | 
			
		||||
  html += ".control-panel { display: flex; gap: 10px; margin-bottom: 20px; flex-wrap: wrap; }";
 | 
			
		||||
  html += ".input-group { display: flex; gap: 10px; margin-bottom: 15px; align-items: center; }";
 | 
			
		||||
  html += ".input-group input { flex: 1; padding: 10px; border: 2px solid #ddd; border-radius: 8px; font-size: 14px; }";
 | 
			
		||||
  html += ".input-group input:focus { outline: none; border-color: #667eea; }";
 | 
			
		||||
  html += ".status { padding: 10px; border-radius: 8px; margin-bottom: 15px; font-weight: bold; }";
 | 
			
		||||
  html += ".status.success { background: #d4edda; color: #155724; border: 1px solid #c3e6cb; }";
 | 
			
		||||
  html += ".status.error { background: #f8d7da; color: #721c24; border: 1px solid #f5c6cb; }";
 | 
			
		||||
  html += ".status.info { background: #d1ecf1; color: #0c5460; border: 1px solid #bee5eb; }";
 | 
			
		||||
  html += ".modal { display: none; position: fixed; z-index: 1000; left: 0; top: 0; width: 100%; height: 100%; background-color: rgba(0,0,0,0.5); }";
 | 
			
		||||
  html += ".modal-content { background-color: white; margin: 10% auto; padding: 30px; border-radius: 15px; width: 90%; max-width: 500px; box-shadow: 0 10px 30px rgba(0,0,0,0.3); }";
 | 
			
		||||
  html += ".close { color: #aaa; float: right; font-size: 28px; font-weight: bold; cursor: pointer; }";
 | 
			
		||||
  html += ".close:hover { color: #000; }";
 | 
			
		||||
  html += "@media (max-width: 600px) { .container { padding: 10px; } .button-grid { grid-template-columns: repeat(auto-fit, minmax(120px, 1fr)); gap: 10px; } .control-panel { flex-direction: column; } }";
 | 
			
		||||
  html += "</style>";
 | 
			
		||||
  html += "</head>";
 | 
			
		||||
  html += "<body>";
 | 
			
		||||
  html += "<div class=\"container\">";
 | 
			
		||||
  html += "<div class=\"header\">";
 | 
			
		||||
  html += "<div class=\"time-display\" id=\"timeDisplay\">--:--:--</div>";
 | 
			
		||||
  html += "<div class=\"date-display\" id=\"dateDisplay\">----年--月--日</div>";
 | 
			
		||||
  html += "</div>";
 | 
			
		||||
  html += "<div class=\"main-content\">";
 | 
			
		||||
  html += "<div class=\"section\">";
 | 
			
		||||
  html += "<div class=\"section-title\">空调控制</div>";
 | 
			
		||||
  html += "<div id=\"statusMessage\" class=\"status info\" style=\"display: none;\"></div>";
 | 
			
		||||
  html += "<div class=\"control-panel\">";
 | 
			
		||||
  html += "<button class=\"btn btn-warning\" onclick=\"toggleRecord()\" id=\"recordBtn\">开始录入</button>";
 | 
			
		||||
  html += "<button class=\"btn btn-secondary\" onclick=\"openSettings()\">设置</button>";
 | 
			
		||||
  html += "</div>";
 | 
			
		||||
  html += "<div class=\"button-grid\" id=\"buttonGrid\">";
 | 
			
		||||
  html += "<button class=\"btn btn-primary\" onclick=\"sendIR('制冷')\">制冷</button>";
 | 
			
		||||
  html += "<button class=\"btn btn-danger\" onclick=\"sendIR('制热')\">制热</button>";
 | 
			
		||||
  html += "<button class=\"btn btn-success\" onclick=\"sendIR('除湿')\">除湿</button>";
 | 
			
		||||
  html += "<button class=\"btn btn-secondary\" onclick=\"sendIR('关机')\">关机</button>";
 | 
			
		||||
  html += "</div>";
 | 
			
		||||
  html += "</div>";
 | 
			
		||||
  html += "<div class=\"section\">";
 | 
			
		||||
  html += "<div class=\"section-title\">自定义按钮</div>";
 | 
			
		||||
  html += "<div class=\"input-group\">";
 | 
			
		||||
  html += "<input type=\"text\" id=\"newButtonName\" placeholder=\"输入按钮名称\">";
 | 
			
		||||
  html += "<button class=\"btn btn-success\" onclick=\"addCustomButton()\">添加按钮</button>";
 | 
			
		||||
  html += "</div>";
 | 
			
		||||
  html += "<div class=\"button-grid\" id=\"customButtonGrid\"></div>";
 | 
			
		||||
  html += "</div>";
 | 
			
		||||
  html += "</div>";
 | 
			
		||||
  html += "</div>";
 | 
			
		||||
  html += "<div id=\"settingsModal\" class=\"modal\">";
 | 
			
		||||
  html += "<div class=\"modal-content\">";
 | 
			
		||||
  html += "<span class=\"close\" onclick=\"closeSettings()\">×</span>";
 | 
			
		||||
  html += "<h2 style=\"margin-bottom: 20px; color: #333;\">系统设置</h2>";
 | 
			
		||||
  html += "<div style=\"margin-bottom: 20px;\">";
 | 
			
		||||
  html += "<h3 style=\"color: #667eea; margin-bottom: 10px;\">时间设置</h3>";
 | 
			
		||||
  html += "<div class=\"input-group\">";
 | 
			
		||||
  html += "<input type=\"number\" id=\"setYear\" placeholder=\"年\" min=\"2020\" max=\"2030\">";
 | 
			
		||||
  html += "<input type=\"number\" id=\"setMonth\" placeholder=\"月\" min=\"1\" max=\"12\">";
 | 
			
		||||
  html += "<input type=\"number\" id=\"setDay\" placeholder=\"日\" min=\"1\" max=\"31\">";
 | 
			
		||||
  html += "</div>";
 | 
			
		||||
  html += "<div class=\"input-group\">";
 | 
			
		||||
  html += "<input type=\"number\" id=\"setHour\" placeholder=\"时\" min=\"0\" max=\"23\">";
 | 
			
		||||
  html += "<input type=\"number\" id=\"setMinute\" placeholder=\"分\" min=\"0\" max=\"59\">";
 | 
			
		||||
  html += "<input type=\"number\" id=\"setSecond\" placeholder=\"秒\" min=\"0\" max=\"59\">";
 | 
			
		||||
  html += "</div>";
 | 
			
		||||
  html += "<button class=\"btn btn-primary\" onclick=\"setDateTime()\">设置时间</button>";
 | 
			
		||||
  html += "</div>";
 | 
			
		||||
  html += "<div style=\"margin-bottom: 20px;\">";
 | 
			
		||||
  html += "<h3 style=\"color: #667eea; margin-bottom: 10px;\">温度设置</h3>";
 | 
			
		||||
  html += "<div class=\"input-group\">";
 | 
			
		||||
  html += "<input type=\"number\" id=\"minTemp\" placeholder=\"最低温度\" min=\"5\" max=\"35\" step=\"0.5\">";
 | 
			
		||||
  html += "<input type=\"number\" id=\"maxTemp\" placeholder=\"最高温度\" min=\"5\" max=\"35\" step=\"0.5\">";
 | 
			
		||||
  html += "</div>";
 | 
			
		||||
  html += "<button class=\"btn btn-primary\" onclick=\"setTemperature()\">设置温度</button>";
 | 
			
		||||
  html += "</div>";
 | 
			
		||||
  html += "</div>";
 | 
			
		||||
  html += "</div>";
 | 
			
		||||
  html += "<script>";
 | 
			
		||||
  html += "let isRecording = false;";
 | 
			
		||||
  html += "function updateTime() {";
 | 
			
		||||
  html += "fetch('/api/time')";
 | 
			
		||||
  html += ".then(response => response.json())";
 | 
			
		||||
  html += ".then(data => {";
 | 
			
		||||
  html += "if (data.success) {";
 | 
			
		||||
  html += "document.getElementById('timeDisplay').textContent = String(data.hour).padStart(2, '0') + ':' + String(data.minute).padStart(2, '0') + ':' + String(data.second).padStart(2, '0');";
 | 
			
		||||
  html += "document.getElementById('dateDisplay').textContent = data.year + '年' + data.month + '月' + data.day + '日';";
 | 
			
		||||
  html += "}";
 | 
			
		||||
  html += "})";
 | 
			
		||||
  html += ".catch(error => console.error('获取时间失败:', error));";
 | 
			
		||||
  html += "}";
 | 
			
		||||
  html += "function showStatus(message, type) {";
 | 
			
		||||
  html += "const statusEl = document.getElementById('statusMessage');";
 | 
			
		||||
  html += "statusEl.textContent = message;";
 | 
			
		||||
  html += "statusEl.className = 'status ' + type;";
 | 
			
		||||
  html += "statusEl.style.display = 'block';";
 | 
			
		||||
  html += "setTimeout(() => { statusEl.style.display = 'none'; }, 3000);";
 | 
			
		||||
  html += "}";
 | 
			
		||||
  html += "function toggleRecord() {";
 | 
			
		||||
  html += "isRecording = !isRecording;";
 | 
			
		||||
  html += "const btn = document.getElementById('recordBtn');";
 | 
			
		||||
  html += "if (isRecording) {";
 | 
			
		||||
  html += "btn.textContent = '停止录入';";
 | 
			
		||||
  html += "btn.className = 'btn btn-danger';";
 | 
			
		||||
  html += "showStatus('录入模式已开启,点击按钮录入红外信号', 'info');";
 | 
			
		||||
  html += "} else {";
 | 
			
		||||
  html += "btn.textContent = '开始录入';";
 | 
			
		||||
  html += "btn.className = 'btn btn-warning';";
 | 
			
		||||
  html += "showStatus('录入模式已关闭', 'info');";
 | 
			
		||||
  html += "}";
 | 
			
		||||
  html += "}";
 | 
			
		||||
  html += "function sendIR(buttonName) {";
 | 
			
		||||
  html += "if (isRecording) {";
 | 
			
		||||
  html += "recordIR(buttonName);";
 | 
			
		||||
  html += "} else {";
 | 
			
		||||
  html += "fetch('/api/send', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ button: buttonName }) })";
 | 
			
		||||
  html += ".then(response => response.json())";
 | 
			
		||||
  html += ".then(data => {";
 | 
			
		||||
  html += "if (data.success) {";
 | 
			
		||||
  html += "showStatus('发送成功: ' + buttonName, 'success');";
 | 
			
		||||
  html += "} else {";
 | 
			
		||||
  html += "showStatus('发送失败: ' + (data.message || '未知错误'), 'error');";
 | 
			
		||||
  html += "}";
 | 
			
		||||
  html += "})";
 | 
			
		||||
  html += ".catch(error => { showStatus('发送失败: 网络错误', 'error'); });";
 | 
			
		||||
  html += "}";
 | 
			
		||||
  html += "}";
 | 
			
		||||
  html += "function recordIR(buttonName) {";
 | 
			
		||||
  html += "showStatus('正在录入 ' + buttonName + ' 的红外信号,请对准遥控器按键...', 'info');";
 | 
			
		||||
  html += "fetch('/api/record', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ button: buttonName }) })";
 | 
			
		||||
  html += ".then(response => response.json())";
 | 
			
		||||
  html += ".then(data => {";
 | 
			
		||||
  html += "if (data.success) {";
 | 
			
		||||
  html += "showStatus('录入成功: ' + buttonName, 'success');";
 | 
			
		||||
  html += "} else {";
 | 
			
		||||
  html += "showStatus('录入失败: ' + (data.message || '未知错误'), 'error');";
 | 
			
		||||
  html += "}";
 | 
			
		||||
  html += "})";
 | 
			
		||||
  html += ".catch(error => { showStatus('录入失败: 网络错误', 'error'); });";
 | 
			
		||||
  html += "}";
 | 
			
		||||
  html += "function addCustomButton() {";
 | 
			
		||||
  html += "const name = document.getElementById('newButtonName').value.trim();";
 | 
			
		||||
  html += "if (!name) { showStatus('请输入按钮名称', 'error'); return; }";
 | 
			
		||||
  html += "fetch('/api/buttons', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ name: name }) })";
 | 
			
		||||
  html += ".then(response => response.json())";
 | 
			
		||||
  html += ".then(data => {";
 | 
			
		||||
  html += "if (data.success) {";
 | 
			
		||||
  html += "showStatus('按钮添加成功', 'success');";
 | 
			
		||||
  html += "document.getElementById('newButtonName').value = '';";
 | 
			
		||||
  html += "loadCustomButtons();";
 | 
			
		||||
  html += "} else {";
 | 
			
		||||
  html += "showStatus('添加失败: ' + (data.message || '未知错误'), 'error');";
 | 
			
		||||
  html += "}";
 | 
			
		||||
  html += "})";
 | 
			
		||||
  html += ".catch(error => { showStatus('添加失败: 网络错误', 'error'); });";
 | 
			
		||||
  html += "}";
 | 
			
		||||
  html += "function deleteCustomButton(name) {";
 | 
			
		||||
  html += "if (!confirm('确定要删除按钮 \"' + name + '\" 吗?')) { return; }";
 | 
			
		||||
  html += "fetch('/api/buttons?name=' + encodeURIComponent(name), { method: 'DELETE' })";
 | 
			
		||||
  html += ".then(response => response.json())";
 | 
			
		||||
  html += ".then(data => {";
 | 
			
		||||
  html += "if (data.success) {";
 | 
			
		||||
  html += "showStatus('按钮删除成功', 'success');";
 | 
			
		||||
  html += "loadCustomButtons();";
 | 
			
		||||
  html += "} else {";
 | 
			
		||||
  html += "showStatus('删除失败: ' + (data.message || '未知错误'), 'error');";
 | 
			
		||||
  html += "}";
 | 
			
		||||
  html += "})";
 | 
			
		||||
  html += ".catch(error => { showStatus('删除失败: 网络错误', 'error'); });";
 | 
			
		||||
  html += "}";
 | 
			
		||||
  html += "function loadCustomButtons() {";
 | 
			
		||||
  html += "fetch('/api/buttons')";
 | 
			
		||||
  html += ".then(response => response.json())";
 | 
			
		||||
  html += ".then(data => {";
 | 
			
		||||
  html += "if (data.success) {";
 | 
			
		||||
  html += "const grid = document.getElementById('customButtonGrid');";
 | 
			
		||||
  html += "grid.innerHTML = '';";
 | 
			
		||||
  html += "data.buttons.forEach(button => {";
 | 
			
		||||
  html += "const btnEl = document.createElement('div');";
 | 
			
		||||
  html += "btnEl.style.position = 'relative';";
 | 
			
		||||
  html += "const mainBtn = document.createElement('button');";
 | 
			
		||||
  html += "mainBtn.className = 'btn btn-primary';";
 | 
			
		||||
  html += "mainBtn.style.width = '100%';";
 | 
			
		||||
  html += "mainBtn.style.marginBottom = '5px';";
 | 
			
		||||
  html += "mainBtn.textContent = button.name;";
 | 
			
		||||
  html += "mainBtn.onclick = function() { sendIR(button.name); };";
 | 
			
		||||
  html += "const delBtn = document.createElement('button');";
 | 
			
		||||
  html += "delBtn.className = 'btn btn-danger';";
 | 
			
		||||
  html += "delBtn.style.width = '100%';";
 | 
			
		||||
  html += "delBtn.style.padding = '5px';";
 | 
			
		||||
  html += "delBtn.style.fontSize = '12px';";
 | 
			
		||||
  html += "delBtn.textContent = '删除';";
 | 
			
		||||
  html += "delBtn.onclick = function() { deleteCustomButton(button.name); };";
 | 
			
		||||
  html += "btnEl.appendChild(mainBtn);";
 | 
			
		||||
  html += "btnEl.appendChild(delBtn);";
 | 
			
		||||
  html += "grid.appendChild(btnEl);";
 | 
			
		||||
  html += "});";
 | 
			
		||||
  html += "}";
 | 
			
		||||
  html += "})";
 | 
			
		||||
  html += ".catch(error => console.error('加载自定义按钮失败:', error));";
 | 
			
		||||
  html += "}";
 | 
			
		||||
  html += "function openSettings() {";
 | 
			
		||||
  html += "document.getElementById('settingsModal').style.display = 'block';";
 | 
			
		||||
  html += "loadSettings();";
 | 
			
		||||
  html += "}";
 | 
			
		||||
  html += "function closeSettings() {";
 | 
			
		||||
  html += "document.getElementById('settingsModal').style.display = 'none';";
 | 
			
		||||
  html += "}";
 | 
			
		||||
  html += "function loadSettings() {";
 | 
			
		||||
  html += "fetch('/api/settings')";
 | 
			
		||||
  html += ".then(response => response.json())";
 | 
			
		||||
  html += ".then(data => {";
 | 
			
		||||
  html += "if (data.success) {";
 | 
			
		||||
  html += "document.getElementById('minTemp').value = data.minTemp;";
 | 
			
		||||
  html += "document.getElementById('maxTemp').value = data.maxTemp;";
 | 
			
		||||
  html += "}";
 | 
			
		||||
  html += "})";
 | 
			
		||||
  html += ".catch(error => console.error('加载设置失败:', error));";
 | 
			
		||||
  html += "}";
 | 
			
		||||
  html += "function setDateTime() {";
 | 
			
		||||
  html += "const year = parseInt(document.getElementById('setYear').value);";
 | 
			
		||||
  html += "const month = parseInt(document.getElementById('setMonth').value);";
 | 
			
		||||
  html += "const day = parseInt(document.getElementById('setDay').value);";
 | 
			
		||||
  html += "const hour = parseInt(document.getElementById('setHour').value);";
 | 
			
		||||
  html += "const minute = parseInt(document.getElementById('setMinute').value);";
 | 
			
		||||
  html += "const second = parseInt(document.getElementById('setSecond').value);";
 | 
			
		||||
  html += "if (!year || !month || !day || isNaN(hour) || isNaN(minute) || isNaN(second)) {";
 | 
			
		||||
  html += "showStatus('请填写完整的日期时间', 'error');";
 | 
			
		||||
  html += "return;";
 | 
			
		||||
  html += "}";
 | 
			
		||||
  html += "fetch('/api/time', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ year, month, day, hour, minute, second }) })";
 | 
			
		||||
  html += ".then(response => response.json())";
 | 
			
		||||
  html += ".then(data => {";
 | 
			
		||||
  html += "if (data.success) {";
 | 
			
		||||
  html += "showStatus('时间设置成功', 'success');";
 | 
			
		||||
  html += "updateTime();";
 | 
			
		||||
  html += "} else {";
 | 
			
		||||
  html += "showStatus('时间设置失败: ' + (data.message || '未知错误'), 'error');";
 | 
			
		||||
  html += "}";
 | 
			
		||||
  html += "})";
 | 
			
		||||
  html += ".catch(error => { showStatus('时间设置失败: 网络错误', 'error'); });";
 | 
			
		||||
  html += "}";
 | 
			
		||||
  html += "function setTemperature() {";
 | 
			
		||||
  html += "const minTemp = parseFloat(document.getElementById('minTemp').value);";
 | 
			
		||||
  html += "const maxTemp = parseFloat(document.getElementById('maxTemp').value);";
 | 
			
		||||
  html += "if (isNaN(minTemp) || isNaN(maxTemp)) {";
 | 
			
		||||
  html += "showStatus('请输入有效的温度值', 'error');";
 | 
			
		||||
  html += "return;";
 | 
			
		||||
  html += "}";
 | 
			
		||||
  html += "if (minTemp >= maxTemp) {";
 | 
			
		||||
  html += "showStatus('最低温度必须小于最高温度', 'error');";
 | 
			
		||||
  html += "return;";
 | 
			
		||||
  html += "}";
 | 
			
		||||
  html += "fetch('/api/settings', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ minTemp, maxTemp }) })";
 | 
			
		||||
  html += ".then(response => response.json())";
 | 
			
		||||
  html += ".then(data => {";
 | 
			
		||||
  html += "if (data.success) {";
 | 
			
		||||
  html += "showStatus('温度设置成功', 'success');";
 | 
			
		||||
  html += "} else {";
 | 
			
		||||
  html += "showStatus('温度设置失败: ' + (data.message || '未知错误'), 'error');";
 | 
			
		||||
  html += "}";
 | 
			
		||||
  html += "})";
 | 
			
		||||
  html += ".catch(error => { showStatus('温度设置失败: 网络错误', 'error'); });";
 | 
			
		||||
  html += "}";
 | 
			
		||||
  html += "document.addEventListener('DOMContentLoaded', function() {";
 | 
			
		||||
  html += "updateTime();";
 | 
			
		||||
  html += "loadCustomButtons();";
 | 
			
		||||
  html += "setInterval(updateTime, 1000);";
 | 
			
		||||
  html += "});";
 | 
			
		||||
  html += "window.onclick = function(event) {";
 | 
			
		||||
  html += "const modal = document.getElementById('settingsModal');";
 | 
			
		||||
  html += "if (event.target == modal) {";
 | 
			
		||||
  html += "closeSettings();";
 | 
			
		||||
  html += "}";
 | 
			
		||||
  html += "}";
 | 
			
		||||
  html += "</script>";
 | 
			
		||||
  html += "</body>";
 | 
			
		||||
  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", "页面未找到");
 | 
			
		||||
}
 | 
			
		||||
		Reference in New Issue
	
	Block a user