Refactor timer duration to support float values and add firmware update functionality in web server
This commit is contained in:
@@ -40,7 +40,7 @@ struct TimerConfig
|
|||||||
int pin;
|
int pin;
|
||||||
int hour;
|
int hour;
|
||||||
int minute;
|
int minute;
|
||||||
int duration; // 持续时间(秒)
|
float duration; // 持续时间(秒,支持小数)
|
||||||
bool repeatDaily; // 每天重复
|
bool repeatDaily; // 每天重复
|
||||||
bool isActive; // 当前是否激活
|
bool isActive; // 当前是否激活
|
||||||
unsigned long startTime; // 开始时间(millis时间戳)
|
unsigned long startTime; // 开始时间(millis时间戳)
|
||||||
|
|||||||
@@ -94,7 +94,7 @@ void TimerManager::update() {
|
|||||||
|
|
||||||
// 检查是否需要关闭
|
// 检查是否需要关闭
|
||||||
if (timers[i].isActive &&
|
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].isActive = false;
|
||||||
timers[i].realStartTime = 0; // 清理真实时间戳
|
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) {
|
if (timerCount >= MAX_TIMERS) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
@@ -128,7 +128,7 @@ bool TimerManager::addTimer(int pin, int hour, int minute, int duration, bool re
|
|||||||
if (!pinValid) return false;
|
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;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -183,7 +183,7 @@ bool TimerManager::removeTimer(int index) {
|
|||||||
return true;
|
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) {
|
if (index < 0 || index >= timerCount) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
@@ -199,7 +199,7 @@ bool TimerManager::updateTimer(int index, int pin, int hour, int minute, int dur
|
|||||||
if (!pinValid) return false;
|
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;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -234,11 +234,11 @@ bool TimerManager::updateTimer(int index, int pin, int hour, int minute, int dur
|
|||||||
}
|
}
|
||||||
|
|
||||||
String TimerManager::getTimersJSON() {
|
String TimerManager::getTimersJSON() {
|
||||||
DynamicJsonDocument doc(2048);
|
JsonDocument doc;
|
||||||
JsonArray array = doc.to<JsonArray>();
|
JsonArray array = doc.to<JsonArray>();
|
||||||
|
|
||||||
for (int i = 0; i < timerCount; i++) {
|
for (int i = 0; i < timerCount; i++) {
|
||||||
JsonObject timer = array.createNestedObject();
|
JsonObject timer = array.add<JsonObject>();
|
||||||
timer["index"] = i;
|
timer["index"] = i;
|
||||||
timer["enabled"] = timers[i].enabled;
|
timer["enabled"] = timers[i].enabled;
|
||||||
timer["pin"] = timers[i].pin;
|
timer["pin"] = timers[i].pin;
|
||||||
@@ -257,11 +257,11 @@ String TimerManager::getTimersJSON() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
String TimerManager::getAvailablePinsJSON() {
|
String TimerManager::getAvailablePinsJSON() {
|
||||||
DynamicJsonDocument doc(1024);
|
JsonDocument doc;
|
||||||
JsonArray array = doc.to<JsonArray>();
|
JsonArray array = doc.to<JsonArray>();
|
||||||
|
|
||||||
for (int i = 0; i < AVAILABLE_PINS_COUNT; i++) {
|
for (int i = 0; i < AVAILABLE_PINS_COUNT; i++) {
|
||||||
JsonObject pin = array.createNestedObject();
|
JsonObject pin = array.add<JsonObject>();
|
||||||
pin["pin"] = AVAILABLE_PINS[i];
|
pin["pin"] = AVAILABLE_PINS[i];
|
||||||
pin["state"] = digitalRead(AVAILABLE_PINS[i]);
|
pin["state"] = digitalRead(AVAILABLE_PINS[i]);
|
||||||
|
|
||||||
@@ -281,7 +281,7 @@ String TimerManager::getAvailablePinsJSON() {
|
|||||||
return result;
|
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;
|
bool pinValid = false;
|
||||||
for (int i = 0; i < AVAILABLE_PINS_COUNT; i++) {
|
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;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (duration > 0) {
|
if (duration > 0.0) {
|
||||||
// 开启引脚指定时间
|
// 开启引脚指定时间
|
||||||
setPin(pin, HIGH, isPWM ? pwmValue : 0);
|
setPin(pin, HIGH, isPWM ? pwmValue : 0);
|
||||||
String modeStr = isPWM ? " PWM模式, 值=" + String(pwmValue) : " 数字模式";
|
String modeStr = isPWM ? " PWM模式, 值=" + String(pwmValue) : " 数字模式";
|
||||||
Serial.println("手动控制:引脚 " + String(pin) + " 开启 " + String(duration) + " 秒" + modeStr);
|
Serial.println("手动控制:引脚 " + String(pin) + " 开启 " + String(duration) + " 秒" + modeStr);
|
||||||
|
|
||||||
// 这里应该使用定时器来关闭,简化处理
|
// 这里应该使用定时器来关闭,简化处理
|
||||||
delay(duration * 1000);
|
delay((unsigned long)(duration * 1000.0));
|
||||||
setPin(pin, LOW, 0);
|
setPin(pin, LOW, 0);
|
||||||
Serial.println("手动控制:引脚 " + String(pin) + " 关闭");
|
Serial.println("手动控制:引脚 " + String(pin) + " 关闭");
|
||||||
} else {
|
} else {
|
||||||
@@ -339,8 +339,18 @@ void TimerManager::saveTimers() {
|
|||||||
EEPROM.write(addr++, timers[i].pin);
|
EEPROM.write(addr++, timers[i].pin);
|
||||||
EEPROM.write(addr++, timers[i].hour);
|
EEPROM.write(addr++, timers[i].hour);
|
||||||
EEPROM.write(addr++, timers[i].minute);
|
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].repeatDaily ? 1 : 0);
|
||||||
EEPROM.write(addr++, timers[i].isPWM ? 1 : 0);
|
EEPROM.write(addr++, timers[i].isPWM ? 1 : 0);
|
||||||
EEPROM.write(addr++, (timers[i].pwmValue >> 8) & 0xFF);
|
EEPROM.write(addr++, (timers[i].pwmValue >> 8) & 0xFF);
|
||||||
@@ -387,13 +397,22 @@ void TimerManager::loadTimers() {
|
|||||||
timers[i].pin = EEPROM.read(addr++);
|
timers[i].pin = EEPROM.read(addr++);
|
||||||
timers[i].hour = EEPROM.read(addr++);
|
timers[i].hour = EEPROM.read(addr++);
|
||||||
timers[i].minute = EEPROM.read(addr++);
|
timers[i].minute = EEPROM.read(addr++);
|
||||||
int durationHigh = EEPROM.read(addr++);
|
|
||||||
int durationLow = EEPROM.read(addr++);
|
// 读取float类型的duration(4字节)
|
||||||
timers[i].duration = (durationHigh << 8) | durationLow;
|
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;
|
timers[i].repeatDaily = EEPROM.read(addr++) == 1;
|
||||||
|
|
||||||
// 检查是否有PWM数据(向后兼容)
|
// 检查是否有PWM数据(向后兼容)
|
||||||
if (addr < EEPROM_SIZE - 16) { // 增加了13个字节的运行时状态数据(9+4)
|
if (addr < EEPROM_SIZE - 20) { // 更新了字节数计算:duration现在是4字节而不是2字节
|
||||||
timers[i].isPWM = EEPROM.read(addr++) == 1;
|
timers[i].isPWM = EEPROM.read(addr++) == 1;
|
||||||
int pwmHigh = EEPROM.read(addr++);
|
int pwmHigh = EEPROM.read(addr++);
|
||||||
int pwmLow = EEPROM.read(addr++);
|
int pwmLow = EEPROM.read(addr++);
|
||||||
@@ -455,7 +474,7 @@ void TimerManager::loadTimers() {
|
|||||||
unsigned long elapsedTime = currentTime - timers[i].startTime;
|
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].isActive = false;
|
||||||
timers[i].realStartTime = 0;
|
timers[i].realStartTime = 0;
|
||||||
@@ -465,7 +484,7 @@ void TimerManager::loadTimers() {
|
|||||||
// 定时器仍然有效,恢复引脚状态
|
// 定时器仍然有效,恢复引脚状态
|
||||||
setPin(timers[i].pin, HIGH, timers[i].isPWM ? timers[i].pwmValue : 0);
|
setPin(timers[i].pin, HIGH, timers[i].isPWM ? timers[i].pwmValue : 0);
|
||||||
String modeStr = timers[i].isPWM ? " PWM(" + String(timers[i].pwmValue) + ")" : "";
|
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 +
|
Serial.println("恢复定时器 " + String(i) + " 状态,引脚 " + String(timers[i].pin) + " 开启" + modeStr +
|
||||||
",剩余时间: " + String(remainingTime / 1000) + "秒");
|
",剩余时间: " + String(remainingTime / 1000) + "秒");
|
||||||
}
|
}
|
||||||
@@ -492,8 +511,8 @@ void TimerManager::saveTimerStates() {
|
|||||||
int addr = TIMER_CONFIG_ADDR + 1; // 跳过定时器数量
|
int addr = TIMER_CONFIG_ADDR + 1; // 跳过定时器数量
|
||||||
|
|
||||||
for (int i = 0; i < timerCount; i++) {
|
for (int i = 0; i < timerCount; i++) {
|
||||||
// 跳过基本配置部分 (10字节)
|
// 跳过基本配置部分 (12字节: enabled(1) + pin(1) + hour(1) + minute(1) + duration(4) + repeatDaily(1) + isPWM(1) + pwmValue(2))
|
||||||
addr += 10;
|
addr += 12;
|
||||||
|
|
||||||
// 更新运行时状态部分 (13字节)
|
// 更新运行时状态部分 (13字节)
|
||||||
EEPROM.write(addr++, timers[i].isActive ? 1 : 0);
|
EEPROM.write(addr++, timers[i].isActive ? 1 : 0);
|
||||||
@@ -545,7 +564,7 @@ TimerConfig TimerManager::getTimer(int index) {
|
|||||||
return timers[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;
|
return empty;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -19,9 +19,9 @@ public:
|
|||||||
TimerManager();
|
TimerManager();
|
||||||
void begin(TimeManager* tm);
|
void begin(TimeManager* tm);
|
||||||
void update();
|
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 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();
|
String getTimersJSON();
|
||||||
void saveTimers();
|
void saveTimers();
|
||||||
void saveTimerStates(); // 新增:仅保存运行时状态
|
void saveTimerStates(); // 新增:仅保存运行时状态
|
||||||
@@ -31,7 +31,7 @@ public:
|
|||||||
TimerConfig getTimer(int index);
|
TimerConfig getTimer(int index);
|
||||||
void setPin(int pin, bool state, int pwmValue = 0);
|
void setPin(int pin, bool state, int pwmValue = 0);
|
||||||
String getAvailablePinsJSON();
|
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();
|
bool hasValidTime();
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
225
src/web_pages.h
225
src/web_pages.h
@@ -371,6 +371,7 @@ const char INDEX_HTML[] PROGMEM = R"rawliteral(
|
|||||||
<button class="tab" onclick="switchTab('manual', this)">手动控制</button>
|
<button class="tab" onclick="switchTab('manual', this)">手动控制</button>
|
||||||
<button class="tab" onclick="switchTab('system', this)">系统状态</button>
|
<button class="tab" onclick="switchTab('system', this)">系统状态</button>
|
||||||
<button class="tab" onclick="switchTab('wifi', this)">WiFi设置</button>
|
<button class="tab" onclick="switchTab('wifi', this)">WiFi设置</button>
|
||||||
|
<button class="tab" onclick="switchTab('firmware', this)">固件更新</button>
|
||||||
</nav>
|
</nav>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
@@ -397,7 +398,7 @@ const char INDEX_HTML[] PROGMEM = R"rawliteral(
|
|||||||
</div>
|
</div>
|
||||||
<div>
|
<div>
|
||||||
<label for="timer-duration" class="block text-sm font-medium text-gray-700 mb-1">持续时间 (秒)</label>
|
<label for="timer-duration" class="block text-sm font-medium text-gray-700 mb-1">持续时间 (秒)</label>
|
||||||
<input type="number" id="timer-duration" min="1" max="86400" value="60" class="w-full p-2 border border-gray-300 rounded-md shadow-sm focus:ring-indigo-500 focus:border-indigo-500">
|
<input type="number" id="timer-duration" max="86400" value="0.3" class="w-full p-2 border border-gray-300 rounded-md shadow-sm focus:ring-indigo-500 focus:border-indigo-500">
|
||||||
</div>
|
</div>
|
||||||
<div class="flex items-center">
|
<div class="flex items-center">
|
||||||
<input type="checkbox" id="timer-repeat" checked class="h-4 w-4 text-indigo-600 border-gray-300 rounded focus:ring-indigo-500">
|
<input type="checkbox" id="timer-repeat" checked class="h-4 w-4 text-indigo-600 border-gray-300 rounded focus:ring-indigo-500">
|
||||||
@@ -460,7 +461,7 @@ const char INDEX_HTML[] PROGMEM = R"rawliteral(
|
|||||||
</div>
|
</div>
|
||||||
<div>
|
<div>
|
||||||
<label for="manual-duration" class="block text-sm font-medium text-gray-700 mb-1">持续时间 (0=切换)</label>
|
<label for="manual-duration" class="block text-sm font-medium text-gray-700 mb-1">持续时间 (0=切换)</label>
|
||||||
<input type="number" id="manual-duration" min="0" max="86400" value="0" class="w-full p-2 border border-gray-300 rounded-md shadow-sm focus:ring-indigo-500 focus:border-indigo-500">
|
<input type="number" id="manual-duration" max="86400" value="0" class="w-full p-2 border border-gray-300 rounded-md shadow-sm focus:ring-indigo-500 focus:border-indigo-500">
|
||||||
</div>
|
</div>
|
||||||
<div>
|
<div>
|
||||||
<label class="flex items-center space-x-2">
|
<label class="flex items-center space-x-2">
|
||||||
@@ -568,6 +569,65 @@ const char INDEX_HTML[] PROGMEM = R"rawliteral(
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<!-- 固件更新 -->
|
||||||
|
<div id="firmware-content" class="tab-content hidden fade-in">
|
||||||
|
<div class="max-w-md mx-auto p-5 border rounded-lg bg-gray-50">
|
||||||
|
<h3 class="text-lg font-semibold mb-4 text-gray-800">🔧 固件更新</h3>
|
||||||
|
<div id="firmware-message" class="hidden mb-4"></div>
|
||||||
|
|
||||||
|
<div class="space-y-4">
|
||||||
|
<div class="p-4 bg-blue-50 border border-blue-200 rounded-lg">
|
||||||
|
<h4 class="font-medium text-blue-800 mb-2">当前固件信息</h4>
|
||||||
|
<div class="text-sm text-blue-700">
|
||||||
|
<div>版本: <span id="current-version">加载中...</span></div>
|
||||||
|
<div>编译时间: <span id="build-time">加载中...</span></div>
|
||||||
|
<div>芯片ID: <span id="firmware-chip-id">加载中...</span></div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div>
|
||||||
|
<label for="firmware-file" class="block text-sm font-medium text-gray-700 mb-2">
|
||||||
|
📁 选择固件文件 (.bin)
|
||||||
|
</label>
|
||||||
|
<input type="file" id="firmware-file" accept=".bin" class="w-full p-2 border border-gray-300 rounded-md shadow-sm focus:ring-indigo-500 focus:border-indigo-500">
|
||||||
|
<div class="mt-2 text-xs text-gray-500">
|
||||||
|
支持的文件格式: .bin (最大 2MB)
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="p-3 text-xs text-yellow-800 rounded-lg bg-yellow-50 border border-yellow-200">
|
||||||
|
⚠️ 警告:固件更新可能需要 2-5 分钟,期间请勿断电或关闭浏览器。更新完成后设备将自动重启。
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="space-y-2">
|
||||||
|
<button id="upload-btn" class="w-full bg-red-600 text-white font-semibold py-3 px-4 rounded-md hover:bg-red-700 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-red-500 transition-colors disabled:opacity-50 disabled:cursor-not-allowed" onclick="startFirmwareUpdate()" disabled>
|
||||||
|
🚀 开始固件更新
|
||||||
|
</button>
|
||||||
|
|
||||||
|
<div id="upload-progress" class="hidden">
|
||||||
|
<div class="w-full bg-gray-200 rounded-full h-3">
|
||||||
|
<div id="progress-bar" class="bg-blue-600 h-3 rounded-full transition-all duration-300" style="width: 0%"></div>
|
||||||
|
</div>
|
||||||
|
<div class="text-sm text-gray-600 text-center mt-2">
|
||||||
|
<span id="progress-text">准备中...</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="border-t pt-4">
|
||||||
|
<h4 class="font-medium text-gray-800 mb-2">更新说明</h4>
|
||||||
|
<ul class="text-sm text-gray-600 space-y-1">
|
||||||
|
<li>• 确保设备连接稳定的电源</li>
|
||||||
|
<li>• 选择正确的固件文件(.bin格式)</li>
|
||||||
|
<li>• 更新过程中请勿断电</li>
|
||||||
|
<li>• 更新完成后设备会自动重启</li>
|
||||||
|
<li>• 如遇问题可重启设备恢复</li>
|
||||||
|
</ul>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</main>
|
</main>
|
||||||
@@ -839,7 +899,7 @@ const char INDEX_HTML[] PROGMEM = R"rawliteral(
|
|||||||
pin: parseInt(pin),
|
pin: parseInt(pin),
|
||||||
hour,
|
hour,
|
||||||
minute,
|
minute,
|
||||||
duration: parseInt(duration),
|
duration: parseFloat(duration),
|
||||||
repeatDaily,
|
repeatDaily,
|
||||||
isPWM: isPWM,
|
isPWM: isPWM,
|
||||||
pwmValue: parseInt(pwmValue)
|
pwmValue: parseInt(pwmValue)
|
||||||
@@ -916,7 +976,7 @@ const char INDEX_HTML[] PROGMEM = R"rawliteral(
|
|||||||
try {
|
try {
|
||||||
const body = {
|
const body = {
|
||||||
pin: parseInt(pin),
|
pin: parseInt(pin),
|
||||||
duration: parseInt(duration) || 0,
|
duration: parseFloat(duration) || 0,
|
||||||
isPWM: isPWM,
|
isPWM: isPWM,
|
||||||
pwmValue: parseInt(pwmValue)
|
pwmValue: parseInt(pwmValue)
|
||||||
};
|
};
|
||||||
@@ -1030,6 +1090,163 @@ const char INDEX_HTML[] PROGMEM = R"rawliteral(
|
|||||||
document.getElementById('timer-pwm-percentage').textContent = percentage + '%';
|
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) {
|
function showMessage(elementId, message, type) {
|
||||||
const el = document.getElementById(elementId);
|
const el = document.getElementById(elementId);
|
||||||
el.textContent = message;
|
el.textContent = message;
|
||||||
|
|||||||
@@ -1,4 +1,8 @@
|
|||||||
#include "web_server.h"
|
#include "web_server.h"
|
||||||
|
#include <ESP8266HTTPUpdateServer.h>
|
||||||
|
#include <Updater.h>
|
||||||
|
|
||||||
|
ESP8266HTTPUpdateServer httpUpdater;
|
||||||
|
|
||||||
WebServer::WebServer(WiFiManager* wm, TimerManager* tm, TimeManager* timeM) : server(WEB_SERVER_PORT) {
|
WebServer::WebServer(WiFiManager* wm, TimerManager* tm, TimeManager* timeM) : server(WEB_SERVER_PORT) {
|
||||||
wifiManager = wm;
|
wifiManager = wm;
|
||||||
@@ -7,6 +11,9 @@ WebServer::WebServer(WiFiManager* wm, TimerManager* tm, TimeManager* timeM) : se
|
|||||||
}
|
}
|
||||||
|
|
||||||
void WebServer::begin() {
|
void WebServer::begin() {
|
||||||
|
// 设置最大上传文件大小为 2MB
|
||||||
|
server.setContentLength(2 * 1024 * 1024);
|
||||||
|
|
||||||
setupRoutes();
|
setupRoutes();
|
||||||
server.begin();
|
server.begin();
|
||||||
Serial.println("Web 服务器启动,端口: " + String(WEB_SERVER_PORT));
|
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/wifi/reset", HTTP_POST, [this]() { handleWiFiReset(); });
|
||||||
server.on("/api/restart-ap", HTTP_POST, [this]() { handleRestartAP(); });
|
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 处理 - 这里会处理动态路由
|
// 404 处理 - 这里会处理动态路由
|
||||||
server.onNotFound([this]() {
|
server.onNotFound([this]() {
|
||||||
String uri = server.uri();
|
String uri = server.uri();
|
||||||
@@ -68,7 +105,7 @@ void WebServer::handleRoot() {
|
|||||||
void WebServer::handleGetStatus() {
|
void WebServer::handleGetStatus() {
|
||||||
enableCORS();
|
enableCORS();
|
||||||
|
|
||||||
DynamicJsonDocument doc(1024);
|
JsonDocument doc;
|
||||||
doc["wifiConnected"] = wifiManager->isConnected();
|
doc["wifiConnected"] = wifiManager->isConnected();
|
||||||
doc["localIP"] = wifiManager->getLocalIP();
|
doc["localIP"] = wifiManager->getLocalIP();
|
||||||
doc["apIP"] = wifiManager->getAPIP();
|
doc["apIP"] = wifiManager->getAPIP();
|
||||||
@@ -95,7 +132,7 @@ void WebServer::handleGetStatus() {
|
|||||||
void WebServer::handleGetSystemInfo() {
|
void WebServer::handleGetSystemInfo() {
|
||||||
enableCORS();
|
enableCORS();
|
||||||
|
|
||||||
DynamicJsonDocument doc(2048);
|
JsonDocument doc;
|
||||||
|
|
||||||
// 基本系统信息
|
// 基本系统信息
|
||||||
doc["chipId"] = String(ESP.getChipId(), HEX);
|
doc["chipId"] = String(ESP.getChipId(), HEX);
|
||||||
@@ -121,48 +158,52 @@ void WebServer::handleGetSystemInfo() {
|
|||||||
doc["uptimeMinutes"] = (uptime / (60 * 1000UL)) % 60;
|
doc["uptimeMinutes"] = (uptime / (60 * 1000UL)) % 60;
|
||||||
doc["uptimeSeconds"] = (uptime / 1000UL) % 60;
|
doc["uptimeSeconds"] = (uptime / 1000UL) % 60;
|
||||||
|
|
||||||
// WiFi 详细信息
|
// WiFi 信息
|
||||||
JsonObject wifi = doc.createNestedObject("wifi");
|
JsonObject wifi = doc["wifi"].to<JsonObject>();
|
||||||
|
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["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()) {
|
if (wifiManager->isConnected()) {
|
||||||
wifi["ssid"] = WiFi.SSID();
|
wifi["statusText"] = "已连接";
|
||||||
wifi["bssid"] = WiFi.BSSIDstr();
|
wifi["modeText"] = "Station模式";
|
||||||
wifi["rssi"] = WiFi.RSSI();
|
wifi["rssi"] = WiFi.RSSI();
|
||||||
wifi["localIP"] = WiFi.localIP().toString();
|
wifi["ssid"] = WiFi.SSID();
|
||||||
wifi["subnetMask"] = WiFi.subnetMask().toString();
|
} else if (wifiManager->isInAPMode()) {
|
||||||
wifi["gatewayIP"] = WiFi.gatewayIP().toString();
|
wifi["statusText"] = "AP模式";
|
||||||
wifi["dnsIP"] = WiFi.dnsIP().toString();
|
wifi["modeText"] = "热点模式";
|
||||||
wifi["macAddress"] = WiFi.macAddress();
|
wifi["rssi"] = 0;
|
||||||
wifi["channel"] = WiFi.channel();
|
wifi["ssid"] = "未连接";
|
||||||
wifi["autoConnect"] = WiFi.getAutoConnect();
|
wifi["apSSID"] = DEFAULT_AP_SSID;
|
||||||
}
|
|
||||||
|
|
||||||
if (wifiManager->isInAPMode()) {
|
|
||||||
wifi["apSSID"] = WiFi.softAPSSID();
|
|
||||||
wifi["apIP"] = WiFi.softAPIP().toString();
|
|
||||||
wifi["apMacAddress"] = WiFi.softAPmacAddress();
|
|
||||||
wifi["apStationCount"] = WiFi.softAPgetStationNum();
|
wifi["apStationCount"] = WiFi.softAPgetStationNum();
|
||||||
|
} else {
|
||||||
|
wifi["statusText"] = "未连接";
|
||||||
|
wifi["modeText"] = "未知";
|
||||||
|
wifi["rssi"] = 0;
|
||||||
|
wifi["ssid"] = "未连接";
|
||||||
}
|
}
|
||||||
|
|
||||||
// 时间信息
|
// 时间信息
|
||||||
JsonObject timeInfo = doc.createNestedObject("time");
|
JsonObject timeInfo = doc["time"].to<JsonObject>();
|
||||||
timeInfo["hasValidTime"] = timeManager->isTimeValid();
|
|
||||||
timeInfo["timeSource"] = timeManager->isTimeValid() ? "NTP" : "系统运行时间";
|
|
||||||
timeInfo["currentTime"] = timeManager->getCurrentTimeString();
|
timeInfo["currentTime"] = timeManager->getCurrentTimeString();
|
||||||
timeInfo["currentDate"] = timeManager->getCurrentDateString();
|
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<JsonArray>();
|
||||||
for (int i = 0; i < AVAILABLE_PINS_COUNT; i++) {
|
for (int i = 0; i < AVAILABLE_PINS_COUNT; i++) {
|
||||||
JsonObject pin = pins.createNestedObject();
|
JsonObject pin = pins.add<JsonObject>();
|
||||||
pin["pin"] = AVAILABLE_PINS[i];
|
pin["pin"] = AVAILABLE_PINS[i]; // 前端期望的字段名
|
||||||
|
pin["number"] = AVAILABLE_PINS[i]; // 保持兼容
|
||||||
pin["state"] = digitalRead(AVAILABLE_PINS[i]);
|
pin["state"] = digitalRead(AVAILABLE_PINS[i]);
|
||||||
|
|
||||||
// 检查是否被定时器占用
|
// 检查是否被定时器占用
|
||||||
bool inUse = false;
|
bool inUse = false;
|
||||||
int timerIndex = -1;
|
int timerIndex = -1;
|
||||||
@@ -179,7 +220,7 @@ void WebServer::handleGetSystemInfo() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// 定时器统计
|
// 定时器统计
|
||||||
JsonObject timerStats = doc.createNestedObject("timerStats");
|
JsonObject timerStats = doc["timerStats"].to<JsonObject>();
|
||||||
timerStats["total"] = timerManager->getTimerCount();
|
timerStats["total"] = timerManager->getTimerCount();
|
||||||
|
|
||||||
int enabled = 0, active = 0, repeatDaily = 0, oneTime = 0;
|
int enabled = 0, active = 0, repeatDaily = 0, oneTime = 0;
|
||||||
@@ -198,12 +239,12 @@ void WebServer::handleGetSystemInfo() {
|
|||||||
timerStats["maxTimers"] = MAX_TIMERS;
|
timerStats["maxTimers"] = MAX_TIMERS;
|
||||||
|
|
||||||
// EEPROM 使用情况
|
// EEPROM 使用情况
|
||||||
JsonObject eeprom = doc.createNestedObject("eeprom");
|
JsonObject eeprom = doc["eeprom"].to<JsonObject>();
|
||||||
eeprom["size"] = EEPROM_SIZE;
|
eeprom["size"] = EEPROM_SIZE;
|
||||||
eeprom["wifiConfigStart"] = WIFI_SSID_ADDR;
|
eeprom["wifiConfigStart"] = WIFI_SSID_ADDR;
|
||||||
eeprom["wifiConfigSize"] = TIMER_CONFIG_ADDR - WIFI_SSID_ADDR;
|
eeprom["wifiConfigSize"] = TIMER_CONFIG_ADDR - WIFI_SSID_ADDR;
|
||||||
eeprom["timerConfigStart"] = TIMER_CONFIG_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);
|
sendJSON(doc);
|
||||||
}
|
}
|
||||||
@@ -221,13 +262,13 @@ void WebServer::handleAddTimer() {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
DynamicJsonDocument doc(1024);
|
JsonDocument doc;
|
||||||
deserializeJson(doc, server.arg("plain"));
|
deserializeJson(doc, server.arg("plain"));
|
||||||
|
|
||||||
int pin = doc["pin"];
|
int pin = doc["pin"];
|
||||||
int hour = doc["hour"];
|
int hour = doc["hour"];
|
||||||
int minute = doc["minute"];
|
int minute = doc["minute"];
|
||||||
int duration = doc["duration"];
|
float duration = doc["duration"];
|
||||||
bool repeatDaily = doc["repeatDaily"].as<bool>();
|
bool repeatDaily = doc["repeatDaily"].as<bool>();
|
||||||
bool isPWM = doc["isPWM"].as<bool>();
|
bool isPWM = doc["isPWM"].as<bool>();
|
||||||
int pwmValue = doc["pwmValue"].is<int>() ? doc["pwmValue"].as<int>() : 512; // 默认值50%
|
int pwmValue = doc["pwmValue"].is<int>() ? doc["pwmValue"].as<int>() : 512; // 默认值50%
|
||||||
@@ -256,13 +297,13 @@ void WebServer::handleUpdateTimer() {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
DynamicJsonDocument doc(1024);
|
JsonDocument doc;
|
||||||
deserializeJson(doc, server.arg("plain"));
|
deserializeJson(doc, server.arg("plain"));
|
||||||
|
|
||||||
int pin = doc["pin"];
|
int pin = doc["pin"];
|
||||||
int hour = doc["hour"];
|
int hour = doc["hour"];
|
||||||
int minute = doc["minute"];
|
int minute = doc["minute"];
|
||||||
int duration = doc["duration"];
|
float duration = doc["duration"];
|
||||||
bool enabled = doc["enabled"];
|
bool enabled = doc["enabled"];
|
||||||
bool repeatDaily = doc["repeatDaily"].as<bool>();
|
bool repeatDaily = doc["repeatDaily"].as<bool>();
|
||||||
bool isPWM = doc["isPWM"].as<bool>();
|
bool isPWM = doc["isPWM"].as<bool>();
|
||||||
@@ -314,11 +355,11 @@ void WebServer::handleManualControl() {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
DynamicJsonDocument doc(512);
|
JsonDocument doc;
|
||||||
deserializeJson(doc, server.arg("plain"));
|
deserializeJson(doc, server.arg("plain"));
|
||||||
|
|
||||||
int pin = doc["pin"];
|
int pin = doc["pin"];
|
||||||
int duration = doc["duration"];
|
float duration = doc["duration"];
|
||||||
bool isPWM = doc["isPWM"].as<bool>();
|
bool isPWM = doc["isPWM"].as<bool>();
|
||||||
int pwmValue = doc["pwmValue"].is<int>() ? doc["pwmValue"].as<int>() : 512; // 默认值50%
|
int pwmValue = doc["pwmValue"].is<int>() ? doc["pwmValue"].as<int>() : 512; // 默认值50%
|
||||||
|
|
||||||
@@ -334,7 +375,7 @@ void WebServer::handleWiFiConfig() {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
DynamicJsonDocument doc(512);
|
JsonDocument doc;
|
||||||
deserializeJson(doc, server.arg("plain"));
|
deserializeJson(doc, server.arg("plain"));
|
||||||
|
|
||||||
String ssid = doc["ssid"];
|
String ssid = doc["ssid"];
|
||||||
@@ -382,7 +423,7 @@ void WebServer::handleRestartAP() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
void WebServer::sendJSON(int code, const String& message, bool success) {
|
void WebServer::sendJSON(int code, const String& message, bool success) {
|
||||||
DynamicJsonDocument doc(512);
|
JsonDocument doc;
|
||||||
doc["success"] = success;
|
doc["success"] = success;
|
||||||
doc["message"] = message;
|
doc["message"] = message;
|
||||||
|
|
||||||
@@ -392,7 +433,7 @@ void WebServer::sendJSON(int code, const String& message, bool success) {
|
|||||||
server.send(code, "application/json", response);
|
server.send(code, "application/json", response);
|
||||||
}
|
}
|
||||||
|
|
||||||
void WebServer::sendJSON(DynamicJsonDocument& doc) {
|
void WebServer::sendJSON(JsonDocument& doc) {
|
||||||
String response;
|
String response;
|
||||||
serializeJson(doc, response);
|
serializeJson(doc, response);
|
||||||
server.send(200, "application/json", response);
|
server.send(200, "application/json", response);
|
||||||
@@ -411,7 +452,7 @@ void WebServer::enableCORS() {
|
|||||||
void WebServer::handleGetPWMConfig() {
|
void WebServer::handleGetPWMConfig() {
|
||||||
enableCORS();
|
enableCORS();
|
||||||
|
|
||||||
DynamicJsonDocument doc(512);
|
JsonDocument doc;
|
||||||
doc["frequency"] = PWM_FREQUENCY;
|
doc["frequency"] = PWM_FREQUENCY;
|
||||||
doc["resolution"] = PWM_RESOLUTION;
|
doc["resolution"] = PWM_RESOLUTION;
|
||||||
doc["maxValue"] = PWM_MAX_VALUE;
|
doc["maxValue"] = PWM_MAX_VALUE;
|
||||||
@@ -420,3 +461,47 @@ void WebServer::handleGetPWMConfig() {
|
|||||||
|
|
||||||
sendJSON(doc);
|
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("固件更新被中止");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
@@ -41,10 +41,11 @@ private:
|
|||||||
void handleWiFiConfig();
|
void handleWiFiConfig();
|
||||||
void handleWiFiReset();
|
void handleWiFiReset();
|
||||||
void handleRestartAP();
|
void handleRestartAP();
|
||||||
|
void handleFirmwareUpdate();
|
||||||
|
|
||||||
// 工具函数
|
// 工具函数
|
||||||
void sendJSON(int code, const String& message, bool success = true);
|
void sendJSON(int code, const String& message, bool success = true);
|
||||||
void sendJSON(DynamicJsonDocument& doc);
|
void sendJSON(JsonDocument& doc);
|
||||||
void enableCORS();
|
void enableCORS();
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user