This commit is contained in:
MiaoMint
2025-08-12 04:24:15 +08:00
commit 4dcbb9c05b
21 changed files with 3289 additions and 0 deletions

53
src/config.h Normal file
View File

@@ -0,0 +1,53 @@
#ifndef CONFIG_H
#define CONFIG_H
// WiFi 配置
#define DEFAULT_AP_SSID "PetIO_Setup"
#define WIFI_TIMEOUT 30000 // 30秒
// NTP 时间同步配置
#define NTP_SERVER "pool.ntp.org"
#define TIME_ZONE 8 // UTC+8 中国时区
#define NTP_UPDATE_INTERVAL 3600000 // 1小时同步一次
// Web 服务器端口
#define WEB_SERVER_PORT 80
// EEPROM 地址配置
#define EEPROM_SIZE 512
#define WIFI_SSID_ADDR 0
#define WIFI_PASSWORD_ADDR 64
#define TIMER_CONFIG_ADDR 128
// 定时器配置
#define MAX_TIMERS 10
#define MAX_SSID_LENGTH 32
#define MAX_PASSWORD_LENGTH 64
// ESP8266 可用引脚列表
const int AVAILABLE_PINS[] = {0, 1, 2, 3, 12, 13, 14, 15, 16};
const int AVAILABLE_PINS_COUNT = sizeof(AVAILABLE_PINS) / sizeof(AVAILABLE_PINS[0]);
// PWM 配置
#define PWM_FREQUENCY 1000 // PWM 频率 1KHz
#define PWM_RESOLUTION 10 // PWM 分辨率 10位 (0-1023)
#define PWM_MAX_VALUE 1023 // PWM 最大值
// 定时器结构体
struct TimerConfig
{
bool enabled;
int pin;
int hour;
int minute;
int duration; // 持续时间(秒)
bool repeatDaily; // 每天重复
bool isActive; // 当前是否激活
unsigned long startTime; // 开始时间millis时间戳
unsigned long lastTriggerDay; // 上次触发的天数(用于每天重复检查)
bool isPWM; // 是否为PWM模式
int pwmValue; // PWM值 (0-1023)
unsigned long realStartTime; // 真实开始时间(时间戳)
};
#endif

137
src/display_manager.cpp Normal file
View File

@@ -0,0 +1,137 @@
#include "display_manager.h"
#include <Wire.h>
DisplayManager::DisplayManager(WiFiManager *wm, TimerManager *tm, TimeManager *tim)
: wifi(wm), timers(tm), timeM(tim),
u8g2(U8G2_R0, /* reset=*/U8X8_PIN_NONE) {}
bool DisplayManager::begin()
{
Wire.begin(OLED_SDA_PIN, OLED_SCL_PIN);
u8g2.setI2CAddress(OLED_I2C_ADDRESS << 1); // U8g2 使用 8-bit 地址
u8g2.begin();
u8g2.setContrast(200);
u8g2.enableUTF8Print();
u8g2.setFont(u8g2_font_6x12_tf); // 默认 ASCII 字体,兼容性更好
// 开机画面
u8g2.clearBuffer();
u8g2.setFont(u8g2_font_wqy12_t_chinese1);
u8g2.drawStr(0, 12, "PetIO Booting...");
u8g2.drawStr(0, 28, "OLED init...");
u8g2.sendBuffer();
return true;
}
void DisplayManager::update()
{
unsigned long now = millis();
if (now - lastDraw >= drawInterval)
{
drawScreen();
lastDraw = now;
}
if (now - lastPageSwitch >= pageInterval)
{
pageIndex = (pageIndex + 1) % 3;
lastPageSwitch = now;
}
}
String DisplayManager::uptimeStr()
{
unsigned long ms = millis();
unsigned long s = ms / 1000UL;
unsigned int d = s / 86400UL;
unsigned int h = (s / 3600UL) % 24;
unsigned int m = (s / 60UL) % 60;
unsigned int sec = s % 60;
char buf[32];
if (d > 0)
sprintf(buf, "%ud %02u:%02u:%02u", d, h, m, sec);
else
sprintf(buf, "%02u:%02u:%02u", h, m, sec);
return String(buf);
}
void DisplayManager::drawScreen()
{
u8g2.clearBuffer();
switch (pageIndex)
{
case 0:
drawPageNetwork();
break;
case 1:
drawPageTimers();
break;
case 2:
drawPageUptime();
break;
}
u8g2.sendBuffer();
}
void DisplayManager::drawPageNetwork()
{
// 标题
u8g2.setFont(u8g2_font_6x12_tf);
u8g2.drawStr(0, 10, "Network");
// SSID 或 AP 名
String line1 = wifi->isConnected() ? String("SSID ") + WiFi.SSID() : String("AP ") + DEFAULT_AP_SSID;
u8g2.drawUTF8(0, 24, line1.c_str());
// IP
String ip = wifi->isConnected() ? wifi->getLocalIP() : wifi->getAPIP();
String ipLine = String("IP ") + ip;
u8g2.drawUTF8(0, 38, ipLine.c_str());
// RSSI/Mode
String line3;
if (wifi->isConnected())
line3 = String("RSSI ") + WiFi.RSSI() + " dBm";
else
line3 = "Mode AP";
u8g2.drawUTF8(0, 52, line3.c_str());
}
void DisplayManager::drawPageTimers()
{
u8g2.setFont(u8g2_font_6x12_tf);
u8g2.drawStr(0, 10, "Timers");
int total = timers->getTimerCount();
int y = 24;
for (int i = 0; i < total && i < 3; i++)
{ // 最多展示3条
TimerConfig t = timers->getTimer(i);
char buf[32];
snprintf(buf, sizeof(buf), "P%d %02d:%02d %ds %s", t.pin, t.hour, t.minute, t.duration, t.repeatDaily ? "R" : "1");
u8g2.drawStr(0, y, buf);
y += 12;
}
char stat[24];
int active = 0;
for (int i = 0; i < total; i++)
{
if (timers->getTimer(i).isActive)
active++;
}
snprintf(stat, sizeof(stat), "%d active / %d", active, total);
int w = u8g2.getStrWidth(stat);
u8g2.drawStr(128 - w, 64, stat);
}
void DisplayManager::drawPageUptime()
{
u8g2.setFont(u8g2_font_6x12_tf);
u8g2.drawStr(0, 10, "System");
String timeStr = timeM->getCurrentTimeString();
String dateStr = timeM->getCurrentDateString();
String timeLine = String("Time ") + timeStr;
String dateLine = String("Date ") + dateStr;
String upLine = String("Up ") + uptimeStr();
u8g2.drawUTF8(0, 24, timeLine.c_str());
u8g2.drawUTF8(0, 38, dateLine.c_str());
u8g2.drawUTF8(0, 52, upLine.c_str());
}

47
src/display_manager.h Normal file
View File

@@ -0,0 +1,47 @@
#ifndef DISPLAY_MANAGER_H
#define DISPLAY_MANAGER_H
#include <Arduino.h>
#include <U8g2lib.h>
#include "wifi_manager.h"
#include "timer_manager.h"
#include "time_manager.h"
// 默认 I2C 地址通常为 0x3C若你的模块丝印为 0x3D请在 config.h 中覆盖
#ifndef OLED_I2C_ADDRESS
#define OLED_I2C_ADDRESS 0x3C
#endif
// 默认 I2C 引脚ESP8266: ··)
#ifndef OLED_SDA_PIN
#define OLED_SDA_PIN 4
#endif
#ifndef OLED_SCL_PIN
#define OLED_SCL_PIN 5
#endif
class DisplayManager {
public:
DisplayManager(WiFiManager* wm, TimerManager* tm, TimeManager* tim);
bool begin();
void update();
private:
WiFiManager* wifi;
TimerManager* timers;
TimeManager* timeM;
U8G2_SSD1315_128X64_NONAME_F_HW_I2C u8g2; // 全缓冲,硬件 I2C
unsigned long lastDraw = 0;
const unsigned long drawInterval = 1000; // 1s 刷新
uint8_t pageIndex = 0; // 0:网络 1:任务 2:运行
unsigned long lastPageSwitch = 0;
const unsigned long pageInterval = 3000; // 3s 轮播
void drawScreen();
void drawPageNetwork();
void drawPageTimers();
void drawPageUptime();
String uptimeStr();
};
#endif // DISPLAY_MANAGER_H

131
src/main.cpp Normal file
View File

@@ -0,0 +1,131 @@
#include <Arduino.h>
#include "config.h"
#include "wifi_manager.h"
#include "timer_manager.h"
#include "time_manager.h"
#include "web_server.h"
// #include "display_manager.h"
// 全局对象
WiFiManager wifiManager;
TimerManager timerManager;
TimeManager timeManager;
WebServer webServer(&wifiManager, &timerManager, &timeManager);
// DisplayManager display(&wifiManager, &timerManager, &timeManager);
// 状态变量
unsigned long lastUpdate = 0;
unsigned long lastWiFiCheck = 0;
unsigned long lastTimeUpdate = 0;
const unsigned long UPDATE_INTERVAL = 1000; // 1秒
const unsigned long WIFI_CHECK_INTERVAL = 30000; // 30秒
const unsigned long TIME_UPDATE_INTERVAL = 10000; // 10秒
void setup()
{
Serial.begin(115200);
delay(1000); // 给串口时间稳定
Serial.println();
Serial.println("=================================");
Serial.println("🐾 PetIO 控制系统启动中...");
Serial.println("=================================");
// // 初始化 OLED 显示
// Serial.println("🖥️ 初始化 OLED 显示屏...");
// display.begin();
// Serial.println("✅ OLED 初始化完成!");
// 初始化时间管理器
Serial.println("🕐 初始化时间管理器...");
timeManager.begin();
Serial.println("✅ 时间管理器初始化完成!");
// 初始化定时器管理器
Serial.println("⏰ 初始化定时器管理器...");
timerManager.begin(&timeManager);
Serial.println("✅ 定时器管理器初始化完成!");
// 初始化 WiFi 管理器
Serial.println("📶 初始化 WiFi 管理器...");
bool wifiConnected = wifiManager.begin();
Serial.println("✅ WiFi 管理器初始化完成!");
// 初始化 Web 服务器
Serial.println("🌐 启动 Web 服务器...");
webServer.begin();
Serial.println("✅ Web 服务器启动完成!");
// 启动信息
Serial.println("=================================");
Serial.println("✅ 系统启动完成!");
Serial.println("=================================");
if (wifiConnected)
{
Serial.println("🌍 WiFi 模式 - 可通过以下地址访问:");
Serial.println(" http://" + wifiManager.getLocalIP());
Serial.println("🕐 NTP 时间同步已启用");
}
else
{
Serial.println("📡 AP 模式 - 请连接以下热点:");
Serial.println(" SSID: " + String(DEFAULT_AP_SSID));
Serial.println(" 密码: 无密码");
Serial.println(" 然后访问: http://" + wifiManager.getAPIP());
Serial.println("⚠️ AP 模式下定时器功能受限,请连接 WiFi 以启用完整功能");
}
Serial.println("=================================");
Serial.println();
// 显示可用引脚信息
Serial.print("🔌 可用引脚: ");
for (int i = 0; i < AVAILABLE_PINS_COUNT; i++)
{
Serial.print(AVAILABLE_PINS[i]);
if (i < AVAILABLE_PINS_COUNT - 1)
Serial.print(", ");
}
Serial.println();
// 显示定时器信息
Serial.println("⏰ 已加载定时器数量: " + String(timerManager.getTimerCount()));
Serial.println();
Serial.println("📝 系统日志:");
Serial.println("----------------------------------------");
}
void loop()
{
unsigned long currentTime = millis();
// 处理 Web 请求
webServer.handleClient();
// 定期更新时间同步
if (currentTime - lastTimeUpdate >= TIME_UPDATE_INTERVAL)
{
timeManager.update();
lastTimeUpdate = currentTime;
}
// 定期更新定时器
if (currentTime - lastUpdate >= UPDATE_INTERVAL)
{
timerManager.update();
lastUpdate = currentTime;
}
// 定期检查 WiFi 连接
if (currentTime - lastWiFiCheck >= WIFI_CHECK_INTERVAL)
{
wifiManager.handleWiFiConnection();
lastWiFiCheck = currentTime;
}
// display.update();
// 让系统有时间处理其他任务
yield();
}

135
src/time_manager.cpp Normal file
View File

@@ -0,0 +1,135 @@
#include "time_manager.h"
#include <ESP8266WiFi.h>
TimeManager::TimeManager() : timeClient(ntpUDP, NTP_SERVER, TIME_ZONE * 3600) {
timeInitialized = false;
lastNTPUpdate = 0;
}
void TimeManager::begin() {
timeClient.begin();
timeClient.setUpdateInterval(NTP_UPDATE_INTERVAL);
Serial.println("时间管理器初始化完成");
// 如果 WiFi 已连接,立即尝试同步时间
if (WiFi.status() == WL_CONNECTED) {
forceSync();
}
}
void TimeManager::update() {
// 只有在 WiFi 连接时才更新 NTP 时间
if (WiFi.status() == WL_CONNECTED) {
timeClient.update();
// 检查是否成功获取到时间
if (!timeInitialized && timeClient.getEpochTime() > 0) {
timeInitialized = true;
lastNTPUpdate = millis();
Serial.println("NTP 时间同步成功: " + getCurrentTimeString());
}
// 定期强制同步
if (timeInitialized && millis() - lastNTPUpdate > NTP_UPDATE_INTERVAL) {
forceSync();
}
}
}
bool TimeManager::isTimeValid() {
// WiFi 连接且时间已初始化
return (WiFi.status() == WL_CONNECTED) && timeInitialized && (timeClient.getEpochTime() > 0);
}
int TimeManager::getCurrentHour() {
if (isTimeValid()) {
return timeClient.getHours();
}
// 如果没有有效时间,返回基于运行时间的模拟时间(仅用于测试)
unsigned long seconds = millis() / 1000;
return (seconds / 3600) % 24;
}
int TimeManager::getCurrentMinute() {
if (isTimeValid()) {
return timeClient.getMinutes();
}
// 如果没有有效时间,返回基于运行时间的模拟时间(仅用于测试)
unsigned long seconds = millis() / 1000;
return (seconds / 60) % 60;
}
int TimeManager::getCurrentDay() {
if (isTimeValid()) {
// 返回自 Unix epoch 以来的天数
return timeClient.getEpochTime() / 86400;
}
// 如果没有有效时间,返回基于运行时间的模拟天数
return millis() / (24 * 60 * 60 * 1000UL);
}
String TimeManager::getCurrentTimeString() {
char timeStr[10];
if (isTimeValid()) {
sprintf(timeStr, "%02d:%02d:%02d",
timeClient.getHours(),
timeClient.getMinutes(),
timeClient.getSeconds());
} else {
sprintf(timeStr, "%02d:%02d:%02d*",
getCurrentHour(),
getCurrentMinute(),
(int)((millis() / 1000) % 60));
}
return String(timeStr);
}
String TimeManager::getCurrentDateString() {
if (isTimeValid()) {
unsigned long epochTime = timeClient.getEpochTime();
// 简单的日期计算
int days = epochTime / 86400;
int year = 1970 + (days / 365);
int month = ((days % 365) / 30) + 1;
int day = (days % 30) + 1;
char dateStr[20];
sprintf(dateStr, "%04d-%02d-%02d", year, month, day);
return String(dateStr);
}
return "未同步";
}
void TimeManager::forceSync() {
if (WiFi.status() == WL_CONNECTED) {
Serial.println("强制同步 NTP 时间...");
timeClient.forceUpdate();
if (timeClient.getEpochTime() > 0) {
timeInitialized = true;
lastNTPUpdate = millis();
Serial.println("NTP 同步成功: " + getCurrentTimeString());
} else {
Serial.println("NTP 同步失败");
}
}
}
bool TimeManager::isWiFiTimeAvailable() {
return WiFi.status() == WL_CONNECTED;
}
unsigned long TimeManager::getEpochTime() {
if (isTimeValid()) {
return timeClient.getEpochTime();
}
return 0; // 无效时间返回0
}

31
src/time_manager.h Normal file
View File

@@ -0,0 +1,31 @@
#ifndef TIME_MANAGER_H
#define TIME_MANAGER_H
#include <WiFiUdp.h>
#include <NTPClient.h>
#include <TimeLib.h>
#include "config.h"
class TimeManager {
private:
WiFiUDP ntpUDP;
NTPClient timeClient;
bool timeInitialized;
unsigned long lastNTPUpdate;
public:
TimeManager();
void begin();
void update();
bool isTimeValid();
int getCurrentHour();
int getCurrentMinute();
int getCurrentDay(); // 获取当前日期(用于每天重复检查)
unsigned long getEpochTime(); // 获取当前时间戳
String getCurrentTimeString();
String getCurrentDateString();
void forceSync();
bool isWiFiTimeAvailable();
};
#endif

560
src/timer_manager.cpp Normal file
View File

@@ -0,0 +1,560 @@
#include "timer_manager.h"
TimerManager::TimerManager() {
timerCount = 0;
timeManager = nullptr;
lastStateSave = 0;
}
void TimerManager::begin(TimeManager* tm) {
timeManager = tm;
// 初始化EEPROM
EEPROM.begin(EEPROM_SIZE);
// 初始化所有可用引脚为输出模式
for (int i = 0; i < AVAILABLE_PINS_COUNT; i++) {
pinMode(AVAILABLE_PINS[i], OUTPUT);
digitalWrite(AVAILABLE_PINS[i], LOW);
// 为PWM引脚设置频率
analogWriteFreq(PWM_FREQUENCY);
analogWriteResolution(PWM_RESOLUTION);
}
loadTimers();
Serial.println("Timer Manager 初始化完成,已加载 " + String(timerCount) + " 个定时器");
// 输出恢复的状态信息
int activeCount = 0;
for (int i = 0; i < timerCount; i++) {
if (timers[i].isActive) {
activeCount++;
}
}
if (activeCount > 0) {
Serial.println("已恢复 " + String(activeCount) + " 个活跃定时器状态");
}
}
void TimerManager::update() {
if (!timeManager) return;
unsigned long currentTime = millis();
int currentHour = timeManager->getCurrentHour();
int currentMinute = timeManager->getCurrentMinute();
unsigned long currentDay = timeManager->getCurrentDay();
bool stateChanged = false;
for (int i = 0; i < timerCount; i++) {
if (!timers[i].enabled) continue;
// 检查是否到达触发时间
bool shouldTrigger = false;
if (!timers[i].isActive &&
timers[i].hour == currentHour &&
timers[i].minute == currentMinute) {
if (timers[i].repeatDaily) {
// 每天重复:检查今天是否已经触发过
if (timers[i].lastTriggerDay != currentDay) {
shouldTrigger = true;
timers[i].lastTriggerDay = currentDay;
}
} else {
// 单次触发
shouldTrigger = true;
}
}
if (shouldTrigger) {
timers[i].isActive = true;
timers[i].startTime = currentTime;
// 保存真实时间戳(如果可用)
if (timeManager->isTimeValid()) {
timers[i].realStartTime = timeManager->getEpochTime();
} else {
timers[i].realStartTime = 0;
}
setPin(timers[i].pin, HIGH, timers[i].isPWM ? timers[i].pwmValue : 0);
String modeStr = timers[i].isPWM ? " PWM(" + String(timers[i].pwmValue) + ")" : "";
Serial.println("定时器 " + String(i) + " 激活,引脚 " + String(timers[i].pin) + " 开启" + modeStr +
(timers[i].repeatDaily ? " (每天重复)" : " (单次)"));
stateChanged = true;
// 如果是单次定时器,触发后自动禁用
if (!timers[i].repeatDaily) {
timers[i].enabled = false;
saveTimers(); // 立即保存配置更改
}
}
// 检查是否需要关闭
if (timers[i].isActive &&
currentTime - timers[i].startTime >= (unsigned long)(timers[i].duration * 1000)) {
timers[i].isActive = false;
timers[i].realStartTime = 0; // 清理真实时间戳
setPin(timers[i].pin, LOW, 0);
Serial.println("定时器 " + String(i) + " 完成,引脚 " + String(timers[i].pin) + " 关闭");
stateChanged = true;
}
}
// 定期保存状态,或者有状态变化时立即保存
if (stateChanged || (currentTime - lastStateSave >= STATE_SAVE_INTERVAL)) {
saveTimerStates();
lastStateSave = currentTime;
}
}
bool TimerManager::addTimer(int pin, int hour, int minute, int duration, bool repeatDaily, bool isPWM, int pwmValue) {
if (timerCount >= MAX_TIMERS) {
return false;
}
// 验证引脚是否可用
bool pinValid = false;
for (int i = 0; i < AVAILABLE_PINS_COUNT; i++) {
if (AVAILABLE_PINS[i] == pin) {
pinValid = true;
break;
}
}
if (!pinValid) return false;
// 验证时间
if (hour < 0 || hour > 23 || minute < 0 || minute > 59 || duration <= 0) {
return false;
}
// 验证PWM值
if (isPWM && (pwmValue < 0 || pwmValue > PWM_MAX_VALUE)) {
return false;
}
timers[timerCount].enabled = true;
timers[timerCount].pin = pin;
timers[timerCount].hour = hour;
timers[timerCount].minute = minute;
timers[timerCount].duration = duration;
timers[timerCount].repeatDaily = repeatDaily;
timers[timerCount].isActive = false;
timers[timerCount].startTime = 0;
timers[timerCount].lastTriggerDay = 0; // 初始化为0
timers[timerCount].isPWM = isPWM;
timers[timerCount].pwmValue = isPWM ? pwmValue : 0;
timers[timerCount].realStartTime = 0; // 初始化真实时间戳
timerCount++;
saveTimers();
String modeStr = isPWM ? " PWM模式, 值=" + String(pwmValue) : " 数字模式";
Serial.println("添加定时器:引脚 " + String(pin) + ", 时间 " + String(hour) + ":" + String(minute) +
", 持续 " + String(duration) + "" + modeStr + (repeatDaily ? " (每天重复)" : " (单次)"));
return true;
}
bool TimerManager::removeTimer(int index) {
if (index < 0 || index >= timerCount) {
return false;
}
// 如果定时器正在运行,先关闭引脚
if (timers[index].isActive) {
setPin(timers[index].pin, LOW, 0);
}
// 移动数组元素
for (int i = index; i < timerCount - 1; i++) {
timers[i] = timers[i + 1];
}
timerCount--;
saveTimers();
Serial.println("删除定时器 " + String(index));
return true;
}
bool TimerManager::updateTimer(int index, int pin, int hour, int minute, int duration, bool enabled, bool repeatDaily, bool isPWM, int pwmValue) {
if (index < 0 || index >= timerCount) {
return false;
}
// 验证引脚
bool pinValid = false;
for (int i = 0; i < AVAILABLE_PINS_COUNT; i++) {
if (AVAILABLE_PINS[i] == pin) {
pinValid = true;
break;
}
}
if (!pinValid) return false;
// 验证时间
if (hour < 0 || hour > 23 || minute < 0 || minute > 59 || duration <= 0) {
return false;
}
// 验证PWM值
if (isPWM && (pwmValue < 0 || pwmValue > PWM_MAX_VALUE)) {
return false;
}
// 如果定时器正在运行且引脚发生变化,先关闭旧引脚
if (timers[index].isActive && timers[index].pin != pin) {
setPin(timers[index].pin, LOW, 0);
timers[index].isActive = false;
}
timers[index].enabled = enabled;
timers[index].pin = pin;
timers[index].hour = hour;
timers[index].minute = minute;
timers[index].duration = duration;
timers[index].repeatDaily = repeatDaily;
timers[index].isPWM = isPWM;
timers[index].pwmValue = isPWM ? pwmValue : 0;
// 如果修改了重复设置,重置触发状态
timers[index].lastTriggerDay = 0;
saveTimers();
Serial.println("更新定时器 " + String(index));
return true;
}
String TimerManager::getTimersJSON() {
DynamicJsonDocument doc(2048);
JsonArray array = doc.to<JsonArray>();
for (int i = 0; i < timerCount; i++) {
JsonObject timer = array.createNestedObject();
timer["index"] = i;
timer["enabled"] = timers[i].enabled;
timer["pin"] = timers[i].pin;
timer["hour"] = timers[i].hour;
timer["minute"] = timers[i].minute;
timer["duration"] = timers[i].duration;
timer["repeatDaily"] = timers[i].repeatDaily;
timer["isActive"] = timers[i].isActive;
timer["isPWM"] = timers[i].isPWM;
timer["pwmValue"] = timers[i].pwmValue;
}
String result;
serializeJson(doc, result);
return result;
}
String TimerManager::getAvailablePinsJSON() {
DynamicJsonDocument doc(1024);
JsonArray array = doc.to<JsonArray>();
for (int i = 0; i < AVAILABLE_PINS_COUNT; i++) {
JsonObject pin = array.createNestedObject();
pin["pin"] = AVAILABLE_PINS[i];
pin["state"] = digitalRead(AVAILABLE_PINS[i]);
// 检查是否被定时器占用
bool inUse = false;
for (int j = 0; j < timerCount; j++) {
if (timers[j].pin == AVAILABLE_PINS[i] && timers[j].isActive) {
inUse = true;
break;
}
}
pin["inUse"] = inUse;
}
String result;
serializeJson(doc, result);
return result;
}
void TimerManager::executeManualControl(int pin, int duration, bool isPWM, int pwmValue) {
// 验证引脚
bool pinValid = false;
for (int i = 0; i < AVAILABLE_PINS_COUNT; i++) {
if (AVAILABLE_PINS[i] == pin) {
pinValid = true;
break;
}
}
if (!pinValid) return;
// 验证PWM值
if (isPWM && (pwmValue < 0 || pwmValue > PWM_MAX_VALUE)) {
return;
}
if (duration > 0) {
// 开启引脚指定时间
setPin(pin, HIGH, isPWM ? pwmValue : 0);
String modeStr = isPWM ? " PWM模式, 值=" + String(pwmValue) : " 数字模式";
Serial.println("手动控制:引脚 " + String(pin) + " 开启 " + String(duration) + "" + modeStr);
// 这里应该使用定时器来关闭,简化处理
delay(duration * 1000);
setPin(pin, LOW, 0);
Serial.println("手动控制:引脚 " + String(pin) + " 关闭");
} else {
// 切换状态
bool currentState = digitalRead(pin);
if (isPWM) {
// PWM模式切换
if (currentState) {
setPin(pin, LOW, 0);
Serial.println("手动控制:引脚 " + String(pin) + " PWM关闭");
} else {
setPin(pin, HIGH, pwmValue);
Serial.println("手动控制:引脚 " + String(pin) + " PWM开启, 值=" + String(pwmValue));
}
} else {
// 数字模式切换
setPin(pin, !currentState, 0);
Serial.println("手动控制:引脚 " + String(pin) + " 数字切换到 " + String(!currentState));
}
}
}
void TimerManager::saveTimers() {
int addr = TIMER_CONFIG_ADDR;
// 保存定时器数量
EEPROM.write(addr++, timerCount);
// 保存每个定时器
for (int i = 0; i < timerCount; i++) {
EEPROM.write(addr++, timers[i].enabled ? 1 : 0);
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);
EEPROM.write(addr++, timers[i].repeatDaily ? 1 : 0);
EEPROM.write(addr++, timers[i].isPWM ? 1 : 0);
EEPROM.write(addr++, (timers[i].pwmValue >> 8) & 0xFF);
EEPROM.write(addr++, timers[i].pwmValue & 0xFF);
// 保存运行时状态
EEPROM.write(addr++, timers[i].isActive ? 1 : 0);
// 保存开始时间4字节
unsigned long startTime = timers[i].startTime;
EEPROM.write(addr++, (startTime >> 24) & 0xFF);
EEPROM.write(addr++, (startTime >> 16) & 0xFF);
EEPROM.write(addr++, (startTime >> 8) & 0xFF);
EEPROM.write(addr++, startTime & 0xFF);
// 保存上次触发天数4字节
unsigned long lastTriggerDay = timers[i].lastTriggerDay;
EEPROM.write(addr++, (lastTriggerDay >> 24) & 0xFF);
EEPROM.write(addr++, (lastTriggerDay >> 16) & 0xFF);
EEPROM.write(addr++, (lastTriggerDay >> 8) & 0xFF);
EEPROM.write(addr++, lastTriggerDay & 0xFF);
// 保存真实开始时间4字节
unsigned long realStartTime = timers[i].realStartTime;
EEPROM.write(addr++, (realStartTime >> 24) & 0xFF);
EEPROM.write(addr++, (realStartTime >> 16) & 0xFF);
EEPROM.write(addr++, (realStartTime >> 8) & 0xFF);
EEPROM.write(addr++, realStartTime & 0xFF);
}
EEPROM.commit();
}
void TimerManager::loadTimers() {
int addr = TIMER_CONFIG_ADDR;
// 读取定时器数量
timerCount = EEPROM.read(addr++);
if (timerCount > MAX_TIMERS) timerCount = 0;
// 读取每个定时器
for (int i = 0; i < timerCount; i++) {
timers[i].enabled = EEPROM.read(addr++) == 1;
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;
timers[i].repeatDaily = EEPROM.read(addr++) == 1;
// 检查是否有PWM数据向后兼容
if (addr < EEPROM_SIZE - 16) { // 增加了13个字节的运行时状态数据9+4
timers[i].isPWM = EEPROM.read(addr++) == 1;
int pwmHigh = EEPROM.read(addr++);
int pwmLow = EEPROM.read(addr++);
timers[i].pwmValue = (pwmHigh << 8) | pwmLow;
// 读取运行时状态
timers[i].isActive = EEPROM.read(addr++) == 1;
// 读取开始时间4字节
unsigned long startTime = 0;
startTime |= ((unsigned long)EEPROM.read(addr++)) << 24;
startTime |= ((unsigned long)EEPROM.read(addr++)) << 16;
startTime |= ((unsigned long)EEPROM.read(addr++)) << 8;
startTime |= (unsigned long)EEPROM.read(addr++);
timers[i].startTime = startTime;
// 读取上次触发天数4字节
unsigned long lastTriggerDay = 0;
lastTriggerDay |= ((unsigned long)EEPROM.read(addr++)) << 24;
lastTriggerDay |= ((unsigned long)EEPROM.read(addr++)) << 16;
lastTriggerDay |= ((unsigned long)EEPROM.read(addr++)) << 8;
lastTriggerDay |= (unsigned long)EEPROM.read(addr++);
timers[i].lastTriggerDay = lastTriggerDay;
// 读取真实开始时间4字节
unsigned long realStartTime = 0;
realStartTime |= ((unsigned long)EEPROM.read(addr++)) << 24;
realStartTime |= ((unsigned long)EEPROM.read(addr++)) << 16;
realStartTime |= ((unsigned long)EEPROM.read(addr++)) << 8;
realStartTime |= (unsigned long)EEPROM.read(addr++);
timers[i].realStartTime = realStartTime;
} else {
// 旧数据没有PWM和运行时状态信息设为默认值
timers[i].isPWM = false;
timers[i].pwmValue = 0;
timers[i].isActive = false;
timers[i].startTime = 0;
timers[i].lastTriggerDay = 0;
timers[i].realStartTime = 0;
}
// 恢复活跃定时器的引脚状态
if (timers[i].isActive) {
// 使用真实时间检查定时器是否应该仍然活跃
if (timeManager && timeManager->isTimeValid() && timers[i].realStartTime > 0) {
// 需要添加获取当前时间戳的方法到TimeManager
// 暂时使用millis()逻辑,但添加更好的时间处理
unsigned long currentTime = millis();
// 重置开始时间为当前时间减去已经运行的时间
// 这样可以在重启后继续正确计时
if (timers[i].startTime > currentTime) {
// millis() 已重置,重新计算开始时间
timers[i].startTime = currentTime;
Serial.println("重启后调整定时器 " + String(i) + " 开始时间");
}
unsigned long elapsedTime = currentTime - timers[i].startTime;
// 检查定时器是否已经超时
if (elapsedTime >= (unsigned long)(timers[i].duration * 1000)) {
// 定时器已经超时,关闭它
timers[i].isActive = false;
timers[i].realStartTime = 0;
setPin(timers[i].pin, LOW, 0);
Serial.println("重启后发现定时器 " + String(i) + " 已超时,关闭引脚 " + String(timers[i].pin));
} else {
// 定时器仍然有效,恢复引脚状态
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;
Serial.println("恢复定时器 " + String(i) + " 状态,引脚 " + String(timers[i].pin) + " 开启" + modeStr +
",剩余时间: " + String(remainingTime / 1000) + "");
}
} else {
// 没有有效的时间或者是旧格式数据,保守处理
unsigned long currentTime = millis();
timers[i].startTime = currentTime;
setPin(timers[i].pin, HIGH, timers[i].isPWM ? timers[i].pwmValue : 0);
String modeStr = timers[i].isPWM ? " PWM(" + String(timers[i].pwmValue) + ")" : "";
Serial.println("恢复定时器 " + String(i) + " 状态,引脚 " + String(timers[i].pin) + " 开启" + modeStr +
"(重启后重新计时)");
}
}
}
}
bool TimerManager::hasValidTime() {
return timeManager && timeManager->isTimeValid();
}
void TimerManager::saveTimerStates() {
// 只保存运行时状态,不保存完整配置
// 这样可以减少EEPROM写入次数延长寿命
int addr = TIMER_CONFIG_ADDR + 1; // 跳过定时器数量
for (int i = 0; i < timerCount; i++) {
// 跳过基本配置部分 (10字节)
addr += 10;
// 更新运行时状态部分 (13字节)
EEPROM.write(addr++, timers[i].isActive ? 1 : 0);
// 更新开始时间4字节
unsigned long startTime = timers[i].startTime;
EEPROM.write(addr++, (startTime >> 24) & 0xFF);
EEPROM.write(addr++, (startTime >> 16) & 0xFF);
EEPROM.write(addr++, (startTime >> 8) & 0xFF);
EEPROM.write(addr++, startTime & 0xFF);
// 更新上次触发天数4字节
unsigned long lastTriggerDay = timers[i].lastTriggerDay;
EEPROM.write(addr++, (lastTriggerDay >> 24) & 0xFF);
EEPROM.write(addr++, (lastTriggerDay >> 16) & 0xFF);
EEPROM.write(addr++, (lastTriggerDay >> 8) & 0xFF);
EEPROM.write(addr++, lastTriggerDay & 0xFF);
// 更新真实开始时间4字节
unsigned long realStartTime = timers[i].realStartTime;
EEPROM.write(addr++, (realStartTime >> 24) & 0xFF);
EEPROM.write(addr++, (realStartTime >> 16) & 0xFF);
EEPROM.write(addr++, (realStartTime >> 8) & 0xFF);
EEPROM.write(addr++, realStartTime & 0xFF);
}
EEPROM.commit();
}
void TimerManager::clearAllTimers() {
// 关闭所有激活的引脚
for (int i = 0; i < timerCount; i++) {
if (timers[i].isActive) {
setPin(timers[i].pin, LOW, 0);
}
}
timerCount = 0;
saveTimers();
Serial.println("所有定时器已清除");
}
int TimerManager::getTimerCount() {
return timerCount;
}
TimerConfig TimerManager::getTimer(int index) {
if (index >= 0 && index < timerCount) {
return timers[index];
}
TimerConfig empty = {false, 0, 0, 0, 0, false, false, 0, 0UL, false, 0, 0UL};
return empty;
}
void TimerManager::setPin(int pin, bool state, int pwmValue) {
if (pwmValue > 0 && state) {
// PWM 模式
analogWrite(pin, pwmValue);
} else {
// 数字模式
digitalWrite(pin, state ? HIGH : LOW);
}
}

38
src/timer_manager.h Normal file
View File

@@ -0,0 +1,38 @@
#ifndef TIMER_MANAGER_H
#define TIMER_MANAGER_H
#include <Arduino.h>
#include <EEPROM.h>
#include <ArduinoJson.h>
#include "config.h"
#include "time_manager.h"
class TimerManager {
private:
TimerConfig timers[MAX_TIMERS];
int timerCount;
TimeManager* timeManager;
unsigned long lastStateSave;
const unsigned long STATE_SAVE_INTERVAL = 30000; // 30秒保存一次状态
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 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);
String getTimersJSON();
void saveTimers();
void saveTimerStates(); // 新增:仅保存运行时状态
void loadTimers();
void clearAllTimers();
int getTimerCount();
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);
bool hasValidTime();
};
#endif

1050
src/web_pages.h Normal file

File diff suppressed because it is too large Load Diff

422
src/web_server.cpp Normal file
View File

@@ -0,0 +1,422 @@
#include "web_server.h"
WebServer::WebServer(WiFiManager* wm, TimerManager* tm, TimeManager* timeM) : server(WEB_SERVER_PORT) {
wifiManager = wm;
timerManager = tm;
timeManager = timeM;
}
void WebServer::begin() {
setupRoutes();
server.begin();
Serial.println("Web 服务器启动,端口: " + String(WEB_SERVER_PORT));
}
void WebServer::handleClient() {
server.handleClient();
}
void WebServer::setupRoutes() {
// 主页
server.on("/", HTTP_GET, [this]() { handleRoot(); });
// API 路由
server.on("/api/status", HTTP_GET, [this]() { handleGetStatus(); });
server.on("/api/system", HTTP_GET, [this]() { handleGetSystemInfo(); });
server.on("/api/timers", HTTP_GET, [this]() { handleGetTimers(); });
server.on("/api/timers", HTTP_POST, [this]() { handleAddTimer(); });
server.on("/api/timers/clear", HTTP_POST, [this]() { handleClearTimers(); });
server.on("/api/pins", HTTP_GET, [this]() { handleGetPins(); });
server.on("/api/pwm/config", HTTP_GET, [this]() { handleGetPWMConfig(); });
server.on("/api/manual", HTTP_POST, [this]() { handleManualControl(); });
server.on("/api/wifi", HTTP_POST, [this]() { handleWiFiConfig(); });
server.on("/api/wifi/reset", HTTP_POST, [this]() { handleWiFiReset(); });
server.on("/api/restart-ap", HTTP_POST, [this]() { handleRestartAP(); });
// 404 处理 - 这里会处理动态路由
server.onNotFound([this]() {
String uri = server.uri();
// 处理定时器的 PUT 和 DELETE 请求
if (uri.startsWith("/api/timers/") && uri != "/api/timers/clear") {
if (server.method() == HTTP_PUT) {
handleUpdateTimer();
return;
} else if (server.method() == HTTP_DELETE) {
handleDeleteTimer();
return;
}
}
// 真正的 404
server.send(404, "text/plain", "Not Found");
});
}
void WebServer::handleRoot() {
enableCORS();
// 如果设备处于AP模式且未连接WiFi显示简化的WiFi设置页面
if (wifiManager->isInAPMode() && !wifiManager->isConnected()) {
server.send_P(200, "text/html", AP_MODE_HTML);
} else {
// 否则显示完整的控制面板
server.send_P(200, "text/html", INDEX_HTML);
}
}
void WebServer::handleGetStatus() {
enableCORS();
DynamicJsonDocument doc(1024);
doc["wifiConnected"] = wifiManager->isConnected();
doc["localIP"] = wifiManager->getLocalIP();
doc["apIP"] = wifiManager->getAPIP();
doc["isAPMode"] = wifiManager->isInAPMode();
doc["hasValidTime"] = timerManager->hasValidTime();
doc["timeSource"] = timerManager->hasValidTime() ? "NTP" : "系统运行时间";
// 获取当前时间
doc["currentTime"] = timeManager->getCurrentTimeString();
doc["currentDate"] = timeManager->getCurrentDateString();
// 统计活跃定时器
int activeCount = 0;
for (int i = 0; i < timerManager->getTimerCount(); i++) {
TimerConfig timer = timerManager->getTimer(i);
if (timer.isActive) activeCount++;
}
doc["activeTimers"] = activeCount;
doc["totalTimers"] = timerManager->getTimerCount();
sendJSON(doc);
}
void WebServer::handleGetSystemInfo() {
enableCORS();
DynamicJsonDocument doc(2048);
// 基本系统信息
doc["chipId"] = String(ESP.getChipId(), HEX);
doc["flashChipId"] = String(ESP.getFlashChipId(), HEX);
doc["flashChipSize"] = ESP.getFlashChipSize();
doc["flashChipRealSize"] = ESP.getFlashChipRealSize();
doc["flashChipSpeed"] = ESP.getFlashChipSpeed();
doc["cpuFreqMHz"] = ESP.getCpuFreqMHz();
doc["freeHeap"] = ESP.getFreeHeap();
doc["heapFragmentation"] = ESP.getHeapFragmentation();
doc["maxFreeBlockSize"] = ESP.getMaxFreeBlockSize();
doc["freeSketchSpace"] = ESP.getFreeSketchSpace();
doc["sketchSize"] = ESP.getSketchSize();
doc["sketchMD5"] = ESP.getSketchMD5();
doc["resetReason"] = ESP.getResetReason();
doc["resetInfo"] = ESP.getResetInfo();
// 运行时间信息
unsigned long uptime = millis();
doc["uptimeMs"] = uptime;
doc["uptimeDays"] = uptime / (24 * 60 * 60 * 1000UL);
doc["uptimeHours"] = (uptime / (60 * 60 * 1000UL)) % 24;
doc["uptimeMinutes"] = (uptime / (60 * 1000UL)) % 60;
doc["uptimeSeconds"] = (uptime / 1000UL) % 60;
// WiFi 详细信息
JsonObject wifi = doc.createNestedObject("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["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["apStationCount"] = WiFi.softAPgetStationNum();
}
// 时间信息
JsonObject timeInfo = doc.createNestedObject("time");
timeInfo["hasValidTime"] = timeManager->isTimeValid();
timeInfo["timeSource"] = timeManager->isTimeValid() ? "NTP" : "系统运行时间";
timeInfo["currentTime"] = timeManager->getCurrentTimeString();
timeInfo["currentDate"] = timeManager->getCurrentDateString();
timeInfo["isWiFiTimeAvailable"] = timeManager->isWiFiTimeAvailable();
// 引脚状态
JsonArray pins = doc.createNestedArray("pins");
for (int i = 0; i < AVAILABLE_PINS_COUNT; i++) {
JsonObject pin = pins.createNestedObject();
pin["pin"] = AVAILABLE_PINS[i];
pin["state"] = digitalRead(AVAILABLE_PINS[i]);
// 检查是否被定时器占用
bool inUse = false;
int timerIndex = -1;
for (int j = 0; j < timerManager->getTimerCount(); j++) {
TimerConfig timer = timerManager->getTimer(j);
if (timer.pin == AVAILABLE_PINS[i] && timer.isActive) {
inUse = true;
timerIndex = j;
break;
}
}
pin["inUse"] = inUse;
pin["timerIndex"] = timerIndex;
}
// 定时器统计
JsonObject timerStats = doc.createNestedObject("timerStats");
timerStats["total"] = timerManager->getTimerCount();
int enabled = 0, active = 0, repeatDaily = 0, oneTime = 0;
for (int i = 0; i < timerManager->getTimerCount(); i++) {
TimerConfig timer = timerManager->getTimer(i);
if (timer.enabled) enabled++;
if (timer.isActive) active++;
if (timer.repeatDaily) repeatDaily++;
else oneTime++;
}
timerStats["enabled"] = enabled;
timerStats["active"] = active;
timerStats["repeatDaily"] = repeatDaily;
timerStats["oneTime"] = oneTime;
timerStats["maxTimers"] = MAX_TIMERS;
// EEPROM 使用情况
JsonObject eeprom = doc.createNestedObject("eeprom");
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
sendJSON(doc);
}
void WebServer::handleGetTimers() {
enableCORS();
server.send(200, "application/json", timerManager->getTimersJSON());
}
void WebServer::handleAddTimer() {
enableCORS();
if (!server.hasArg("plain")) {
sendJSON(400, "缺少请求数据", false);
return;
}
DynamicJsonDocument doc(1024);
deserializeJson(doc, server.arg("plain"));
int pin = doc["pin"];
int hour = doc["hour"];
int minute = doc["minute"];
int duration = doc["duration"];
bool repeatDaily = doc["repeatDaily"].as<bool>();
bool isPWM = doc["isPWM"].as<bool>();
int pwmValue = doc["pwmValue"].is<int>() ? doc["pwmValue"].as<int>() : 512; // 默认值50%
if (timerManager->addTimer(pin, hour, minute, duration, repeatDaily, isPWM, pwmValue)) {
sendJSON(200, "定时器添加成功");
} else {
sendJSON(400, "定时器添加失败,请检查参数", false);
}
}
void WebServer::handleUpdateTimer() {
enableCORS();
String uri = server.uri();
int lastSlash = uri.lastIndexOf('/');
if (lastSlash == -1) {
sendJSON(400, "无效的请求路径", false);
return;
}
int index = uri.substring(lastSlash + 1).toInt();
if (!server.hasArg("plain")) {
sendJSON(400, "缺少请求数据", false);
return;
}
DynamicJsonDocument doc(1024);
deserializeJson(doc, server.arg("plain"));
int pin = doc["pin"];
int hour = doc["hour"];
int minute = doc["minute"];
int duration = doc["duration"];
bool enabled = doc["enabled"];
bool repeatDaily = doc["repeatDaily"].as<bool>();
bool isPWM = doc["isPWM"].as<bool>();
int pwmValue = doc["pwmValue"].is<int>() ? doc["pwmValue"].as<int>() : 512; // 默认值50%
if (timerManager->updateTimer(index, pin, hour, minute, duration, enabled, repeatDaily, isPWM, pwmValue)) {
sendJSON(200, "定时器更新成功");
} else {
sendJSON(400, "定时器更新失败", false);
}
}
void WebServer::handleDeleteTimer() {
enableCORS();
String uri = server.uri();
int lastSlash = uri.lastIndexOf('/');
if (lastSlash == -1) {
sendJSON(400, "无效的请求路径", false);
return;
}
int index = uri.substring(lastSlash + 1).toInt();
if (timerManager->removeTimer(index)) {
sendJSON(200, "定时器删除成功");
} else {
sendJSON(400, "定时器删除失败", false);
}
}
void WebServer::handleClearTimers() {
enableCORS();
timerManager->clearAllTimers();
sendJSON(200, "所有定时器已清除");
}
void WebServer::handleGetPins() {
enableCORS();
server.send(200, "application/json", timerManager->getAvailablePinsJSON());
}
void WebServer::handleManualControl() {
enableCORS();
if (!server.hasArg("plain")) {
sendJSON(400, "缺少请求数据", false);
return;
}
DynamicJsonDocument doc(512);
deserializeJson(doc, server.arg("plain"));
int pin = doc["pin"];
int duration = doc["duration"];
bool isPWM = doc["isPWM"].as<bool>();
int pwmValue = doc["pwmValue"].is<int>() ? doc["pwmValue"].as<int>() : 512; // 默认值50%
timerManager->executeManualControl(pin, duration, isPWM, pwmValue);
sendJSON(200, "手动控制执行成功");
}
void WebServer::handleWiFiConfig() {
enableCORS();
if (!server.hasArg("plain")) {
sendJSON(400, "缺少请求数据", false);
return;
}
DynamicJsonDocument doc(512);
deserializeJson(doc, server.arg("plain"));
String ssid = doc["ssid"];
String password = doc["password"];
if (ssid.length() == 0) {
sendJSON(400, "SSID 不能为空", false);
return;
}
// 保存 WiFi 凭据
wifiManager->saveWiFiCredentials(ssid, password);
sendJSON(200, "WiFi 配置已保存,设备将重启");
// 延迟重启以确保响应发送完成
delay(1000);
ESP.restart();
}
void WebServer::handleWiFiReset() {
enableCORS();
// 清除 WiFi 凭据
wifiManager->saveWiFiCredentials("", "");
sendJSON(200, "WiFi 设置已重置,设备将重启为 AP 模式");
// 延迟重启
delay(1000);
ESP.restart();
}
void WebServer::handleRestartAP() {
enableCORS();
// 重启为AP模式清除WiFi凭据并重启
wifiManager->saveWiFiCredentials("", "");
sendJSON(200, "设备将重启为热点模式");
// 延迟重启
delay(1000);
ESP.restart();
}
void WebServer::sendJSON(int code, const String& message, bool success) {
DynamicJsonDocument doc(512);
doc["success"] = success;
doc["message"] = message;
String response;
serializeJson(doc, response);
server.send(code, "application/json", response);
}
void WebServer::sendJSON(DynamicJsonDocument& doc) {
String response;
serializeJson(doc, response);
server.send(200, "application/json", response);
}
void WebServer::enableCORS() {
server.sendHeader("Access-Control-Allow-Origin", "*");
server.sendHeader("Access-Control-Allow-Methods", "GET, POST, PUT, DELETE, OPTIONS");
server.sendHeader("Access-Control-Allow-Headers", "Content-Type");
if (server.method() == HTTP_OPTIONS) {
server.send(204);
}
}
void WebServer::handleGetPWMConfig() {
enableCORS();
DynamicJsonDocument doc(512);
doc["frequency"] = PWM_FREQUENCY;
doc["resolution"] = PWM_RESOLUTION;
doc["maxValue"] = PWM_MAX_VALUE;
doc["minValue"] = 0;
doc["defaultValue"] = PWM_MAX_VALUE / 2; // 50%
sendJSON(doc);
}

51
src/web_server.h Normal file
View File

@@ -0,0 +1,51 @@
#ifndef WEB_SERVER_H
#define WEB_SERVER_H
#include <ESP8266WebServer.h>
#include <ArduinoJson.h>
#include "config.h"
#include "wifi_manager.h"
#include "timer_manager.h"
#include "time_manager.h"
#include "web_pages.h"
class WebServer {
private:
ESP8266WebServer server;
WiFiManager* wifiManager;
TimerManager* timerManager;
TimeManager* timeManager;
public:
WebServer(WiFiManager* wm, TimerManager* tm, TimeManager* timeM);
void begin();
void handleClient();
private:
void setupRoutes();
// 页面路由
void handleRoot();
// API 路由
void handleGetStatus();
void handleGetSystemInfo();
void handleGetTimers();
void handleAddTimer();
void handleUpdateTimer();
void handleDeleteTimer();
void handleClearTimers();
void handleGetPins();
void handleGetPWMConfig();
void handleManualControl();
void handleWiFiConfig();
void handleWiFiReset();
void handleRestartAP();
// 工具函数
void sendJSON(int code, const String& message, bool success = true);
void sendJSON(DynamicJsonDocument& doc);
void enableCORS();
};
#endif

135
src/wifi_manager.cpp Normal file
View File

@@ -0,0 +1,135 @@
#include "wifi_manager.h"
WiFiManager::WiFiManager() {
isAPMode = false;
}
bool WiFiManager::begin() {
EEPROM.begin(EEPROM_SIZE);
savedSSID = loadWiFiSSID();
savedPassword = loadWiFiPassword();
Serial.println("WiFi Manager 初始化");
Serial.println("保存的 SSID: " + savedSSID);
// 如果有保存的 WiFi 信息,尝试连接
if (savedSSID.length() > 0) {
Serial.println("尝试连接到保存的 WiFi...");
if (connectToWiFi(savedSSID, savedPassword)) {
Serial.println("连接成功IP: " + WiFi.localIP().toString());
return true;
}
}
// 连接失败或没有保存的信息,启动 AP 模式
Serial.println("启动 AP 模式");
setupAP();
return false;
}
void WiFiManager::setupAP() {
WiFi.mode(WIFI_AP);
// AP 模式无密码,更容易连接
WiFi.softAP(DEFAULT_AP_SSID);
isAPMode = true;
Serial.println("AP 模式启动");
Serial.println("SSID: " + String(DEFAULT_AP_SSID));
Serial.println("密码: 无密码");
Serial.println("AP IP: " + WiFi.softAPIP().toString());
}
bool WiFiManager::connectToWiFi(const String& ssid, const String& password) {
WiFi.mode(WIFI_STA);
WiFi.begin(ssid.c_str(), password.c_str());
unsigned long startTime = millis();
while (WiFi.status() != WL_CONNECTED && millis() - startTime < WIFI_TIMEOUT) {
delay(500);
Serial.print(".");
}
if (WiFi.status() == WL_CONNECTED) {
isAPMode = false;
return true;
}
return false;
}
void WiFiManager::saveWiFiCredentials(const String& ssid, const String& password) {
// 清空 EEPROM 区域
for (int i = 0; i < MAX_SSID_LENGTH + MAX_PASSWORD_LENGTH; i++) {
EEPROM.write(WIFI_SSID_ADDR + i, 0);
}
// 保存 SSID
for (int i = 0; i < ssid.length() && i < MAX_SSID_LENGTH - 1; i++) {
EEPROM.write(WIFI_SSID_ADDR + i, ssid[i]);
}
// 保存 Password
for (int i = 0; i < password.length() && i < MAX_PASSWORD_LENGTH - 1; i++) {
EEPROM.write(WIFI_PASSWORD_ADDR + i, password[i]);
}
EEPROM.commit();
savedSSID = ssid;
savedPassword = password;
Serial.println("WiFi 凭据已保存");
}
String WiFiManager::loadWiFiSSID() {
String ssid = "";
for (int i = 0; i < MAX_SSID_LENGTH; i++) {
char c = EEPROM.read(WIFI_SSID_ADDR + i);
if (c == 0) break;
ssid += c;
}
return ssid;
}
String WiFiManager::loadWiFiPassword() {
String password = "";
for (int i = 0; i < MAX_PASSWORD_LENGTH; i++) {
char c = EEPROM.read(WIFI_PASSWORD_ADDR + i);
if (c == 0) break;
password += c;
}
return password;
}
bool WiFiManager::isConnected() {
return !isAPMode && WiFi.status() == WL_CONNECTED;
}
bool WiFiManager::isInAPMode() {
return isAPMode;
}
String WiFiManager::getLocalIP() {
if (isConnected()) {
return WiFi.localIP().toString();
}
return "";
}
String WiFiManager::getAPIP() {
if (isAPMode) {
return WiFi.softAPIP().toString();
}
return "";
}
void WiFiManager::handleWiFiConnection() {
if (!isAPMode && WiFi.status() != WL_CONNECTED) {
Serial.println("WiFi 连接丢失,重新连接...");
if (!connectToWiFi(savedSSID, savedPassword)) {
Serial.println("重连失败,启动 AP 模式");
setupAP();
}
}
}

29
src/wifi_manager.h Normal file
View File

@@ -0,0 +1,29 @@
#ifndef WIFI_MANAGER_H
#define WIFI_MANAGER_H
#include <ESP8266WiFi.h>
#include <EEPROM.h>
#include "config.h"
class WiFiManager {
private:
String savedSSID;
String savedPassword;
bool isAPMode;
public:
WiFiManager();
bool begin();
void setupAP();
bool connectToWiFi(const String& ssid, const String& password);
void saveWiFiCredentials(const String& ssid, const String& password);
String loadWiFiSSID();
String loadWiFiPassword();
bool isConnected();
bool isInAPMode();
String getLocalIP();
String getAPIP();
void handleWiFiConnection();
};
#endif