Chapter 03

Wi-Fi 连接与网络

掌握 ESP32 的三种 Wi-Fi 工作模式、事件驱动联网与 HTTP/HTTPS 通信

Wi-Fi 工作模式

ESP32 的 Wi-Fi 驱动支持三种工作模式,可以根据项目需求灵活切换。理解这三种模式是设计 IoT 产品网络架构的基础。

STA(Station)
站点模式:ESP32 作为客户端连接到现有 Wi-Fi 路由器,获取 IP 地址后可访问局域网和互联网。最常见的 IoT 使用场景,如传感器上报、云端通信。
AP(Access Point)
热点模式:ESP32 自己作为无线接入点,其他设备(手机、平板)可连接到它。常用于设备初始配置(Provisioning),让用户通过手机浏览器输入家庭 Wi-Fi 密码。
APSTA(混合)
同时作为 STA 和 AP。ESP32 一方面连接上级路由器,另一方面对下游设备提供 Wi-Fi 接入,可充当简单的 Wi-Fi 中继或家庭网关。
Wi-Fi 事件系统
ESP-IDF 使用事件循环(event loop)异步处理 Wi-Fi 状态变化:WIFI_EVENT_STA_CONNECTED、IP_EVENT_STA_GOT_IP、WIFI_EVENT_STA_DISCONNECTED 等,程序通过注册回调函数响应。
lwIP
轻量级 IP 协议栈(lightweight IP),ESP-IDF 内置,实现了 TCP/UDP/DNS/DHCP/IPv4/IPv6 等协议,上层的 HTTP、MQTT 等组件都基于它工作。

Wi-Fi STA 连接流程

app_main() │ ▼ esp_netif_init() ← 初始化网络接口(必须最先调用) │ ▼ esp_event_loop_create_default() ← 创建默认事件循环 │ ▼ esp_netif_create_default_wifi_sta() ← 创建 STA 网络接口 │ ▼ esp_wifi_init(cfg) ← 初始化 Wi-Fi 驱动,传入配置 │ ▼ 注册事件处理函数 ← WIFI_EVENT / IP_EVENT │ ▼ esp_wifi_set_mode(WIFI_MODE_STA) esp_wifi_set_config(SSID/Password) esp_wifi_start() ← 触发 WIFI_EVENT_STA_START │ ▼ [事件: WIFI_EVENT_STA_START] │ ▼ esp_wifi_connect() ← 开始扫描并连接 │ ▼ [事件: WIFI_EVENT_STA_CONNECTED] │ ▼ DHCP 获取 IP ← lwIP 自动处理 │ ▼ [事件: IP_EVENT_STA_GOT_IP] ← ★ 此时可以进行网络通信 │ ▼ 业务代码(HTTP/MQTT/WebSocket...)

STA 模式完整代码

#include "esp_wifi.h"
#include "esp_event.h"
#include "esp_log.h"
#include "esp_netif.h"
#include "freertos/event_groups.h"
#include "nvs_flash.h"

#define WIFI_SSID       "YourSSID"
#define WIFI_PASSWORD   "YourPassword"
#define WIFI_RETRY_MAX  5
#define WIFI_CONNECTED_BIT  BIT0
#define WIFI_FAIL_BIT       BIT1

static const char *TAG = "WIFI";
static EventGroupHandle_t s_wifi_event_group;
static int s_retry_num = 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_START) {
        esp_wifi_connect();
    } else if (event_base == WIFI_EVENT && event_id == WIFI_EVENT_STA_DISCONNECTED) {
        if (s_retry_num < WIFI_RETRY_MAX) {
            esp_wifi_connect();
            s_retry_num++;
            ESP_LOGW(TAG, "重连中... 第 %d 次", s_retry_num);
        } else {
            xEventGroupSetBits(s_wifi_event_group, WIFI_FAIL_BIT);
        }
    } else if (event_base == IP_EVENT && event_id == IP_EVENT_STA_GOT_IP) {
        ip_event_got_ip_t *event = (ip_event_got_ip_t *)event_data;
        ESP_LOGI(TAG, "获取 IP: " IPSTR, IP2STR(&event->ip_info.ip));
        s_retry_num = 0;
        xEventGroupSetBits(s_wifi_event_group, WIFI_CONNECTED_BIT);
    }
}

void wifi_init_sta(void)
{
    s_wifi_event_group = xEventGroupCreate();
    ESP_ERROR_CHECK(esp_netif_init());
    ESP_ERROR_CHECK(esp_event_loop_create_default());
    esp_netif_create_default_wifi_sta();

    wifi_init_config_t cfg = WIFI_INIT_CONFIG_DEFAULT();
    ESP_ERROR_CHECK(esp_wifi_init(&cfg));

    esp_event_handler_instance_register(WIFI_EVENT, ESP_EVENT_ANY_ID,
                                         &event_handler, NULL, NULL);
    esp_event_handler_instance_register(IP_EVENT, IP_EVENT_STA_GOT_IP,
                                         &event_handler, NULL, NULL);

    wifi_config_t wifi_config = {
        .sta = {
            .ssid     = WIFI_SSID,
            .password = WIFI_PASSWORD,
        },
    };
    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());

    /* 等待连接成功或彻底失败 */
    EventBits_t bits = xEventGroupWaitBits(s_wifi_event_group,
        WIFI_CONNECTED_BIT | WIFI_FAIL_BIT, pdFALSE, pdFALSE, portMAX_DELAY);

    if (bits & WIFI_CONNECTED_BIT) {
        ESP_LOGI(TAG, "Wi-Fi 已连接!");
    } else {
        ESP_LOGE(TAG, "Wi-Fi 连接失败");
    }
}

void app_main(void)
{
    nvs_flash_init();    // Wi-Fi 驱动需要 NVS 存储校准数据
    wifi_init_sta();
    /* 此后可开始网络通信 */
}

HTTP 客户端

ESP-IDF 内置 esp_http_client 组件,支持 HTTP 和 HTTPS、GET/POST/PUT 等方法、分块传输等功能,是调用 REST API 和云端服务的标准方式。

#include "esp_http_client.h"
#include "esp_log.h"

static const char *TAG = "HTTP";

/* 事件处理回调(可选) */
static esp_err_t http_event_handler(esp_http_client_event_t *evt)
{
    switch(evt->event_id) {
        case HTTP_EVENT_ON_DATA:
            /* 接收到数据时回调,evt->data 和 evt->data_len */
            ESP_LOGI(TAG, "收到 %d 字节", evt->data_len);
            break;
        default: break;
    }
    return ESP_OK;
}

void http_get_example(void)
{
    esp_http_client_config_t config = {
        .url            = "http://httpbin.org/get",
        .event_handler  = http_event_handler,
        .buffer_size    = 1024,
    };
    esp_http_client_handle_t client = esp_http_client_init(&config);
    esp_err_t err = esp_http_client_perform(client);

    if (err == ESP_OK) {
        ESP_LOGI(TAG, "HTTP GET 状态码: %d,内容长度: %lld",
            esp_http_client_get_status_code(client),
            esp_http_client_get_content_length(client));
    }
    esp_http_client_cleanup(client);
}

void http_post_json(void)
{
    const char *post_data = "{\"temperature\":25.6,\"humidity\":65.3}";

    esp_http_client_config_t config = {
        .url    = "http://your-server.com/api/data",
        .method = HTTP_METHOD_POST,
    };
    esp_http_client_handle_t client = esp_http_client_init(&config);
    esp_http_client_set_header(client, "Content-Type", "application/json");
    esp_http_client_set_post_field(client, post_data, strlen(post_data));
    esp_http_client_perform(client);
    esp_http_client_cleanup(client);
}

HTTPS 与证书验证

生产环境中必须使用 HTTPS 来保护数据传输。ESP-IDF 使用 mbedTLS 实现 TLS,证书验证是防止中间人攻击的关键。

不要跳过证书验证!

有些教程为图方便设置 .skip_cert_common_name_check = true 或不提供 CA 证书,这相当于不做 HTTPS 验证,与 HTTP 一样不安全。IoT 设备在生产中必须正确配置服务器 CA 证书。

/* 将服务器的 CA 证书嵌入固件(CMakeLists.txt 中添加 target_add_binary_data) */
extern const char server_cert_pem_start[] asm("_binary_server_cert_pem_start");
extern const char server_cert_pem_end[]   asm("_binary_server_cert_pem_end");

void https_get(void)
{
    esp_http_client_config_t config = {
        .url          = "https://api.example.com/sensors",
        .cert_pem     = server_cert_pem_start,   // 服务器 CA 证书
        .transport_type = HTTP_TRANSPORT_OVER_SSL,
    };
    esp_http_client_handle_t client = esp_http_client_init(&config);
    esp_http_client_perform(client);
    esp_http_client_cleanup(client);
}

OTA 空中升级原理

OTA(Over-The-Air)升级让 ESP32 无需物理连接即可更新固件,是 IoT 产品运维的核心功能。ESP-IDF 通过分区表实现:Flash 中划分两个 OTA 分区(ota_0 和 ota_1),轮流烧录新固件,验证成功后切换启动。

Flash 分区表(OTA 典型布局): ┌────────────────────────┐ 0x000000 │ bootloader (32KB) │ 引导加载程序,检查 OTA 状态 ├────────────────────────┤ 0x008000 │ nvs (24KB) │ 非易失性存储(Wi-Fi/配置) ├────────────────────────┤ 0x00e000 │ otadata (8KB) │ 记录当前活动分区(ota_0/ota_1) ├────────────────────────┤ 0x010000 │ ota_0 (1.9MB) │ ← 当前运行固件 ├────────────────────────┤ 0x1f0000 │ ota_1 (1.9MB) │ ← OTA 下载目标分区 ├────────────────────────┤ 0x3d0000 │ spiffs (192KB) │ 文件系统 └────────────────────────┘ 0x400000 OTA 升级流程: 1. 连接 HTTP 服务器,下载新固件 2. 边下载边写入 ota_1 分区 3. 校验完整性(SHA256) 4. 更新 otadata,标记 ota_1 为下一次启动 5. 重启,bootloader 从 ota_1 启动 6. 启动成功后调用 esp_ota_mark_app_valid_cancel_rollback() 7. 若启动失败,bootloader 自动回滚到 ota_0
#include "esp_ota_ops.h"
#include "esp_http_client.h"

#define OTA_URL "https://your-server.com/firmware.bin"
#define OTA_BUF_SIZE 4096

void ota_task(void *arg)
{
    esp_ota_handle_t update_handle;
    const esp_partition_t *update_partition = esp_ota_get_next_update_partition(NULL);

    esp_ota_begin(update_partition, OTA_WITH_SEQUENTIAL_WRITES, &update_handle);

    /* 下载并写入固件... */
    char buf[OTA_BUF_SIZE];
    int data_read;
    /* ... HTTP 读取循环 ... */
    esp_ota_write(update_handle, buf, data_read);

    esp_ota_end(update_handle);
    esp_ota_set_boot_partition(update_partition);
    esp_restart();
    vTaskDelete(NULL);
}
使用 esp-idf-lib 的 OTA 示例更完整

ESP-IDF 官方提供了 examples/system/ota/native_ota_example,包含完整的 HTTPS OTA 流程,建议直接参考该示例,它处理了分块下载、错误回滚等边界情况。