Add firmware versioning and improve timer precision in web server

This commit is contained in:
MiaoMint
2025-08-31 18:06:19 +08:00
parent 44c707f212
commit e580af7965
5 changed files with 63 additions and 15 deletions

View File

@@ -1,6 +1,12 @@
#ifndef CONFIG_H #ifndef CONFIG_H
#define CONFIG_H #define CONFIG_H
// 固件版本信息
#define FIRMWARE_VERSION "v2.1.0"
#define BUILD_DATE __DATE__
#define BUILD_TIME __TIME__
#define FIRMWARE_NAME "PetIO"
// WiFi 配置 // WiFi 配置
#define DEFAULT_AP_SSID "PetIO_Setup" #define DEFAULT_AP_SSID "PetIO_Setup"
#define WIFI_TIMEOUT 30000 // 30秒 #define WIFI_TIMEOUT 30000 // 30秒

View File

@@ -17,7 +17,7 @@ WebServer webServer(&wifiManager, &timerManager, &timeManager);
unsigned long lastUpdate = 0; unsigned long lastUpdate = 0;
unsigned long lastWiFiCheck = 0; unsigned long lastWiFiCheck = 0;
unsigned long lastTimeUpdate = 0; unsigned long lastTimeUpdate = 0;
const unsigned long UPDATE_INTERVAL = 1000; // 1 const unsigned long UPDATE_INTERVAL = 10; // 10毫秒 - 高精度定时器更新
const unsigned long WIFI_CHECK_INTERVAL = 30000; // 30秒 const unsigned long WIFI_CHECK_INTERVAL = 30000; // 30秒
const unsigned long TIME_UPDATE_INTERVAL = 10000; // 10秒 const unsigned long TIME_UPDATE_INTERVAL = 10000; // 10秒
@@ -100,9 +100,16 @@ void loop()
{ {
unsigned long currentTime = millis(); unsigned long currentTime = millis();
// 处理 Web 请求 // 处理 Web 请求 (优先级最高)
webServer.handleClient(); webServer.handleClient();
// 高频更新定时器 (10ms间隔确保定时器精度)
if (currentTime - lastUpdate >= UPDATE_INTERVAL)
{
timerManager.update();
lastUpdate = currentTime;
}
// 定期更新时间同步 // 定期更新时间同步
if (currentTime - lastTimeUpdate >= TIME_UPDATE_INTERVAL) if (currentTime - lastTimeUpdate >= TIME_UPDATE_INTERVAL)
{ {
@@ -110,13 +117,6 @@ void loop()
lastTimeUpdate = currentTime; lastTimeUpdate = currentTime;
} }
// 定期更新定时器
if (currentTime - lastUpdate >= UPDATE_INTERVAL)
{
timerManager.update();
lastUpdate = currentTime;
}
// 定期检查 WiFi 连接 // 定期检查 WiFi 连接
if (currentTime - lastWiFiCheck >= WIFI_CHECK_INTERVAL) if (currentTime - lastWiFiCheck >= WIFI_CHECK_INTERVAL)
{ {

View File

@@ -81,7 +81,8 @@ void TimerManager::update() {
String modeStr = timers[i].isPWM ? " PWM(" + String(timers[i].pwmValue) + ")" : ""; String modeStr = timers[i].isPWM ? " PWM(" + String(timers[i].pwmValue) + ")" : "";
Serial.println("定时器 " + String(i) + " 激活,引脚 " + String(timers[i].pin) + " 开启" + modeStr + Serial.println("定时器 " + String(i) + " 激活,引脚 " + String(timers[i].pin) + " 开启" + modeStr +
(timers[i].repeatDaily ? " (每天重复)" : " (单次)")); (timers[i].repeatDaily ? " (每天重复)" : " (单次)") +
", 预期运行时间: " + String(timers[i].duration * 1000.0) + "ms");
stateChanged = true; stateChanged = true;
@@ -94,13 +95,13 @@ void TimerManager::update() {
// 检查是否需要关闭 // 检查是否需要关闭
if (timers[i].isActive && if (timers[i].isActive &&
currentTime - timers[i].startTime >= (unsigned long)(timers[i].duration * 1000.0)) { currentTime - timers[i].startTime >= (unsigned long)(timers[i].duration * 1000.0 + 0.5)) {
timers[i].isActive = false; timers[i].isActive = false;
timers[i].realStartTime = 0; // 清理真实时间戳 timers[i].realStartTime = 0; // 清理真实时间戳
setPin(timers[i].pin, LOW, 0); setPin(timers[i].pin, LOW, 0);
Serial.println("定时器 " + String(i) + " 完成,引脚 " + String(timers[i].pin) + " 关闭"); Serial.println("定时器 " + String(i) + " 完成,引脚 " + String(timers[i].pin) + " 关闭,实际运行时间: " + String(currentTime - timers[i].startTime) + "ms");
stateChanged = true; stateChanged = true;
} }
} }

View File

@@ -640,6 +640,8 @@ const char INDEX_HTML[] PROGMEM = R"rawliteral(
systemInfo: null systemInfo: null
}; };
let isLoadingData = false;
// --- Class definitions for styling --- // --- Class definitions for styling ---
const activeTabClasses = ['active', 'text-indigo-600', 'font-semibold']; const activeTabClasses = ['active', 'text-indigo-600', 'font-semibold'];
const inactiveTabClasses = ['text-gray-500', 'hover:text-gray-700']; const inactiveTabClasses = ['text-gray-500', 'hover:text-gray-700'];
@@ -653,10 +655,34 @@ const char INDEX_HTML[] PROGMEM = R"rawliteral(
tab.classList.add(...inactiveTabClasses); tab.classList.add(...inactiveTabClasses);
} }
}); });
// Set current time as default for timer
setCurrentTimeAsDefault();
loadData(); loadData();
setInterval(loadData, 5000); // 使用异步轮询替代 setInterval
startPolling();
}); });
function setCurrentTimeAsDefault() {
const now = new Date();
const hours = String(now.getHours()).padStart(2, '0');
const minutes = String(now.getMinutes()).padStart(2, '0');
const currentTime = `${hours}:${minutes}`;
document.getElementById('timer-time').value = currentTime;
}
function startPolling() {
async function poll() {
if (!isLoadingData) {
await loadData();
}
// 等待5秒后再次轮询
setTimeout(poll, 5000);
}
poll();
}
function switchTab(tabName, clickedTab) { function switchTab(tabName, clickedTab) {
document.querySelectorAll('.tab-content').forEach(content => content.classList.add('hidden')); document.querySelectorAll('.tab-content').forEach(content => content.classList.add('hidden'));
document.querySelectorAll('.tab').forEach(tab => { document.querySelectorAll('.tab').forEach(tab => {
@@ -674,6 +700,11 @@ const char INDEX_HTML[] PROGMEM = R"rawliteral(
} }
async function loadData() { async function loadData() {
if (isLoadingData) {
return; // 如果正在加载,直接返回
}
isLoadingData = true;
try { try {
const [statusRes, timersRes, pinsRes] = await Promise.all([ const [statusRes, timersRes, pinsRes] = await Promise.all([
fetch('/api/status'), fetch('/api/status'),
@@ -698,6 +729,8 @@ const char INDEX_HTML[] PROGMEM = R"rawliteral(
} catch (error) { } catch (error) {
console.error(':', error); console.error(':', error);
document.getElementById('connection-status').innerHTML = `<span class="inline-flex items-center px-2.5 py-0.5 rounded-full bg-red-100 text-red-800"></span>`; document.getElementById('connection-status').innerHTML = `<span class="inline-flex items-center px-2.5 py-0.5 rounded-full bg-red-100 text-red-800"></span>`;
} finally {
isLoadingData = false;
} }
} }
@@ -1128,8 +1161,8 @@ const char INDEX_HTML[] PROGMEM = R"rawliteral(
const response = await fetch('/api/system'); const response = await fetch('/api/system');
if (response.ok) { if (response.ok) {
const data = await response.json(); const data = await response.json();
document.getElementById('current-version').textContent = `v1.0.0`; document.getElementById('current-version').textContent = data.firmwareVersion || 'v1.0.0';
document.getElementById('build-time').textContent = new Date().toLocaleDateString(); document.getElementById('build-time').textContent = data.buildDateTime || '';
document.getElementById('firmware-chip-id').textContent = data.chipId || ''; document.getElementById('firmware-chip-id').textContent = data.chipId || '';
} }
} catch (error) { } catch (error) {

View File

@@ -1,4 +1,5 @@
#include "web_server.h" #include "web_server.h"
#include "config.h"
#include <ESP8266HTTPUpdateServer.h> #include <ESP8266HTTPUpdateServer.h>
#include <Updater.h> #include <Updater.h>
@@ -134,6 +135,13 @@ void WebServer::handleGetSystemInfo() {
JsonDocument doc; JsonDocument doc;
// 固件信息
doc["firmwareVersion"] = FIRMWARE_VERSION;
doc["firmwareName"] = FIRMWARE_NAME;
doc["buildDate"] = BUILD_DATE;
doc["buildTime"] = BUILD_TIME;
doc["buildDateTime"] = String(BUILD_DATE) + " " + String(BUILD_TIME);
// 基本系统信息 // 基本系统信息
doc["chipId"] = String(ESP.getChipId(), HEX); doc["chipId"] = String(ESP.getChipId(), HEX);
doc["flashChipId"] = String(ESP.getFlashChipId(), HEX); doc["flashChipId"] = String(ESP.getFlashChipId(), HEX);