跳到主要内容

WiFi网络

概述

本章节将介绍如何使用 nanoFramework 配置 WiFi 网络连接,包括 STA 模式的自动重连机制和 AP 模式的热点配置。

ESP32-S3 内置 WiFi 模块,支持两种工作模式:

模式说明典型应用
STA 模式作为站点连接到路由器连接互联网、云平台通信
AP 模式作为热点让其他设备连接设备配网

所需 NuGet 包

包名说明
nanoFramework.System.Device.WifiWiFi 核心操作库
nanoFramework.Networking网络辅助工具(含 WifiNetworkHelper)
nanoFramework.Iot.Device.DhcpServerDHCP 服务器库(AP模式必需)
nanoFramework.Logging日志记录框架(可选)
nanoFramework.Logging.Debug调试日志实现(可选)
nanoFramework.Runtime.Events事件运行时支持
nanoFramework.Runtime.Native原生运行时支持
nanoFramework.System.Net网络功能支持

核心概念

网络状态枚举

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

WiFi 管理器类

使用示例

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;

// 构造函数:初始化WIFI管理器
// 1. 创建日志记录器
// 2. 初始化同步事件
// 3. 获取系统WIFI适配器
public WifiManager()
{
// 创建日志记录器
_logger = LogDispatcher.LoggerFactory.CreateLogger("WifiManager");

// 初始化云端唤醒事件(非信号状态)
CloudWakeUpEvent = new AutoResetEvent(false);

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

#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等操作
private 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}");

// ========== 步骤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 = 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服务器,否则AP可能不会广播SSID
try
{
var dhcpServer = new DhcpServer
{
CaptivePortalUrl = "http://" + ipAddress
};
bool dhcpStarted = dhcpServer.Start(IPAddress.Parse(ipAddress), new IPAddress(new byte[] { 255, 255, 255, 0 }));
if (dhcpStarted)
{
_logger.LogInformation("[WifiManager] DHCP服务器启动成功");
}
else
{
_logger.LogWarning("[WifiManager] DHCP服务器启动失败,AP可能无法正常工作");
}
}
catch (Exception ex)
{
_logger.LogWarning($"[WifiManager] 启动DHCP服务器异常: {ex.Message}");
}

// 等待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
}
}

Program.cs

using Microsoft.Extensions.Logging;
using nanoFramework.Logging;
using nanoFramework.Logging.Debug;
using System;
using System.Diagnostics;
using System.Threading;
using WIFI_StaApTest;

namespace WIFI_StaApTest
{
public class Program
{
// STA配置
const string STA_SSID = "Xiaomi15";
const string STA_PASSWORD = "12345678";

// AP配置
const string AP_SSID = "YF3300_ESP32S3-AP";
const string AP_PASSWORD = "12345678";

private static ILogger _logger;

public static void Main()
{
// 1. 初始化日志工厂(必须先初始化)
LogDispatcher.LoggerFactory = new DebugLoggerFactory();

// 2. 创建 Logger 实例
_logger = LogDispatcher.LoggerFactory.CreateLogger("Program");

_logger.LogInformation($"YF3300_ESP32S3 WIFI功能测试");

// 启用 AP 模式
TestAPFunction();

// 启用 STA 模式
// TestSTAFunction();

Thread.Sleep(Timeout.Infinite);
}

static void TestSTAFunction()
{
_logger.LogInformation($"开始测试STA功能");
WifiManager wifiManager = new WifiManager();

bool success = wifiManager.ConnectSTA(STA_SSID, STA_PASSWORD);
if (!success)
{
_logger.LogError($"连接STA失败");
return;
}

Thread.Sleep(Timeout.Infinite);
}

// 测试AP功能
static void TestAPFunction()
{
_logger.LogInformation($"开始测试AP功能");
WifiManager wifiManager = new WifiManager();

bool success = wifiManager.StartAP(AP_SSID, AP_PASSWORD);

if (success)
{
_logger.LogInformation($"启动AP成功");
}
else
{
_logger.LogError($"启动AP失败");
}

Thread.Sleep(Timeout.Infinite);
}
}
}

注意事项

  1. STA/AP 互斥:ESP32 的 WiFi 模块在同一时间只能工作在一种模式,切换模式前需要先禁用当前模式。

  2. 日志初始化:使用日志功能前需要先初始化 LogDispatcher.LoggerFactory

  3. 重连机制:示例中包含了完整的重连机制,包括:

    • 防抖定时器(避免频繁触发重连)
    • 指数退避策略(重试间隔逐渐增加)
    • 取消令牌(安全终止重连任务)
  4. AP 模式配置

    • 密码长度要求:WPA2 模式下最少 8 位
    • 默认 IP 地址:192.168.4.1
    • 必须启动 DHCP 服务器才能正常工作
  5. 网络可达性检测:通过 DNS 解析验证网络是否真正可达,确保后续 NTP、MQTT 等操作能正常工作。