【ESP32-S3 从入门到精通-06】2026 最新 Wi-Fi 网络开发与配网技术全实战(Station/AP/TCP/UDP/SmartConfig)
写在前面
大家好,我是 EmbeddedCore。上一讲我们学习了 FreeRTOS 实时操作系统,掌握了多任务编程、任务间通信与同步的核心技术。从这一讲开始,我们将正式进入物联网的核心 ——网络开发。
Wi-Fi 是 ESP32-S3 最具竞争力的功能之一,也是物联网设备连接云端、实现智能化的基础。无论是远程控制设备、数据上传云端,还是固件 OTA 升级,都离不开 Wi-Fi 网络的支持。
本讲将从最基础的 Wi-Fi 连接讲起,一步步带你掌握 ESP32-S3 的所有 Wi-Fi 相关功能。我们不仅会学习如何连接路由器和创建热点,还会深入学习 TCP/UDP 网络通信、HTTP 协议以及各种智能配网技术。学完本讲,你将能够独立开发出能够连接互联网的物联网设备。
一、Wi-Fi 基础与 ESP32-S3 Wi-Fi 架构
1.1 Wi-Fi 基本概念
Wi-Fi 是一种基于 IEEE 802.11 标准的无线局域网技术,它使用 2.4GHz 和 5GHz 频段进行数据传输。ESP32-S3 支持 Wi-Fi 4(802.11b/g/n)标准,最高传输速率可达 150Mbps。
1.2 ESP32-S3 Wi-Fi 工作模式
ESP32-S3 支持三种 Wi-Fi 工作模式:
表格
| 工作模式 | 描述 | 适用场景 |
|---|---|---|
| Station 模式(站点模式) | ESP32-S3 作为一个无线终端,连接到路由器等 Wi-Fi 热点 | 大多数物联网应用,设备需要连接互联网 |
| AP 模式(接入点模式) | ESP32-S3 自己创建一个 Wi-Fi 热点,其他设备可以连接到它 | 智能配网、设备直连、无路由器环境 |
| 混合模式(Station+AP) | ESP32-S3 同时作为站点和接入点 | 设备在连接路由器的同时,也允许其他设备连接自己 |
1.3 ESP-IDF Wi-Fi 事件驱动架构
ESP-IDF 的 Wi-Fi 子系统采用事件驱动架构,这是 ESP-IDF v5.0 之后最重要的变化之一。当 Wi-Fi 状态发生变化时(如连接成功、断开连接、获取 IP 地址等),系统会发送相应的事件,我们只需要注册事件处理函数来响应这些事件即可。
这种架构的优点是:
- 代码结构清晰,逻辑分明
- 不需要在主循环中轮询 Wi-Fi 状态
- 响应及时,效率高
ESP-IDF 中最重要的两个事件组是:
WIFI_EVENT:Wi-Fi 相关事件,如WIFI_EVENT_STA_CONNECTED、WIFI_EVENT_STA_DISCONNECTED等IP_EVENT:IP 相关事件,如IP_EVENT_STA_GOT_IP、IP_EVENT_STA_LOST_IP等
二、Wi-Fi Station 模式开发(连接路由器)
Station 模式是最常用的 Wi-Fi 工作模式,它允许 ESP32-S3 连接到现有的 Wi-Fi 路由器,从而访问互联网。
2.1 Wi-Fi Station 初始化流程
ESP32-S3 Wi-Fi Station 模式的初始化流程如下:
- 初始化 NVS Flash(用于存储 Wi-Fi 配置信息)
- 初始化网络接口
- 创建默认事件循环
- 创建默认 Wi-Fi Station 网络接口
- 初始化 Wi-Fi 驱动
- 注册 Wi-Fi 事件和 IP 事件处理函数
- 配置 Wi-Fi 参数(SSID 和密码)
- 设置 Wi-Fi 模式为 Station 模式
- 启动 Wi-Fi 驱动
2.2 实战项目 1:Wi-Fi 自动连接与重连
这是最基础的 Wi-Fi 实验,我们将实现 ESP32-S3 自动连接到指定的 Wi-Fi 路由器,并在断开连接时自动重连。
完整代码:
c
运行
#include <stdio.h> #include "freertos/FreeRTOS.h" #include "freertos/task.h" #include "esp_wifi.h" #include "esp_event.h" #include "nvs_flash.h" #include "esp_log.h" static const char *TAG = "WIFI_STA"; #define WIFI_SSID "你的Wi-Fi名称" #define WIFI_PASS "你的Wi-Fi密码" // 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 && event_id == WIFI_EVENT_STA_START) { // Wi-Fi启动完成,开始连接 ESP_LOGI(TAG, "Wi-Fi started, connecting to %s...", WIFI_SSID); esp_wifi_connect(); } else if (event_base == WIFI_EVENT && event_id == WIFI_EVENT_STA_CONNECTED) { // 连接成功 ESP_LOGI(TAG, "Connected to %s successfully", WIFI_SSID); } else if (event_base == WIFI_EVENT && event_id == WIFI_EVENT_STA_DISCONNECTED) { // 连接断开,自动重连 ESP_LOGW(TAG, "Wi-Fi disconnected, retrying..."); 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, "Got IP address: " IPSTR, IP2STR(&event->ip_info.ip)); ESP_LOGI(TAG, "Gateway: " IPSTR, IP2STR(&event->ip_info.gw)); ESP_LOGI(TAG, "Netmask: " IPSTR, IP2STR(&event->ip_info.netmask)); } } void wifi_init_sta(void) { // 1. 初始化NVS Flash 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. 创建默认Wi-Fi Station网络接口 esp_netif_create_default_wifi_sta(); // 5. 初始化Wi-Fi驱动 wifi_init_config_t cfg = WIFI_INIT_CONFIG_DEFAULT(); ESP_ERROR_CHECK(esp_wifi_init(&cfg)); // 6. 注册事件处理函数 ESP_ERROR_CHECK(esp_event_handler_register(WIFI_EVENT, ESP_EVENT_ANY_ID, &wifi_event_handler, NULL)); ESP_ERROR_CHECK(esp_event_handler_register(IP_EVENT, IP_EVENT_STA_GOT_IP, &wifi_event_handler, NULL)); // 7. 配置Wi-Fi参数 wifi_config_t wifi_config = { .sta = { .ssid = WIFI_SSID, .password = WIFI_PASS, .threshold.authmode = WIFI_AUTH_WPA2_PSK, // 只连接WPA2加密的热点 }, }; // 8. 设置Wi-Fi模式为Station模式 ESP_ERROR_CHECK(esp_wifi_set_mode(WIFI_MODE_STA)); // 9. 配置Wi-Fi参数 ESP_ERROR_CHECK(esp_wifi_set_config(WIFI_IF_STA, &wifi_config)); // 10. 启动Wi-Fi ESP_ERROR_CHECK(esp_wifi_start()); ESP_LOGI(TAG, "Wi-Fi Station initialization completed"); } void app_main(void) { wifi_init_sta(); }代码说明:
- 我们使用了 ESP-IDF 的日志系统(
ESP_LOGI、ESP_LOGW)来输出调试信息,比printf更专业 - 添加了自动重连功能,当 Wi-Fi 断开连接时会自动尝试重新连接
- 设置了认证模式为
WIFI_AUTH_WPA2_PSK,只连接安全的 WPA2 加密热点 - 打印了详细的网络信息,包括 IP 地址、网关和子网掩码
实验步骤:
- 将代码中的
WIFI_SSID和WIFI_PASS修改为你自己的 Wi-Fi 名称和密码 - 编译烧录代码到 ESP32-S3 开发板
- 打开串口监控,你将看到类似以下的输出:
plaintext
I (27) wifi:wifi driver task: 3ffc1d88, prio:23, stack:3584, core=0 I (31) system_api: Base MAC address is not set, read default base MAC address from BLK0 of EFUSE I (41) wifi:wifi firmware version: 7d240d5 I (45) wifi:wifi certification version: v7.0 I (49) wifi:config NVS flash: enabled I (53) wifi:config nano formating: disabled I (57) wifi:Init data frame dynamic rx buffer num: 32 I (62) wifi:Init management frame dynamic rx buffer num: 32 I (67) wifi:Init management frame dynamic tx buffer num: 32 I (72) wifi:Init static rx buffer size: 1600 I (76) wifi:Init static rx buffer num: 10 I (80) wifi:Init dynamic rx buffer num: 32 I (84) wifi_init: rx ba win: 6 I (88) wifi_init: tcpip mbox: 32 I (92) wifi_init: udp mbox: 6 I (96) wifi_init: tcp mbox: 6 I (100) wifi_init: tcp tx win: 5744 I (104) wifi_init: tcp rx win: 5744 I (108) wifi_init: tcp mss: 1440 I (112) wifi_init: WiFi IRAM OP enabled I (116) wifi_init: WiFi RX IRAM OP enabled I (121) phy_init: phy_version 4670,phy0 Jun 21 2025,13:29:07 I (130) wifi:set rx active PTI: 0, rx ack PTI: 0, and default PTI: 0 I (136) wifi:mode : sta (7c:df:a1:xx:xx:xx) I (140) wifi:enable tsf I (143) WIFI_STA: Wi-Fi started, connecting to your_ssid... I (148) wifi:new:<1,0>, old:<1,0>, ap:<255,255>, sta:<1,0>, prof:1 I (856) wifi:state: init -> auth (b0) I (860) wifi:state: auth -> assoc (0) I (864) wifi:state: assoc -> run (10) I (872) wifi:connected with your_ssid, aid = 1, channel 1, BW20, bssid = xx:xx:xx:xx:xx:xx I (881) wifi:security: WPA2-PSK, phy: bgn, rssi: -58 I (886) WIFI_STA: Connected to your_ssid successfully I (891) wifi:pm start, type: 1 I (957) wifi:AP's beacon interval = 102400 us, DTIM period = 1 I (1456) esp_netif_handlers: sta ip: 192.168.1.100, mask: 255.255.255.0, gw: 192.168.1.1 I (1465) WIFI_STA: Got IP address: 192.168.1.100 I (1471) WIFI_STA: Gateway: 192.168.1.1 I (1476) WIFI_STA: Netmask: 255.255.255.0
恭喜你!你的 ESP32-S3 已经成功连接到 Wi-Fi 网络了!
2.3 Wi-Fi 扫描功能
ESP32-S3 还支持 Wi-Fi 扫描功能,可以扫描周围所有可用的 Wi-Fi 热点。
Wi-Fi 扫描代码:
c
运行
void wifi_scan(void) { ESP_LOGI(TAG, "Scanning Wi-Fi networks..."); wifi_scan_config_t scan_config = { .ssid = NULL, .bssid = NULL, .channel = 0, .show_hidden = true }; ESP_ERROR_CHECK(esp_wifi_scan_start(&scan_config, true)); uint16_t ap_count = 0; ESP_ERROR_CHECK(esp_wifi_scan_get_ap_num(&ap_count)); wifi_ap_record_t *ap_records = (wifi_ap_record_t *)malloc(sizeof(wifi_ap_record_t) * ap_count); ESP_ERROR_CHECK(esp_wifi_scan_get_ap_records(&ap_count, ap_records)); ESP_LOGI(TAG, "Found %d Wi-Fi networks:", ap_count); for(int i = 0; i < ap_count; i++) { ESP_LOGI(TAG, "%d. SSID: %s, Channel: %d, RSSI: %d dBm", i+1, ap_records[i].ssid, ap_records[i].primary, ap_records[i].rssi); } free(ap_records); }三、Wi-Fi AP 模式开发(创建热点)
AP 模式允许 ESP32-S3 自己创建一个 Wi-Fi 热点,其他设备(如手机、电脑)可以连接到这个热点,实现设备之间的直接通信。
3.1 实战项目 2:创建 Wi-Fi 热点
完整代码:
c
运行
#include <stdio.h> #include "freertos/FreeRTOS.h" #include "freertos/task.h" #include "esp_wifi.h" #include "esp_event.h" #include "nvs_flash.h" #include "esp_log.h" #include "esp_netif.h" static const char *TAG = "WIFI_AP"; #define AP_SSID "ESP32-S3-AP" #define AP_PASS "12345678" #define AP_CHANNEL 1 #define AP_MAX_CONN 4 static void wifi_event_handler(void* arg, esp_event_base_t event_base, int32_t event_id, void* event_data) { if (event_id == WIFI_EVENT_AP_STACONNECTED) { wifi_event_ap_staconnected_t* event = (wifi_event_ap_staconnected_t*) event_data; ESP_LOGI(TAG, "Station " MACSTR " connected, AID: %d", MAC2STR(event->mac), event->aid); } else if (event_id == WIFI_EVENT_AP_STADISCONNECTED) { wifi_event_ap_stadisconnected_t* event = (wifi_event_ap_stadisconnected_t*) event_data; ESP_LOGI(TAG, "Station " MACSTR " disconnected, AID: %d", MAC2STR(event->mac), event->aid); } } void wifi_init_ap(void) { ESP_ERROR_CHECK(nvs_flash_init()); 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)); ESP_ERROR_CHECK(esp_event_handler_register(WIFI_EVENT, ESP_EVENT_ANY_ID, &wifi_event_handler, NULL)); wifi_config_t wifi_config = { .ap = { .ssid = AP_SSID, .ssid_len = strlen(AP_SSID), .password = AP_PASS, .channel = AP_CHANNEL, .authmode = WIFI_AUTH_WPA2_PSK, .max_connection = AP_MAX_CONN, }, }; ESP_ERROR_CHECK(esp_wifi_set_mode(WIFI_MODE_AP)); ESP_ERROR_CHECK(esp_wifi_set_config(WIFI_IF_AP, &wifi_config)); ESP_ERROR_CHECK(esp_wifi_start()); ESP_LOGI(TAG, "Wi-Fi AP started"); ESP_LOGI(TAG, "SSID: %s", AP_SSID); ESP_LOGI(TAG, "Password: %s", AP_PASS); ESP_LOGI(TAG, "Channel: %d", AP_CHANNEL); ESP_LOGI(TAG, "Max connections: %d", AP_MAX_CONN); } void app_main(void) { wifi_init_ap(); }实验现象:
- 编译烧录代码后,ESP32-S3 会创建一个名为 "ESP32-S3-AP" 的 Wi-Fi 热点
- 你可以用手机或电脑搜索并连接这个热点,密码是 "12345678"
- 当有设备连接或断开时,串口会打印相应的信息
四、TCP/UDP 网络通信
Wi-Fi 连接成功后,我们就可以进行网络通信了。TCP 和 UDP 是互联网中最常用的两种传输层协议。
4.1 TCP vs UDP
表格
| 特性 | TCP | UDP |
|---|---|---|
| 连接方式 | 面向连接 | 无连接 |
| 可靠性 | 可靠,保证数据按序到达 | 不可靠,不保证数据到达和顺序 |
| 速度 | 较慢 | 较快 |
| 适用场景 | 文件传输、网页浏览、邮件 | 视频通话、语音通话、实时数据传输 |
4.2 实战项目 3:TCP 服务器
我们将创建一个 TCP 服务器,监听端口 8080,接收客户端发送的数据并原样返回(回显服务器)。
完整代码:
c
运行
#include <stdio.h> #include <string.h> #include "freertos/FreeRTOS.h" #include "freertos/task.h" #include "esp_wifi.h" #include "esp_event.h" #include "nvs_flash.h" #include "esp_log.h" #include "lwip/sockets.h" #include "lwip/netdb.h" static const char *TAG = "TCP_SERVER"; #define WIFI_SSID "你的Wi-Fi名称" #define WIFI_PASS "你的Wi-Fi密码" #define TCP_PORT 8080 #define BUF_SIZE 1024 // Wi-Fi初始化代码(同上,此处省略) void wifi_init_sta(void) { ... } void tcp_server_task(void *pvParameters) { char rx_buffer[BUF_SIZE]; char addr_str[128]; int addr_family = AF_INET; int ip_protocol = IPPROTO_IP; struct sockaddr_in dest_addr; dest_addr.sin_addr.s_addr = htonl(INADDR_ANY); dest_addr.sin_family = AF_INET; dest_addr.sin_port = htons(TCP_PORT); // 创建socket int listen_sock = socket(addr_family, SOCK_STREAM, ip_protocol); if(listen_sock < 0) { ESP_LOGE(TAG, "Unable to create socket: errno %d", errno); vTaskDelete(NULL); return; } // 绑定socket int err = bind(listen_sock, (struct sockaddr *)&dest_addr, sizeof(dest_addr)); if(err != 0) { ESP_LOGE(TAG, "Socket unable to bind: errno %d", errno); close(listen_sock); vTaskDelete(NULL); return; } // 开始监听 err = listen(listen_sock, 1); if(err != 0) { ESP_LOGE(TAG, "Error occurred during listen: errno %d", errno); close(listen_sock); vTaskDelete(NULL); return; } ESP_LOGI(TAG, "TCP server listening on port %d", TCP_PORT); while(1) { struct sockaddr_in source_addr; socklen_t addr_len = sizeof(source_addr); // 等待客户端连接 int sock = accept(listen_sock, (struct sockaddr *)&source_addr, &addr_len); if(sock < 0) { ESP_LOGE(TAG, "Unable to accept connection: errno %d", errno); break; } // 转换客户端IP地址为字符串 inet_ntoa_r(source_addr.sin_addr, addr_str, sizeof(addr_str) - 1); ESP_LOGI(TAG, "Client connected: %s", addr_str); // 处理客户端数据 while(1) { // 接收数据 int len = recv(sock, rx_buffer, sizeof(rx_buffer) - 1, 0); if(len < 0) { ESP_LOGE(TAG, "Error occurred during recv: errno %d", errno); break; } else if(len == 0) { ESP_LOGI(TAG, "Client disconnected"); break; } // 处理数据 rx_buffer[len] = '\0'; ESP_LOGI(TAG, "Received %d bytes from %s: %s", len, addr_str, rx_buffer); // 回显数据 send(sock, rx_buffer, len, 0); } // 关闭连接 close(sock); } close(listen_sock); vTaskDelete(NULL); } void app_main(void) { wifi_init_sta(); // 创建TCP服务器任务 xTaskCreate(tcp_server_task, "tcp_server", 4096, NULL, 5, NULL); }实验步骤:
- 修改代码中的 Wi-Fi 名称和密码
- 编译烧录代码
- 打开串口监控,记录 ESP32-S3 的 IP 地址
- 打开电脑上的网络调试助手(如 NetAssist)
- 选择 TCP 客户端模式,输入 ESP32-S3 的 IP 地址和端口 8080,点击连接
- 发送任意数据,你将收到 ESP32-S3 原样返回的数据
4.3 实战项目 4:UDP 客户端
我们将创建一个 UDP 客户端,向指定的服务器发送数据。
完整代码:
c
运行
#include <stdio.h> #include <string.h> #include "freertos/FreeRTOS.h" #include "freertos/task.h" #include "esp_wifi.h" #include "esp_event.h" #include "nvs_flash.h" #include "esp_log.h" #include "lwip/sockets.h" #include "lwip/netdb.h" static const char *TAG = "UDP_CLIENT"; #define WIFI_SSID "你的Wi-Fi名称" #define WIFI_PASS "你的Wi-Fi密码" #define UDP_SERVER_IP "192.168.1.100" // 你的电脑IP地址 #define UDP_SERVER_PORT 8080 #define BUF_SIZE 1024 // Wi-Fi初始化代码(同上,此处省略) void wifi_init_sta(void) { ... } void udp_client_task(void *pvParameters) { char tx_buffer[BUF_SIZE]; int count = 0; struct sockaddr_in dest_addr; dest_addr.sin_family = AF_INET; dest_addr.sin_port = htons(UDP_SERVER_PORT); inet_pton(AF_INET, UDP_SERVER_IP, &dest_addr.sin_addr); // 创建socket int sock = socket(AF_INET, SOCK_DGRAM, IPPROTO_UDP); if(sock < 0) { ESP_LOGE(TAG, "Unable to create socket: errno %d", errno); vTaskDelete(NULL); return; } ESP_LOGI(TAG, "UDP client started, sending to %s:%d", UDP_SERVER_IP, UDP_SERVER_PORT); while(1) { // 构造数据 sprintf(tx_buffer, "Hello from ESP32-S3! Count: %d", count++); // 发送数据 int err = sendto(sock, tx_buffer, strlen(tx_buffer), 0, (struct sockaddr *)&dest_addr, sizeof(dest_addr)); if(err < 0) { ESP_LOGE(TAG, "Error occurred during sendto: errno %d", errno); } else { ESP_LOGI(TAG, "Sent %d bytes: %s", err, tx_buffer); } vTaskDelay(pdMS_TO_TICKS(1000)); } close(sock); vTaskDelete(NULL); } void app_main(void) { wifi_init_sta(); // 创建UDP客户端任务 xTaskCreate(udp_client_task, "udp_client", 4096, NULL, 5, NULL); }五、HTTP 客户端开发
HTTP 是互联网中最常用的应用层协议,我们可以使用 HTTP 协议访问互联网上的各种 API。
5.1 ESP-IDF HTTP 客户端组件
ESP-IDF 提供了一个功能强大的 HTTP 客户端组件esp_http_client,它支持 GET、POST、PUT、DELETE 等 HTTP 方法,支持 HTTPS,支持重定向,支持 Cookie 等。
5.2 实战项目 5:HTTP 客户端获取天气信息
我们将使用 HTTP 客户端访问和风天气 API,获取实时天气信息。
完整代码:
c
运行
#include <stdio.h> #include <string.h> #include "freertos/FreeRTOS.h" #include "freertos/task.h" #include "esp_wifi.h" #include "esp_event.h" #include "nvs_flash.h" #include "esp_log.h" #include "esp_http_client.h" #include "cJSON.h" static const char *TAG = "HTTP_CLIENT"; #define WIFI_SSID "你的Wi-Fi名称" #define WIFI_PASS "你的Wi-Fi密码" #define WEATHER_API_KEY "你的和风天气API密钥" #define CITY_ID "101010100" // 北京的城市ID // Wi-Fi初始化代码(同上,此处省略) void wifi_init_sta(void) { ... } // HTTP事件处理函数 esp_err_t http_event_handler(esp_http_client_event_t *evt) { static char *response_buffer = NULL; static int response_len = 0; switch(evt->event_id) { case HTTP_EVENT_ON_DATA: // 接收HTTP响应数据 if(!esp_http_client_is_chunked_response(evt->client)) { // 重新分配缓冲区 response_buffer = realloc(response_buffer, response_len + evt->data_len + 1); memcpy(response_buffer + response_len, evt->data, evt->data_len); response_len += evt->data_len; response_buffer[response_len] = '\0'; } break; case HTTP_EVENT_ON_FINISH: // HTTP请求完成,解析JSON数据 if(response_buffer != NULL) { ESP_LOGI(TAG, "HTTP response: %s", response_buffer); // 解析JSON cJSON *root = cJSON_Parse(response_buffer); if(root != NULL) { cJSON *now = cJSON_GetObjectItemCaseSensitive(root, "now"); if(now != NULL) { cJSON *temp = cJSON_GetObjectItemCaseSensitive(now, "temp"); cJSON *text = cJSON_GetObjectItemCaseSensitive(now, "text"); if(temp != NULL && text != NULL) { ESP_LOGI(TAG, "=================================="); ESP_LOGI(TAG, "北京天气: %s", text->valuestring); ESP_LOGI(TAG, "温度: %s℃", temp->valuestring); ESP_LOGI(TAG, "=================================="); } } cJSON_Delete(root); } free(response_buffer); response_buffer = NULL; response_len = 0; } break; default: break; } return ESP_OK; } void http_client_task(void *pvParameters) { char url[256]; // 构造API请求URL sprintf(url, "https://devapi.qweather.com/v7/weather/now?location=%s&key=%s", CITY_ID, WEATHER_API_KEY); esp_http_client_config_t config = { .url = url, .event_handler = http_event_handler, .skip_cert_common_name_check = true, // 跳过证书验证(仅用于测试) }; while(1) { ESP_LOGI(TAG, "Fetching weather information..."); 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 request successful, status code: %d", esp_http_client_get_status_code(client)); } else { ESP_LOGE(TAG, "HTTP request failed: %s", esp_err_to_name(err)); } esp_http_client_cleanup(client); vTaskDelay(pdMS_TO_TICKS(300000)); // 每5分钟更新一次天气 } vTaskDelete(NULL); } void app_main(void) { wifi_init_sta(); // 创建HTTP客户端任务 xTaskCreate(http_client_task, "http_client", 8192, NULL, 5, NULL); }注意:
- 你需要先到和风天气官网(和风天气开发服务 ~ 强大、丰富的天气数据服务)注册账号,获取免费的 API 密钥
- 将代码中的
WEATHER_API_KEY替换为你自己的 API 密钥 - 你可以在和风天气官网查询你所在城市的 ID,替换
CITY_ID
六、智能配网技术
在实际的物联网产品中,我们不可能将 Wi-Fi 名称和密码硬编码到固件中。我们需要一种方式,让用户能够方便地将设备连接到自己的 Wi-Fi 网络,这就是智能配网技术。
ESP32-S3 支持多种智能配网方式:
- SmartConfig 一键配网:手机 APP 发送包含 Wi-Fi 名称和密码的广播包,ESP32-S3 监听并解析这些广播包
- AP 配网:ESP32-S3 创建一个热点,用户用手机连接这个热点,然后通过网页或 APP 将 Wi-Fi 信息发送给 ESP32-S3
- BLE 配网:通过蓝牙低功耗将 Wi-Fi 信息发送给 ESP32-S3
6.1 实战项目 6:SmartConfig 一键配网
SmartConfig 是最常用的配网方式,用户只需要在手机 APP 中输入 Wi-Fi 密码,点击配网即可完成设备连接。
完整代码:
c
运行
#include <stdio.h> #include <string.h> #include "freertos/FreeRTOS.h" #include "freertos/task.h" #include "esp_wifi.h" #include "esp_event.h" #include "nvs_flash.h" #include "esp_log.h" #include "esp_smartconfig.h" static const char *TAG = "SMARTCONFIG"; static EventGroupHandle_t s_wifi_event_group; const int WIFI_CONNECTED_BIT = BIT0; const int ESPTOUCH_DONE_BIT = BIT1; 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 && event_id == WIFI_EVENT_STA_START) { xEventGroupSetBits(s_wifi_event_group, WIFI_CONNECTED_BIT); } else if (event_base == WIFI_EVENT && event_id == WIFI_EVENT_STA_DISCONNECTED) { ESP_LOGW(TAG, "Wi-Fi disconnected"); esp_wifi_connect(); xEventGroupClearBits(s_wifi_event_group, WIFI_CONNECTED_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, "Got IP address: " IPSTR, IP2STR(&event->ip_info.ip)); xEventGroupSetBits(s_wifi_event_group, WIFI_CONNECTED_BIT); } } static void smartconfig_event_handler(void* arg, esp_event_base_t event_base, int32_t event_id, void* event_data) { if (event_base == SC_EVENT && event_id == SC_EVENT_SCAN_DONE) { ESP_LOGI(TAG, "Scan done"); } else if (event_base == SC_EVENT && event_id == SC_EVENT_FOUND_CHANNEL) { ESP_LOGI(TAG, "Found channel"); } else if (event_base == SC_EVENT && event_id == SC_EVENT_GOT_SSID_PSWD) { ESP_LOGI(TAG, "Got SSID and password"); smartconfig_event_got_ssid_pswd_t *evt = (smartconfig_event_got_ssid_pswd_t *)event_data; wifi_config_t wifi_config; bzero(&wifi_config, sizeof(wifi_config_t)); memcpy(wifi_config.sta.ssid, evt->ssid, sizeof(wifi_config.sta.ssid)); memcpy(wifi_config.sta.password, evt->password, sizeof(wifi_config.sta.password)); ESP_LOGI(TAG, "SSID: %s", wifi_config.sta.ssid); ESP_LOGI(TAG, "Password: %s", wifi_config.sta.password); // 配置Wi-Fi ESP_ERROR_CHECK(esp_wifi_set_config(WIFI_IF_STA, &wifi_config)); // 连接Wi-Fi ESP_ERROR_CHECK(esp_wifi_connect()); } else if (event_base == SC_EVENT && event_id == SC_EVENT_SEND_ACK_DONE) { xEventGroupSetBits(s_wifi_event_group, ESPTOUCH_DONE_BIT); } } void smartconfig_task(void *pvParameters) { EventBits_t uxBits; // 启动SmartConfig ESP_ERROR_CHECK(esp_smartconfig_set_type(SC_TYPE_ESPTOUCH)); smartconfig_start_config_t cfg = SMARTCONFIG_START_CONFIG_DEFAULT(); ESP_ERROR_CHECK(esp_smartconfig_start(&cfg)); while(1) { // 等待Wi-Fi连接和SmartConfig完成 uxBits = xEventGroupWaitBits(s_wifi_event_group, WIFI_CONNECTED_BIT | ESPTOUCH_DONE_BIT, true, false, portMAX_DELAY); if(uxBits & WIFI_CONNECTED_BIT) { ESP_LOGI(TAG, "Wi-Fi connected"); } if(uxBits & ESPTOUCH_DONE_BIT) { ESP_LOGI(TAG, "SmartConfig completed"); esp_smartconfig_stop(); vTaskDelete(NULL); } } } void app_main(void) { ESP_ERROR_CHECK(nvs_flash_init()); ESP_ERROR_CHECK(esp_netif_init()); ESP_ERROR_CHECK(esp_event_loop_create_default()); s_wifi_event_group = xEventGroupCreate(); esp_netif_create_default_wifi_sta(); wifi_init_config_t cfg = WIFI_INIT_CONFIG_DEFAULT(); ESP_ERROR_CHECK(esp_wifi_init(&cfg)); ESP_ERROR_CHECK(esp_event_handler_register(WIFI_EVENT, ESP_EVENT_ANY_ID, &wifi_event_handler, NULL)); ESP_ERROR_CHECK(esp_event_handler_register(IP_EVENT, IP_EVENT_STA_GOT_IP, &wifi_event_handler, NULL)); ESP_ERROR_CHECK(esp_event_handler_register(SC_EVENT, ESP_EVENT_ANY_ID, &smartconfig_event_handler, NULL)); ESP_ERROR_CHECK(esp_wifi_set_mode(WIFI_MODE_STA)); ESP_ERROR_CHECK(esp_wifi_start()); // 创建SmartConfig任务 xTaskCreate(smartconfig_task, "smartconfig", 4096, NULL, 5, NULL); }实验步骤:
- 编译烧录代码到 ESP32-S3 开发板
- 打开手机上的 ESP-Touch APP(可以在应用商店搜索下载)
- 确保手机连接到你想要让 ESP32-S3 连接的 Wi-Fi 网络
- 在 APP 中输入 Wi-Fi 密码,点击 "确认" 开始配网
- 观察串口输出,当看到 "SmartConfig completed" 时,说明配网成功
七、Wi-Fi 开发最佳实践
- 使用事件驱动架构:不要在主循环中轮询 Wi-Fi 状态,使用事件处理函数响应 Wi-Fi 状态变化
- 实现自动重连:当 Wi-Fi 断开连接时,自动尝试重新连接
- 合理设置任务优先级:网络相关任务的优先级应该高于一般的应用任务
- 注意内存管理:网络通信会使用大量内存,及时释放不需要的内存
- 处理网络错误:网络通信是不可靠的,一定要处理各种可能的错误情况
- 使用 HTTPS:在生产环境中,一定要使用 HTTPS 协议进行数据传输,保证数据安全
八、常见问题解答与避坑指南
8.1 Wi-Fi 连接失败怎么办?
解决方案:
- 检查 Wi-Fi 名称和密码是否正确
- 确保 Wi-Fi 路由器工作正常
- 确保 ESP32-S3 在 Wi-Fi 信号覆盖范围内
- 检查 Wi-Fi 加密方式是否支持(ESP32-S3 不支持 WEP 加密)
- 尝试重启 ESP32-S3 和路由器
8.2 获取不到 IP 地址怎么办?
解决方案:
- 检查路由器的 DHCP 服务是否开启
- 确保路由器的 IP 地址池没有用完
- 尝试重启路由器
- 尝试使用静态 IP 地址
8.3 TCP 通信不稳定怎么办?
解决方案:
- 检查网络连接是否稳定
- 增加数据重传机制
- 合理设置 socket 超时时间
- 避免在网络任务中执行耗时操作
8.4 SmartConfig 配网失败怎么办?
解决方案:
- 确保手机和 ESP32-S3 在同一个 2.4GHz Wi-Fi 网络下(ESP32-S3 不支持 5GHz Wi-Fi 配网)
- 确保 Wi-Fi 名称和密码不包含特殊字符
- 尝试靠近路由器,增强信号
- 尝试重启手机和 ESP32-S3
- 尝试使用 AP 配网方式
8.5 HTTP 请求失败怎么办?
解决方案:
- 检查网络连接是否正常
- 检查 URL 是否正确
- 检查 API 密钥是否正确
- 确保 API 服务器正常工作
- 尝试增加超时时间
九、课后作业
- 基础练习:实现 ESP32-S3 连接到 Wi-Fi 路由器
- 进阶练习:创建一个 TCP 服务器,接收电脑发送的数据并回显
- 综合练习:实现 HTTP 客户端,获取天气信息并显示在 OLED 显示屏上
- 挑战练习:实现 SmartConfig 一键配网功能,配网成功后自动连接到 Wi-Fi 并上报设备信息
十、下期预告
第 7 讲将学习蓝牙 BLE 开发,这是 ESP32-S3 另一个重要的无线通信功能。我们将详细讲解 BLE 广播、连接、服务与特征值、数据传输等核心概念,并通过实战项目演示如何实现 ESP32-S3 与手机之间的 BLE 通信。
写在最后
Wi-Fi 网络开发是物联网开发的核心技能,也是 ESP32-S3 最强大的功能之一。希望通过本讲的学习,你能够熟练掌握 ESP32-S3 的 Wi-Fi 开发技术,能够独立开发出能够连接互联网的物联网设备。
本讲的所有实战代码我已经上传到了我的 GitHub 仓库,大家可以在评论区留言获取。如果你在学习过程中遇到任何问题,欢迎在评论区留言讨论。
点赞收藏不迷路,关注我,带你从零基础成为 ESP32-S3 开发高手!
ESP32-S3、ESP-IDF v5.5、Wi-Fi Station、Wi-Fi AP、TCP 通信、UDP 通信、HTTP 客户端、SmartConfig 一键配网、物联网
