caner 6 months ago
parent
commit
46386a81fe

BIN
.DS_Store


+ 167 - 0
sketch_sep25a/CODE_DOCUMENTATION.md

@@ -0,0 +1,167 @@
+# 代码详细文档
+
+## 主程序文件 (sketch_sep25a.ino)
+
+### 功能概述
+主程序文件负责初始化各个模块并协调它们的工作。程序采用非阻塞式设计,确保即使WiFi未连接也能正常运行。
+
+### 主要组件
+1. **WiFiConfigurator**: WiFi配置管理器
+2. **NtcThermistor**: NTC温度传感器管理器
+3. **RelayController**: 继电器控制器
+
+### 核心逻辑
+```cpp
+void loop() {
+    // 处理WiFi配置(即使WiFi未连接也继续运行)
+    wifiConfig.loop();
+    
+    // 定期更新
+    unsigned long currentMillis = millis();
+    if (currentMillis - lastUpdate >= updateInterval) {
+        lastUpdate = currentMillis;
+        
+        // 尝试同步时间(仅在WiFi可用时有效,失败不影响其他功能)
+        relays.updateNtpTime();
+        
+        // 更新温度
+        ntcSensor.updateTemperatures();
+        
+        // 打印状态信息
+        Serial.print("运行时间: ");
+        Serial.print((currentMillis - relays.getPowerOnTime())/1000);
+        Serial.print("秒, 当前小时: ");
+        Serial.print(relays.getCurrentHour());
+        Serial.print(", 温度 - GPIO2: ");
+        Serial.print(ntcSensor.getTemperature1String());
+        Serial.print(", GPIO3: ");
+        Serial.print(ntcSensor.getTemperature2String());
+        Serial.print(", GPIO6: ");
+        Serial.println(ntcSensor.getTemperature3String());
+        
+        // 获取温度值并控制继电器
+        float tempGpio2 = ntcSensor.getTemperature1();
+        float tempGpio3 = ntcSensor.getTemperature2();
+        float tempGpio6 = ntcSensor.getTemperature3();
+        
+        relays.controlByTempConditions(tempGpio2, tempGpio3, tempGpio6);
+        relays.handleTimedTasks(tempGpio6);
+    }
+}
+```
+
+## WiFi配置模块 (WiFiConfigurator)
+
+### 功能概述
+WiFi配置模块提供了完整的WiFi管理功能,包括:
+- 热点模式配置
+- Web界面配置
+- WiFi连接管理
+- 连接状态指示
+
+### 核心类: WiFiConfigurator
+
+#### 构造函数
+```cpp
+WiFiConfigurator(int slowLedPin, int fastLedPin, int configButtonPin,
+                const char* ap_ssid, const char* ap_password,
+                IPAddress apIP = IPAddress(192, 168, 4, 1),
+                IPAddress gateway = IPAddress(192, 168, 4, 1),
+                IPAddress subnet = IPAddress(255, 255, 255, 0));
+```
+
+#### 主要方法
+- `begin()`: 初始化WiFi配置模块
+- `loop()`: 主循环处理函数,需要在主程序loop中定期调用
+- `isConnected()`: 检查是否已连接WiFi
+- `getIPAddress()`: 获取当前IP地址
+
+#### 工作流程
+1. 初始化时检查是否已保存WiFi配置
+2. 如果有配置则尝试连接WiFi
+3. 监控配置按钮状态,长按5秒进入配置模式
+4. 配置模式下启动热点并提供Web配置界面
+
+## NTC温度传感器模块 (NtcThermistor)
+
+### 功能概述
+NTC温度传感器模块负责读取和处理NTC热敏电阻的温度数据,支持多路传感器同时工作。
+
+### 核心类: NtcThermistor
+
+#### 构造函数
+```cpp
+NtcThermistor(int pin1, int pin2, int pin3,
+             float referenceResistance = 10000.0,
+             float ntcNominalResistance = 10000.0,
+             float ntcBetaValue = 3950.0,
+             float referenceTemperature = 25.0,
+             int adcResolution = 4095);
+```
+
+#### 主要方法
+- `updateTemperatures()`: 更新所有温度读数
+- `getTemperature1()`, `getTemperature2()`, `getTemperature3()`: 获取各路温度值
+- `getTemperature1String()`, `getTemperature2String()`, `getTemperature3String()`: 获取格式化的温度字符串
+
+#### 计算原理
+使用标准NTC热敏电阻计算公式:
+```
+1/T = 1/T0 + 1/B * ln(R/R0)
+```
+其中:
+- T: 当前温度(开尔文)
+- T0: 标称温度(开尔文,默认25°C)
+- B: NTC的Beta值(默认3950)
+- R: 当前电阻值
+- R0: 标称电阻值(默认10kΩ)
+
+## 继电器控制模块 (RelayController)
+
+### 功能概述
+继电器控制模块实现了多种控制逻辑,包括温度条件控制和定时任务控制,并新增了温度滞后控制功能。
+
+### 核心类: RelayController
+
+#### 构造函数
+```cpp
+RelayController(int pin22, int pin23, int pin4);
+```
+
+#### 主要方法
+- `begin()`: 初始化继电器模块
+- `controlByTempConditions()`: 根据温度条件控制继电器
+- `handleTimedTasks()`: 处理定时任务
+- `updateNtpTime()`: 更新网络时间
+- `getPowerOnTime()`: 获取系统开机时间
+
+#### 控制逻辑详解
+
+##### 温度条件控制
+```cpp
+void RelayController::controlByTempConditions(float tempGpio2, float tempGpio3, float tempGpio6,
+                                           float diffThreshold, float gpio6High, float gpio6Low,
+                                           float highTempOn, float highTempOff)
+```
+
+###### 新增温度滞后控制(高温控制)
+- 当GPIO6温度高于`highTempOn`(默认35°C)时,开启继电器22并标记保持状态
+- 当GPIO6温度低于`highTempOff`(默认25°C)且已触发保持状态时,才关闭继电器22
+- 这种控制方式可以避免在临界温度点频繁开关继电器
+
+###### 原有温度差控制逻辑
+- 当GPIO2和GPIO3温度差小于`-diffThreshold`(默认-15°C)且GPIO6温度高于`gpio6High`(默认35°C)时,开启继电器22
+- 当GPIO2和GPIO3温度差大于`diffThreshold`(默认15°C)且GPIO6温度低于`gpio6Low`(默认15°C)时,开启继电器23
+- 注意:原有控制逻辑仅在未触发高温保持时生效
+
+##### 定时任务控制
+```cpp
+void RelayController::handleTimedTasks(float currentGpio6Temp)
+```
+- 每天8点开启继电器4,持续15分钟
+- 记录继电器22的开启时间和温度,用于超时保护
+
+#### 安全机制
+1. 继电器初始状态为高阻态(HIGH_Z)
+2. 继电器22具有超时保护机制
+3. 系统支持无WiFi运行,不影响基本控制功能

+ 106 - 0
sketch_sep25a/NtcThermistor.cpp

@@ -0,0 +1,106 @@
+#include "NtcThermistor.h"
+
+// 构造函数
+NtcThermistor::NtcThermistor(int pin1, int pin2, int pin3,
+                             float referenceResistance,
+                             float ntcNominalResistance,
+                             float ntcBetaValue,
+                             float referenceTemperature,
+                             int adcResolution) :
+    _pin1(pin1),
+    _pin2(pin2),
+    _pin3(pin3),
+    _referenceResistance(referenceResistance),
+    _ntcNominalResistance(ntcNominalResistance),
+    _ntcBetaValue(ntcBetaValue),
+    _referenceTemperature(referenceTemperature + 273.15),  // 转换为开尔文
+    _adcResolution(adcResolution) {
+    
+    // 初始化温度值
+    _temperature1 = 0.0;
+    _temperature2 = 0.0;
+    _temperature3 = 0.0;
+    
+    // 配置引脚为输入
+    pinMode(_pin1, INPUT);
+    pinMode(_pin2, INPUT);
+    pinMode(_pin3, INPUT);
+}
+
+// 读取单个NTC温度(优化版本)
+float NtcThermistor::readTemperature(int pin) {
+    // 多次采样取平均,减少噪声
+    int adcSum = 0;
+    for (int i = 0; i < 20; i++) {
+        adcSum += analogRead(pin);
+        delayMicroseconds(100);
+    }
+    int adcValue = adcSum / 20;
+    
+    // 调试:打印ADC原始值(方便排查问题)
+    Serial.print("Pin ");
+    Serial.print(pin);
+    Serial.print(" ADC: ");
+    Serial.print(adcValue);
+    
+    // 防止除以零错误(放宽范围,避免误判)
+    if (adcValue < 5 || adcValue > _adcResolution - 5) {
+        Serial.println(" -> 超出范围");
+        return -273.15;
+    }
+    
+    // 计算NTC电阻(关键公式修正)
+    // 注意:这里使用(float)强制转换,避免整数除法导致精度丢失
+    float resistance = _referenceResistance * ((float)_adcResolution / adcValue - 1.0);
+    Serial.print(" | 电阻: ");
+    Serial.print(resistance);
+    Serial.print("Ω");
+    
+    // 使用B参数方程计算温度(修正计算顺序)
+    float steinhart;
+    steinhart = resistance / _ntcNominalResistance;     // (R/R0)
+    steinhart = log(steinhart);                          // ln(R/R0)
+    steinhart /= _ntcBetaValue;                          // 1/B * ln(R/R0)
+    steinhart += 1.0 / _referenceTemperature;            // + 1/T0 (T0为25℃的开尔文温度)
+    steinhart = 1.0 / steinhart;                         // 计算得到开尔文温度
+    float celsius = steinhart - 273.15;                  // 转换为摄氏度
+    
+    Serial.print(" | 温度: ");
+    Serial.println(celsius);
+    
+    return celsius;
+}
+
+// 更新所有温度读数
+void NtcThermistor::updateTemperatures() {
+    _temperature1 = readTemperature(_pin1);
+    _temperature2 = readTemperature(_pin2);
+    _temperature3 = readTemperature(_pin3);
+}
+
+// 获取温度值
+float NtcThermistor::getTemperature1() {
+    return _temperature1;
+}
+
+float NtcThermistor::getTemperature2() {
+    return _temperature2;
+}
+
+float NtcThermistor::getTemperature3() {
+    return _temperature3;
+}
+
+// 获取格式化的温度字符串
+String NtcThermistor::getTemperature1String() {
+    return String(_temperature1, 1) + " °C";
+}
+
+String NtcThermistor::getTemperature2String() {
+    return String(_temperature2, 1) + " °C";
+}
+
+String NtcThermistor::getTemperature3String() {
+    return String(_temperature3, 1) + " °C";
+}
+    

+ 51 - 0
sketch_sep25a/NtcThermistor.h

@@ -0,0 +1,51 @@
+#ifndef NtcThermistor_h
+#define NtcThermistor_h
+
+#include <Arduino.h>
+
+class NtcThermistor {
+private:
+    // NTC引脚
+    const int _pin1;
+    const int _pin2;
+    const int _pin3;
+    
+    // NTC参数 - 可根据实际元件修改
+    const float _referenceResistance;  // 参考电阻值(欧姆)
+    const float _ntcNominalResistance; // NTC在标称温度下的电阻(欧姆)
+    const float _ntcBetaValue;         // NTC的Beta值
+    const float _referenceTemperature; // 标称温度(摄氏度,通常为25°C)
+    const int _adcResolution;          // ADC分辨率(如10位=1024)
+    
+    // 温度存储
+    float _temperature1;
+    float _temperature2;
+    float _temperature3;
+    
+    // 读取单个NTC温度的内部方法
+    float readTemperature(int pin);
+    
+public:
+    // 构造函数
+    NtcThermistor(int pin1, int pin2, int pin3,
+                 float referenceResistance = 10000.0,
+                 float ntcNominalResistance = 10000.0,
+                 float ntcBetaValue = 3950.0,
+                 float referenceTemperature = 25.0,
+                 int adcResolution = 4095);  // ESP32默认ADC分辨率
+    
+    // 更新所有温度读数
+    void updateTemperatures();
+    
+    // 获取温度值
+    float getTemperature1();
+    float getTemperature2();
+    float getTemperature3();
+    
+    // 获取格式化的温度字符串
+    String getTemperature1String();
+    String getTemperature2String();
+    String getTemperature3String();
+};
+
+#endif

+ 85 - 0
sketch_sep25a/README.md

@@ -0,0 +1,85 @@
+# ESP32-C6 智能空调控制器
+
+## 项目概述
+
+本项目基于ESP32-C6开发板,实现了一个智能空调控制器。该控制器具备WiFi配置功能、温度监测功能以及继电器控制功能,可以实现基于温度条件的自动控制和定时任务控制。
+
+## 功能特性
+
+### 1. WiFi配置功能
+- 支持通过Web界面配置WiFi连接
+- 提供热点模式(AP模式)进行初始配置
+- 自动保存WiFi配置信息
+- 支持连接状态指示灯显示
+
+### 2. 温度监测功能
+- 支持3路NTC热敏电阻温度传感器
+- 实时监测并显示各传感器温度
+- 使用标准NTC热敏电阻计算公式进行温度转换
+
+### 3. 继电器控制功能
+- 3路继电器控制输出
+- 支持基于温度条件的自动控制(包含温度滞后控制)
+- 支持定时任务控制
+- 具备安全保护机制
+
+## 硬件连接
+
+### LED指示灯
+- 慢闪LED (GPIO19): 系统运行状态指示
+- 快闪LED (GPIO20): WiFi配置状态指示
+
+### 按钮
+- 配置按钮 (GPIO21): 长按5秒进入WiFi配置模式
+
+### 温度传感器
+- NTC传感器1 (GPIO2)
+- NTC传感器2 (GPIO3)
+- NTC传感器3 (GPIO6)
+
+### 继电器控制
+- 继电器1 (GPIO22)
+- 继电器2 (GPIO23)
+- 定时继电器 (GPIO4)
+
+## 使用方法
+
+### 初始配置
+1. 首次使用或需要重新配置WiFi时,长按配置按钮5秒
+2. 系统将进入热点模式,创建名为"ESP32-C6-配置"的WiFi热点
+3. 连接该热点(密码: 12345678)
+4. 打开浏览器访问 `192.168.4.1` 进入配置界面
+5. 选择WiFi网络并输入密码进行连接
+
+### 正常运行
+- 系统支持无WiFi运行,即使WiFi未连接也能正常执行温度监测和继电器控制
+- 温度数据通过串口实时输出
+- 继电器根据预设逻辑自动控制
+
+## 代码结构
+
+```
+├── sketch_sep25a.ino          # 主程序文件
+├── WiFiConfigurator.h/.cpp    # WiFi配置模块
+├── NtcThermistor.h/.cpp       # NTC温度传感器模块
+├── RelayController.h/.cpp     # 继电器控制模块
+```
+
+## 控制逻辑
+
+### 温度条件控制
+- 继电器22: 
+  - 当GPIO6温度高于35°C时开启,并保持开启状态直到温度降至25°C以下(温度滞后控制)
+  - 当GPIO2-GPIO3温度差小于-15°C且GPIO6温度高于35°C时开启(原有控制逻辑,仅在未触发高温保持时生效)
+- 继电器23: 当GPIO2-GPIO3温度差大于15°C且GPIO6温度低于15°C时开启
+- 继电器4: 定时任务控制,每天8点开启15分钟
+
+### 定时任务
+- 系统会尝试同步网络时间(仅在WiFi连接时有效)
+- 根据当前小时执行定时任务
+
+## 注意事项
+1. 确保电源供应稳定,继电器工作时电流较大
+2. NTC传感器需正确连接参考电阻
+3. 首次使用前请确认所有引脚连接正确
+4. 系统具有开机时间记录功能,可用于计算运行时长

+ 211 - 0
sketch_sep25a/RelayController.cpp

@@ -0,0 +1,211 @@
+#include "RelayController.h"
+#include <WiFi.h>
+#include <time.h>
+
+// 构造函数
+RelayController::RelayController(int pin22, int pin23, int pin4) 
+    : _relayPin22(pin22), 
+      _relayPin23(pin23),
+      _relayPin4(pin4),
+      _relay22State(HIGH_Z),
+      _relay23State(HIGH_Z),
+      _relay4State(HIGH_Z),
+      _relay22TimeoutCheck(false),
+      _relay22TempHold(false),  // 初始化为未触发
+      _relay4LastOnCycle(0),
+      _powerOnTime(0),
+      _simulatedUnixTime(0)
+{}
+
+// 初始化
+void RelayController::begin() {
+    // 所有继电器初始化为高阻态
+    pinMode(_relayPin22, INPUT);
+    pinMode(_relayPin23, INPUT);
+    pinMode(_relayPin4, INPUT);
+    
+    _powerOnTime = millis();
+    _simulatedUnixTime = 0;
+    
+    Serial.println("继电器初始化完成,所有继电器默认高阻态");
+}
+
+// GPIO22控制方法
+void RelayController::setRelay22HighZ() {
+    if (_relay22State != HIGH_Z) {
+        pinMode(_relayPin22, INPUT);
+        _relay22State = HIGH_Z;
+        _relay22TimeoutCheck = false;
+        _relay22TempHold = false;  // 重置温度保持标记
+        Serial.println("GPIO22继电器已设为高阻态");
+    }
+}
+
+void RelayController::turnOnRelay22(float currentGpio6Temp) {
+    if (_relay22State != ON) {
+        pinMode(_relayPin22, OUTPUT);
+        digitalWrite(_relayPin22, HIGH);
+        _relay22State = ON;
+        
+        _relay22StartTime = millis();
+        _gpio6TempAtRelay22On = currentGpio6Temp;
+        _relay22TimeoutCheck = true;
+        Serial.println("GPIO22继电器已打开(高电平)");
+    }
+}
+
+// GPIO23控制方法
+void RelayController::setRelay23HighZ() {
+    if (_relay23State != HIGH_Z) {
+        pinMode(_relayPin23, INPUT);
+        _relay23State = HIGH_Z;
+        Serial.println("GPIO23继电器已设为高阻态");
+    }
+}
+
+void RelayController::turnOnRelay23() {
+    if (_relay23State != ON) {
+        pinMode(_relayPin23, OUTPUT);
+        digitalWrite(_relayPin23, HIGH);
+        _relay23State = ON;
+        Serial.println("GPIO23继电器已打开(高电平)");
+    }
+}
+
+// GPIO4定时继电器控制方法
+void RelayController::setRelay4HighZ() {
+    if (_relay4State != HIGH_Z) {
+        pinMode(_relayPin4, INPUT);
+        _relay4State = HIGH_Z;
+        Serial.println("GPIO4定时继电器已设为高阻态");
+    }
+}
+
+void RelayController::turnOnRelay4() {
+    if (_relay4State != ON) {
+        pinMode(_relayPin4, OUTPUT);
+        digitalWrite(_relayPin4, HIGH);
+        _relay4State = ON;
+        _relay4OnTime = millis();
+        Serial.println("GPIO4定时继电器已打开(高电平)");
+    }
+}
+
+// 主控制逻辑(包含温度滞后控制)
+void RelayController::controlByTempConditions(float tempGpio2, float tempGpio3, float tempGpio6,
+                                            float diffThreshold, float gpio6High, float gpio6Low,
+                                            float highTempOn, float highTempOff) {
+    float diff = tempGpio2 - tempGpio3;
+    Serial.print("温度差 (GPIO2 - GPIO3): ");
+    Serial.print(diff);
+    Serial.print(" °C, GPIO6温度: ");
+    Serial.print(tempGpio6);
+    Serial.println(" °C");
+    
+    // 新增:高温控制逻辑(带滞后)
+    // 当温度高于35℃时打开继电器并标记保持状态
+    if (tempGpio6 > highTempOn) {
+        turnOnRelay22(tempGpio6);
+        _relay22TempHold = true;  // 标记为需要滞后关闭
+    }
+    // 当温度低于25℃且已触发保持状态时,才关闭继电器
+    else if (tempGpio6 < highTempOff && _relay22TempHold) {
+        setRelay22HighZ();
+        _relay22TempHold = false;  // 重置保持状态
+    }
+    // 原有温度差控制逻辑(仅在未触发高温保持时生效)
+    else if (!_relay22TempHold) {
+        // 控制GPIO22:原有条件
+        if (diff < -diffThreshold && tempGpio6 > gpio6High) {
+            turnOnRelay22(tempGpio6);
+        } else {
+            setRelay22HighZ();
+        }
+    }
+    
+    // GPIO23控制逻辑(保持不变)
+    if (diff > diffThreshold && tempGpio6 < gpio6Low) {
+        turnOnRelay23();
+    } else {
+        setRelay23HighZ();
+    }
+}
+
+// 定时任务处理(保持不变)
+void RelayController::handleTimedTasks(float currentGpio6Temp) {
+    unsigned long currentTime = millis();
+    
+    // 1. GPIO22超时检测
+    if (_relay22TimeoutCheck && _relay22State == ON) {
+        if (currentTime - _relay22StartTime >= 15 * 60 * 1000) {  // 15分钟
+            float tempIncrease = currentGpio6Temp - _gpio6TempAtRelay22On;
+            
+            if (tempIncrease < 5.0) {
+                setRelay22HighZ();
+                Serial.println("警告:加热没有成功!15分钟内温度未上升5度");
+            } else {
+                _relay22TimeoutCheck = false;
+                Serial.println("加热成功,温度上升达标");
+            }
+        }
+    }
+    
+    // 2. 定时继电器GPIO4控制
+    unsigned long currentCycle = get24hCycleCount();
+    int currentHour = getCurrentHour();
+    
+    if (currentHour == _dailyOnHour && currentCycle != _relay4LastOnCycle) {
+        turnOnRelay4();
+        _relay4LastOnCycle = currentCycle;
+    }
+    
+    if (_relay4State == ON && (currentTime - _relay4OnTime) >= _onDuration) {
+        setRelay4HighZ();
+    }
+}
+
+// 时间管理方法(保持不变)
+void RelayController::updateNtpTime() {
+    if (WiFi.status() == WL_CONNECTED) {
+        configTime(8 * 3600, 0, "pool.ntp.org", "time.nist.gov");
+        time_t now;
+        time(&now);
+        
+        if (now > 1609459200) {
+            _simulatedUnixTime = now - ((millis() - _powerOnTime) / 1000);
+            Serial.println("NTP时间同步成功,校准模拟时间");
+        }
+    }
+}
+
+unsigned long RelayController::getCurrentUnixTime() {
+    return _simulatedUnixTime + ((millis() - _powerOnTime) / 1000);
+}
+
+int RelayController::getCurrentHour() {
+    unsigned long secondsSincePowerOn = (millis() - _powerOnTime) / 1000;
+    return (secondsSincePowerOn / 3600) % 24;
+}
+
+unsigned long RelayController::get24hCycleCount() {
+    unsigned long secondsSincePowerOn = (millis() - _powerOnTime) / 1000;
+    return secondsSincePowerOn / (24 * 3600);
+}
+
+unsigned long RelayController::getPowerOnTime() {
+    return _powerOnTime;
+}
+
+// 获取状态字符串
+String RelayController::getRelay22State() {
+    return (_relay22State == ON) ? "打开(高电平)" : "高阻态";
+}
+
+String RelayController::getRelay23State() {
+    return (_relay23State == ON) ? "打开(高电平)" : "高阻态";
+}
+
+String RelayController::getRelay4State() {
+    return (_relay4State == ON) ? "打开(高电平)" : "高阻态";
+}
+    

+ 75 - 0
sketch_sep25a/RelayController.h

@@ -0,0 +1,75 @@
+#ifndef RelayController_h
+#define RelayController_h
+
+#include <Arduino.h>
+
+class RelayController {
+private:
+    // 继电器引脚定义
+    const int _relayPin22;  // GPIO22继电器
+    const int _relayPin23;  // GPIO23继电器
+    const int _relayPin4;   // GPIO4定时继电器
+    
+    // 状态枚举
+    enum RelayState { HIGH_Z, ON };
+    RelayState _relay22State;
+    RelayState _relay23State;
+    RelayState _relay4State;
+    
+    // GPIO22超时检测变量
+    unsigned long _relay22StartTime;
+    bool _relay22TimeoutCheck;
+    float _gpio6TempAtRelay22On;
+    
+    // 新增:温度滞后控制变量
+    bool _relay22TempHold;  // 标记是否因高温开启需要滞后关闭
+    
+    // 定时继电器与时间管理变量
+    const unsigned long _dailyOnHour = 8;
+    const unsigned long _onDuration = 15 * 60 * 1000;
+    unsigned long _relay4LastOnCycle;
+    unsigned long _relay4OnTime;
+    unsigned long _powerOnTime;
+    unsigned long _simulatedUnixTime;
+
+public:
+    // 构造函数
+    RelayController(int pin22, int pin23, int pin4);
+    
+    // 初始化
+    void begin();
+    
+    // 继电器控制方法
+    void setRelay22HighZ();
+    void turnOnRelay22(float currentGpio6Temp);
+    
+    void setRelay23HighZ();
+    void turnOnRelay23();
+    
+    void setRelay4HighZ();
+    void turnOnRelay4();
+    
+    // 主控制逻辑(增加温度滞后控制)
+    void controlByTempConditions(float tempGpio2, float tempGpio3, float tempGpio6,
+                               float diffThreshold = 15.0, float gpio6High = 35.0, 
+                               float gpio6Low = 15.0, float highTempOn = 35.0,  // 开启温度
+                               float highTempOff = 25.0);  // 关闭温度
+    
+    // 定时任务处理
+    void handleTimedTasks(float currentGpio6Temp);
+    
+    // 时间管理
+    void updateNtpTime();
+    unsigned long getCurrentUnixTime();
+    int getCurrentHour();
+    unsigned long get24hCycleCount();
+    unsigned long getPowerOnTime();
+
+    // 获取状态
+    String getRelay22State();
+    String getRelay23State();
+    String getRelay4State();
+};
+
+#endif
+    

+ 824 - 0
sketch_sep25a/WiFiConfigurator.cpp

@@ -0,0 +1,824 @@
+#include "WiFiConfigurator.h"
+
+// 静态实例指针初始化
+WiFiConfigurator* WiFiConfigurator::_instance = nullptr;
+
+// HTML页面内容
+const char captive_html[] PROGMEM = R"rawliteral(
+<!DOCTYPE HTML>
+<html>
+<head>
+    <meta charset="UTF-8">
+    <meta http-equiv="refresh" content="0;url=/">
+</head>
+<body>
+</body>
+</html>
+)rawliteral";
+
+const char index_html[] PROGMEM = R"rawliteral(
+<!DOCTYPE HTML>
+<html>
+<head>
+    <meta charset="UTF-8">
+    <title>WiFi配置</title>
+    <meta name="viewport" content="width=device-width, initial-scale=1.0">
+    <style>
+        * { box-sizing: border-box; margin: 0; padding: 0; font-family: -apple-system, BlinkMacSystemFont, sans-serif; }
+        body { background-color: #f5f5f7; color: #333; padding: 20px; }
+        .container { max-width: 500px; margin: 50px auto; background: white; padding: 30px; border-radius: 18px; box-shadow: 0 4px 20px rgba(0,0,0,0.05); }
+        h1 { color: #1d1d1f; text-align: center; margin-bottom: 25px; font-weight: 600; }
+        .form-group { margin-bottom: 20px; }
+        .wifi-selector { display: flex; gap: 10px; align-items: flex-end; }
+        label { display: block; margin-bottom: 8px; font-weight: 500; color: #6e6e73; }
+        select, input { width: 100%; padding: 14px; border: 1px solid #d2d2d7; border-radius: 10px; font-size: 16px; }
+        select:focus, input:focus { outline: none; border-color: #0071e3; box-shadow: 0 0 0 2px rgba(0, 113, 227, 0.2); }
+        button { background-color: #0071e3; color: white; border: none; cursor: pointer; font-weight: 500; padding: 14px; border-radius: 10px; margin-top: 10px; }
+        button:hover { background-color: #0077ed; }
+        button:disabled { background-color: #94bfff; cursor: not-allowed; }
+        .refresh-btn { padding: 14px; width: auto; white-space: nowrap; }
+        .connect-btn { width: 100%; }
+        .signal { display: inline-block; width: 80px; text-align: right; color: #86868b; }
+        .status-bar { text-align: center; margin-top: 20px; color: #6e6e73; font-size: 14px; }
+        .message { padding: 15px; margin: 15px 0; border-radius: 8px; text-align: center; }
+        .success { background-color: #eaf8ed; color: #147d39; }
+        .error { background-color: #fee; color: #c51e24; }
+        .loading { background-color: #f0f7ff; color: #0066cc; }
+        .password-hint { font-size: 12px; color: #86868b; margin-top: 5px; }
+        .spinner { display: inline-block; width: 20px; height: 20px; border: 3px solid rgba(0,113,227,.3); border-radius: 50%; border-top-color: #0071e3; animation: spin 1s ease-in-out infinite; margin-right: 8px; }
+        @keyframes spin { to { transform: rotate(360deg); } }
+        .connection-status { margin-top: 15px; padding: 10px; border-radius: 8px; background-color: #f5f5f7; text-align: center; font-size: 14px; }
+        .progress-container { margin-top: 10px; background-color: #eaeaea; border-radius: 10px; overflow: hidden; }
+        .progress-bar { height: 8px; background-color: #0071e3; width: 0%; transition: width 0.3s ease; }
+        .timeout-warning { color: #ff9500; font-size: 12px; text-align: center; margin-top: 10px; }
+    </style>
+    <script>
+        let checkStatusInterval;
+        let timeoutTimer;
+        let remainingTime = 120; // 120秒超时
+        
+        // 更新超时提示
+        function updateTimeoutDisplay() {
+            const element = document.getElementById('timeoutWarning');
+            if (element) {
+                element.textContent = `配置热点将在 ${remainingTime} 秒后自动关闭`;
+            }
+            
+            if (remainingTime <= 0) {
+                clearInterval(timeoutTimer);
+                return;
+            }
+            
+            remainingTime--;
+        }
+        
+        function startCheckingStatus() {
+            // 禁用表单防止重复提交
+            document.getElementById('connectBtn').disabled = true;
+            document.getElementById('ssid').disabled = true;
+            document.getElementById('password').disabled = true;
+            document.querySelector('.refresh-btn').disabled = true;
+            
+            // 重置超时时间
+            resetTimeout();
+            
+            // 显示初始状态
+            updateStatus({
+                connecting: true,
+                status: "准备连接...",
+                progress: 0
+            });
+            
+            // 每200ms检查一次状态
+            checkStatusInterval = setInterval(() => {
+                fetch('/status')
+                    .then(response => {
+                        if (!response.ok) throw new Error('网络错误');
+                        return response.json();
+                    })
+                    .then(data => {
+                        updateStatus(data);
+                        
+                        // 如果连接完成,停止检查
+                        if (!data.connecting) {
+                            clearInterval(checkStatusInterval);
+                            
+                            // 连接成功时显示提示并跳转
+                            if (data.success) {
+                                setTimeout(() => {
+                                    window.location.href = '/success';
+                                }, 1000);
+                            } else {
+                                // 连接失败时重新启用表单,允许重试
+                                enableForm();
+                            }
+                        }
+                    })
+                    .catch(err => {
+                        console.error('获取状态失败:', err);
+                        updateStatus({
+                            connecting: false,
+                            status: "通信错误,请重试",
+                            progress: 0
+                        });
+                        clearInterval(checkStatusInterval);
+                        enableForm();
+                    });
+            }, 200);
+        }
+        
+        function updateStatus(data) {
+            const statusElement = document.getElementById('connectionStatus');
+            const progressBar = document.getElementById('progressBar');
+            
+            statusElement.innerHTML = data.connecting ? 
+                '<span class="spinner"></span>' + data.status : data.status;
+            
+            statusElement.className = 'connection-status ' + 
+                (data.connecting ? 'loading' : (data.success ? 'success' : 'error'));
+                
+            progressBar.style.width = data.progress + '%';
+        }
+        
+        function enableForm() {
+            // 确保表单元素可操作
+            document.getElementById('connectBtn').disabled = false;
+            document.getElementById('ssid').disabled = false;
+            document.getElementById('password').disabled = false;
+            document.querySelector('.refresh-btn').disabled = false;
+            
+            // 聚焦到密码输入框
+            document.getElementById('password').focus();
+        }
+        
+        // 重置超时计时器
+        function resetTimeout() {
+            remainingTime = 120;
+            clearInterval(timeoutTimer);
+            timeoutTimer = setInterval(updateTimeoutDisplay, 1000);
+            updateTimeoutDisplay();
+        }
+        
+        // 密码验证
+        function validatePassword() {
+            const password = document.getElementById('password').value;
+            const hintElement = document.getElementById('passwordHint');
+            
+            if (password.length === 0) {
+                hintElement.textContent = '请输入WiFi密码';
+                hintElement.style.color = '#86868b';
+                return true;
+            } else if (password.length < 8 && password.length > 0) {
+                hintElement.textContent = '警告: 大多数WiFi密码至少需要8个字符';
+                hintElement.style.color = '#ff9500';
+                return false;
+            } else {
+                hintElement.textContent = '';
+                return true;
+            }
+        }
+        
+        window.onload = function() {
+            // 初始化超时显示
+            const timeoutDiv = document.createElement('div');
+            timeoutDiv.id = 'timeoutWarning';
+            timeoutDiv.className = 'timeout-warning';
+            document.querySelector('.container').appendChild(timeoutDiv);
+            resetTimeout();
+            
+            const passwordInput = document.getElementById('password');
+            passwordInput.addEventListener('input', validatePassword);
+            
+            const connectForm = document.getElementById('connectForm');
+            connectForm.addEventListener('submit', function(e) {
+                e.preventDefault();
+                if (validatePassword()) {
+                    // 提交表单数据到后端
+                    const formData = new FormData(connectForm);
+                    fetch('/connect', {
+                        method: 'POST',
+                        body: formData
+                    }).then(() => {
+                        startCheckingStatus();
+                    });
+                }
+            });
+            
+            // 为刷新按钮添加超时重置
+            document.querySelector('.refresh-btn').addEventListener('click', function() {
+                resetTimeout();
+            });
+        }
+    </script>
+</head>
+<body>
+    <div class="container">
+        <h1>WiFi网络配置</h1>
+        
+        %MESSAGE_BOX%
+        
+        <form id="connectForm">
+            <div class="form-group">
+                <label for="ssid">选择WiFi网络:</label>
+                <div class="wifi-selector">
+                    <select id="ssid" name="ssid" required style="flex: 1;">
+                        %WIFI_LIST%
+                    </select>
+                    <button type="button" class="refresh-btn" onclick="window.location.href='/refresh'">
+                        刷新
+                    </button>
+                </div>
+            </div>
+            <div class="form-group">
+                <label for="password">WiFi密码:</label>
+                <input type="password" id="password" name="password" placeholder="请输入WiFi密码">
+                <div id="passwordHint" class="password-hint"></div>
+            </div>
+            <button type="submit" id="connectBtn" class="connect-btn">
+                连接网络
+            </button>
+            <div id="connectionStatus" class="connection-status">
+                等待操作...
+            </div>
+            <div class="progress-container">
+                <div id="progressBar" class="progress-bar"></div>
+            </div>
+        </form>
+        <div class="status-bar">
+            设备热点: %AP_SSID%
+        </div>
+    </div>
+</body>
+</html>
+)rawliteral";
+
+const char success_html[] PROGMEM = R"rawliteral(
+<!DOCTYPE HTML>
+<html>
+<head>
+    <meta charset="UTF-8">
+    <title>连接成功</title>
+    <meta name="viewport" content="width=device-width, initial-scale=1.0">
+    <style>
+        * { box-sizing: border-box; margin: 0; padding: 0; font-family: -apple-system, BlinkMacSystemFont, sans-serif; }
+        body { background-color: #f5f5f7; display: flex; flex-direction: column; align-items: center; justify-content: center; min-height: 100vh; padding: 20px; color: #333; }
+        .container { text-align: center; max-width: 400px; width: 100%; padding: 30px; background: white; border-radius: 18px; box-shadow: 0 4px 20px rgba(0,0,0,0.05); }
+        h1 { margin-bottom: 20px; font-weight: 600; color: #147d39; }
+        .message { margin: 20px 0; line-height: 1.6; color: #6e6e73; }
+        .countdown { color: #0071e3; font-weight: 600; margin: 15px 0; }
+    </style>
+    <script>
+        // 5秒后提示用户切换网络
+        let countdown = 5;
+        function updateCountdown() {
+            document.getElementById('countdown').textContent = countdown;
+            if (countdown <= 0) {
+                document.getElementById('countdownMessage').textContent = 
+                    '请切换到已配置的WiFi网络';
+                return;
+            }
+            countdown--;
+            setTimeout(updateCountdown, 1000);
+        }
+        window.onload = updateCountdown;
+    </script>
+</head>
+<body>
+    <div class="container">
+        <h1>连接成功!</h1>
+        
+        <div class="message">
+            <p>已成功连接到 %SSID%</p>
+            <p>IP地址: %IP_ADDRESS%</p>
+            <div id="countdownMessage" class="countdown">
+                配置热点将在 <span id="countdown">5</span> 秒后关闭
+            </div>
+        </div>
+    </div>
+</body>
+</html>
+)rawliteral";
+
+const char error_html[] PROGMEM = R"rawliteral(
+<!DOCTYPE HTML>
+<html>
+<head>
+    <meta charset="UTF-8">
+    <title>连接失败</title>
+    <meta name="viewport" content="width=device-width, initial-scale=1.0">
+    <style>
+        * { box-sizing: border-box; margin: 0; padding: 0; font-family: -apple-system, BlinkMacSystemFont, sans-serif; }
+        body { background-color: #f5f5f7; display: flex; flex-direction: column; align-items: center; justify-content: center; min-height: 100vh; padding: 20px; color: #333; }
+        .container { text-align: center; max-width: 400px; width: 100%; padding: 30px; background: white; border-radius: 18px; box-shadow: 0 4px 20px rgba(0,0,0,0.05); }
+        h1 { margin-bottom: 20px; font-weight: 600; color: #c51e24; }
+        .message { margin: 20px 0; line-height: 1.6; color: #6e6e73; }
+        button { background-color: #0071e3; color: white; border: none; cursor: pointer; font-weight: 500; padding: 14px 24px; border-radius: 10px; margin-top: 20px; font-size: 16px; }
+        button:hover { background-color: #0077ed; }
+    </style>
+</head>
+<body>
+    <div class="container">
+        <h1>连接失败</h1>
+        
+        <div class="message">
+            <p>无法连接到 %SSID%</p>
+            <p>%ERROR_MESSAGE%</p>
+        </div>
+        
+        <button onclick="window.location.href='/'">返回重试</button>
+    </div>
+</body>
+</html>
+)rawliteral";
+
+// 构造函数
+WiFiConfigurator::WiFiConfigurator(int slowLedPin, int fastLedPin, int configButtonPin,
+                                 const char* ap_ssid, const char* ap_password,
+                                 IPAddress apIP, IPAddress gateway, IPAddress subnet)
+    : _slowLedPin(slowLedPin),
+      _fastLedPin(fastLedPin),
+      _configButtonPin(configButtonPin),
+      _ap_ssid(ap_ssid),
+      _ap_password(ap_password),
+      _apIP(apIP),
+      _gateway(gateway),
+      _subnet(subnet),
+      _server(80),
+      _captive_html(captive_html),
+      _index_html(index_html),
+      _success_html(success_html),
+      _error_html(error_html) {
+    _instance = this;
+}
+
+// 初始化函数
+void WiFiConfigurator::begin() {
+    // 初始化LED
+    pinMode(_slowLedPin, OUTPUT);
+    pinMode(_fastLedPin, OUTPUT);
+    
+    // 初始化按键,使用内部上拉电阻
+    pinMode(_configButtonPin, INPUT_PULLUP);
+    
+    Serial.println("WiFi配置器初始化...");
+    
+    _preferences.begin("wifi-config", false);
+    
+    // 启动时立即扫描WiFi
+    scanWiFiNetworks();
+    
+    // 尝试连接已保存的WiFi
+    String savedSsid = _preferences.getString("ssid", "");
+    String savedPassword = _preferences.getString("password", "");
+    
+    if (savedSsid.length() > 0) {
+        Serial.println("尝试连接已保存的WiFi...");
+        connectToWiFi(savedSsid.c_str(), savedPassword.c_str());
+    } else {
+        startAPMode();
+    }
+}
+
+// 主循环处理函数
+void WiFiConfigurator::loop() {
+    // 处理按键输入
+    handleConfigButton();
+    
+    // LED控制
+    unsigned long currentMillis = millis();
+    
+    // 慢闪LED
+    if (currentMillis - _previousSlowMillis >= _slowInterval) {
+        _previousSlowMillis = currentMillis;
+        _slowLedState = !_slowLedState;
+        digitalWrite(_slowLedPin, _slowLedState);
+    }
+    
+    // 快闪LED - 连接过程中加速闪烁
+    if (_connectionInProgress) {
+        if (currentMillis - _previousFastMillis >= 100) {  // 加速闪烁
+            _previousFastMillis = currentMillis;
+            _fastLedState = !_fastLedState;
+            digitalWrite(_fastLedPin, _fastLedState);
+        }
+    } else if (WiFi.getMode() == WIFI_AP || WiFi.status() != WL_CONNECTED) {
+        if (currentMillis - _previousFastMillis >= _fastInterval) {
+            _previousFastMillis = currentMillis;
+            _fastLedState = !_fastLedState;
+            digitalWrite(_fastLedPin, _fastLedState);
+        }
+    } else {
+        digitalWrite(_fastLedPin, HIGH);
+    }
+    
+    // 持续处理网络请求
+    if (WiFi.getMode() & WIFI_AP) {  // 只要AP模式开启就处理请求
+        _dnsServer.processNextRequest();
+        _server.handleClient();
+        
+        // 检查AP模式超时(仅在连接未进行时)
+        if (!_connectionInProgress && _apTimeout > 0 && millis() - _apTimeout > _apTimeoutDuration) {
+            Serial.println("AP模式超时,关闭热点");
+            WiFi.softAPdisconnect(true);
+            _server.stop();
+            _dnsServer.stop();
+            _apTimeout = 0;
+        }
+    }
+    
+    // 连接成功后保持AP模式一段时间
+    if (_connectionSuccess && (WiFi.getMode() & WIFI_AP) && 
+        millis() - _successNotificationTime > _successNotificationDelay) {
+        Serial.println("成功通知时间已到,关闭AP模式");
+        WiFi.softAPdisconnect(true);
+        _server.stop();
+        _dnsServer.stop();
+        _apTimeout = 0;
+    }
+}
+
+// 检查是否已连接WiFi
+bool WiFiConfigurator::isConnected() {
+    return WiFi.status() == WL_CONNECTED;
+}
+
+// 获取当前连接的SSID
+String WiFiConfigurator::getCurrentSSID() {
+    return WiFi.status() == WL_CONNECTED ? WiFi.SSID() : "";
+}
+
+// 获取IP地址
+String WiFiConfigurator::getIPAddress() {
+    return WiFi.status() == WL_CONNECTED ? WiFi.localIP().toString() : "";
+}
+
+// 处理配置按键
+
+void WiFiConfigurator::handleConfigButton() {
+    // 读取按键状态(LOW表示按下,因为使用了内部上拉)
+    int buttonState = digitalRead(_configButtonPin);
+    // static bool slowLedAlwaysOn = false;  // 标记慢闪灯是否已常亮
+    
+    // 按键按下
+    if (buttonState == LOW && !_buttonPressed) {
+        _buttonPressed = true;
+        _buttonPressStartTime = millis();
+        // slowLedAlwaysOn = false;  // 重置状态
+        Serial.println("配置按键被按下...");
+        // 开始快速闪烁LED,表示正在计时
+        _fastLedState = HIGH;
+        digitalWrite(_fastLedPin, _fastLedState);
+    }
+    // 按键释放
+    else if (buttonState == HIGH && _buttonPressed) {
+        unsigned long pressDuration = millis() - _buttonPressStartTime;
+        _buttonPressed = false;
+        // slowLedAlwaysOn = false;  // 重置状态
+        
+        // 检查按下时间是否达到要求
+        if (pressDuration >= _configButtonPressDuration) {
+            Serial.println("按键长按5秒以上,进入配网模式...");
+            enterConfigMode();
+        } else {
+            Serial.printf("按键释放,按下时间: %d毫秒(不足5秒)\n", pressDuration);
+        }
+    }
+    // 按键持续按下中
+    else if (buttonState == LOW && _buttonPressed) {
+        unsigned long pressDuration = millis() - _buttonPressStartTime;
+        
+        // 每100ms检查一次(提高响应速度)
+        if (pressDuration % 100 < 10) {
+            // 计算已按下的秒数
+            int secondsPressed = pressDuration / 1000;
+            Serial.printf("按键持续按下中: %d秒/%d秒\n", secondsPressed, _configButtonPressDuration / 1000);
+
+            // 快闪LED提示用户还在计时
+            _fastLedState = !_fastLedState;
+            digitalWrite(_fastLedPin, _fastLedState);
+            
+            // 关键修改:按住期间达到5秒且尚未设置常亮
+            if (pressDuration >= _configButtonPressDuration) {
+            // if (pressDuration >= _configButtonPressDuration && !slowLedAlwaysOn) {
+                Serial.println("已按住5秒,慢闪灯常亮");
+                _slowLedState = HIGH;  // 设置慢闪LED状态为常亮
+                digitalWrite(_slowLedPin, _slowLedState);
+                // slowLedAlwaysOn = true;  // 标记已常亮,避免重复设置
+            }
+        }
+    }
+}
+
+// 进入配网模式
+void WiFiConfigurator::enterConfigMode() {
+    // 清除已保存的WiFi配置
+    _preferences.putString("ssid", "");
+    _preferences.putString("password", "");
+    
+    // 停止当前的WiFi连接
+    WiFi.disconnect();
+    
+    // 重新启动AP模式和Web服务
+    startAPMode();
+    
+    // 闪烁LED提示进入配网模式
+    for (int i = 0; i < 5; i++) {
+        digitalWrite(_slowLedPin, HIGH);
+        digitalWrite(_fastLedPin, HIGH);
+        delay(200);
+        digitalWrite(_slowLedPin, LOW);
+        digitalWrite(_fastLedPin, LOW);
+        delay(200);
+    }
+    
+    Serial.println("已进入配网模式,请连接设备热点进行配置");
+}
+
+// 扫描WiFi网络并缓存结果
+void WiFiConfigurator::scanWiFiNetworks() {
+    Serial.println("开始扫描WiFi网络...");
+    int n = WiFi.scanNetworks(false, false, false, 100);
+    Serial.printf("发现 %d 个WiFi网络\n", n);
+    
+    _wifiScanResults = "";
+    for (int i = 0; i < n; i++) {
+        int signalStrength = map(WiFi.RSSI(i), -100, -50, 0, 4);
+        String signalBar = "";
+        for(int j = 0; j < signalStrength; j++) signalBar += "●";
+        for(int j = signalStrength; j < 4; j++) signalBar += "○";
+        
+        _wifiScanResults += "<option value=\"" + WiFi.SSID(i) + "\">" + 
+                   WiFi.SSID(i) + " <span class=\"signal\">" + signalBar + "</span></option>";
+                   
+        // 串口打印扫描结果
+        Serial.printf("网络: %s, 信号强度: %d, 加密方式: %d\n", 
+                      WiFi.SSID(i).c_str(), WiFi.RSSI(i), WiFi.encryptionType(i));
+    }
+    WiFi.scanDelete();
+}
+
+void WiFiConfigurator::startAPMode() {
+    Serial.println("启动AP模式...");
+    
+    // 重置连接状态
+    _connectionInProgress = false;
+    _connectionCompleted = false;
+    _connectionSuccess = false;
+    _connectionStatus = "";
+    _connectionProgress = 0;
+    
+    // 配置为AP模式
+    WiFi.mode(WIFI_AP);
+    delay(200); // 增加延迟确保模式切换完成
+    bool configSuccess = WiFi.softAPConfig(_apIP, _gateway, _subnet);
+    Serial.print("AP配置: ");
+    Serial.println(configSuccess ? "成功" : "失败");
+    
+    // 启动AP
+    bool apStarted = WiFi.softAP(_ap_ssid, _ap_password, 1, false, 4);
+    if (!apStarted) {
+        Serial.println("AP启动失败,尝试默认配置...");
+        apStarted = WiFi.softAP(_ap_ssid, _ap_password);
+    }
+    Serial.print("AP启动: ");
+    Serial.println(apStarted ? "成功" : "失败");
+    
+    // 配置DNS
+    _dnsServer.setErrorReplyCode(DNSReplyCode::NoError);
+    bool dnsStarted = _dnsServer.start(_dnsPort, "*", _apIP);
+    Serial.print("DNS服务器启动: ");
+    Serial.println(dnsStarted ? "成功" : "失败");
+    
+    // 苹果Captive Portal支持
+    _server.on("/hotspot-detect.html", [](){
+        _instance->_server.send(200, "text/html", _instance->_captive_html);
+    });
+    _server.on("/library/test/success.html", [](){
+        _instance->_server.send(200, "text/html", _instance->_captive_html);
+    });
+    
+    // 主要路由
+    _server.on("/", handleRoot);
+    _server.on("/refresh", handleRefresh);
+    _server.on("/connect", HTTP_POST, handleConnect);
+    _server.on("/status", handleStatus);
+    _server.on("/success", handleSuccess);
+    _server.on("/error", handleError);
+    _server.onNotFound(handleNotFound);
+    
+    _server.begin();
+    Serial.print("AP IP地址: ");
+    Serial.println(WiFi.softAPIP());
+    
+    // 重置AP超时计时器
+    _apTimeout = millis();
+}
+
+void WiFiConfigurator::connectToWiFi(const char* ssid, const char* password) {
+    _currentSsid = ssid;
+    
+    _connectionInProgress = true;
+    _connectionCompleted = false;
+    _connectionSuccess = false;
+    _connectionProgress = 0;
+    
+    Serial.println("\n===== [DEBUG] 开始连接WiFi流程 =====\n");
+    
+    // 完全断开所有WiFi连接
+    Serial.println("[DEBUG] 1. 正在清除之前的WiFi连接信息...");
+    WiFi.disconnect(true); 
+    delay(500); // 增加一点延迟,确保清除操作完成
+    
+    // 配置为AP+STA模式
+    Serial.println("[DEBUG] 2. 设置WiFi模式为 AP+STA...");
+    WiFi.mode(WIFI_AP_STA);
+    delay(200);
+    
+    // 打印将要使用的SSID和密码
+    Serial.print("[DEBUG] 3. 准备连接到网络...");
+    Serial.print(" SSID: '");
+    Serial.print(ssid);
+    Serial.print("' ");
+    Serial.print(" Password: '");
+    Serial.print(password);
+    Serial.println("'");
+    
+    // 开始连接
+    Serial.println("[DEBUG] 4. 调用 WiFi.begin()...");
+    WiFi.begin(ssid, password);
+    
+    // 等待连接结果(最多20秒)
+    Serial.println("[DEBUG] 5. 开始等待连接结果...");
+    unsigned long startAttemptTime = millis();
+    while (WiFi.status() != WL_CONNECTED && millis() - startAttemptTime < 20000) {
+        
+        // 打印当前状态码
+        Serial.printf("[DEBUG] 状态码: %d (等待连接中...)\n", WiFi.status());
+
+        int elapsedSeconds = (millis() - startAttemptTime) / 1000;
+        _connectionProgress = 20 + (elapsedSeconds * 70) / 20;
+        if (_connectionProgress > 90) _connectionProgress = 90;
+        
+        _dnsServer.processNextRequest();
+        _server.handleClient();
+        
+        delay(1000);
+    }
+    
+    // 检查最终连接结果
+    Serial.println("\n[DEBUG] 6. 连接尝试结束,检查最终结果...");
+    if (WiFi.status() == WL_CONNECTED) {
+        _connectionSuccess = true;
+        String ipAddress = WiFi.localIP().toString();
+        _connectionStatus = "成功连接到 " + _currentSsid + ",IP地址: " + ipAddress;
+        Serial.println("[DEBUG] => 连接成功!");
+        Serial.printf("===== [DEBUG] 成功连接到 '%s',IP: %s =====\n", ssid, ipAddress.c_str());
+        
+        _preferences.putString("ssid", ssid);
+        _preferences.putString("password", password);
+        
+        _connectionCompleted = true;
+        _connectionInProgress = false;
+        _connectionProgress = 100;  // 标记进度为100%
+        
+        // 不立即关闭AP,而是记录成功时间,稍后在loop中关闭
+        _successNotificationTime = millis();
+        Serial.println("连接成功,保持AP模式5秒以确保客户端收到通知");
+        
+    } else {
+        _connectionSuccess = false;
+        int errorCode = WiFi.status();
+        Serial.println("[DEBUG] => 连接失败!");
+        Serial.printf("[DEBUG] 最终状态码: %d\n", errorCode);
+
+        String errorMsg;
+        switch(errorCode) {
+            case WL_CONNECT_FAILED: errorMsg = "连接失败(密码错误或网络拒绝接入)"; break;
+            case WL_CONNECTION_LOST: errorMsg = "连接丢失"; break;
+            case WL_DISCONNECTED: errorMsg = "已断开连接"; break;
+            default: errorMsg = "未知错误(代码: " + String(errorCode) + ")";
+        }
+        
+        _connectionStatus = "连接失败: " + errorMsg;
+        Serial.println(_connectionStatus);
+        Serial.printf("===== [DEBUG] 连接 '%s' 失败 =====\n", ssid);
+        
+        _connectionCompleted = true;
+        _connectionInProgress = false;
+        _apTimeout = millis();
+        
+        if (!(WiFi.getMode() & WIFI_AP)) {
+            Serial.println("[DEBUG] 意外退出AP模式,重新启动...");
+            WiFi.mode(WIFI_AP_STA);
+            delay(200);
+            _server.begin();
+            _dnsServer.start(_dnsPort, "*", _apIP);
+        }
+        _server.handleClient();
+    }
+}
+
+// HTTP请求处理函数实现
+void WiFiConfigurator::handleRoot() {
+    // 每次访问主页都重置AP超时
+    _instance->_apTimeout = millis();
+    
+    String html = _instance->_index_html;
+    String messageBox = "";
+    
+    // 显示最后一次连接结果
+    if (_instance->_connectionCompleted) {
+        if (_instance->_connectionSuccess) {
+            messageBox = "<div class='message success'>" + _instance->_connectionStatus + "</div>";
+        } else if (_instance->_connectionStatus.length() > 0) {
+            messageBox = "<div class='message error'>" + _instance->_connectionStatus + "</div>";
+        }
+        // 重置连接完成状态,只显示一次
+        _instance->_connectionCompleted = false;
+    }
+    
+    // 使用缓存的WiFi扫描结果
+    html.replace("%MESSAGE_BOX%", messageBox);
+    html.replace("%WIFI_LIST%", _instance->_wifiScanResults);
+    html.replace("%AP_SSID%", _instance->_ap_ssid);
+    
+    // 发送响应
+    _instance->_server.send(200, "text/html; charset=UTF-8", html);
+}
+
+void WiFiConfigurator::handleRefresh() {
+    Serial.println("用户请求刷新WiFi列表");
+    _instance->scanWiFiNetworks();  // 重新扫描WiFi
+    
+    // 重置AP超时
+    _instance->_apTimeout = millis();
+    
+    handleRoot();        // 刷新后显示主页
+}
+
+void WiFiConfigurator::handleConnect() {
+    // 检查是否有必要的参数
+    if (!_instance->_server.hasArg("ssid") || !_instance->_server.hasArg("password")) {
+        _instance->_connectionStatus = "参数错误:请填写WiFi名称和密码";
+        Serial.println(_instance->_connectionStatus);
+        _instance->_server.sendHeader("Location", "/error");
+        _instance->_server.send(302, "text/plain", "");
+        return;
+    }
+    
+    String ssid = _instance->_server.arg("ssid");
+    String password = _instance->_server.arg("password");
+    Serial.printf("收到连接请求: SSID=%s, 密码长度=%d\n", ssid.c_str(), password.length());
+    
+    // 重置AP超时计时器
+    _instance->_apTimeout = millis();
+    Serial.println("用户点击连接,重置AP超时计时器");
+    
+    // 立即响应,告知客户端开始连接
+    _instance->_server.sendHeader("Connection", "close");
+    _instance->_server.send(200, "text/plain", "连接开始");
+    
+    // 在后台进行连接操作
+    _instance->connectToWiFi(ssid.c_str(), password.c_str());
+}
+
+void WiFiConfigurator::handleStatus() {
+    // 每次状态查询都重置AP超时
+    _instance->_apTimeout = millis();
+    
+    // 构建JSON响应
+    String json = "{";
+    json += "\"connecting\":" + String(_instance->_connectionInProgress ? "true" : "false") + ",";
+    json += "\"success\":" + String(_instance->_connectionSuccess ? "true" : "false") + ",";
+    json += "\"progress\":" + String(_instance->_connectionProgress) + ",";
+    json += "\"status\":\"" + _instance->_connectionStatus + "\"";
+    json += "}";
+    
+    _instance->_server.send(200, "application/json", json);
+}
+
+void WiFiConfigurator::handleSuccess() {
+    String html = _instance->_success_html;
+    html.replace("%SSID%", _instance->_currentSsid);
+    html.replace("%IP_ADDRESS%", WiFi.localIP().toString());
+    _instance->_server.send(200, "text/html; charset=UTF-8", html);
+}
+
+void WiFiConfigurator::handleError() {
+    String html = _instance->_error_html;
+    html.replace("%SSID%", _instance->_currentSsid);
+    
+    // 生成更具体的错误信息
+    String errorMsg = "请检查:";
+    errorMsg += "<br>- WiFi密码是否正确";
+    errorMsg += "<br>- 路由器信号是否稳定";
+    errorMsg += "<br>- 网络是否允许新设备接入";
+    
+    html.replace("%ERROR_MESSAGE%", errorMsg);
+    _instance->_server.send(200, "text/html; charset=UTF-8", html);
+}
+
+void WiFiConfigurator::handleNotFound() {
+    _instance->_server.sendHeader("Location", "/");
+    _instance->_server.send(302, "text/plain", "");
+}

+ 104 - 0
sketch_sep25a/WiFiConfigurator.h

@@ -0,0 +1,104 @@
+#ifndef WiFiConfigurator_h
+#define WiFiConfigurator_h
+
+#include <WiFi.h>
+#include <WebServer.h>
+#include <Preferences.h>
+#include <DNSServer.h>
+
+class WiFiConfigurator {
+private:
+    // 引脚定义
+    const int _slowLedPin;
+    const int _fastLedPin;
+    const int _configButtonPin;
+    
+    // 网络配置
+    const char* _ap_ssid;
+    const char* _ap_password;
+    const byte _dnsPort = 53;
+    IPAddress _apIP;
+    IPAddress _gateway;
+    IPAddress _subnet;
+    
+    // 网络对象
+    WebServer _server;
+    DNSServer _dnsServer;
+    Preferences _preferences;
+    
+    // 全局状态变量
+    String _connectionStatus = "";
+    int _connectionProgress = 0;
+    bool _connectionInProgress = false;
+    bool _connectionCompleted = false;
+    bool _connectionSuccess = false;
+    String _wifiScanResults = "";
+    String _currentSsid = "";
+    unsigned long _apTimeout = 0;
+    const unsigned long _apTimeoutDuration = 120000;  // 2分钟
+    unsigned long _successNotificationTime = 0;
+    const unsigned long _successNotificationDelay = 5000;  // 5秒
+    
+    // LED控制变量
+    unsigned long _previousSlowMillis = 0;
+    unsigned long _previousFastMillis = 0;
+    bool _slowLedState = LOW;
+    bool _fastLedState = LOW;
+    const unsigned long _slowInterval = 1000;
+    const unsigned long _fastInterval = 200;
+    
+    // 按键控制变量
+    unsigned long _buttonPressStartTime = 0;
+    bool _buttonPressed = false;
+    const unsigned long _configButtonPressDuration = 5000;  // 5秒
+    
+    // HTML页面定义
+    const char* _captive_html;
+    const char* _index_html;
+    const char* _success_html;
+    const char* _error_html;
+    
+    // 内部方法
+    void handleConfigButton();
+    void enterConfigMode();
+    void scanWiFiNetworks();
+    void startAPMode();
+    void connectToWiFi(const char* ssid, const char* password);
+    
+    // HTTP请求处理函数
+    static void handleRoot();
+    static void handleRefresh();
+    static void handleConnect();
+    static void handleStatus();
+    static void handleSuccess();
+    static void handleError();
+    static void handleNotFound();
+    
+    // 静态指针用于回调函数访问实例
+    static WiFiConfigurator* _instance;
+
+public:
+    // 构造函数
+    WiFiConfigurator(int slowLedPin, int fastLedPin, int configButtonPin,
+                    const char* ap_ssid, const char* ap_password,
+                    IPAddress apIP = IPAddress(192, 168, 4, 1),
+                    IPAddress gateway = IPAddress(192, 168, 4, 1),
+                    IPAddress subnet = IPAddress(255, 255, 255, 0));
+    
+    // 初始化函数
+    void begin();
+    
+    // 主循环处理函数
+    void loop();
+    
+    // 检查是否已连接WiFi
+    bool isConnected();
+    
+    // 获取当前连接的SSID
+    String getCurrentSSID();
+    
+    // 获取IP地址
+    String getIPAddress();
+};
+
+#endif

+ 81 - 0
sketch_sep25a/sketch_sep25a.ino

@@ -0,0 +1,81 @@
+#include "WiFiConfigurator.h"
+#include "NtcThermistor.h"
+#include "RelayController.h"
+#include <WiFi.h>
+
+// 引脚定义
+const int slowLedPin = 19;    // 慢闪LED
+const int fastLedPin = 20;    // 快闪LED
+const int configButtonPin = 21; // 配置按钮
+
+// NTC温度传感器引脚
+const int ntcPin1 = 2;       // GPIO2
+const int ntcPin2 = 3;       // GPIO3
+const int ntcPin3 = 6;       // GPIO6
+
+// 继电器引脚
+const int relayPin22 = 22;   //继电器使用GPIO22
+const int relayPin23 = 23;   //继电器使用GPIO23
+const int relayPin4 = 4;     // 定时继电器使用GPIO4
+
+// 热点配置
+const char* ap_ssid = "ESP32-C6-配置";
+const char* ap_password = "12345678";
+
+// 创建实例
+WiFiConfigurator wifiConfig(slowLedPin, fastLedPin, configButtonPin, ap_ssid, ap_password);
+NtcThermistor ntcSensor(ntcPin1, ntcPin2, ntcPin3);
+RelayController relays(relayPin22, relayPin23, relayPin4);
+
+// 更新间隔
+unsigned long lastUpdate = 0;
+const unsigned long updateInterval = 1000;  // 1秒更新一次
+
+void setup() {
+    Serial.begin(115200);
+    Serial.println("系统启动中...");
+    
+    // 初始化组件
+    wifiConfig.begin();
+    relays.begin();  // 初始化继电器为高阻态,记录开机时间
+    
+    Serial.println("系统启动完成(支持无WiFi运行)");
+}
+
+void loop() {
+    // 处理WiFi配置(即使WiFi未连接也继续运行)
+    wifiConfig.loop();
+    
+    // 定期更新
+    unsigned long currentMillis = millis();
+    if (currentMillis - lastUpdate >= updateInterval) {
+        lastUpdate = currentMillis;
+        
+        // 尝试同步时间(仅在WiFi可用时有效,失败不影响其他功能)
+        relays.updateNtpTime();
+        
+        // 更新温度
+        ntcSensor.updateTemperatures();
+        
+        // 打印状态信息
+        Serial.print("运行时间: ");
+        Serial.print((currentMillis - relays.getPowerOnTime())/1000);
+        Serial.print("秒, 当前小时: ");
+        Serial.print(relays.getCurrentHour());
+        Serial.print(", 温度 - GPIO2: ");
+        Serial.print(ntcSensor.getTemperature1String());
+        Serial.print(", GPIO3: ");
+        Serial.print(ntcSensor.getTemperature2String());
+        Serial.print(", GPIO6: ");
+        Serial.println(ntcSensor.getTemperature3String());
+        
+        // 获取温度值并控制继电器
+        float tempGpio2 = ntcSensor.getTemperature1();
+        float tempGpio3 = ntcSensor.getTemperature2();
+        float tempGpio6 = ntcSensor.getTemperature3();
+        
+        relays.controlByTempConditions(tempGpio2, tempGpio3, tempGpio6);
+        relays.handleTimedTasks(tempGpio6);
+    }
+}
+