4、WiFi连接与网络基础:ESP32连接WiFi热点,扫描可用WiFi网络,处理WiFi连接状态机,实现自动重连机制
好,咱们进入第四章。这一章要啃的,是ESP32作为智能音箱的“命根子”——WiFi连接。
你想想看,一个智能音箱如果连不上网,那跟一块砖头有什么区别?语音指令发不出去,音乐播不了,智能家居控制更是空谈。所以,WiFi连接的稳定性和可靠性,直接决定了这个产品的生死。
我个人习惯,在开始任何网络项目之前,先把WiFi这块的“地基”打牢。ESP32的WiFi库其实封装得不错,但坑也不少。今天我们就从零开始,把连接、扫描、状态机、自动重连这些核心机制,一个一个掰开揉碎了讲清楚。
4.1 连接WiFi热点:从“Hello World”到“Hello Network”
先来个最简单的。让ESP32连上你家里的路由器。这就像你第一次用手机输入WiFi密码一样,简单,但至关重要。
代码其实就几行,但背后的逻辑值得琢磨。
#include <WiFi.h>
const char* ssid = "你的WiFi名称";
const char* password = "你的WiFi密码";
void setup() {
Serial.begin(115200);
delay(1000);
WiFi.begin(ssid, password);
Serial.print("正在连接WiFi");
while (WiFi.status() != WL_CONNECTED) {
delay(500);
Serial.print(".");
}
Serial.println("");
Serial.println("WiFi连接成功!");
Serial.print("IP地址: ");
Serial.println(WiFi.localIP());
}
void loop() {
// 你的主程序逻辑
}
这段代码,说白了就是让ESP32去敲门,然后一直等,等到门开了为止。但这里有个问题——如果密码错了呢?如果路由器没开呢?它会一直卡在 while 循环里,出不来。这在产品里是绝对不能接受的。
4.2 扫描可用WiFi网络:让设备“看见”周围的世界
有时候,我们不知道周围有哪些WiFi热点。或者,我们想让用户从列表里选一个。这时候就需要扫描功能。
ESP32的WiFi扫描,说白了就是让设备像雷达一样,发出探测信号,然后听周围路由器的回应。
void scanNetworks() {
int n = WiFi.scanNetworks();
Serial.println("扫描完成");
if (n == 0) {
Serial.println("没有找到WiFi网络");
} else {
Serial.print("找到 ");
Serial.print(n);
Serial.println(" 个网络");
for (int i = 0; i < n; i++) {
Serial.print(i + 1);
Serial.print(": ");
Serial.print(WiFi.SSID(i));
Serial.print(" (信号强度: ");
Serial.print(WiFi.RSSI(i));
Serial.print(" dBm)");
Serial.println((WiFi.encryptionType(i) == WIFI_AUTH_OPEN) ? " [开放]" : " [加密]");
delay(10);
}
}
}
这里有个小细节:WiFi.RSSI() 返回的是信号强度,单位是dBm。数值越大(越接近0),信号越好。比如 -30dBm 就比 -80dBm 强得多。
4.3 WiFi连接状态机:让连接变得“有章可循”
好了,现在我们要解决那个“卡死”的问题。怎么做?用状态机。
状态机,听起来很高大上,其实就是一个有限状态自动机。说白了,就是把WiFi连接的过程,拆成几个明确的状态,然后根据当前状态和事件,决定下一步做什么。
我一般把WiFi连接分成这几个状态:
| 状态 | 描述 | 典型行为 |
|---|---|---|
WIFI_DISCONNECTED |
未连接或连接断开 | 尝试连接或等待重连 |
WIFI_CONNECTING |
正在连接中 | 显示连接动画,超时处理 |
WIFI_CONNECTED |
已成功连接 | 获取IP,启动网络服务 |
WIFI_RECONNECTING |
自动重连中 | 指数退避重试 |
来看看代码实现:
typedef enum {
WIFI_DISCONNECTED,
WIFI_CONNECTING,
WIFI_CONNECTED,
WIFI_RECONNECTING
} wifi_state_t;
wifi_state_t currentState = WIFI_DISCONNECTED;
unsigned long lastAttemptTime = 0;
int reconnectAttempts = 0;
void wifiStateMachine() {
switch (currentState) {
case WIFI_DISCONNECTED:
Serial.println("状态: 未连接,准备连接...");
WiFi.begin(ssid, password);
currentState = WIFI_CONNECTING;
lastAttemptTime = millis();
break;
case WIFI_CONNECTING:
if (WiFi.status() == WL_CONNECTED) {
Serial.println("状态: 连接成功!");
currentState = WIFI_CONNECTED;
reconnectAttempts = 0; // 重置重连次数
} else if (millis() - lastAttemptTime > 10000) { // 10秒超时
Serial.println("状态: 连接超时,进入重连模式");
currentState = WIFI_RECONNECTING;
}
break;
case WIFI_CONNECTED:
if (WiFi.status() != WL_CONNECTED) {
Serial.println("状态: 连接丢失!");
currentState = WIFI_DISCONNECTED;
}
// 在这里处理已连接的业务逻辑
break;
case WIFI_RECONNECTING:
// 指数退避:每次重连间隔翻倍,但不超过5分钟
unsigned long waitTime = min(300000, 1000 * (1 << reconnectAttempts));
if (millis() - lastAttemptTime > waitTime) {
Serial.print("状态: 尝试第 ");
Serial.print(reconnectAttempts + 1);
Serial.println(" 次重连...");
WiFi.reconnect();
currentState = WIFI_CONNECTING;
lastAttemptTime = millis();
reconnectAttempts++;
}
break;
}
}
这段代码,你仔细看,其实就是一个“有限状态机”的典型实现。每个状态只做一件事,状态之间的转换清晰明了。这样,你的程序就不会乱,也不会卡死。
4.4 自动重连机制:让设备“打不死的小强”
WiFi断连是家常便饭。路由器重启、信号干扰、甚至有人不小心踢了电源线。所以,自动重连机制是必须的。
我刚才在状态机里已经实现了重连逻辑,但这里有几个关键点要强调:
- 指数退避(Exponential Backoff):不要一断就连,连不上就疯狂重试。这样只会让路由器更忙,设备更耗电。正确的做法是:第一次等1秒,第二次等2秒,第三次等4秒...以此类推,直到一个上限(比如5分钟)。
- 重连次数限制:不能无限重试。我一般设置一个最大次数(比如10次),如果10次都连不上,就进入深度休眠或通知用户。
- 连接状态回调:ESP32提供了WiFi事件回调机制,可以让你在连接成功、断开等事件发生时,自动执行一些操作。
来看看事件回调的用法:
void WiFiEvent(WiFiEvent_t event) {
switch (event) {
case SYSTEM_EVENT_STA_GOT_IP:
Serial.println("获取到IP地址!");
Serial.print("IP: ");
Serial.println(WiFi.localIP());
// 可以在这里启动MQTT、HTTP等服务
break;
case SYSTEM_EVENT_STA_DISCONNECTED:
Serial.println("WiFi断开连接!");
// 可以在这里记录断连时间,触发重连
break;
default:
break;
}
}
void setup() {
WiFi.onEvent(WiFiEvent);
WiFi.begin(ssid, password);
}
这个回调机制,我个人非常喜欢。它让你不用在主循环里轮询 WiFi.status(),系统会自动通知你。这样代码更干净,也更高效。
delay(),结果导致WiFi任务被阻塞,整个系统崩溃。记住,回调函数里不要做耗时操作,只做标记和通知。真正的处理逻辑,放到主循环或任务里去。
4.5 实战建议:让WiFi连接更健壮
好了,理论讲完了,来点实战经验。我做了这么多物联网项目,总结了几条让WiFi连接更健壮的建议:
- 保存WiFi配置到NVS:第一次连接成功后,把ssid和密码保存到ESP32的非易失性存储(NVS)中。下次启动时,直接读取,不用重新配网。
- 使用WiFiManager库:如果你想让用户通过手机APP或网页来配网,可以用WiFiManager库。它提供了一个Web界面,用户可以在上面选择WiFi并输入密码。
- 心跳检测:即使WiFi状态显示已连接,也不代表网络真的通。我习惯每隔30秒ping一下网关或云服务器,如果连续3次ping不通,就主动断开重连。
- 区分“断连”和“信号弱”:有时候WiFi没断,但信号极差,导致数据传输失败。这时候可以设置一个RSSI阈值,低于阈值就主动切换或重连。
嗯,说到心跳检测,我记得有一次做智能家居网关,设备在角落里,信号时好时坏。WiFi状态一直显示已连接,但数据就是发不出去。后来加了心跳检测,才发现问题。从那以后,我再也不相信 WiFi.status() 的“表面功夫”了。
4.6 小结
这一章,我们从一个简单的WiFi连接开始,逐步深入到扫描、状态机、自动重连。你想想看,这些机制组合起来,就是一个健壮的WiFi连接系统。
下一章,我们会在这个基础上,加入HTTP和WebSocket通信,让智能音箱真正“开口说话”。但在此之前,请确保你的WiFi连接足够稳定。毕竟,地基不牢,地动山摇。
好,今天就到这里。动手试试吧,把代码烧录到你的ESP32上,看看它能不能稳定地连上WiFi,并且在断网后自动恢复。有任何问题,欢迎在课程群里交流。