init
This commit is contained in:
53
src/config.h
Normal file
53
src/config.h
Normal 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
137
src/display_manager.cpp
Normal 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
47
src/display_manager.h
Normal 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
131
src/main.cpp
Normal 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
135
src/time_manager.cpp
Normal 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
31
src/time_manager.h
Normal 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
560
src/timer_manager.cpp
Normal 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
38
src/timer_manager.h
Normal 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
1050
src/web_pages.h
Normal file
File diff suppressed because it is too large
Load Diff
422
src/web_server.cpp
Normal file
422
src/web_server.cpp
Normal 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
51
src/web_server.h
Normal 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
135
src/wifi_manager.cpp
Normal 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
29
src/wifi_manager.h
Normal 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
|
||||
Reference in New Issue
Block a user