Appearance
第九章: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:思考题
Wi-Fi 连接时为什么要用事件组而不是直接轮询?
MQTT 的 QoS 0、1、2 分别是什么含义?IoT 传感器数据上报应该用哪个 QoS?
ESP32-S3 同时运行 Wi-Fi 和 ADC2 会有冲突,这是为什么?如何解决?
HTTP 和 MQTT 各适合什么场景?如果要实现实时控制(延迟 < 100ms),应该选哪个?
本章小结
| 知识点 | 掌握程度自评 |
|---|---|
| Wi-Fi STA 连接 | ⬜⬜⬜⬜⬜ |
| Wi-Fi 事件处理 | ⬜⬜⬜⬜⬜ |
| HTTP 客户端 GET/POST | ⬜⬜⬜⬜⬜ |
| MQTT 发布订阅 | ⬜⬜⬜⬜⬜ |
| JSON 解析 | ⬜⬜⬜⬜⬜ |
| HTTP 服务器 | ⬜⬜⬜⬜⬜ |
上一章:← 存储与 NVS下一章:BLE 蓝牙编程 →