From 44c707f2123797dffaaef05c040f416ef0d5e21c Mon Sep 17 00:00:00 2001 From: MiaoMint <44718819+MiaoMint@users.noreply.github.com> Date: Sat, 30 Aug 2025 21:03:40 +0800 Subject: [PATCH] Refactor timer duration to support float values and add firmware update functionality in web server --- src/config.h | 2 +- src/timer_manager.cpp | 65 +++++++----- src/timer_manager.h | 6 +- src/web_pages.h | 225 +++++++++++++++++++++++++++++++++++++++++- src/web_server.cpp | 173 +++++++++++++++++++++++--------- src/web_server.h | 3 +- 6 files changed, 398 insertions(+), 76 deletions(-) diff --git a/src/config.h b/src/config.h index f73b7a2..d031bb6 100644 --- a/src/config.h +++ b/src/config.h @@ -40,7 +40,7 @@ struct TimerConfig int pin; int hour; int minute; - int duration; // 持续时间(秒) + float duration; // 持续时间(秒,支持小数) bool repeatDaily; // 每天重复 bool isActive; // 当前是否激活 unsigned long startTime; // 开始时间(millis时间戳) diff --git a/src/timer_manager.cpp b/src/timer_manager.cpp index 23504e1..a162263 100644 --- a/src/timer_manager.cpp +++ b/src/timer_manager.cpp @@ -94,7 +94,7 @@ void TimerManager::update() { // 检查是否需要关闭 if (timers[i].isActive && - currentTime - timers[i].startTime >= (unsigned long)(timers[i].duration * 1000)) { + currentTime - timers[i].startTime >= (unsigned long)(timers[i].duration * 1000.0)) { timers[i].isActive = false; timers[i].realStartTime = 0; // 清理真实时间戳 @@ -112,7 +112,7 @@ void TimerManager::update() { } } -bool TimerManager::addTimer(int pin, int hour, int minute, int duration, bool repeatDaily, bool isPWM, int pwmValue) { +bool TimerManager::addTimer(int pin, int hour, int minute, float duration, bool repeatDaily, bool isPWM, int pwmValue) { if (timerCount >= MAX_TIMERS) { return false; } @@ -128,7 +128,7 @@ bool TimerManager::addTimer(int pin, int hour, int minute, int duration, bool re if (!pinValid) return false; // 验证时间 - if (hour < 0 || hour > 23 || minute < 0 || minute > 59 || duration <= 0) { + if (hour < 0 || hour > 23 || minute < 0 || minute > 59 || duration <= 0.0) { return false; } @@ -183,7 +183,7 @@ bool TimerManager::removeTimer(int index) { return true; } -bool TimerManager::updateTimer(int index, int pin, int hour, int minute, int duration, bool enabled, bool repeatDaily, bool isPWM, int pwmValue) { +bool TimerManager::updateTimer(int index, int pin, int hour, int minute, float duration, bool enabled, bool repeatDaily, bool isPWM, int pwmValue) { if (index < 0 || index >= timerCount) { return false; } @@ -199,7 +199,7 @@ bool TimerManager::updateTimer(int index, int pin, int hour, int minute, int dur if (!pinValid) return false; // 验证时间 - if (hour < 0 || hour > 23 || minute < 0 || minute > 59 || duration <= 0) { + if (hour < 0 || hour > 23 || minute < 0 || minute > 59 || duration <= 0.0) { return false; } @@ -234,11 +234,11 @@ bool TimerManager::updateTimer(int index, int pin, int hour, int minute, int dur } String TimerManager::getTimersJSON() { - DynamicJsonDocument doc(2048); + JsonDocument doc; JsonArray array = doc.to(); for (int i = 0; i < timerCount; i++) { - JsonObject timer = array.createNestedObject(); + JsonObject timer = array.add(); timer["index"] = i; timer["enabled"] = timers[i].enabled; timer["pin"] = timers[i].pin; @@ -257,11 +257,11 @@ String TimerManager::getTimersJSON() { } String TimerManager::getAvailablePinsJSON() { - DynamicJsonDocument doc(1024); + JsonDocument doc; JsonArray array = doc.to(); for (int i = 0; i < AVAILABLE_PINS_COUNT; i++) { - JsonObject pin = array.createNestedObject(); + JsonObject pin = array.add(); pin["pin"] = AVAILABLE_PINS[i]; pin["state"] = digitalRead(AVAILABLE_PINS[i]); @@ -281,7 +281,7 @@ String TimerManager::getAvailablePinsJSON() { return result; } -void TimerManager::executeManualControl(int pin, int duration, bool isPWM, int pwmValue) { +void TimerManager::executeManualControl(int pin, float duration, bool isPWM, int pwmValue) { // 验证引脚 bool pinValid = false; for (int i = 0; i < AVAILABLE_PINS_COUNT; i++) { @@ -297,14 +297,14 @@ void TimerManager::executeManualControl(int pin, int duration, bool isPWM, int p return; } - if (duration > 0) { + if (duration > 0.0) { // 开启引脚指定时间 setPin(pin, HIGH, isPWM ? pwmValue : 0); String modeStr = isPWM ? " PWM模式, 值=" + String(pwmValue) : " 数字模式"; Serial.println("手动控制:引脚 " + String(pin) + " 开启 " + String(duration) + " 秒" + modeStr); // 这里应该使用定时器来关闭,简化处理 - delay(duration * 1000); + delay((unsigned long)(duration * 1000.0)); setPin(pin, LOW, 0); Serial.println("手动控制:引脚 " + String(pin) + " 关闭"); } else { @@ -339,8 +339,18 @@ void TimerManager::saveTimers() { EEPROM.write(addr++, timers[i].pin); EEPROM.write(addr++, timers[i].hour); EEPROM.write(addr++, timers[i].minute); - EEPROM.write(addr++, (timers[i].duration >> 8) & 0xFF); - EEPROM.write(addr++, timers[i].duration & 0xFF); + + // 保存float类型的duration(4字节) + union { + float f; + uint8_t bytes[4]; + } durationConverter; + durationConverter.f = timers[i].duration; + EEPROM.write(addr++, durationConverter.bytes[0]); + EEPROM.write(addr++, durationConverter.bytes[1]); + EEPROM.write(addr++, durationConverter.bytes[2]); + EEPROM.write(addr++, durationConverter.bytes[3]); + EEPROM.write(addr++, timers[i].repeatDaily ? 1 : 0); EEPROM.write(addr++, timers[i].isPWM ? 1 : 0); EEPROM.write(addr++, (timers[i].pwmValue >> 8) & 0xFF); @@ -387,13 +397,22 @@ void TimerManager::loadTimers() { timers[i].pin = EEPROM.read(addr++); timers[i].hour = EEPROM.read(addr++); timers[i].minute = EEPROM.read(addr++); - int durationHigh = EEPROM.read(addr++); - int durationLow = EEPROM.read(addr++); - timers[i].duration = (durationHigh << 8) | durationLow; + + // 读取float类型的duration(4字节) + union { + float f; + uint8_t bytes[4]; + } durationConverter; + durationConverter.bytes[0] = EEPROM.read(addr++); + durationConverter.bytes[1] = EEPROM.read(addr++); + durationConverter.bytes[2] = EEPROM.read(addr++); + durationConverter.bytes[3] = EEPROM.read(addr++); + timers[i].duration = durationConverter.f; + timers[i].repeatDaily = EEPROM.read(addr++) == 1; // 检查是否有PWM数据(向后兼容) - if (addr < EEPROM_SIZE - 16) { // 增加了13个字节的运行时状态数据(9+4) + if (addr < EEPROM_SIZE - 20) { // 更新了字节数计算:duration现在是4字节而不是2字节 timers[i].isPWM = EEPROM.read(addr++) == 1; int pwmHigh = EEPROM.read(addr++); int pwmLow = EEPROM.read(addr++); @@ -455,7 +474,7 @@ void TimerManager::loadTimers() { unsigned long elapsedTime = currentTime - timers[i].startTime; // 检查定时器是否已经超时 - if (elapsedTime >= (unsigned long)(timers[i].duration * 1000)) { + if (elapsedTime >= (unsigned long)(timers[i].duration * 1000.0)) { // 定时器已经超时,关闭它 timers[i].isActive = false; timers[i].realStartTime = 0; @@ -465,7 +484,7 @@ void TimerManager::loadTimers() { // 定时器仍然有效,恢复引脚状态 setPin(timers[i].pin, HIGH, timers[i].isPWM ? timers[i].pwmValue : 0); String modeStr = timers[i].isPWM ? " PWM(" + String(timers[i].pwmValue) + ")" : ""; - unsigned long remainingTime = (timers[i].duration * 1000) - elapsedTime; + unsigned long remainingTime = (unsigned long)(timers[i].duration * 1000.0) - elapsedTime; Serial.println("恢复定时器 " + String(i) + " 状态,引脚 " + String(timers[i].pin) + " 开启" + modeStr + ",剩余时间: " + String(remainingTime / 1000) + "秒"); } @@ -492,8 +511,8 @@ void TimerManager::saveTimerStates() { int addr = TIMER_CONFIG_ADDR + 1; // 跳过定时器数量 for (int i = 0; i < timerCount; i++) { - // 跳过基本配置部分 (10字节) - addr += 10; + // 跳过基本配置部分 (12字节: enabled(1) + pin(1) + hour(1) + minute(1) + duration(4) + repeatDaily(1) + isPWM(1) + pwmValue(2)) + addr += 12; // 更新运行时状态部分 (13字节) EEPROM.write(addr++, timers[i].isActive ? 1 : 0); @@ -545,7 +564,7 @@ TimerConfig TimerManager::getTimer(int index) { return timers[index]; } - TimerConfig empty = {false, 0, 0, 0, 0, false, false, 0, 0UL, false, 0, 0UL}; + TimerConfig empty = {false, 0, 0, 0, 0.0, false, false, 0, 0UL, false, 0, 0UL}; return empty; } diff --git a/src/timer_manager.h b/src/timer_manager.h index 749c888..a4c671b 100644 --- a/src/timer_manager.h +++ b/src/timer_manager.h @@ -19,9 +19,9 @@ public: TimerManager(); void begin(TimeManager* tm); void update(); - bool addTimer(int pin, int hour, int minute, int duration, bool repeatDaily = false, bool isPWM = false, int pwmValue = 512); + bool addTimer(int pin, int hour, int minute, float duration, bool repeatDaily = false, bool isPWM = false, int pwmValue = 512); bool removeTimer(int index); - bool updateTimer(int index, int pin, int hour, int minute, int duration, bool enabled, bool repeatDaily = false, bool isPWM = false, int pwmValue = 512); + bool updateTimer(int index, int pin, int hour, int minute, float duration, bool enabled, bool repeatDaily = false, bool isPWM = false, int pwmValue = 512); String getTimersJSON(); void saveTimers(); void saveTimerStates(); // 新增:仅保存运行时状态 @@ -31,7 +31,7 @@ public: TimerConfig getTimer(int index); void setPin(int pin, bool state, int pwmValue = 0); String getAvailablePinsJSON(); - void executeManualControl(int pin, int duration, bool isPWM = false, int pwmValue = 512); + void executeManualControl(int pin, float duration, bool isPWM = false, int pwmValue = 512); bool hasValidTime(); }; diff --git a/src/web_pages.h b/src/web_pages.h index e1d55e1..9ac2044 100644 --- a/src/web_pages.h +++ b/src/web_pages.h @@ -371,6 +371,7 @@ const char INDEX_HTML[] PROGMEM = R"rawliteral( + @@ -397,7 +398,7 @@ const char INDEX_HTML[] PROGMEM = R"rawliteral(
- +
@@ -460,7 +461,7 @@ const char INDEX_HTML[] PROGMEM = R"rawliteral(
- +
+ + + @@ -839,7 +899,7 @@ const char INDEX_HTML[] PROGMEM = R"rawliteral( pin: parseInt(pin), hour, minute, - duration: parseInt(duration), + duration: parseFloat(duration), repeatDaily, isPWM: isPWM, pwmValue: parseInt(pwmValue) @@ -916,7 +976,7 @@ const char INDEX_HTML[] PROGMEM = R"rawliteral( try { const body = { pin: parseInt(pin), - duration: parseInt(duration) || 0, + duration: parseFloat(duration) || 0, isPWM: isPWM, pwmValue: parseInt(pwmValue) }; @@ -1030,6 +1090,163 @@ const char INDEX_HTML[] PROGMEM = R"rawliteral( document.getElementById('timer-pwm-percentage').textContent = percentage + '%'; } + // 固件更新相关函数 + document.addEventListener('DOMContentLoaded', function() { + const fileInput = document.getElementById('firmware-file'); + const uploadBtn = document.getElementById('upload-btn'); + + if (fileInput && uploadBtn) { + fileInput.addEventListener('change', function() { + const file = this.files[0]; + if (file) { + if (file.size > 2 * 1024 * 1024) { // 2MB限制 + showMessage('firmware-message', '文件大小不能超过 2MB', 'error'); + this.value = ''; + uploadBtn.disabled = true; + return; + } + if (!file.name.toLowerCase().endsWith('.bin')) { + showMessage('firmware-message', '请选择 .bin 格式的固件文件', 'error'); + this.value = ''; + uploadBtn.disabled = true; + return; + } + uploadBtn.disabled = false; + showMessage('firmware-message', `已选择文件: ${file.name} (${(file.size / 1024).toFixed(1)} KB)`, 'info'); + } else { + uploadBtn.disabled = true; + } + }); + } + + // 加载当前固件信息 + loadFirmwareInfo(); + }); + + async function loadFirmwareInfo() { + try { + const response = await fetch('/api/system'); + if (response.ok) { + const data = await response.json(); + document.getElementById('current-version').textContent = `v1.0.0`; + document.getElementById('build-time').textContent = new Date().toLocaleDateString(); + document.getElementById('firmware-chip-id').textContent = data.chipId || '未知'; + } + } catch (error) { + console.error('加载固件信息失败:', error); + } + } + + async function startFirmwareUpdate() { + const fileInput = document.getElementById('firmware-file'); + const file = fileInput.files[0]; + + if (!file) { + showMessage('firmware-message', '请先选择固件文件', 'error'); + return; + } + + if (!confirm('确定要更新固件吗?更新过程中请勿断电,设备将自动重启。')) { + return; + } + + const uploadBtn = document.getElementById('upload-btn'); + const progressDiv = document.getElementById('upload-progress'); + const progressBar = document.getElementById('progress-bar'); + const progressText = document.getElementById('progress-text'); + + // 禁用上传按钮并显示进度条 + uploadBtn.disabled = true; + progressDiv.classList.remove('hidden'); + progressBar.style.width = '0%'; + progressText.textContent = '正在上传固件...'; + + const formData = new FormData(); + formData.append('firmware', file); + + console.log('开始上传固件文件:', file.name, file.size, 'bytes'); + + try { + const xhr = new XMLHttpRequest(); + + // 监听上传进度 + xhr.upload.onprogress = function(e) { + if (e.lengthComputable) { + const percentComplete = Math.round((e.loaded / e.total) * 100); + progressBar.style.width = percentComplete + '%'; + progressText.textContent = `上传中... ${percentComplete}%`; + } + }; + + // 监听状态变化 + xhr.onreadystatechange = function() { + if (xhr.readyState === XMLHttpRequest.DONE) { + if (xhr.status === 200) { + progressBar.style.width = '100%'; + progressText.textContent = '上传完成,正在刷写固件...'; + showMessage('firmware-message', '固件上传成功,正在更新...请等待设备重启', 'success'); + + // 模拟刷写进度 + setTimeout(() => { + progressText.textContent = '固件更新中,请稍候...'; + }, 1000); + + setTimeout(() => { + progressText.textContent = '更新完成,设备即将重启...'; + showMessage('firmware-message', '固件更新完成!设备将在几秒钟后重启', 'success'); + }, 3000); + + setTimeout(() => { + window.location.reload(); + }, 8000); + } else { + console.error('上传失败,状态码:', xhr.status); + let errorMessage = '未知错误'; + try { + const response = JSON.parse(xhr.responseText); + errorMessage = response.message || errorMessage; + } catch (e) { + errorMessage = `服务器错误 (${xhr.status})`; + } + showMessage('firmware-message', `更新失败: ${errorMessage}`, 'error'); + resetUploadUI(); + } + } + }; + + xhr.onerror = function() { + console.error('网络错误或请求失败'); + showMessage('firmware-message', '上传失败,请检查网络连接', 'error'); + resetUploadUI(); + }; + + xhr.onabort = function() { + console.error('上传被中止'); + showMessage('firmware-message', '上传被中止', 'error'); + resetUploadUI(); + }; + + xhr.open('POST', '/api/firmware/update', true); + console.log('发送固件上传请求到:', '/api/firmware/update'); + xhr.send(formData); + + } catch (error) { + console.error('固件更新失败:', error); + showMessage('firmware-message', '更新失败: ' + error.message, 'error'); + resetUploadUI(); + } + } + + function resetUploadUI() { + const uploadBtn = document.getElementById('upload-btn'); + const progressDiv = document.getElementById('upload-progress'); + const fileInput = document.getElementById('firmware-file'); + + uploadBtn.disabled = false; + progressDiv.classList.add('hidden'); + fileInput.value = ''; + } + function showMessage(elementId, message, type) { const el = document.getElementById(elementId); el.textContent = message; diff --git a/src/web_server.cpp b/src/web_server.cpp index b049798..0eed2d0 100644 --- a/src/web_server.cpp +++ b/src/web_server.cpp @@ -1,4 +1,8 @@ #include "web_server.h" +#include +#include + +ESP8266HTTPUpdateServer httpUpdater; WebServer::WebServer(WiFiManager* wm, TimerManager* tm, TimeManager* timeM) : server(WEB_SERVER_PORT) { wifiManager = wm; @@ -7,6 +11,9 @@ WebServer::WebServer(WiFiManager* wm, TimerManager* tm, TimeManager* timeM) : se } void WebServer::begin() { + // 设置最大上传文件大小为 2MB + server.setContentLength(2 * 1024 * 1024); + setupRoutes(); server.begin(); Serial.println("Web 服务器启动,端口: " + String(WEB_SERVER_PORT)); @@ -33,6 +40,36 @@ void WebServer::setupRoutes() { server.on("/api/wifi/reset", HTTP_POST, [this]() { handleWiFiReset(); }); server.on("/api/restart-ap", HTTP_POST, [this]() { handleRestartAP(); }); + // 固件更新测试端点 + server.on("/api/firmware/info", HTTP_GET, [this]() { + enableCORS(); + JsonDocument doc; + doc["version"] = "1.0.0"; + doc["buildTime"] = __DATE__ " " __TIME__; + doc["chipId"] = String(ESP.getChipId(), HEX); + doc["flashSize"] = ESP.getFlashChipSize(); + doc["freeSpace"] = ESP.getFreeSketchSpace(); + sendJSON(doc); + }); + + server.on("/api/firmware/update", HTTP_POST, + [this]() { + // 处理上传完成后的响应 + enableCORS(); + if (Update.hasError()) { + sendJSON(500, "固件更新失败", false); + } else { + sendJSON(200, "固件更新成功,设备即将重启"); + delay(1000); + ESP.restart(); + } + }, + [this]() { + // 处理文件上传过程 + handleFirmwareUpdate(); + } + ); + // 404 处理 - 这里会处理动态路由 server.onNotFound([this]() { String uri = server.uri(); @@ -68,7 +105,7 @@ void WebServer::handleRoot() { void WebServer::handleGetStatus() { enableCORS(); - DynamicJsonDocument doc(1024); + JsonDocument doc; doc["wifiConnected"] = wifiManager->isConnected(); doc["localIP"] = wifiManager->getLocalIP(); doc["apIP"] = wifiManager->getAPIP(); @@ -95,7 +132,7 @@ void WebServer::handleGetStatus() { void WebServer::handleGetSystemInfo() { enableCORS(); - DynamicJsonDocument doc(2048); + JsonDocument doc; // 基本系统信息 doc["chipId"] = String(ESP.getChipId(), HEX); @@ -121,48 +158,52 @@ void WebServer::handleGetSystemInfo() { doc["uptimeMinutes"] = (uptime / (60 * 1000UL)) % 60; doc["uptimeSeconds"] = (uptime / 1000UL) % 60; - // WiFi 详细信息 - JsonObject wifi = doc.createNestedObject("wifi"); + // WiFi 信息 + JsonObject wifi = doc["wifi"].to(); + wifi["connected"] = wifiManager->isConnected(); + wifi["localIP"] = wifiManager->getLocalIP(); + wifi["macAddress"] = WiFi.macAddress(); + wifi["apIP"] = wifiManager->getAPIP(); + wifi["isAPMode"] = wifiManager->isInAPMode(); + + // WiFi状态信息 wifi["status"] = WiFi.status(); - wifi["statusText"] = wifiManager->isConnected() ? "已连接" : (wifiManager->isInAPMode() ? "AP模式" : "断开连接"); - wifi["mode"] = WiFi.getMode(); - wifi["modeText"] = WiFi.getMode() == WIFI_STA ? "STA" : (WiFi.getMode() == WIFI_AP ? "AP" : "AP+STA"); - if (wifiManager->isConnected()) { - wifi["ssid"] = WiFi.SSID(); - wifi["bssid"] = WiFi.BSSIDstr(); + wifi["statusText"] = "已连接"; + wifi["modeText"] = "Station模式"; wifi["rssi"] = WiFi.RSSI(); - wifi["localIP"] = WiFi.localIP().toString(); - wifi["subnetMask"] = WiFi.subnetMask().toString(); - wifi["gatewayIP"] = WiFi.gatewayIP().toString(); - wifi["dnsIP"] = WiFi.dnsIP().toString(); - wifi["macAddress"] = WiFi.macAddress(); - wifi["channel"] = WiFi.channel(); - wifi["autoConnect"] = WiFi.getAutoConnect(); - } - - if (wifiManager->isInAPMode()) { - wifi["apSSID"] = WiFi.softAPSSID(); - wifi["apIP"] = WiFi.softAPIP().toString(); - wifi["apMacAddress"] = WiFi.softAPmacAddress(); + wifi["ssid"] = WiFi.SSID(); + } else if (wifiManager->isInAPMode()) { + wifi["statusText"] = "AP模式"; + wifi["modeText"] = "热点模式"; + wifi["rssi"] = 0; + wifi["ssid"] = "未连接"; + wifi["apSSID"] = DEFAULT_AP_SSID; wifi["apStationCount"] = WiFi.softAPgetStationNum(); + } else { + wifi["statusText"] = "未连接"; + wifi["modeText"] = "未知"; + wifi["rssi"] = 0; + wifi["ssid"] = "未连接"; } // 时间信息 - JsonObject timeInfo = doc.createNestedObject("time"); - timeInfo["hasValidTime"] = timeManager->isTimeValid(); - timeInfo["timeSource"] = timeManager->isTimeValid() ? "NTP" : "系统运行时间"; + JsonObject timeInfo = doc["time"].to(); timeInfo["currentTime"] = timeManager->getCurrentTimeString(); timeInfo["currentDate"] = timeManager->getCurrentDateString(); - timeInfo["isWiFiTimeAvailable"] = timeManager->isWiFiTimeAvailable(); + timeInfo["hasValidTime"] = timeManager->isTimeValid(); + timeInfo["isValid"] = timeManager->isTimeValid(); + timeInfo["timeSource"] = timeManager->isTimeValid() ? "NTP网络时间" : "系统运行时间"; + timeInfo["ntpEnabled"] = wifiManager->isConnected(); + timeInfo["uptime"] = millis() / 1000; // 运行时间(秒) // 引脚状态 - JsonArray pins = doc.createNestedArray("pins"); + JsonArray pins = doc["pins"].to(); for (int i = 0; i < AVAILABLE_PINS_COUNT; i++) { - JsonObject pin = pins.createNestedObject(); - pin["pin"] = AVAILABLE_PINS[i]; + JsonObject pin = pins.add(); + pin["pin"] = AVAILABLE_PINS[i]; // 前端期望的字段名 + pin["number"] = AVAILABLE_PINS[i]; // 保持兼容 pin["state"] = digitalRead(AVAILABLE_PINS[i]); - // 检查是否被定时器占用 bool inUse = false; int timerIndex = -1; @@ -179,7 +220,7 @@ void WebServer::handleGetSystemInfo() { } // 定时器统计 - JsonObject timerStats = doc.createNestedObject("timerStats"); + JsonObject timerStats = doc["timerStats"].to(); timerStats["total"] = timerManager->getTimerCount(); int enabled = 0, active = 0, repeatDaily = 0, oneTime = 0; @@ -198,12 +239,12 @@ void WebServer::handleGetSystemInfo() { timerStats["maxTimers"] = MAX_TIMERS; // EEPROM 使用情况 - JsonObject eeprom = doc.createNestedObject("eeprom"); + JsonObject eeprom = doc["eeprom"].to(); eeprom["size"] = EEPROM_SIZE; eeprom["wifiConfigStart"] = WIFI_SSID_ADDR; eeprom["wifiConfigSize"] = TIMER_CONFIG_ADDR - WIFI_SSID_ADDR; eeprom["timerConfigStart"] = TIMER_CONFIG_ADDR; - eeprom["timerConfigUsed"] = 1 + (timerManager->getTimerCount() * 7); // 1 byte count + 7 bytes per timer + eeprom["timerConfigUsed"] = 1 + (timerManager->getTimerCount() * 25); // 1 byte count + 25 bytes per timer (12 config + 13 runtime) sendJSON(doc); } @@ -221,13 +262,13 @@ void WebServer::handleAddTimer() { return; } - DynamicJsonDocument doc(1024); + JsonDocument doc; deserializeJson(doc, server.arg("plain")); int pin = doc["pin"]; int hour = doc["hour"]; int minute = doc["minute"]; - int duration = doc["duration"]; + float duration = doc["duration"]; bool repeatDaily = doc["repeatDaily"].as(); bool isPWM = doc["isPWM"].as(); int pwmValue = doc["pwmValue"].is() ? doc["pwmValue"].as() : 512; // 默认值50% @@ -256,13 +297,13 @@ void WebServer::handleUpdateTimer() { return; } - DynamicJsonDocument doc(1024); + JsonDocument doc; deserializeJson(doc, server.arg("plain")); int pin = doc["pin"]; int hour = doc["hour"]; int minute = doc["minute"]; - int duration = doc["duration"]; + float duration = doc["duration"]; bool enabled = doc["enabled"]; bool repeatDaily = doc["repeatDaily"].as(); bool isPWM = doc["isPWM"].as(); @@ -314,11 +355,11 @@ void WebServer::handleManualControl() { return; } - DynamicJsonDocument doc(512); + JsonDocument doc; deserializeJson(doc, server.arg("plain")); int pin = doc["pin"]; - int duration = doc["duration"]; + float duration = doc["duration"]; bool isPWM = doc["isPWM"].as(); int pwmValue = doc["pwmValue"].is() ? doc["pwmValue"].as() : 512; // 默认值50% @@ -334,7 +375,7 @@ void WebServer::handleWiFiConfig() { return; } - DynamicJsonDocument doc(512); + JsonDocument doc; deserializeJson(doc, server.arg("plain")); String ssid = doc["ssid"]; @@ -382,7 +423,7 @@ void WebServer::handleRestartAP() { } void WebServer::sendJSON(int code, const String& message, bool success) { - DynamicJsonDocument doc(512); + JsonDocument doc; doc["success"] = success; doc["message"] = message; @@ -392,7 +433,7 @@ void WebServer::sendJSON(int code, const String& message, bool success) { server.send(code, "application/json", response); } -void WebServer::sendJSON(DynamicJsonDocument& doc) { +void WebServer::sendJSON(JsonDocument& doc) { String response; serializeJson(doc, response); server.send(200, "application/json", response); @@ -411,7 +452,7 @@ void WebServer::enableCORS() { void WebServer::handleGetPWMConfig() { enableCORS(); - DynamicJsonDocument doc(512); + JsonDocument doc; doc["frequency"] = PWM_FREQUENCY; doc["resolution"] = PWM_RESOLUTION; doc["maxValue"] = PWM_MAX_VALUE; @@ -420,3 +461,47 @@ void WebServer::handleGetPWMConfig() { sendJSON(doc); } + +void WebServer::handleFirmwareUpdate() { + HTTPUpload& upload = server.upload(); + + if (upload.status == UPLOAD_FILE_START) { + Serial.printf("固件更新开始: %s\n", upload.filename.c_str()); + + // 检查文件扩展名 + if (!upload.filename.endsWith(".bin")) { + Serial.println("错误:不支持的文件格式"); + return; + } + + // 开始OTA更新 + uint32_t maxSketchSpace = (ESP.getFreeSketchSpace() - 0x1000) & 0xFFFFF000; + if (!Update.begin(maxSketchSpace)) { + Serial.println("错误:无法开始固件更新"); + Update.printError(Serial); + return; + } + Serial.println("固件更新已开始..."); + } + else if (upload.status == UPLOAD_FILE_WRITE) { + // 写入固件数据 + Serial.printf("写入 %u 字节...\n", upload.currentSize); + if (Update.write(upload.buf, upload.currentSize) != upload.currentSize) { + Serial.println("错误:固件写入失败"); + Update.printError(Serial); + } + } + else if (upload.status == UPLOAD_FILE_END) { + // 完成固件更新 + if (Update.end(true)) { + Serial.printf("固件更新成功: %u 字节\n", upload.totalSize); + } else { + Serial.println("错误:固件更新完成失败"); + Update.printError(Serial); + } + } + else if (upload.status == UPLOAD_FILE_ABORTED) { + Update.end(); + Serial.println("固件更新被中止"); + } +} diff --git a/src/web_server.h b/src/web_server.h index b038684..76ff779 100644 --- a/src/web_server.h +++ b/src/web_server.h @@ -41,10 +41,11 @@ private: void handleWiFiConfig(); void handleWiFiReset(); void handleRestartAP(); + void handleFirmwareUpdate(); // 工具函数 void sendJSON(int code, const String& message, bool success = true); - void sendJSON(DynamicJsonDocument& doc); + void sendJSON(JsonDocument& doc); void enableCORS(); };