Skip to content

第九章:Wi-Fi 编程

9.1 Wi-Fi 工作模式

STA(Station)模式:连接到路由器,最常用
AP(Access Point)模式:自己当热点
STA+AP 混合模式:同时连接路由器和提供热点

9.2 STA 模式连接 Wi-Fi

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

#define WIFI_SSID       "你的WiFi名称"
#define WIFI_PASS       "你的WiFi密码"
#define WIFI_MAX_RETRY  5

#define WIFI_CONNECTED_BIT  BIT0
#define WIFI_FAIL_BIT       BIT1

static EventGroupHandle_t wifi_event_group;
static int retry_count = 0;
static const char *TAG = "WIFI";

// Wi-Fi 事件处理
static void wifi_event_handler(void *arg, esp_event_base_t event_base,
                                int32_t event_id, void *event_data)
{
    if (event_base == WIFI_EVENT) {
        switch (event_id) {
            case WIFI_EVENT_STA_START:
                esp_wifi_connect();
                break;

            case WIFI_EVENT_STA_DISCONNECTED:
                if (retry_count < WIFI_MAX_RETRY) {
                    esp_wifi_connect();
                    retry_count++;
                    ESP_LOGW(TAG, "重连中... (%d/%d)", retry_count, WIFI_MAX_RETRY);
                } else {
                    xEventGroupSetBits(wifi_event_group, WIFI_FAIL_BIT);
                    ESP_LOGE(TAG, "Wi-Fi 连接失败");
                }
                break;
        }
    } 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));
        retry_count = 0;
        xEventGroupSetBits(wifi_event_group, WIFI_CONNECTED_BIT);
    }
}

// 初始化并连接 Wi-Fi
esp_err_t wifi_connect(const char *ssid, const char *password)
{
    wifi_event_group = xEventGroupCreate();

    // 初始化网络接口
    ESP_ERROR_CHECK(esp_netif_init());
    ESP_ERROR_CHECK(esp_event_loop_create_default());
    esp_netif_create_default_wifi_sta();

    // 初始化 Wi-Fi
    wifi_init_config_t cfg = WIFI_INIT_CONFIG_DEFAULT();
    ESP_ERROR_CHECK(esp_wifi_init(&cfg));

    // 注册事件处理
    esp_event_handler_instance_t inst_wifi, inst_ip;
    ESP_ERROR_CHECK(esp_event_handler_instance_register(
        WIFI_EVENT, ESP_EVENT_ANY_ID, &wifi_event_handler, NULL, &inst_wifi));
    ESP_ERROR_CHECK(esp_event_handler_instance_register(
        IP_EVENT, IP_EVENT_STA_GOT_IP, &wifi_event_handler, NULL, &inst_ip));

    // 配置 Wi-Fi
    wifi_config_t wifi_cfg = {
        .sta = {
            .threshold.authmode = WIFI_AUTH_WPA2_PSK,
        },
    };
    strncpy((char *)wifi_cfg.sta.ssid,     ssid,     sizeof(wifi_cfg.sta.ssid));
    strncpy((char *)wifi_cfg.sta.password, password, sizeof(wifi_cfg.sta.password));

    ESP_ERROR_CHECK(esp_wifi_set_mode(WIFI_MODE_STA));
    ESP_ERROR_CHECK(esp_wifi_set_config(WIFI_IF_STA, &wifi_cfg));
    ESP_ERROR_CHECK(esp_wifi_start());

    // 等待连接结果
    EventBits_t bits = xEventGroupWaitBits(
        wifi_event_group,
        WIFI_CONNECTED_BIT | WIFI_FAIL_BIT,
        pdFALSE, pdFALSE, pdMS_TO_TICKS(10000));

    if (bits & WIFI_CONNECTED_BIT) {
        ESP_LOGI(TAG, "Wi-Fi 连接成功: %s", ssid);
        return ESP_OK;
    } else {
        ESP_LOGE(TAG, "Wi-Fi 连接失败");
        return ESP_FAIL;
    }
}

void app_main(void)
{
    nvs_flash_init();
    wifi_connect(WIFI_SSID, WIFI_PASS);
}

9.3 HTTP 客户端

c
#include "esp_http_client.h"

static const char *TAG = "HTTP";

// HTTP 事件处理
esp_err_t http_event_handler(esp_http_client_event_t *evt)
{
    static char *response_buf = NULL;
    static int   response_len = 0;

    switch (evt->event_id) {
        case HTTP_EVENT_ON_CONNECTED:
            ESP_LOGD(TAG, "HTTP 已连接");
            break;

        case HTTP_EVENT_ON_DATA:
            // 接收响应数据
            if (!esp_http_client_is_chunked_response(evt->client)) {
                if (response_buf == NULL) {
                    response_buf = malloc(evt->data_len + 1);
                    response_len = 0;
                }
                memcpy(response_buf + response_len, evt->data, evt->data_len);
                response_len += evt->data_len;
                response_buf[response_len] = '\0';
            }
            break;

        case HTTP_EVENT_ON_FINISH:
            if (response_buf) {
                ESP_LOGI(TAG, "响应: %s", response_buf);
                free(response_buf);
                response_buf = NULL;
                response_len = 0;
            }
            break;

        case HTTP_EVENT_DISCONNECTED:
            ESP_LOGD(TAG, "HTTP 断开");
            break;

        default:
            break;
    }
    return ESP_OK;
}

// GET 请求
void http_get(const char *url)
{
    esp_http_client_config_t config = {
        .url            = url,
        .event_handler  = http_event_handler,
        .timeout_ms     = 5000,
    };
    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));
    } else {
        ESP_LOGE(TAG, "HTTP GET 失败: %s", esp_err_to_name(err));
    }
    esp_http_client_cleanup(client);
}

// POST 请求(发送 JSON)
void http_post_json(const char *url, const char *json_body)
{
    esp_http_client_config_t config = {
        .url           = url,
        .event_handler = http_event_handler,
        .timeout_ms    = 5000,
    };
    esp_http_client_handle_t client = esp_http_client_init(&config);

    esp_http_client_set_method(client, HTTP_METHOD_POST);
    esp_http_client_set_header(client, "Content-Type", "application/json");
    esp_http_client_set_post_field(client, json_body, strlen(json_body));

    esp_err_t err = esp_http_client_perform(client);
    if (err == ESP_OK) {
        ESP_LOGI(TAG, "HTTP POST 完成,状态码: %d",
                 esp_http_client_get_status_code(client));
    }
    esp_http_client_cleanup(client);
}

9.4 MQTT 客户端(IoT 核心协议)

c
#include "mqtt_client.h"

#define MQTT_BROKER_URI  "mqtt://broker.emqx.io:1883"
#define MQTT_TOPIC_PUB   "esp32s3/sensor/data"
#define MQTT_TOPIC_SUB   "esp32s3/control/#"

static esp_mqtt_client_handle_t mqtt_client;
static const char *TAG = "MQTT";

// MQTT 事件处理
static void mqtt_event_handler(void *arg, esp_event_base_t base,
                                int32_t event_id, void *event_data)
{
    esp_mqtt_event_handle_t event = event_data;

    switch (event->event_id) {
        case MQTT_EVENT_CONNECTED:
            ESP_LOGI(TAG, "MQTT 已连接");
            // 订阅主题
            esp_mqtt_client_subscribe(mqtt_client, MQTT_TOPIC_SUB, 1);
            break;

        case MQTT_EVENT_DISCONNECTED:
            ESP_LOGW(TAG, "MQTT 断开,自动重连...");
            break;

        case MQTT_EVENT_SUBSCRIBED:
            ESP_LOGI(TAG, "订阅成功,msg_id=%d", event->msg_id);
            break;

        case MQTT_EVENT_DATA:
            // 收到消息
            ESP_LOGI(TAG, "收到消息:");
            ESP_LOGI(TAG, "  主题: %.*s", event->topic_len, event->topic);
            ESP_LOGI(TAG, "  数据: %.*s", event->data_len, event->data);
            break;

        case MQTT_EVENT_ERROR:
            ESP_LOGE(TAG, "MQTT 错误");
            break;

        default:
            break;
    }
}

void mqtt_init(void)
{
    esp_mqtt_client_config_t mqtt_cfg = {
        .broker.address.uri = MQTT_BROKER_URI,
        .credentials.client_id = "esp32s3_001",
    };
    mqtt_client = esp_mqtt_client_init(&mqtt_cfg);
    esp_mqtt_client_register_event(mqtt_client, ESP_EVENT_ANY_ID,
                                   mqtt_event_handler, NULL);
    esp_mqtt_client_start(mqtt_client);
}

// 发布传感器数据
void mqtt_publish_sensor(float temp, float humidity)
{
    char payload[128];
    snprintf(payload, sizeof(payload),
             "{\"temp\":%.1f,\"humidity\":%.1f,\"ts\":%lld}",
             temp, humidity, esp_timer_get_time() / 1000);

    int msg_id = esp_mqtt_client_publish(
        mqtt_client, MQTT_TOPIC_PUB, payload, 0, 1, 0);
    ESP_LOGI(TAG, "发布消息,msg_id=%d", msg_id);
}

9.5 AP 模式(配网热点)

c
void wifi_ap_init(const char *ssid, const char *password)
{
    ESP_ERROR_CHECK(esp_netif_init());
    ESP_ERROR_CHECK(esp_event_loop_create_default());
    esp_netif_create_default_wifi_ap();

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

    wifi_config_t ap_cfg = {
        .ap = {
            .max_connection = 4,
            .authmode       = WIFI_AUTH_WPA2_PSK,
        },
    };
    strncpy((char *)ap_cfg.ap.ssid,     ssid,     sizeof(ap_cfg.ap.ssid));
    strncpy((char *)ap_cfg.ap.password, password, sizeof(ap_cfg.ap.password));
    ap_cfg.ap.ssid_len = strlen(ssid);

    ESP_ERROR_CHECK(esp_wifi_set_mode(WIFI_MODE_AP));
    ESP_ERROR_CHECK(esp_wifi_set_config(WIFI_IF_AP, &ap_cfg));
    ESP_ERROR_CHECK(esp_wifi_start());

    ESP_LOGI(TAG, "AP 已启动: SSID=%s", ssid);
    ESP_LOGI(TAG, "AP IP: 192.168.4.1");
}

📝 第九章练习题

练习 9-1:Wi-Fi 连接(基础)

目标:掌握 Wi-Fi STA 模式

任务

  • 连接到家庭 Wi-Fi
  • 打印获得的 IP、网关、子网掩码
  • 每 5 秒 ping 一次 8.8.8.8,打印延迟
  • 断线后自动重连

练习 9-2:HTTP 天气查询(基础)

目标:HTTP GET + JSON 解析

任务

  • 调用免费天气 API(如 wttr.in)
  • 解析 JSON 响应(使用 cJSON 库)
  • 打印当前城市的温度、天气状况
  • 每 10 分钟更新一次

API 示例http://wttr.in/Beijing?format=j1

cJSON 使用

c
#include "cJSON.h"
cJSON *root = cJSON_Parse(response_str);
cJSON *temp = cJSON_GetObjectItem(root, "temp_C");
ESP_LOGI(TAG, "温度: %s°C", temp->valuestring);
cJSON_Delete(root);

练习 9-3:MQTT 传感器上报(进阶)

目标:MQTT 发布订阅

任务

  • 连接公共 MQTT Broker(broker.emqx.io)
  • 每 5 秒发布一次模拟传感器数据(温度、湿度随机值)
  • 订阅控制主题,接收 LED 控制命令
  • 消息格式:JSON

主题设计

发布:esp32s3/{设备ID}/sensor  → {"temp":25.3,"humi":60.1}
订阅:esp32s3/{设备ID}/control → {"led":"on"} 或 {"led":"off"}

练习 9-4:Web 服务器(进阶)

目标:HTTP 服务端

任务:在 ESP32-S3 上运行 HTTP 服务器:

  • GET / → 返回 HTML 页面,显示系统状态
  • GET /api/status → 返回 JSON 格式的系统信息
  • POST /api/led → 控制 LED(body: {"state":"on"}
  • 用手机浏览器访问 ESP32 的 IP 地址测试

使用 esp_http_server

c
#include "esp_http_server.h"
httpd_handle_t server;
httpd_config_t config = HTTPD_DEFAULT_CONFIG();
httpd_start(&server, &config);

练习 9-5:OTA 固件升级(挑战)

目标:Over-The-Air 固件更新

任务

  • 在电脑上搭建简单 HTTP 服务器(Python: python -m http.server
  • 实现 OTA 升级:ESP32 从 HTTP 服务器下载新固件并更新
  • 升级前验证固件版本,避免降级
  • 升级失败时自动回滚

关键 API

c
#include "esp_ota_ops.h"
#include "esp_https_ota.h"

esp_https_ota_config_t ota_config = {
    .http_config = &http_config,
};
esp_https_ota(&ota_config);

练习 9-6:思考题

  1. Wi-Fi 连接时为什么要用事件组而不是直接轮询?

  2. MQTT 的 QoS 0、1、2 分别是什么含义?IoT 传感器数据上报应该用哪个 QoS?

  3. ESP32-S3 同时运行 Wi-Fi 和 ADC2 会有冲突,这是为什么?如何解决?

  4. HTTP 和 MQTT 各适合什么场景?如果要实现实时控制(延迟 < 100ms),应该选哪个?


本章小结

知识点掌握程度自评
Wi-Fi STA 连接⬜⬜⬜⬜⬜
Wi-Fi 事件处理⬜⬜⬜⬜⬜
HTTP 客户端 GET/POST⬜⬜⬜⬜⬜
MQTT 发布订阅⬜⬜⬜⬜⬜
JSON 解析⬜⬜⬜⬜⬜
HTTP 服务器⬜⬜⬜⬜⬜

上一章← 存储与 NVS下一章BLE 蓝牙编程 →

个人知识库