跳到主要内容

AP配网

概述

AP 配网(Access Point Provisioning)是一种常用的物联网设备配网方式。设备自身开启一个 Wi-Fi 热点,用户使用手机或电脑连接该热点,然后通过内置的 Web 配置页面输入目标路由器的 SSID 和密码,设备收到后切换至 STA 模式连接目标网络。

本实现基于 nanoFramework,专为 YF3300-ESP32S3 等嵌入式设备设计,具有以下特点:

  • 完整的状态机:管理配网过程的各个阶段(空闲、AP 配网中、连接中、成功、失败)。
  • Web 服务器集成:提供友好的配置页面,支持表单提交和结果反馈。
  • WiFi 自动重连:STA 模式下支持断线重连,采用防抖机制和指数退避策略。
  • 网络可达性检测:通过 DNS 解析验证网络是否真正连通,避免假连接。
  • 凭证持久化:支持将配网成功的凭证保存至 Flash,设备重启后可自动连接。
  • STA / AP 互斥:确保两种模式切换时硬件资源正确释放。

架构说明

AP 配网功能由以下核心类协作完成:

类名职责
APConfigManager.cs配网流程总控,管理状态、超时、事件,协调 Web 服务器和 Wi-Fi 管理器。
WifiManager.cs底层 Wi-Fi 操作封装,支持 STA 连接、AP 热点、重连、凭证存储等。
WebServerController.csHTTP 请求处理,提供配网页面、接收表单、验证并触发实际配网。
Program.cs示例入口,展示如何初始化并使用 AP 配网功能。

工作流程

  1. 调用 APConfigManager.StartAPConfig(),设备启动 AP 热点(SSID 固定为 YF3300_ESP32S3,密码 yf123456)。
  2. 启动 Web 服务器,监听 http://192.168.4.1
  3. 用户连接热点并访问配置页面,填写目标 WiFi 信息并提交。
  4. Web 控制器收到请求后,调用 WifiManager.ConnectSTA() 尝试连接。
  5. 连接成功后,保存凭证(可选)、验证网络可达性、触发 OnConfigCompleted 事件。
  6. 配网完成,设备可转入正常工作模式。若失败,自动重新开启 AP 模式。

代码实现

APConfigManager.cs

using Microsoft.Extensions.Logging;
using nanoFramework.Logging;
using nanoFramework.WebServer;
using System;
using System.Net;
using System.Threading;
using WIFI_StaApTest;


namespace APConfig
{
// AP配网状态枚举
public enum APConfigState
{
Idle, // 空闲
APConfiguring, // 正在配网
Connecting, // 正在连接
Configured, // 配网成功
Failed // 配网失败
}

// 配网完成事件参数
public class APConfigResultEventArgs : EventArgs
{
public bool Success { get; set; }
public string SSID { get; set; }
public string Message { get; set; }
}

internal class APConfigManager
{
// 日志记录器
private ILogger _logger;

// WIFI管理器(外部注入,不内部创建)
private WifiManager _wifiManager;

// Web服务器实例
private WebServer _webServer;

// 当前配网状态
public APConfigState CurrentState { get; private set; } = APConfigState.Idle;

// 配网完成事件(通知外部配网结果)
public event APConfigResultEventHandler OnConfigCompleted;
public delegate void APConfigResultEventHandler(object sender, APConfigResultEventArgs e);

// AP配网参数
private const string AP_SSID = "YF3300_ESP32S3"; // 热点名称
private const string AP_PASSWORD = "yf123456"; // 热点密码(WPA2需>=8)
private const string AP_IP = "192.168.4.1"; // AP网关IP

// Web服务器参数
private const int WEB_PORT = 80; // Web服务器端口
private const int CONFIG_TIMEOUT = 600000; // 配网超时时间(10分钟)

// 配网任务取消令牌
private CancellationTokenSource _configCts;

// 配网超时定时器
private Timer _timeoutTimer;

// 当前配网的SSID和密码(用于传递给WebServerController)
public static string PendingSSID { get; set; }
public static string PendingPassword { get; set; }
public static bool ConfigSubmitted { get; set; } = false;

// WifiManager实例(供WebServerController访问)
private static WifiManager _staticWifiManager;

// 获取WifiManager实例
public static WifiManager GetWifiManager()
{
return _staticWifiManager;
}

// 触发配网完成事件(由外部调用)
public void RaiseConfigCompleted(bool success, string ssid, string message)
{
if (OnConfigCompleted != null)
{
OnConfigCompleted(this, new APConfigResultEventArgs()
{
Success = success,
SSID = ssid,
Message = message
});
}
}

// 构造函数:注入WIFI管理器实例
public APConfigManager(WifiManager wifiManager)
{
if (wifiManager == null)
{
throw new ArgumentNullException(nameof(wifiManager));
}
_wifiManager = wifiManager;
_staticWifiManager = wifiManager; // 保存到静态字段
_logger = LogDispatcher.LoggerFactory.CreateLogger("APConfigManager");
}

#region 公共API

// 检查是否已有保存的WIFI配置
public bool HasSavedWifiConfig()
{
return _wifiManager.HasSavedConfig();
}

// 尝试使用已保存的配置连接WIFI
public bool TryConnectSavedWifiConfig()
{
if (_wifiManager.LoadCredentials(out string ssid, out string password))
{
_logger.LogInformation($"[APConfigManager] 尝试使用已保存的WIFI配置进行连接: {ssid}");
return _wifiManager.ConnectSTA(ssid, password, enableReconnect: true);
}
return false;
}

// 开始配网
public bool StartAPConfig()
{
if (CurrentState != APConfigState.Idle)
{
_logger.LogWarning("[APConfigManager] 当前AP状态不是空闲状态,无法开始配网");
return false;
}

_logger.LogInformation("[APConfigManager] 开始配网");

// 步骤一:启动AP热点
bool apStarted = _wifiManager.StartAP(AP_SSID, AP_PASSWORD, AP_IP);
if (!apStarted)
{
_logger.LogError("[APConfigManager] 启动AP热点失败");
CurrentState = APConfigState.Failed;
return false;
}

CurrentState = APConfigState.APConfiguring;
_logger.LogInformation($"[APConfigManager] AP热点已启动,热点名称:{AP_SSID}, IP:{AP_IP}");

// 步骤二:启动Web服务器
// 【重要】等待网络接口完全初始化后再启动Web服务器
Thread.Sleep(1000);
try
{
// 重置配网提交标志
ConfigSubmitted = false;

// 创建Web服务器实例
// 【重要】在AP模式下,必须显式指定绑定的IP地址,否则会绑定到错误的网络接口
_webServer = new WebServer(WEB_PORT, HttpProtocol.Http, IPAddress.Parse(AP_IP), new Type[] { typeof(WebServerController) });
_webServer.Start();
_logger.LogInformation($"[APConfigManager] Web服务器已启动,端口:{WEB_PORT},绑定IP:{AP_IP}");
}
catch (Exception e)
{
_logger.LogError($"[APConfigManager] 启动Web服务器失败: {e.Message}");
StopAPConfig();
return false;
}

// 步骤三:启动配网超时定时器
_configCts = new CancellationTokenSource();
_timeoutTimer = new Timer(OnTimeoutCallback, null, CONFIG_TIMEOUT, Timeout.Infinite);

_logger.LogInformation($"[APConfigManager] 请连接WIFI {AP_SSID},并访问 http://{AP_IP}");
_logger.LogInformation($"[APConfigManager] 配网超时定时器已启动,超时时间:{CONFIG_TIMEOUT / 1000} 秒");

return true;
}

// 停止AP配网(清理资源)
public void StopAPConfig()
{
_logger.LogInformation("[APConfigManager] 停止AP配网");

// 停止超时定时器
if (_timeoutTimer != null)
{
_timeoutTimer.Dispose();
_timeoutTimer = null;
}

// 取消配网任务
if (_configCts != null)
{
_configCts.Cancel();
_configCts.Dispose();
_configCts = null;
}

// 关闭Web服务器
if (_webServer != null)
{
_webServer.Stop();
_webServer.Dispose();
_webServer = null;
}

// 关闭AP热点
_wifiManager.StopAP();

CurrentState = APConfigState.Idle;
_logger.LogInformation("[APConfigManager] AP配网已停止");
}

// 处理配网提交(由WebServerController调用)
public void HandleConfigSubmit(string ssid, string password)
{
_logger.LogInformation($"[APConfigManager] 收到配网请求:{ssid}");

// 停止超时定时器
if (_timeoutTimer != null)
{
_timeoutTimer.Change(Timeout.Infinite, Timeout.Infinite);
}

// 停止Web服务器
if (_webServer != null)
{
_webServer.Stop();
}

CurrentState = APConfigState.Connecting;

// 尝试连接WIFI
bool connected = _wifiManager.ConnectSTA(ssid, password, enableReconnect: true);

if (connected)
{
// ========== 验证网络可达性 ==========
// 检查WiFi是否真的可以上网(通过DNS解析验证)
_logger.LogInformation("[APConfigManager] 正在验证网络可达性...");
bool networkReachable = _wifiManager.IsNetworkReachable();

if (!networkReachable)
{
_logger.LogWarning("[APConfigManager] WiFi已连接但网络不可达,请检查路由器是否连接外网");
// 仍然保存凭证,但提示用户网络可能不可用
}
else
{
_logger.LogInformation("[APConfigManager] 网络可达性验证成功,可以正常上网");
}

// ========== 保存WIFI凭证到Flash ==========
// 【测试时可注释以下2行,关闭持久化存储功能】
_wifiManager.SaveCredentials(ssid, password);
_logger.LogInformation($"[APConfigManager] 已保存WIFI凭证:{ssid}");

CurrentState = APConfigState.Configured;
_logger.LogInformation($"[APConfigManager] 配网成功!SSID:{ssid}");

// 触发配网完成事件
if (OnConfigCompleted != null)
{
OnConfigCompleted(this, new APConfigResultEventArgs()
{
Success = true,
SSID = ssid,
Message = networkReachable ? "配网成功!网络可达" : "配网成功!但网络不可达,请检查路由器"
});
}
}
else
{
CurrentState = APConfigState.Failed;
_logger.LogError($"[APConfigManager] 配网失败!SSID:{ssid}");

// 触发配网失败事件
if (OnConfigCompleted != null)
{
OnConfigCompleted(this, new APConfigResultEventArgs()
{
Success = false,
SSID = ssid,
Message = "配网失败!"
});
}

// 重新启动AP配网
Thread.Sleep(2000);
CurrentState = APConfigState.Idle;
StartAPConfig();
}
}

#endregion

#region 私有方法

// 超时回调
private void OnTimeoutCallback(object state)
{
_logger.LogWarning("[APConfigManager] 配网超时!");

// 触发配网超时事件
if (OnConfigCompleted != null)
{
OnConfigCompleted(this, new APConfigResultEventArgs()
{
Success = false,
SSID = null,
Message = "配网超时!"
});
}

// 停止AP配网
StopAPConfig();
}

#endregion
}
}

WebServer.cs

using nanoFramework.WebServer;
using System;
using System.IO;
using System.Text;

namespace APConfig
{
// Web服务器控制器
// 处理AP配网的HTTP请求
// 【重要】必须是public类,否则WebServer无法实例化
public class WebServerController
{

// ========== 配网页面路由 ==========

// GET / - 配网主页
[Route("")]
[Method("GET")]
public void GetIndex(WebServerEventArgs e)
{
// 直接输出HTML,不进行任何检查
string html = GetConfigPageHtml();
e.Context.Response.ContentType = "text/html";
WebServer.OutputAsStream(e.Context.Response, html);
}

// POST /config - 处理配网表单提交
[Route("config")]
[Method("POST")]
public void PostConfig(WebServerEventArgs e)
{
try
{
// 解析表单数据
string body = GetRequestBody(e);

// 解析SSID和密码
string ssid = ParseFormField(body, "ssid");
string password = ParseFormField(body, "password");

if (string.IsNullOrEmpty(ssid) || string.IsNullOrEmpty(password))
{
string errorHtml = GetErrorPageHtml("SSID和密码不能为空!");
e.Context.Response.ContentType = "text/html";
WebServer.OutputAsStream(e.Context.Response, errorHtml);
return;
}

// ========== 直接处理配网 ==========
// 获取WifiManager实例
var wifiManager = APConfigManager.GetWifiManager();

// 尝试连接WiFi
bool connected = wifiManager.ConnectSTA(ssid, password, enableReconnect: false);

if (!connected)
{
string errorHtml = GetErrorPageHtml("WiFi连接失败,请检查SSID和密码是否正确");
e.Context.Response.ContentType = "text/html";
WebServer.OutputAsStream(e.Context.Response, errorHtml);
return;
}

// 验证网络可达性
bool networkReachable = wifiManager.IsNetworkReachable();

if (!networkReachable)
{
// wifiManager.SaveCredentials(ssid, password); // 注释掉:不永久保存WiFi配置
string warningHtml = GetWarningPageHtml(ssid, "WiFi已连接但网络不可达,请检查路由器是否连接外网");
e.Context.Response.ContentType = "text/html";
WebServer.OutputAsStream(e.Context.Response, warningHtml);

// 通知APConfigManager配网完成
APConfigManager.PendingSSID = ssid;
APConfigManager.PendingPassword = password;
APConfigManager.ConfigSubmitted = true;
return;
}

// 配网完全成功
// wifiManager.SaveCredentials(ssid, password); // 注释掉:不永久保存WiFi配置
string successHtml = GetSuccessPageHtml(ssid);
e.Context.Response.ContentType = "text/html";
WebServer.OutputAsStream(e.Context.Response, successHtml);

// 通知APConfigManager配网完成
APConfigManager.PendingSSID = ssid;
APConfigManager.PendingPassword = password;
APConfigManager.ConfigSubmitted = true;
}
catch (Exception ex)
{
string errorHtml = GetErrorPageHtml("配网失败:" + ex.Message);
e.Context.Response.ContentType = "text/html";
WebServer.OutputAsStream(e.Context.Response, errorHtml);
}
}

// GET /scan - 扫描WiFi网络(AP模式下不支持)
[Route("scan")]
[Method("GET")]
public void GetScan(WebServerEventArgs e)
{
// AP模式下无法扫描WiFi网络
string jsonResult = "{\"success\":false,\"message\":\"AP模式下不支持WiFi扫描,请手动输入WiFi名称\"}";
e.Context.Response.ContentType = "application/json";
WebServer.OutputAsStream(e.Context.Response, jsonResult);
}

// ========== 辅助方法 ==========

// 获取请求体
private string GetRequestBody(WebServerEventArgs e)
{
try
{
var request = e.Context.Request;

// 获取请求体长度
long contentLength = request.ContentLength64;
if (contentLength == 0)
{
return string.Empty;
}

// 读取请求体
byte[] buffer = new byte[(int)contentLength];
request.InputStream.Read(buffer, 0, (int)contentLength);
return Encoding.UTF8.GetString(buffer, 0, (int)contentLength);
}
catch (Exception ex)
{
return string.Empty;
}
}

// 解析表单字段
private static string ParseFormField(string body, string fieldName)
{
try
{
// 格式: ssid=MyWiFi&password=12345678
string searchPattern = fieldName + "=";
int startIndex = body.IndexOf(searchPattern);
if (startIndex < 0)
{
return string.Empty;
}

startIndex += searchPattern.Length;
int endIndex = body.IndexOf('&', startIndex);
if (endIndex < 0)
{
endIndex = body.Length;
}

string value = body.Substring(startIndex, endIndex - startIndex);

// URL解码
return UrlDecode(value);
}
catch
{
return string.Empty;
}
}

// URL解码
private static string UrlDecode(string value)
{
// 处理 %XX 格式的编码和 + 号
StringBuilder result = new StringBuilder();
for (int i = 0; i < value.Length; i++)
{
if (value[i] == '+')
{
// + 号转换为空格
result.Append(' ');
}
else if (value[i] == '%' && i + 2 < value.Length)
{
// 解析十六进制值
string hex = value.Substring(i + 1, 2);
byte b = HexToByte(hex);
if (b > 0)
{
result.Append((char)b);
i += 2;
}
else
{
result.Append(value[i]);
}
}
else
{
result.Append(value[i]);
}
}
return result.ToString();
}

// 十六进制字符串转字节
private static byte HexToByte(string hex)
{
try
{
byte high = HexCharToValue(hex[0]);
byte low = HexCharToValue(hex[1]);
return (byte)((high << 4) | low);
}
catch
{
return 0;
}
}

// 十六进制字符转数值
private static byte HexCharToValue(char c)
{
if (c >= '0' && c <= '9')
{
return (byte)(c - '0');
}
else if (c >= 'A' && c <= 'F')
{
return (byte)(c - 'A' + 10);
}
else if (c >= 'a' && c <= 'f')
{
return (byte)(c - 'a' + 10);
}
return 0;
}

// ========== HTML页面模板 ==========

// 配网主页HTML
private static string GetConfigPageHtml()
{
string html = "<!DOCTYPE html>";
html += "<html><head>";
html += "<meta charset=\"UTF-8\">";
html += "<meta name=\"viewport\" content=\"width=device-width, initial-scale=1.0\">";
html += "<title>YF3300-ESP32S3 WiFi配网</title>";
html += "<style>";
html += "body{font-family:Arial;margin:20px;background:#f0f0f0;}";
html += ".container{max-width:400px;margin:0 auto;background:white;padding:20px;border-radius:10px;}";
html += "h1{color:#333;text-align:center;}";
html += ".form-group{margin-bottom:15px;}";
html += "label{display:block;margin-bottom:5px;color:#666;}";
html += "input{width:100%;padding:10px;border:1px solid #ddd;border-radius:5px;box-sizing:border-box;}";
html += "button{width:100%;padding:12px;background:#007bff;color:white;border:none;border-radius:5px;font-size:16px;cursor:pointer;}";
html += ".footer{text-align:center;margin-top:20px;color:#999;font-size:12px;}";
html += ".tip{background:#fff3cd;border:1px solid #ffc107;padding:10px;border-radius:5px;margin-bottom:15px;font-size:14px;}";
html += "</style></head><body>";
html += "<div class=\"container\">";
html += "<h1>YF3300-ESP32S3 WiFi配网</h1>";
html += "<div class=\"tip\">请输入您要连接的WiFi名称和密码</div>";
html += "<form action=\"/config\" method=\"POST\">";
html += "<div class=\"form-group\">";
html += "<label>WiFi名称 (SSID)</label>";
html += "<input type=\"text\" name=\"ssid\" placeholder=\"请输入WiFi名称\" required>";
html += "</div>";
html += "<div class=\"form-group\">";
html += "<label>WiFi密码</label>";
html += "<input type=\"password\" name=\"password\" placeholder=\"请输入WiFi密码\" required>";
html += "</div>";
html += "<button type=\"submit\">开始配网</button>";
html += "</form>";
html += "<div class=\"footer\">YF3300-ESP32S3 v1.0</div>";
html += "</div></body></html>";
return html;
}

// 成功页面HTML
private static string GetSuccessPageHtml(string ssid)
{
string html = "<!DOCTYPE html>";
html += "<html><head>";
html += "<meta charset=\"UTF-8\">";
html += "<meta name=\"viewport\" content=\"width=device-width, initial-scale=1.0\">";
html += "<title>配网成功</title>";
html += "<style>";
html += "body{font-family:Arial;margin:20px;background:#f0f0f0;}";
html += ".container{max-width:400px;margin:0 auto;background:white;padding:20px;border-radius:10px;text-align:center;}";
html += "h1{color:#28a745;}";
html += ".info{margin:20px 0;padding:15px;background:#f8f9fa;border-radius:5px;}";
html += "</style></head><body>";
html += "<div class=\"container\">";
html += "<h1>配网成功!</h1>";
html += "<div class=\"info\">";
html += "<p>正在连接WiFi: <strong>" + ssid + "</strong></p>";
html += "</div>";
html += "<p>设备将自动连接WiFi...</p>";
html += "<p>请重新连接您的WiFi网络</p>";
html += "</div></body></html>";
return html;
}

// 错误页面HTML
private static string GetErrorPageHtml(string message)
{
string html = "<!DOCTYPE html>";
html += "<html><head>";
html += "<meta charset=\"UTF-8\">";
html += "<meta name=\"viewport\" content=\"width=device-width, initial-scale=1.0\">";
html += "<title>配网失败</title>";
html += "<style>";
html += "body{font-family:Arial;margin:20px;background:#f0f0f0;}";
html += ".container{max-width:400px;margin:0 auto;background:white;padding:20px;border-radius:10px;text-align:center;}";
html += "h1{color:#dc3545;}";
html += ".error{margin:20px 0;padding:15px;background:#f8d7da;border-radius:5px;color:#721c24;}";
html += "a{display:inline-block;margin-top:20px;padding:10px 20px;background:#007bff;color:white;text-decoration:none;border-radius:5px;}";
html += "</style></head><body>";
html += "<div class=\"container\">";
html += "<h1>配网失败</h1>";
html += "<div class=\"error\">";
html += "<p>" + message + "</p>";
html += "</div>";
html += "<a href=\"/\">重新配网</a>";
html += "</div></body></html>";
return html;
}

// 警告页面HTML(WiFi连接成功但网络不可达)
private static string GetWarningPageHtml(string ssid, string message)
{
string html = "<!DOCTYPE html>";
html += "<html><head>";
html += "<meta charset=\"UTF-8\">";
html += "<meta name=\"viewport\" content=\"width=device-width, initial-scale=1.0\">";
html += "<title>配网警告</title>";
html += "<style>";
html += "body{font-family:Arial;margin:20px;background:#f0f0f0;}";
html += ".container{max-width:400px;margin:0 auto;background:white;padding:20px;border-radius:10px;text-align:center;}";
html += "h1{color:#ffc107;}";
html += ".warning{margin:20px 0;padding:15px;background:#fff3cd;border-radius:5px;color:#856404;}";
html += ".info{margin:10px 0;color:#666;}";
html += "</style></head><body>";
html += "<div class=\"container\">";
html += "<h1>⚠️ 配网警告</h1>";
html += "<div class=\"info\">";
html += "<p>已连接WiFi: " + ssid + "</p>";
html += "</div>";
html += "<div class=\"warning\">";
html += "<p>" + message + "</p>";
html += "</div>";
html += "<p style=\"color:#999;font-size:12px;\">设备将自动重启...</p>";
html += "</div></body></html>";
return html;
}
}
}

WifiManager.cs

using System;
using System.Device.Wifi;
using System.Net;
using System.Net.NetworkInformation;
using System.Threading;
using Microsoft.Extensions.Logging;
using nanoFramework.Logging;
using nanoFramework.Networking;
using Iot.Device.DhcpServer;

namespace WIFI_StaApTest
{
public enum WifiState
{
Idle, // 空闲状态
Connecting, // 连接中
Connected, // 已连接
Reconnecting, // 重连中
APMode // AP模式
}

// internal:仅本程序集可见,封装内部实现
// 不对外暴露具体实现细节,保证API稳定性
internal class WifiManager
{
// 日志记录器
private ILogger _logger;

// WIFI硬件适配器(系统唯一)
private WifiAdapter _wifiAdapter;

// 当前连接的网络凭证
private string _currentSSID = null;
private string _currentPassword = null;

// ========== 状态机与同步工具 ==========
// 当前WIFI状态(线程安全的状态机)
public WifiState CurrentState { get; private set; } = WifiState.Idle;

// 云端通信唤醒事件(连接成功时通知上云线程)
public AutoResetEvent CloudWakeUpEvent { get; private set; }

// 重连任务取消令牌(用于安全终止重连线程)
private CancellationTokenSource _reconnectCts;

// 网络状态防抖定时器(避免频繁触发重连)
private Timer _debounceTimer;

// DHCP服务器实例(AP模式下使用)
private DhcpServer _dhcpServer;

// 构造函数:初始化WIFI管理器
// 1. 创建日志记录器
// 2. 初始化同步事件
// 3. 获取系统WIFI适配器
public WifiManager()
{
// 初始化云端唤醒事件(非信号状态)
CloudWakeUpEvent = new AutoResetEvent(false);

// 获取系统中的第一个WIFI适配器
var adapters = WifiAdapter.FindAllAdapters();
if (adapters == null || adapters.Length == 0)
{
throw new Exception("未找到 WIFI 硬件适配器");
}
_wifiAdapter = adapters[0];

// 创建日志记录器(放在最后,因为需要确保其他初始化完成)
try
{
_logger = LogDispatcher.LoggerFactory.CreateLogger("WifiManager");
}
catch
{
// 如果日志创建失败,不影响程序运行
//Debug.WriteLine("WifiManager: 日志记录器创建失败");
_logger = null;
return;
}
}

#region STA模式连接

// 连接到指定的WIFI网络(STA模式)
public bool ConnectSTA(string ssid, string password, bool enableReconnect = true)
{
// 保存连接凭证
_currentSSID = ssid;
_currentPassword = password;

// ========== 连接前检查 ==========
// 检查是否已经连接(避免重复连接)
if (IsSTAConnected())
{
_logger.LogInformation($"[WifiManager] 已连接到网络,无需重复连接");
CurrentState = WifiState.Connected;

if (enableReconnect)
{
RegisterNetworkEvents();
}
return true;
}

// 更新状态为连接中
CurrentState = WifiState.Connecting;

// 首次连接,超时15秒
var firstTryCts = new CancellationTokenSource(15000);
bool connectResult = ConnectWithHelper(ssid, password, firstTryCts.Token);

// 根据连接结果更新状态
CurrentState = connectResult ? WifiState.Connected : WifiState.Reconnecting;

// 如果启用自动重连
if (enableReconnect)
{
// 注册网络状态变化事件
RegisterNetworkEvents();

// 如果首次连接失败,立即启动重连任务
if (!connectResult)
{
StartReconnectTask();
}
}

return connectResult;
}

// 注册网络状态变化事件(事件驱动)
private void RegisterNetworkEvents()
{
// 订阅网络地址变化事件
NetworkChange.NetworkAddressChanged += NetworkChange_NetworkAddressChanged;

// 初始化防抖定时器(3秒防抖窗口)
if (_debounceTimer == null)
{
_debounceTimer = new Timer(DebounceAction, null, Timeout.Infinite, Timeout.Infinite);
}

_logger.LogInformation("[WifiManager] 已注册防抖网络事件");
}

// 取消注册网络事件
public void UnregisterNetworkEvents()
{
NetworkChange.NetworkAddressChanged -= NetworkChange_NetworkAddressChanged;
_logger.LogInformation("[WifiManager] 已取消网络事件");
}
#endregion

#region 防抖核心逻辑
// 网络状态变化事件处理函数(防抖入口)
// 当检测到网络波动时,重置防抖计时器
private void NetworkChange_NetworkAddressChanged(object sender, EventArgs e)
{
// AP模式下忽略网络事件
if (CurrentState == WifiState.APMode)
{
return;
}

_logger.LogInformation("[WifiManager] 检测到波动,重置3秒计时...");

// 重置防抖定时器,3秒后执行实际检测
// 如果3秒内再次触发事件,计时器将被重新设置
if (_debounceTimer != null)
{
_debounceTimer.Change(3000, Timeout.Infinite);
}
}

// 防抖动作 - 防抖窗口结束后执行的实际检测逻辑
private void DebounceAction(object state)
{
// 获取实际连接状态
bool actuallyConnected = IsSTAConnected();

// 状态流转判断
// 场景1:已连接 -> 断开
if (CurrentState == WifiState.Connected && !actuallyConnected)
{
CurrentState = WifiState.Reconnecting;
_logger.LogWarning("[WifiManager] 确认断开,触发重连...");
StartReconnectTask();
}
// 场景2:连接中/重连中 -> 已连接
else if ((CurrentState == WifiState.Reconnecting || CurrentState == WifiState.Connecting) && actuallyConnected)
{
CurrentState = WifiState.Connected;
_logger.LogInformation("[WifiManager] 确认恢复!唤醒上云线程...");
CloudWakeUpEvent.Set(); // 唤醒等待中的云端通信线程
}
}
#endregion

#region 安全重连核心逻辑
// 启动安全重连任务
// 取消之前的重连任务(如果存在),启动新的重连任务
private void StartReconnectTask()
{
// 取消之前的重连任务(如果正在运行)
if (_reconnectCts != null)
{
_reconnectCts.Cancel();
}

// 创建新的取消令牌源
_reconnectCts = new CancellationTokenSource();

// 在新线程中启动重连任务(nanoFramework不支持ThreadPool和IsBackground)
Thread reconnectThread = new Thread(() => AttemptReconnect(_reconnectCts.Token));
reconnectThread.Start();
}

// 执行重连尝试(带指数退避策略)
private void AttemptReconnect(CancellationToken ct)
{
int retryCount = 0; // 重试次数
const int maxRetryDelay = 60000; // 最大重试延迟(60秒)

// 循环重连,直到连接成功或被取消
while (CurrentState == WifiState.Reconnecting)
{
// 检查是否被取消
if (ct.IsCancellationRequested)
{
_logger.LogInformation("[WifiManager] 重连任务已被取消");
break;
}

// 尝试连接
if (ConnectWithHelper(_currentSSID, _currentPassword, ct))
{
// 连接成功,更新状态并退出循环
CurrentState = WifiState.Connected;
_logger.LogInformation("[WifiManager] 重连成功!");
CloudWakeUpEvent.Set(); // 唤醒云端通信线程
break;
}

// 计算指数退避延迟:2^n * 2000ms,最大60秒
int delay = Math.Min((int)Math.Pow(2, retryCount) * 2000, maxRetryDelay);

_logger.LogWarning($"[WifiManager] 第 {retryCount + 1} 次失败,等待 {delay / 1000} 秒后重试...");

// 分段等待,每500ms检查一次取消信号
for (int elapsed = 0; elapsed < delay; elapsed += 500)
{
if (ct.IsCancellationRequested)
{
_logger.LogInformation("[WifiManager] 等待期间收到取消信号");
return;
}
Thread.Sleep(500);
}

retryCount++;
}
}
#endregion

#region 底层连接辅助
// 底层连接辅助函数 使用 nanoFramework 内置的 WifiNetworkHelper 进行连接
private bool ConnectWithHelper(string ssid, string password, CancellationToken ct)
{
try
{
// 使用内置助手类连接,禁用日期时间检查(嵌入式设备可能没有RTC)
bool success = WifiNetworkHelper.ConnectDhcp(
ssid,
password,
token: ct // 取消令牌
);

if (success)
{
// 获取并记录分配的IP地址
var ip = NetworkInterface.GetAllNetworkInterfaces()[0].IPv4Address;
_logger.LogInformation($"[WifiManager] 连接成功,IP: {ip}");
}

return success;
}
catch (OperationCanceledException)
{
// 连接被取消
_logger.LogInformation("[WifiManager] 连接操作已取消");
return false;
}
catch (Exception ex)
{
// ConnectDhcp可能在物理连接成功后因DNS/NTP检查失败而抛异常
// 需要检查实际连接状态
_logger.LogWarning($"[WifiManager] ConnectDhcp异常: {ex.Message}");

// 检查实际连接状态
if (IsSTAConnected())
{
var ip = NetworkInterface.GetAllNetworkInterfaces()[0].IPv4Address;
_logger.LogInformation($"[WifiManager] 实际已连接,IP: {ip}");
return true;
}

return false;
}
}

// 检查STA模式是否已连接(带网络可达性检测)
// 判断条件:
// 1. 无线STA接口 + 非零IP + 接口状态正常(基本连接)
// 2. DNS解析成功(网络可达性,确保NTP/MQTT能正常工作)
private bool IsSTAConnected()
{
try
{
string deviceIp = null;

// 步骤1:检查基本连接状态
foreach (var nic in NetworkInterface.GetAllNetworkInterfaces())
{
if (nic.NetworkInterfaceType == NetworkInterfaceType.Wireless80211)
{
string ip = nic.IPv4Address.ToString();
// nanoFramework中NetworkInterface没有OperationalStatus属性
// 仅通过IP地址判断连接状态
bool hasValidIP = (ip != "0.0.0.0" && ip != "127.0.0.1");

if (hasValidIP)
{
deviceIp = ip;
break;
}
}
}

// 如果基本连接都不满足,直接返回false
if (string.IsNullOrEmpty(deviceIp))
{
return false;
}

// 步骤2:网络可达性检测(DNS解析)
// 通过解析域名验证网络是否真正可达
// 这确保后续NTP校时、MQTT等网络操作能正常工作
if (IsNetworkReachable())
{
_logger.LogInformation($"[WifiManager] STA已连接,IP: {deviceIp},网络可达");
return true;
}
else
{
_logger.LogWarning($"[WifiManager] STA有IP但网络不可达");
return false;
}
}
catch (Exception ex)
{
_logger.LogWarning($"[WifiManager] 检查连接状态异常: {ex.Message}");
}
return false;
}

// 网络可达性检测(通过DNS解析验证)
// 返回true表示网络可达,可以进行NTP校时、MQTT等操作
public bool IsNetworkReachable()
{
try
{
// 尝试解析公共DNS域名
// 使用多个域名提高可靠性
string[] testDomains = { "www.baidu.com", "time.windows.com", "pool.ntp.org" , "ntp.ntsc.ac.cn", "ntp.aliyun.com" };

foreach (string domain in testDomains)
{
try
{
_logger.LogInformation($"[WifiManager] 正在检测网络可达性: {domain}");
var hostEntry = System.Net.Dns.GetHostEntry(domain);

if (hostEntry != null && hostEntry.AddressList != null && hostEntry.AddressList.Length > 0)
{
_logger.LogInformation($"[WifiManager] DNS解析成功: {domain} -> {hostEntry.AddressList[0]}");
return true;
}
}
catch { /* 继续尝试下一个域名 */ }
}

_logger.LogWarning("[WifiManager] 所有域名解析失败,网络不可达");
return false;
}
catch (Exception ex)
{
_logger.LogWarning($"[WifiManager] 网络可达性检测异常: {ex.Message}");
return false;
}
}
#endregion

#region AP模式(热点模式)
// 启动AP热点模式(参考官方WifiAP示例实现)
// 【配置AP必需的7个关键步骤】:
// 1. 取消STA相关任务和事件(STA/AP互斥)
// 2. 彻底关闭STA模式(使用Wireless80211Configuration.Options = None | SmartConfig)
// 3. 查找WirelessAP网络接口
// 4. 配置静态IP地址
// 5. 通过SpecificConfigId获取正确的配置槽
// 6. 设置AP参数(SSID/密码/认证类型/Options)
// 7. 启动DHCP服务器(关键!否则AP不会广播SSID)
// 热点密码(WPA2模式下最少8位)
// AP静态IP地址(默认192.168.4.1)
public bool StartAP(string ssid, string password, string ipAddress = "192.168.4.1")
{
_logger.LogInformation($"[WifiManager] AP准备启动...");

// ========== 步骤1:互斥保护 - 取消STA相关任务 ==========
// STA模式与AP模式互斥,必须先清理STA相关资源
if (_reconnectCts != null) _reconnectCts.Cancel();
Thread.Sleep(200); // 等待任务取消完成
UnregisterNetworkEvents(); // 取消网络状态监听
CurrentState = WifiState.APMode; // 更新状态机

// ========== 步骤2:彻底关闭STA模式(关键!否则射频冲突) ==========
// 参考官方Wireless80211.Disable()实现
// 使用 None | SmartConfig 确保STA完全禁用
DisableSTAMode();
Thread.Sleep(300); // 等待射频切换完成

// ========== 步骤3:查找WirelessAP网络接口 ==========
NetworkInterface apInterface = null;
foreach (var nic in NetworkInterface.GetAllNetworkInterfaces())
{
if (nic.NetworkInterfaceType == NetworkInterfaceType.WirelessAP)
{
apInterface = nic;
break;
}
}
if (apInterface == null)
{
_logger.LogError("[WifiManager] 未找到AP网络接口");
return false;
}

// ========== 步骤4:配置AP静态IP地址 ==========
// 参数:IP地址、子网掩码、网关(AP自身作为网关)
apInterface.EnableStaticIPv4(ipAddress, "255.255.255.0", ipAddress);
_logger.LogInformation($"[WifiManager] AP接口IP配置完成: {ipAddress}");

// 【关键】等待网络接口完全初始化
// 配置IP后需要等待一段时间,否则后续Socket操作会失败
Thread.Sleep(1000);

// ========== 步骤5:获取AP配置对象(关键!使用SpecificConfigId) ==========
// 【重要】必须通过接口的SpecificConfigId获取配置对象
// 直接new WirelessAPConfiguration(1)可能修改错误的配置槽
WirelessAPConfiguration apConfig = null;
try
{
WirelessAPConfiguration[] apConfigs = WirelessAPConfiguration.GetAllWirelessAPConfigurations();
if (apConfigs != null && apConfigs.Length > 0)
{
uint configIdUInt = apInterface.SpecificConfigId;
int configId = (int)configIdUInt;
apConfig = (configId >= 0 && configId < apConfigs.Length) ? apConfigs[configId] : apConfigs[0];
}
}
catch (Exception ex)
{
_logger.LogWarning($"[WifiManager] 获取AP配置失败: {ex.Message}");
}
if (apConfig == null) apConfig = new WirelessAPConfiguration(0);

// ========== 步骤6:配置AP热点参数 ==========
apConfig.Ssid = ssid; // 热点名称
apConfig.Password = password; // 热点密码(WPA2需≥8位)
apConfig.Authentication = System.Net.NetworkInformation.AuthenticationType.WPA2; // 认证类型:WPA2
apConfig.Encryption = EncryptionType.WPA2_PSK; // 加密类型:WPA2-PSK
apConfig.Channel = 6; // WIFI信道(信道6兼容性最好)
apConfig.MaxConnections = 8; // 最大连接数
// 【关键】Options必须同时包含AutoStart和Enable
// AutoStart: 配置保存后立即启动AP
// Enable: 启用AP模式
apConfig.Options = WirelessAPConfiguration.ConfigurationOptions.AutoStart
| WirelessAPConfiguration.ConfigurationOptions.Enable;

// 保存配置到Flash
apConfig.SaveConfiguration();
_logger.LogInformation($"[WifiManager] AP配置已保存,SSID: {ssid}");

// ========== 步骤7:(关键)启动DHCP服务器 ==========
// DHCP服务器为连接的设备分配IP地址
// 同时配置Captive Portal URL、DNS服务器和网关
try
{
_logger.LogInformation("[WifiManager] 正在启动DHCP服务器...");
_dhcpServer = new DhcpServer
{
CaptivePortalUrl = "http://" + ipAddress,
DnsServer = IPAddress.Parse(ipAddress),
Gateway = IPAddress.Parse(ipAddress)
};
bool dhcpStarted = _dhcpServer.Start(IPAddress.Parse(ipAddress), new IPAddress(new byte[] { 255, 255, 255, 0 }));
if (dhcpStarted)
{
_logger.LogInformation("[WifiManager] DHCP服务器启动成功");
}
else
{
_logger.LogError("[WifiManager] DHCP服务器启动失败!");
_logger.LogError("[WifiManager] 可能原因:网络接口未就绪或端口被占用");
}
}
catch (Exception ex)
{
_logger.LogError($"[WifiManager] 启动DHCP服务器异常: {ex.Message}");
_logger.LogError($"[WifiManager] 异常类型: {ex.GetType().Name}");
}

// 等待AP启动(增加等待时间确保射频模块初始化完成)
Thread.Sleep(2000);

// 验证AP是否真正启动
bool apStarted = VerifyAPStarted(apInterface, ipAddress);
if (!apStarted)
{
// nanoFramework.Runtime.Native.Power.RebootDevice();
_logger.LogWarning("[WifiManager] AP配置已保存,但射频可能未立即启动");
_logger.LogWarning("[WifiManager] 建议重启设备以确保AP完全生效");
// 不强制重启,让用户决定是否重启
}

_logger.LogInformation($"[WifiManager] AP启动成功,IP: {ipAddress}");
return true;

// 验证AP是否真正启动
bool VerifyAPStarted(NetworkInterface ni, string expectedIP)
{
try
{
// 检查IP是否配置正确
if (ni.IPv4Address != expectedIP)
{
_logger.LogWarning($"[WifiManager] AP IP不匹配: 期望 {expectedIP}, 实际 {ni.IPv4Address}");
return false;
}

// 获取当前AP配置检查Options
var currentConfigs = WirelessAPConfiguration.GetAllWirelessAPConfigurations();
if (currentConfigs != null && currentConfigs.Length > 0)
{
uint configId = ni.SpecificConfigId;
if (configId < currentConfigs.Length)
{
var currentConfig = currentConfigs[configId];
bool hasAutoStart = (currentConfig.Options & WirelessAPConfiguration.ConfigurationOptions.AutoStart) != 0;
bool hasEnable = (currentConfig.Options & WirelessAPConfiguration.ConfigurationOptions.Enable) != 0;

if (!hasAutoStart || !hasEnable)
{
_logger.LogWarning("[WifiManager] AP Options未正确设置");
return false;
}
}
}
return true;
}
catch (Exception ex)
{
_logger.LogWarning($"[WifiManager] 验证AP状态失败: {ex.Message}");
return false;
}
}
}

// 禁用STA模式(参考官方示例Wireless80211.Disable())
// 使用System.Net.NetworkInformation中的Wireless80211Configuration
private void DisableSTAMode()
{
try
{
// 获取STA网络接口
NetworkInterface staInterface = null;
foreach (var nic in NetworkInterface.GetAllNetworkInterfaces())
{
if (nic.NetworkInterfaceType == NetworkInterfaceType.Wireless80211)
{
staInterface = nic;
break;
}
}

if (staInterface != null)
{
// 通过接口的SpecificConfigId获取STA配置(与官方一致)
var staConfigs = Wireless80211Configuration.GetAllWireless80211Configurations();
if (staConfigs != null && staConfigs.Length > 0)
{
uint configId = staInterface.SpecificConfigId;
Wireless80211Configuration staConfig = staConfigs[configId];

// 禁用STA模式(与官方一致:None | SmartConfig)
staConfig.Options = Wireless80211Configuration.ConfigurationOptions.None |
Wireless80211Configuration.ConfigurationOptions.SmartConfig;
staConfig.SaveConfiguration();
_logger.LogInformation("[WifiManager] STA模式已禁用");
}
}
}
catch (Exception ex)
{
_logger.LogWarning($"[WifiManager] 禁用STA模式失败: {ex.Message}");
// 降级方案:断开连接
try { _wifiAdapter.Disconnect(); } catch { }
}
}

// 关闭AP热点模式(参考官方示例WirelessAP.Disable())
public void StopAP()
{
// 获取当前AP配置
var configs = WirelessAPConfiguration.GetAllWirelessAPConfigurations();

if (configs != null && configs.Length > 0)
{
// 禁用AP模式(与官方一致:使用None而非Disable)
configs[0].Options = WirelessAPConfiguration.ConfigurationOptions.None;
// 保存配置以应用更改
configs[0].SaveConfiguration();
}

// 更新状态为空闲
CurrentState = WifiState.Idle;
_logger.LogInformation("[WifiManager] AP 已关闭");
}
#endregion

#region 断开连接
// 断开STA连接并安全地取消所有相关任务
public void DisconnectSTA()
{
// 取消重连任务
if (_reconnectCts != null)
{
_reconnectCts.Cancel();
}

// 取消注册网络事件
UnregisterNetworkEvents();

// 断开WIFI连接
_wifiAdapter.Disconnect();

// 更新状态为空闲
CurrentState = WifiState.Idle;

_logger.LogInformation("[WifiManager] STA 已断开连接");
}
#endregion

#region WiFi凭证存储(官方方式)

// 获取无线网络接口(私有方法)
private NetworkInterface GetWirelessInterface()
{
var interfaces = NetworkInterface.GetAllNetworkInterfaces();
foreach (var iface in interfaces)
{
if (iface.NetworkInterfaceType == NetworkInterfaceType.Wireless80211)
{
return iface;
}
}
return null;
}

// 保存WiFi凭证到Flash(使用官方Wireless80211Configuration)
// 官方推荐方式:WiFi配置存储在设备的Configuration分区,重启后固件自动连接
public bool SaveCredentials()
{
try
{
// 检查当前是否有凭证
if (string.IsNullOrEmpty(_currentSSID))
{
_logger.LogWarning("[WifiManager] 没有WiFi凭证可保存");
return false;
}

// 获取无线网络接口
var iface = GetWirelessInterface();
if (iface == null)
{
_logger.LogError("[WifiManager] 未找到无线网络接口");
return false;
}

// 获取WiFi配置
var configs = Wireless80211Configuration.GetAllWireless80211Configurations();
var config = configs[iface.SpecificConfigId];

// 设置WiFi参数
config.Ssid = _currentSSID;
config.Password = _currentPassword;
config.Authentication = System.Net.NetworkInformation.AuthenticationType.WPA2;
config.Encryption = EncryptionType.WPA2_PSK;
config.Options = Wireless80211Configuration.ConfigurationOptions.Enable;

// 保存配置(重启后固件会自动连接)
config.SaveConfiguration();
_logger.LogInformation($"[WifiManager] WiFi凭证已保存: {_currentSSID}");
return true;
}
catch (Exception ex)
{
_logger.LogError($"[WifiManager] 保存凭证失败: {ex.Message}");
return false;
}
}

// 保存指定的WiFi凭证到Flash
// 参数:ssid - WiFi名称,password - WiFi密码
public bool SaveCredentials(string ssid, string password)
{
// 更新当前凭证
_currentSSID = ssid;
_currentPassword = password;

// 调用无参版本保存
return SaveCredentials();
}

// 从Flash读取WiFi凭证(使用官方Wireless80211Configuration)
// 返回:true表示读取成功,false表示读取失败或无配置
public bool LoadCredentials(out string ssid, out string password)
{
ssid = null;
password = null;

try
{
// 获取所有WiFi配置
var configs = Wireless80211Configuration.GetAllWireless80211Configurations();

foreach (var config in configs)
{
// 查找第一个有效的配置
if (!string.IsNullOrEmpty(config.Ssid))
{
ssid = config.Ssid;
password = config.Password;

// 更新当前凭证
_currentSSID = ssid;
_currentPassword = password;

_logger.LogInformation($"[WifiManager] 读取凭证成功: {ssid}");
return true;
}
}

_logger.LogInformation("[WifiManager] 未找到已保存的WiFi配置");
return false;
}
catch (Exception ex)
{
_logger.LogError($"[WifiManager] 读取凭证失败: {ex.Message}");
return false;
}
}

// 检查是否有已保存的WiFi配置
public bool HasSavedConfig()
{
try
{
var configs = Wireless80211Configuration.GetAllWireless80211Configurations();
foreach (var config in configs)
{
if (!string.IsNullOrEmpty(config.Ssid))
{
return true;
}
}
return false;
}
catch
{
return false;
}
}

// 清除保存的WiFi配置
public bool ClearCredentials()
{
try
{
// 获取无线网络接口
var iface = GetWirelessInterface();
if (iface == null)
{
_logger.LogError("[WifiManager] 未找到无线网络接口");
return false;
}

// 获取WiFi配置并清除
var configs = Wireless80211Configuration.GetAllWireless80211Configurations();
var config = configs[iface.SpecificConfigId];

config.Ssid = "";
config.Password = "";
config.Options = Wireless80211Configuration.ConfigurationOptions.None;
config.SaveConfiguration();

// 清空内存中的凭证
_currentSSID = null;
_currentPassword = null;

_logger.LogInformation("[WifiManager] WiFi凭证已清除");
return true;
}
catch (Exception ex)
{
_logger.LogError($"[WifiManager] 清除凭证失败: {ex.Message}");
return false;
}
}

#endregion
}
}

Program.cs

using Microsoft.Extensions.Logging;
using nanoFramework.Logging;
using nanoFramework.WebServer;
using System;
using System.Net;
using System.Threading;
using WIFI_StaApTest;


namespace APConfig
{
// AP配网状态枚举
public enum APConfigState
{
Idle, // 空闲
APConfiguring, // 正在配网
Connecting, // 正在连接
Configured, // 配网成功
Failed // 配网失败
}

// 配网完成事件参数
public class APConfigResultEventArgs : EventArgs
{
public bool Success { get; set; }
public string SSID { get; set; }
public string Message { get; set; }
}

internal class APConfigManager
{
// 日志记录器
private ILogger _logger;

// WIFI管理器(外部注入,不内部创建)
private WifiManager _wifiManager;

// Web服务器实例
private WebServer _webServer;

// 当前配网状态
public APConfigState CurrentState { get; private set; } = APConfigState.Idle;

// 配网完成事件(通知外部配网结果)
public event APConfigResultEventHandler OnConfigCompleted;
public delegate void APConfigResultEventHandler(object sender, APConfigResultEventArgs e);

// AP配网参数
private const string AP_SSID = "YF3300_ESP32S3"; // 热点名称
private const string AP_PASSWORD = "yf123456"; // 热点密码(WPA2需>=8)
private const string AP_IP = "192.168.4.1"; // AP网关IP

// Web服务器参数
private const int WEB_PORT = 80; // Web服务器端口
private const int CONFIG_TIMEOUT = 600000; // 配网超时时间(10分钟)

// 配网任务取消令牌
private CancellationTokenSource _configCts;

// 配网超时定时器
private Timer _timeoutTimer;

// 当前配网的SSID和密码(用于传递给WebServerController)
public static string PendingSSID { get; set; }
public static string PendingPassword { get; set; }
public static bool ConfigSubmitted { get; set; } = false;

// WifiManager实例(供WebServerController访问)
private static WifiManager _staticWifiManager;

// 获取WifiManager实例
public static WifiManager GetWifiManager()
{
return _staticWifiManager;
}

// 触发配网完成事件(由外部调用)
public void RaiseConfigCompleted(bool success, string ssid, string message)
{
if (OnConfigCompleted != null)
{
OnConfigCompleted(this, new APConfigResultEventArgs()
{
Success = success,
SSID = ssid,
Message = message
});
}
}

// 构造函数:注入WIFI管理器实例
public APConfigManager(WifiManager wifiManager)
{
if (wifiManager == null)
{
throw new ArgumentNullException(nameof(wifiManager));
}
_wifiManager = wifiManager;
_staticWifiManager = wifiManager; // 保存到静态字段
_logger = LogDispatcher.LoggerFactory.CreateLogger("APConfigManager");
}

#region 公共API

// 检查是否已有保存的WIFI配置
public bool HasSavedWifiConfig()
{
return _wifiManager.HasSavedConfig();
}

// 尝试使用已保存的配置连接WIFI
public bool TryConnectSavedWifiConfig()
{
if (_wifiManager.LoadCredentials(out string ssid, out string password))
{
_logger.LogInformation($"[APConfigManager] 尝试使用已保存的WIFI配置进行连接: {ssid}");
return _wifiManager.ConnectSTA(ssid, password, enableReconnect: true);
}
return false;
}

// 开始配网
public bool StartAPConfig()
{
if (CurrentState != APConfigState.Idle)
{
_logger.LogWarning("[APConfigManager] 当前AP状态不是空闲状态,无法开始配网");
return false;
}

_logger.LogInformation("[APConfigManager] 开始配网");

// 步骤一:启动AP热点
bool apStarted = _wifiManager.StartAP(AP_SSID, AP_PASSWORD, AP_IP);
if (!apStarted)
{
_logger.LogError("[APConfigManager] 启动AP热点失败");
CurrentState = APConfigState.Failed;
return false;
}

CurrentState = APConfigState.APConfiguring;
_logger.LogInformation($"[APConfigManager] AP热点已启动,热点名称:{AP_SSID}, IP:{AP_IP}");

// 步骤二:启动Web服务器
// 【重要】等待网络接口完全初始化后再启动Web服务器
Thread.Sleep(1000);
try
{
// 重置配网提交标志
ConfigSubmitted = false;

// 创建Web服务器实例
// 【重要】在AP模式下,必须显式指定绑定的IP地址,否则会绑定到错误的网络接口
_webServer = new WebServer(WEB_PORT, HttpProtocol.Http, IPAddress.Parse(AP_IP), new Type[] { typeof(WebServerController) });
_webServer.Start();
_logger.LogInformation($"[APConfigManager] Web服务器已启动,端口:{WEB_PORT},绑定IP:{AP_IP}");
}
catch (Exception e)
{
_logger.LogError($"[APConfigManager] 启动Web服务器失败: {e.Message}");
StopAPConfig();
return false;
}

// 步骤三:启动配网超时定时器
_configCts = new CancellationTokenSource();
_timeoutTimer = new Timer(OnTimeoutCallback, null, CONFIG_TIMEOUT, Timeout.Infinite);

_logger.LogInformation($"[APConfigManager] 请连接WIFI {AP_SSID},并访问 http://{AP_IP}");
_logger.LogInformation($"[APConfigManager] 配网超时定时器已启动,超时时间:{CONFIG_TIMEOUT / 1000} 秒");

return true;
}

// 停止AP配网(清理资源)
public void StopAPConfig()
{
_logger.LogInformation("[APConfigManager] 停止AP配网");

// 停止超时定时器
if (_timeoutTimer != null)
{
_timeoutTimer.Dispose();
_timeoutTimer = null;
}

// 取消配网任务
if (_configCts != null)
{
_configCts.Cancel();
_configCts.Dispose();
_configCts = null;
}

// 关闭Web服务器
if (_webServer != null)
{
_webServer.Stop();
_webServer.Dispose();
_webServer = null;
}

// 关闭AP热点
_wifiManager.StopAP();

CurrentState = APConfigState.Idle;
_logger.LogInformation("[APConfigManager] AP配网已停止");
}

// 处理配网提交(由WebServerController调用)
public void HandleConfigSubmit(string ssid, string password)
{
_logger.LogInformation($"[APConfigManager] 收到配网请求:{ssid}");

// 停止超时定时器
if (_timeoutTimer != null)
{
_timeoutTimer.Change(Timeout.Infinite, Timeout.Infinite);
}

// 停止Web服务器
if (_webServer != null)
{
_webServer.Stop();
}

CurrentState = APConfigState.Connecting;

// 尝试连接WIFI
bool connected = _wifiManager.ConnectSTA(ssid, password, enableReconnect: true);

if (connected)
{
// ========== 验证网络可达性 ==========
// 检查WiFi是否真的可以上网(通过DNS解析验证)
_logger.LogInformation("[APConfigManager] 正在验证网络可达性...");
bool networkReachable = _wifiManager.IsNetworkReachable();

if (!networkReachable)
{
_logger.LogWarning("[APConfigManager] WiFi已连接但网络不可达,请检查路由器是否连接外网");
// 仍然保存凭证,但提示用户网络可能不可用
}
else
{
_logger.LogInformation("[APConfigManager] 网络可达性验证成功,可以正常上网");
}

// ========== 保存WIFI凭证到Flash ==========
// 【测试时可注释以下2行,关闭持久化存储功能】
_wifiManager.SaveCredentials(ssid, password);
_logger.LogInformation($"[APConfigManager] 已保存WIFI凭证:{ssid}");

CurrentState = APConfigState.Configured;
_logger.LogInformation($"[APConfigManager] 配网成功!SSID:{ssid}");

// 触发配网完成事件
if (OnConfigCompleted != null)
{
OnConfigCompleted(this, new APConfigResultEventArgs()
{
Success = true,
SSID = ssid,
Message = networkReachable ? "配网成功!网络可达" : "配网成功!但网络不可达,请检查路由器"
});
}
}
else
{
CurrentState = APConfigState.Failed;
_logger.LogError($"[APConfigManager] 配网失败!SSID:{ssid}");

// 触发配网失败事件
if (OnConfigCompleted != null)
{
OnConfigCompleted(this, new APConfigResultEventArgs()
{
Success = false,
SSID = ssid,
Message = "配网失败!"
});
}

// 重新启动AP配网
Thread.Sleep(2000);
CurrentState = APConfigState.Idle;
StartAPConfig();
}
}

#endregion

#region 私有方法

// 超时回调
private void OnTimeoutCallback(object state)
{
_logger.LogWarning("[APConfigManager] 配网超时!");

// 触发配网超时事件
if (OnConfigCompleted != null)
{
OnConfigCompleted(this, new APConfigResultEventArgs()
{
Success = false,
SSID = null,
Message = "配网超时!"
});
}

// 停止AP配网
StopAPConfig();
}

#endregion
}
}