修复农历转换的bug
This commit is contained in:
@@ -1,31 +1,55 @@
|
||||
//使用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();
|
||||
|
||||
// 初始化核心模块
|
||||
@@ -36,43 +60,768 @@ void setup() {
|
||||
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("智能决策结果: ");
|
||||
// 定期执行核心判断逻辑
|
||||
unsigned long currentTime = millis();
|
||||
if (currentTime - lastJudgeTime >= judgeInterval) {
|
||||
lastJudgeTime = currentTime;
|
||||
executeJudgeLogic();
|
||||
}
|
||||
|
||||
switch(decision) {
|
||||
case JUDGE_NO_ACTION:
|
||||
Serial.println("无需操作 - 当前环境正常");
|
||||
break;
|
||||
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("开启制冷模式 - 检测到需要降温");
|
||||
Serial.println("执行操作: 开启制冷");
|
||||
sendStoredIRSignal("制冷");
|
||||
break;
|
||||
|
||||
case JUDGE_TURN_ON_HEATING:
|
||||
Serial.println("开启制暖模式 - 检测到需要升温");
|
||||
Serial.println("执行操作: 开启制暖");
|
||||
sendStoredIRSignal("制热");
|
||||
break;
|
||||
|
||||
case JUDGE_TURN_OFF_AC:
|
||||
Serial.println("关闭空调 - 检测到无人或离开");
|
||||
Serial.println("执行操作: 关闭空调");
|
||||
sendStoredIRSignal("关机");
|
||||
break;
|
||||
|
||||
case JUDGE_ADJUST_TEMP:
|
||||
Serial.println("打开除湿 - 优化舒适度");
|
||||
Serial.println("执行操作: 除湿");
|
||||
sendStoredIRSignal("除湿");
|
||||
break;
|
||||
|
||||
case JUDGE_NO_ACTION:
|
||||
Serial.println("执行操作: 无需操作");
|
||||
break;
|
||||
|
||||
case JUDGE_ERROR:
|
||||
Serial.println("系统错误 - 请检查传感器连接");
|
||||
Serial.println("执行操作: 判断出错");
|
||||
break;
|
||||
|
||||
default:
|
||||
Serial.println("未知状态");
|
||||
Serial.println("执行操作: 未知结果");
|
||||
break;
|
||||
}
|
||||
|
||||
Serial.println("========================================");
|
||||
Serial.println();
|
||||
|
||||
// 等待30秒后再次执行判断
|
||||
delay(5000);
|
||||
}
|
||||
|
||||
// 发送存储的红外信号
|
||||
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