4、WiFi基础与Station模式:WiFi协议栈简介、ESP32连接路由器、WiFi事件处理、自动重连机制

好,咱们进入WiFi部分。说实话,在智能家居网关里,WiFi是那个「承上启下」的角色——它负责跟你的手机、云服务器打交道。Zigbee和蓝牙搞定设备端,WiFi搞定网络端。这一章,我带你把ESP32的WiFi Station模式玩透。

4.1 WiFi协议栈,到底是个啥?

很多初学者一听到「协议栈」三个字就头大。其实说白了,它就是一套分层的通信规则。你想想看,你的手机和路由器之间要传数据,总得有人管「怎么连上」「数据包怎么拆」「出错怎么办」这些事吧?

ESP32的WiFi协议栈,底层是硬件MAC层,往上走是TCP/IP协议栈(LwIP),再往上才是我们写的应用代码。我个人习惯把这三层记成:物理连接 → 网络通信 → 业务逻辑

核心要点:ESP32的WiFi驱动已经封装好了,我们不需要碰底层。你只需要调用API,告诉它「我要连哪个路由器,密码是多少」就行。但理解分层结构,对排查问题很有帮助。

我在项目中遇到过一个问题:设备连上WiFi但就是发不出数据。查了半天,发现是LwIP的内存池配小了。你看,不懂协议栈分层,这种问题根本无从下手。

4.2 Station模式:让ESP32连上路由器

Station模式,就是让ESP32扮演一个「客户端」角色,去连接你家里的无线路由器。这是最常用的模式,没有之一。

代码其实很简单,我直接给你看核心流程:

#include "esp_wifi.h"
#include "esp_event.h"
#include "nvs_flash.h"

void wifi_init_sta(void)
{
    // 1. 初始化NVS(非易失性存储)
    esp_err_t ret = nvs_flash_init();
    if (ret == ESP_ERR_NVS_NO_FREE_PAGES || ret == ESP_ERR_NVS_NEW_VERSION_FOUND) {
        ESP_ERROR_CHECK(nvs_flash_erase());
        ret = nvs_flash_init();
    }
    ESP_ERROR_CHECK(ret);

    // 2. 初始化网络接口
    ESP_ERROR_CHECK(esp_netif_init());

    // 3. 创建默认事件循环
    ESP_ERROR_CHECK(esp_event_loop_create_default());

    // 4. 创建WiFi Station网络接口
    esp_netif_create_default_wifi_sta();

    // 5. 初始化WiFi驱动
    wifi_init_config_t cfg = WIFI_INIT_CONFIG_DEFAULT();
    ESP_ERROR_CHECK(esp_wifi_init(&cfg));

    // 6. 配置并启动
    wifi_config_t wifi_config = {
        .sta = {
            .ssid = "你的路由器SSID",
            .password = "你的路由器密码",
            .threshold.authmode = WIFI_AUTH_WPA2_PSK,
        },
    };
    ESP_ERROR_CHECK(esp_wifi_set_mode(WIFI_MODE_STA));
    ESP_ERROR_CHECK(esp_wifi_set_config(WIFI_IF_STA, &wifi_config));
    ESP_ERROR_CHECK(esp_wifi_start());

    ESP_LOGI(TAG, "WiFi初始化完成,正在连接...");
}

嗯,这里要注意:NVS初始化不能省。WiFi的配置信息(比如MAC地址、校准数据)都存在NVS里。我第一次用ESP32时跳过了这步,结果WiFi死活起不来,折腾了半小时才发现。

4.3 WiFi事件处理:别傻等,要监听

你可能会想:「我调用完esp_wifi_start(),是不是就自动连上了?」不是的。WiFi连接是异步操作,你需要通过事件处理来知道「连上了没」「断开了没」。

ESP32的事件机制,说白了就是「你注册一个回调函数,系统有消息就通知你」。我个人觉得这个设计很优雅,比轮询舒服多了。

static void event_handler(void* arg, esp_event_base_t event_base,
                          int32_t event_id, void* event_data)
{
    if (event_base == WIFI_EVENT && event_id == WIFI_EVENT_STA_START) {
        // WiFi启动成功,开始连接
        esp_wifi_connect();
        ESP_LOGI(TAG, "WiFi启动,开始连接路由器");
    }
    else if (event_base == WIFI_EVENT && event_id == WIFI_EVENT_STA_DISCONNECTED) {
        // 断开了!需要重连
        ESP_LOGI(TAG, "WiFi断开,5秒后重连...");
        esp_wifi_connect();
    }
    else if (event_base == IP_EVENT && event_id == IP_EVENT_STA_GOT_IP) {
        // 拿到IP地址了,连接成功!
        ip_event_got_ip_t* event = (ip_event_got_ip_t*) event_data;
        ESP_LOGI(TAG, "获取到IP: " IPSTR, IP2STR(&event->ip_info.ip));
    }
}

// 注册事件处理器
ESP_ERROR_CHECK(esp_event_handler_register(WIFI_EVENT, ESP_EVENT_ANY_ID, &event_handler, NULL));
ESP_ERROR_CHECK(esp_event_handler_register(IP_EVENT, IP_EVENT_STA_GOT_IP, &event_handler, NULL));

你看,三个关键事件:

  • WIFI_EVENT_STA_START:WiFi驱动启动完毕,这时候才能调用esp_wifi_connect()
  • WIFI_EVENT_STA_DISCONNECTED:连接断开,触发重连
  • IP_EVENT_STA_GOT_IP:拿到IP地址,这才是真正「连上了」的标志

我的经验:千万不要在WIFI_EVENT_STA_START里做太多耗时操作。我曾经在里面加了个延时,结果导致事件循环卡死,WiFi连不上。事件回调里只做「轻量级」操作,重活交给任务队列。

4.4 自动重连机制:别让设备掉线

智能家居设备最怕什么?掉线。你想想看,半夜灯控失灵,用户肯定骂娘。所以自动重连是必须的。

ESP32的WiFi驱动其实自带了一个简单的重连机制——调用esp_wifi_set_auto_connect(true)后,断线时会自动尝试重连。但说实话,这个机制太「傻」了,它不会做退避,断了就猛连,反而可能把路由器搞崩。

我建议自己实现一个带退避的重连逻辑:

#define MAX_RETRY 10          // 最大重试次数
#define BASE_RETRY_MS 1000    // 初始重试间隔(1秒)

static int retry_count = 0;

static void event_handler(void* arg, esp_event_base_t event_base,
                          int32_t event_id, void* event_data)
{
    if (event_base == WIFI_EVENT && event_id == WIFI_EVENT_STA_DISCONNECTED) {
        if (retry_count < MAX_RETRY) {
            // 指数退避:1s, 2s, 4s, 8s...
            int delay_ms = BASE_RETRY_MS << retry_count;
            if (delay_ms > 30000) delay_ms = 30000; // 最大30秒
            
            ESP_LOGI(TAG, "第%d次重连,等待%dms", retry_count + 1, delay_ms);
            vTaskDelay(pdMS_TO_TICKS(delay_ms));
            esp_wifi_connect();
            retry_count++;
        } else {
            ESP_LOGE(TAG, "重连次数耗尽,进入深度休眠");
            // 这里可以触发报警或进入低功耗模式
        }
    }
    else if (event_base == IP_EVENT && event_id == IP_EVENT_STA_GOT_IP) {
        // 连上了,重置重试计数
        retry_count = 0;
        ESP_LOGI(TAG, "重连成功,重置计数器");
    }
}

注意:指数退避不是越复杂越好。我曾经设计过一个「动态退避算法」,根据信号强度调整间隔,结果测试时发现逻辑太复杂,反而引入了bug。后来老老实实用固定指数退避,稳定运行两年没出过问题。有时候,简单就是可靠。

4.5 实战中的几个坑

最后,我把自己踩过的坑列出来,你遇到了直接对照:

问题 现象 解决办法
WiFi初始化失败 esp_wifi_init返回错误 检查NVS是否初始化,WiFi堆大小是否足够
连不上路由器 一直触发DISCONNECTED事件 检查SSID/密码,确认路由器是2.4GHz频段
拿到IP后很快断开 DHCP租约问题 设置静态IP,或缩短DHCP请求超时
重连越来越慢 重试次数耗尽 检查退避逻辑,确认retry_count在成功时清零

嗯,这一章的内容就这些。WiFi Station模式是网关的基础,你把它吃透了,后面的TCP通信、MQTT连接就顺理成章。下一章我们聊ESP32的SoftAP模式——让设备自己变成热点,这在配网场景里特别有用。