news 2026/9/30 5:46:17

ESP32联网实战:ESP-IDF实现WiFi获取天气与DHT11温湿度

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
ESP32联网实战:ESP-IDF实现WiFi获取天气与DHT11温湿度

做嵌入式这几年,我一直觉得ESP32最让人上头的一点就是——它不只是个单片机,而是一块自带WiFi和蓝牙的“小开发板电脑”。很多朋友在Arduino上玩过ESP8266、ESP32,一旦切换到ESP-IDF,第一道坎就是WiFi连接和网络请求怎么写。这节系列教程我就拿一个非常典型的场景来拆:ESP32通过WiFi获取温度和天气信息。一方面用DHT11读本地温度湿度,另一方面从网络API拉取城市天气数据,两条线汇合到串口日志里,把从硬件采集到云端数据解析的整条链路完整走通。这篇适合已经搭好ESP-IDF开发环境、想进阶到联网项目的开发者。


1. 项目需求拆解:本地传感器温度和城市天气信息如何合并

1.1 标题背后的真实需求

这个项目标题看起来简单——“ESP32获取温度和天气信息”,但仔细一拆,你会发现它其实包含了两组完全不同的数据流:

  • 本地温度:通过DHT11/DS18B20这类温湿度传感器,读取当前环境的温度、湿度,数据源在板卡本地;
  • 天气信息:通过WiFi联网,请求第三方天气API,获取某个城市当前的温度、天气现象、风力风向,数据源在云端。

这两组数据放在一起,正好构成了一个典型的物联网数据终端:本地采集 + 云端同步。很多智能家居项目、环境监测站、桌面天气摆件,本质上都是这个架构。区别只在于把数据展示在OLED屏、手机App还是网页上。

标题里的“05-2”也透露出这是系列教程的一个递进节点:前面大概率已经讲了GPIO、定时器、I2C之类的外设操作,这一节开始往“联网”走。所以你需要的不是一个单独点亮的Demo,而是一套能继续往上扩展的基础框架——这也是我为什么要把WiFi连接、HTTP请求、JSON解析这些底层模块单独抽出来讲的原因。

1.2 方案选型:为什么选HTTP+JSON而不是MQTT

第一次做这类项目时,很容易在通信协议上纠结:用HTTP去轮询天气,还是用MQTT去订阅?我的建议很直接——纯天气查询场景,HTTP GET请求 + JSON解析是成本最低、最不容易出错的选择。

原因有三点:

  1. 天气API基本都是RESTful风格,一个GET请求就能拿到完整数据,响应是标准的JSON格式,没有多余的学习成本;
  2. MQTT适合设备端和服务器之间需要双向实时通信的场景,而天气数据是远程服务器主动提供、设备被动获取的,MQTT的优势体现不出来;
  3. ESP-IDF原生提供了esp_http_client组件和cJSON库,接口封装得很好,不用引入额外依赖。

当然,如果你的目标是做“设备上报温湿度到云平台,再从云平台下发指令”,那MQTT就是更优解,这个后续系列可以展开。这次先把HTTP这条链路吃透。


2. 硬件连接与IDF工程准备

2.1 硬件清单与接线

硬件部分我选了最稳妥的一套组合,都是手边容易买到的:

器件型号/规格用途
主控ESP32-DevKitC(ESP-WROOM-32)运行ESP-IDF程序
温湿度传感器DHT11(或DHT22)读取本地温湿度
电阻4.7kΩ ~ 10kΩ上拉电阻DHT11数据线上拉
面包板+杜邦线若干连接电路

接线其实很简单,DHT11一共三个引脚(有些模块是四个脚,其中一个是空脚),按下面接就行:

  • VCC → 3.3V(DHT11可以3.3V供电,但注意线长别太长)
  • GND → GND
  • DATA → GPIO4(这个引脚可以自己改,代码里对应调整即可)
  • 在DATA和VCC之间接一个4.7kΩ上拉电阻

这里说下为什么需要上拉电阻。DHT11用的是单总线协议,总线空闲状态下是高电平,主机和设备通过拉低总线来发起通信。如果没有外部上拉,信号线会因为寄生电容和各种噪声导致电平不稳定,最典型的现象就是读取到的数据忽大忽小、偶尔全是0xFF。这个电阻不是可选项,是必选项。

2.2 创建IDF工程与menuconfig配置

我的开发环境是ESP-IDF v5.2,在Ubuntu下用idf.py命令行工具管理工程。新版本的IDF都支持直接创建工程目录:

idf.py create-project esp32_weather cd esp32_weather

工程创建好后,需要用menuconfig配置WiFi信息。IDF的WiFi示例框架里通常会使用“Example Connection Configuration”这套Kconfig配置,我建议你沿用这个惯例,这样代码可读性更好,换网络时也不用改源码重新编译。

在工程根目录新建一个Kconfig.projbuild文件:

menu "Example Connection Configuration" config EXAMPLE_WIFI_SSID string "WiFi SSID" default "myssid" config EXAMPLE_WIFI_PASSWORD string "WiFi Password" default "mypassword" endmenu

然后运行:

idf.py menuconfig

在“Example Connection Configuration”菜单里填入你自己的WiFi账号和密码。这样程序里就能通过CONFIG_EXAMPLE_WIFI_SSID和CONFIG_EXAMPLE_WIFI_PASSWORD拿到配置项,换环境时不用动代码。

注意:如果路由器开了5GHz频段,ESP32的WiFi是连不上的。2.4GHz是标配。调试阶段建议让开发板离路由器近一点,排除信号强度干扰。


3. WiFi连接与HTTP请求的代码实现

3.1 用事件组管理WiFi连接状态

ESP-IDF的WiFi编程模型和Arduino差异很大。Arduino的WiFi.begin()是阻塞式的,连接成功返回1,失败返回0。IDF里WiFi是基于事件驱动的——你调用esp_wifi_start()之后,系统在后台异步连接,你通过事件处理器(event handler)来感知连接状态。

新手最容易栽的地方就是:调用了esp_wifi_connect()后立刻去发HTTP请求,结果失败。因为此时WiFi还没连上,更别说获取到IP地址了。

我习惯用**事件组(Event Group)**来同步连接状态。首先定义几个事件位:

#define WIFI_CONNECTED_BIT BIT0 #define WIFI_FAIL_BIT BIT1 static EventGroupHandle_t s_wifi_event_group;

然后在事件处理器里,根据事件类型设置对应的位:

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) { esp_wifi_connect(); ESP_LOGI(TAG, "retry connecting to AP..."); } 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: " IPSTR, IP2STR(&event->ip_info.ip)); xEventGroupSetBits(s_wifi_event_group, WIFI_CONNECTED_BIT); } }

初始化WiFi的代码顺序非常关键:

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_t instance_any_id; ESP_ERROR_CHECK(esp_event_handler_instance_register( WIFI_EVENT, ESP_EVENT_ANY_ID, &event_handler, NULL, &instance_any_id)); ESP_ERROR_CHECK(esp_event_handler_instance_register( IP_EVENT, IP_EVENT_STA_GOT_IP, &event_handler, NULL, &instance_any_id)); wifi_config_t wifi_config = { .sta = { .ssid = CONFIG_EXAMPLE_WIFI_SSID, .password = CONFIG_EXAMPLE_WIFI_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()); 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, "wifi connected"); } else { ESP_LOGE(TAG, "wifi connect failed"); } }

其中有三个点容易踩坑,单独拎出来说:

  • esp_netif_init()必须在WiFi初始化之前调用,它是网络接口层的基础。顺序反了,esp_wifi_init()直接报错。
  • nvs_flash_init()也别漏。WiFi驱动内部会用到NVS存储校准数据,不初始化会返回ESP_ERR_NVS_NO_FREE_PAGES之类的错误。标准做法是在app_main最开始调用,如果返回ESP_ERR_NVS_NO_FREE_PAGES或ESP_ERR_NVS_NEW_VERSION_FOUND,就执行nvs_flash_erase()再重新初始化。
  • 我把断线重连逻辑直接放在WIFI_EVENT_STA_DISCONNECTED里调用esp_wifi_connect(),这对家用路由器来说够用了。官方推荐的esp_wifi_set_auto_reconnect也能用,但显式重连能在日志里看到每次尝试状态,调试阶段更直观。

事件组的xEventGroupWaitBits在等待时是阻塞的,对于main任务来说没问题。如果你后续想把连接逻辑放到后台任务里,可以把超时时间改成pdMS_TO_TICKS(10000)之类的有限值,避免整个系统卡死在这。

3.2 esp_http_client请求天气API

WiFi通了之后,下一步就是发HTTP请求。这里我用的是esp_http_client组件,它的使用思路是:先配置一个esp_http_client_config_t结构体,然后初始化客户端,执行请求,通过事件回调接收响应数据。

对于免费、无需注册的天气API,我推荐用7Timer的接口:

http://www.7timer.info/bin/api.pl?lon=113.17&lat=23.09&product=civil&output=json

这个接口的优势是:

  • 纯HTTP明文传输,不需要TLS证书,省去esp_crt_bundle_attach之类的证书配置;
  • 免费、无需API Key,拿来测试最方便;
  • 返回的JSON结构里包含temp2m(2米处温度)、rh2m(相对湿度)、wind10m(10米风速风向)、weather(天气代码)等字段,刚好覆盖我们的需求。

示例里我用的是广州的经纬度(113.17, 23.09),你可以换成自己城市的经纬度。请求代码:

static const char *WEATHER_URL = "http://www.7timer.info/bin/api.pl?lon=113.17&lat=23.09&product=civil&output=json"; static char response_buffer[4096]; static int response_len = 0; static esp_err_t http_event_handler(esp_http_client_event_t *evt) { switch (evt->event_id) { case HTTP_EVENT_ON_DATA: int copy_len = evt->data_len; if (response_len + copy_len < sizeof(response_buffer)) { memcpy(response_buffer + response_len, evt->data, copy_len); response_len += copy_len; response_buffer[response_len] = '\0'; } break; default: break; } return ESP_OK; } esp_err_t fetch_weather_data(void) { response_len = 0; memset(response_buffer, 0, sizeof(response_buffer)); esp_http_client_config_t config = { .url = WEATHER_URL, .method = HTTP_METHOD_GET, .event_handler = http_event_handler, .timeout_ms = 10000, }; 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 response len = %d", response_len); } else { ESP_LOGE(TAG, "HTTP request failed: %s", esp_err_to_name(err)); } esp_http_client_cleanup(client); return err; }

这里有一个非常关键的细节:esp_http_client把响应体通过事件回调分片返回,不是一次性塞给你的。在HTTP_EVENT_ON_DATA事件里拿到的evt->data只是当前这一片数据,所以必须自己维护一个缓冲区,用memcpy把每次拿到的数据追加到缓冲区末尾,最后拼成完整响应。我遇到过很多人在这个问题上卡住——直接在HTTP_EVENT_ON_DATA里打日志,发现日志在打印,但缓冲区却是空的,就是没做追加逻辑。

另外,response_buffer我用的是静态数组,4KB对天气API来说够用了。但如果你要请求的接口返回数据较大(比如包含未来7天逐小时的详细预报),建议用动态内存分配或者加大缓冲区,否则截断的JSON会直接导致解析失败。

3.3 cJSON解析天气数据的关键细节

有了完整的JSON字符串,接下来用ESP-IDF内置的cJSON库解析。7Timer接口返回的数据结构类似这样:

{ "product": "civil", "init": "2025041700", "dataseries": [ { "timepoint": 3, "cloudcover": 1, "seeing": 8, "transparency": 5, "lifted_index": -3, "rh2m": 60, "wind10m": { "direction": "NW", "speed": 3 }, "temp2m": 17, "prec_type": "none" }, ... ] }

解析的代码逻辑并不复杂,但有几个坑必须注意:

void parse_weather_json(const char *json_str) { cJSON *root = cJSON_Parse(json_str); if (root == NULL) { ESP_LOGE(TAG, "JSON parse error"); return; } cJSON *dataseries = cJSON_GetObjectItem(root, "dataseries"); if (dataseries == NULL || !cJSON_IsArray(dataseries)) { ESP_LOGE(TAG, "dataseries not found or not array"); cJSON_Delete(root); return; } cJSON *first = cJSON_GetArrayItem(dataseries, 0); if (first == NULL) { ESP_LOGE(TAG, "dataseries is empty"); cJSON_Delete(root); return; } cJSON *temp2m = cJSON_GetObjectItem(first, "temp2m"); cJSON *rh2m = cJSON_GetObjectItem(first, "rh2m"); cJSON *wind10m = cJSON_GetObjectItem(first, "wind10m"); if (cJSON_IsNumber(temp2m)) { ESP_LOGI(TAG, "remote temp: %d", temp2m->valueint); } if (cJSON_IsNumber(rh2m)) { ESP_LOGI(TAG, "remote humidity: %d", rh2m->valueint); } if (cJSON_IsObject(wind10m)) { cJSON *direction = cJSON_GetObjectItem(wind10m, "direction"); cJSON *speed = cJSON_GetObjectItem(wind10m, "speed"); if (cJSON_IsString(direction)) { ESP_LOGI(TAG, "wind direction: %s", direction->valuestring); } if (cJSON_IsNumber(speed)) { ESP_LOGI(TAG, "wind speed: %d m/s", speed->valueint); } } cJSON_Delete(root); }

第一个坑:每次调用cJSON_GetObjectItem之后,先判断返回的指针是否为NULL,再用cJSON_IsNumber、cJSON_IsString这类类型检查函数验证类型。如果API返回的结果和预期不符,比如某个字段缺失或类型变了,直接访问指针会崩溃。我一开始图省事,拿到指针就取valueint,结果某次网络代理把JSON内容改了,程序直接重启。

第二个坑:解析完一定要调用cJSON_Delete(root)释放内存。cJSON会为整个JSON树分配动态内存,不释放的话,每次请求涨一块,跑几小时就OOM了。ESP32的内存本来就不宽裕,这种泄漏在长时间运行的项目里是致命的。

第三个坑:cJSON的数值类型默认是double,valueint是把valuedouble转成int的结果。对于温度、湿度这种整数范围的数据,用valueint没问题。但如果你要解析小数(比如气压1013.25),必须用valuedouble,不然精度丢失。


4. DHT11温湿度读取与显示整合

4.1 简易DHT11驱动的实现思路

DHT11的读取时序在ESP-IDF里属于比较经典的GPIO位操作。它的协议核心是单总线:主机先发一个起始信号,然后释放总线,DHT11回应一个80us的低电平和80us的高电平,接着连续输出40bit数据(8bit湿度整数、8bit湿度小数、8bit温度整数、8bit温度小数、8bit校验和)。

每一位数据的表示方式是通过高电平持续的时间来区分的:

  • 逻辑0:高电平持续约26~28us
  • 逻辑1:高电平持续约70us

所以读DHT11的本质就是测量GPIO高电平的持续时间。在ESP-IDF里可以用esp_rom_delay_us()做微秒级延时,配合gpio_get_level()轮询。

驱动代码的核心逻辑:

#define DHT11_PIN GPIO_NUM_4 void dht11_start(void) { gpio_set_direction(DHT11_PIN, GPIO_MODE_OUTPUT_OD); gpio_set_level(DHT11_PIN, 0); esp_rom_delay_us(20000); // 拉低至少18ms gpio_set_level(DHT11_PIN, 1); esp_rom_delay_us(30); // 拉高20~40us gpio_set_direction(DHT11_PIN, GPIO_MODE_INPUT); } int dht11_read_byte(void) { int val = 0; for (int i = 0; i < 8; i++) { while (gpio_get_level(DHT11_PIN) == 0); // 等待低电平结束 esp_rom_delay_us(40); // 延时判断位值 if (gpio_get_level(DHT11_PIN) == 1) { val |= (1 << (7 - i)); } while (gpio_get_level(DHT11_PIN) == 1); // 等待高电平结束 } return val; }

这段代码我特地用GPIO_MODE_OUTPUT_OD(开漏输出)而不是推挽输出,原因在于单总线协议里,主机和设备都会拉低总线,如果用推挽输出,驱动能力过强可能会把DHT11拉低总线的动作打乱,造成读取异常。开漏模式允许信号线被任意一方拉低,行为上更接近协议设计要求。

实际项目里不建议把DHT11驱动写得这么简陋,更好的做法是用定时器捕获或者RMT驱动来测量脉宽,准确性和稳定性都会大幅提升。不过对于这个项目的演示目的,上面的轮询方式已经够用,只要确保两次读取间隔不小于1秒——DHT11的数据更新频率就是1Hz,你读得再快拿到的也是缓存值。读太快还会让传感器进入异常状态,表现为连续读到0xFF或校验失败。

4.2 main任务串接全流程

上面的模块分开看都不复杂,but把它们串起来就需要考虑任务结构和调度了。我设计了一个简单的顺序流:

  1. 初始化NVS;
  2. 初始化WiFi并等待连接;
  3. 连接成功后,调用fetch_weather_data()获取天气;
  4. 调用parse_weather_json()解析;
  5. 读取DHT11本地温湿度;
  6. 把本地数据和远端天气数据一起打印到日志,然后延时30秒进入下一轮。

app_main的代码框架如下:

void app_main(void) { ESP_ERROR_CHECK(nvs_flash_init()); wifi_init_sta(); while (1) { if (fetch_weather_data() == ESP_OK) { parse_weather_json(response_buffer); } int temp = 0, humi = 0; if (dht11_read_data(&temp, &humi) == ESP_OK) { ESP_LOGI(TAG, "local temp: %d.%d C, humi: %d.%d %%", temp / 10, temp % 10, humi / 10, humi % 10); } else { ESP_LOGE(TAG, "dht11 read failed"); } vTaskDelay(pdMS_TO_TICKS(30000)); } }

这里的dht11_read_data是上面驱动代码的封装,内部会完成起始时序、读取40bit数据、校验和验证。把校验和验证写进这个函数很重要,DHT11的校验规则是“湿度整数+湿度小数+温度整数+温度小数”的低8位等于校验字节,如果校验失败就丢弃这批数据,避免把错误数据打印出来误导人。

日志输出大概是这样的效果:

I (1234) main: got ip: 192.168.1.100 I (1345) main: HTTP response len = 2864 I (1345) main: remote temp: 27 I (1345) main: remote humidity: 82 I (1345) main: wind direction: S, wind speed: 2 m/s I (1350) main: local temp: 26.3 C, humi: 58.5 %

到这一步,整个项目就算跑通了。你会在串口里同时看到两个温度:一个是ESP32所在房间的实时温度,一个是目标城市的气象站温度。有意思吧?我实测时经常遇到室内温度比室外低四五度的情况,空调一开温差更明显。


5. 调试经验:从日志定位问题的三条排错链路

5.1 WiFi连接失败的排查顺序

WiFi连不上是这个项目里出现频率最高的问题。每次有朋友来问我“为什么连不上”,我都让他们按下面这个顺序查:

第一步看NVS。启动日志里如果出现nvs: not initialized或者ESP_ERR_NO_FREE_PAGES,先检查nvs_flash_init()调用返回值。注意,首次烧录时NVS分区可能是新的,正确做法是遇到ESP_ERR_NVS_NO_FREE_PAGES就调用nvs_flash_erase()擦除掉所有内容,再重新nvs_flash_init()。

第二步看WiFi事件日志。WIFI_EVENT_STA_DISCONNECTED事件的event_data里带有一个reason字段,这是最重要的诊断信息。常见的reason码:

Reason含义处理
201密码错误检查CONFIG_EXAMPLE_WIFI_PASSWORD
154次握手超时检查路由器是否设置了MAC过滤
2收到NACK路由器信号弱或AP未就绪
203DHCP失败检查路由器DHCP池是否耗尽

把reason打印出来,基本上问题就定位了一半。超过80%的情况是密码填错或者没切换到2.4GHz频段。

第三步看IP获取。WiFi连接成功不代表能上网,只有IP_EVENT_STA_GOT_IP触发后,网络才算真正可用。如果卡在这一步,要么是路由器的DHCP服务有问题,要么是ESP32的MAC地址被路由器拉黑了。

5.2 JSON解析跑飞的常见原因

JSON解析失败,首先把response_buffer里的原始内容用ESP_LOGI打印出来看一眼。我发现很多“解析失败”其实是HTTP请求本身就没成功,拿到的是一段HTML错误页。比如7Timer接口偶尔会被某些网络环境劫持返回运营商广告页,这时候解析当然失败。

另一个常见情况是响应被截断。我之前用2KB缓冲区请求一个预报接口,数据量稍微一大,JSON字符串被拦腰截断,解析结果要么是NULL要么是乱码。解决办法是看HTTP_EVENT_ON_DATA里是否出现过buf == NULL且data_len > 0的情况,或者检查response_len是否接近缓冲区上限。遇到这类问题,直接加大缓冲区最省事。

还有一点容易被忽略:esp_http_client在拿到HTTP响应头后,会调用事件回调通知应用层,这时响应体还没开始传输。如果你在HTTP_EVENT_ON_HEADER事件里去解析JSON,拿到的一定是空数据。一定要等到HTTP_EVENT_ON_DATA或HTTP_EVENT_DISCONNECTED之后再处理响应数据。

5.3 DHT11数据异常的物理层原因

软件逻辑看着没问题,但DHT11读数就是不对,这时候优先怀疑物理层。我在面包板上调试时遇到过三个典型问题:

一是没接上拉电阻。DHT11模块板上如果已经自带上拉电阻,你就不用外接;但裸的DHT11传感器必须外接。没上拉的表现是:能读到数据但数值不稳定,或者偶尔返回0xFF。

二是杜邦线太长。DHT11的信号线如果超过20cm,在时序要求严格的位判断环节容易出问题。特别是手机充电器、开关电源附近的电磁干扰会让高电平脉宽测出来偏大或偏小。解决办法是缩短线材,尽量让传感器靠近开发板。

三是GPIO模式设置不对。前面代码里我用的是开漏输出,但有些人的驱动代码用的是GPIO_MODE_OUTPUT,拉低总线之后再拉高时,推挽输出的强驱动能力会让信号线电平爬升过快,而DHT11在响应阶段也需要拉低总线,两者冲突导致时序完全错乱。全部改成开漏输出后,问题立刻消失。


6. 换用HTTPS天气API的进阶建议与扩展方向

6.1 生产环境尽量用HTTPS接口

7Timer这种裸HTTP接口在开发验证阶段非常方便,但真要部署到实际项目里,我建议换成HTTPS的天气服务(比如和风天气、OpenWeatherMap)。理由很简单:HTTP是明文传输,API Key如果放在URL里,在公共WiFi环境下很容易被中间人截获。

用ESP-IDF的esp_http_client请求HTTPS接口,相比HTTP多一个证书配置步骤。在esp_http_client_config_t加入:

esp_http_client_config_t config = { .url = "https://devapi.qweather.com/v7/weather/now?location=101010100&key=YOUR_KEY", .method = HTTP_METHOD_GET, .event_handler = http_event_handler, .timeout_ms = 10000, .crt_bundle_attach = esp_crt_bundle_attach, };

esp_crt_bundle_attach会让HTTP客户端使用ESP-IDF内置的CA证书包验证服务器身份。在menuconfig里需要确保Component config → mbedTLS → Enable ESP certificate bundle这个选项是开启的。默认一般是开着的,但如果你裁剪过组件,可能要回去检查一下。

如果不开证书校验直接访问HTTPS接口,最常见的结果是TLS握手失败,日志里报mbedtls_ssl_setup failed或者ssl handshake failed。这个问题的本质是客户端没有可信任的根证书,光把URL改成https是不够的。

6.2 这整套框架还能怎么扩展

现在你已经有了三个可复用的基础模块:WiFi事件组同步、HTTP数据拉取、JSON解析。再往下扩展就是想象力问题了,几个我觉得值得尝试的方向:

  • 把数据和天气同时显示到OLED屏(SSD1306):用I2C接口的0.96寸OLED屏,把本地温度、湿度、天气图标一次性展示出来,就是一个完整的桌面天气站。改造成本很低,只是把ESP_LOGI换成ssd1306_display_text的调用。

  • 上报到物联网平台:把本地DHT11的数据用MQTT发布到巴法云或阿里云IoT平台,在手机端用小程序或App实时查看。这里的HTTP请求框架可以留着用来拿天气,新增MQTT连接模块负责上云。

  • 做室内外温差提醒:通过比较本地温度和API返回的室外温度,在温差超过阈值时驱动蜂鸣器或LED报警。比如夏天开空调时室内外温差超过8度,容易感冒,这个提醒就很有实际价值。

我在结束这节之前,再分享一个实际调试时的经验:一定要把esp_http_client的超时时间设得合理一些。默认值可能是5秒,对国内某些网络环境来说偏短。我实测过,访问海外API在弱网下响应经常需要8~10秒,如果超时设太短,HTTP请求会频繁失败。另外,串口日志的TAG要区分开,WiFi日志、HTTP日志、JSON解析日志、DHT11日志各用一个TAG,用idf.py monitor配合--filter参数过滤查看,定位问题会高效很多。这个习惯我从做这个项目开始养成了,后来调MQTT、调BLE,都靠这个省了不少时间。

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/9/30 5:45:30

Lab色彩模式实战:从锐化校色到色彩管理的完整指南

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

作者头像 李华
网站建设 2026/9/30 5:45:16

蛋白质亚细胞定位预测:AAIndex编码+CNN-BiLSTM实战指南

简介&#xff1a;本资源是一篇发表于《计算机应用》期刊的学术论文&#xff0c;面向生物信息学、计算生物学及人工智能交叉领域的研究者与高年级本科生/研究生&#xff0c;聚焦蛋白质亚细胞定位这一关键功能预测问题。论文提出基于堆栈式降噪自编码器&#xff08;SDAE&#xff…

作者头像 李华
网站建设 2026/9/30 5:45:06

用Python微调BERT做提取式摘要:论文代码核心解读与实战

简介&#xff1a;面向自然语言处理开发者与研究者&#xff0c;这份基于Python的BertSum实现围绕BERT微调完成抽取式摘要任务&#xff0c;覆盖从文本预处理、预训练模型加载、任务层构建到训练评估与后处理的完整流程。压缩包共36个文件&#xff0c;以20个py脚本为主&#xff0c…

作者头像 李华
网站建设 2026/9/30 5:44:48

微调BERT实现提取式摘要:句子分类与ROUGE评估实战

简介&#xff1a;面向自然语言处理开发者的BERT摘要生成实战代码包&#xff0c;基于Python实现论文中的BertSum方案&#xff0c;解决如何利用预训练BERT完成抽取式摘要任务。资源分为数据预处理、模型构建、训练评估三大模块&#xff1a;数据端需将原始文本转换成BERT可识别的T…

作者头像 李华
网站建设 2026/9/30 5:44:33

WorkBuddy定时任务+微信推送:打造每日AI日报自动化流程

1. 为什么我要给 WorkBuddy 设一个"十点半闹钟"每天早上到工位&#xff0c;第一件事不是泡咖啡&#xff0c;而是打开各种信息源翻一遍&#xff1a;行业新闻、竞品动态、技术社区热帖、昨天没看完的文档更新。这套动作熟练之后大概要花二十分钟&#xff0c;但问题是它…

作者头像 李华
网站建设 2026/9/30 5:44:12

TensorFlow工程实践:从安装陷阱到生产级部署全链路指南

1. 这不是“又一个深度学习框架”——TensorFlow 的真实定位与误用陷阱 很多人第一次听说 TensorFlow&#xff0c;是在某篇“AI入门指南”里看到它和 PyTorch 并列排在“主流框架”名单上&#xff1b;也有人是在公司技术选型会上&#xff0c;听到架构师说“我们用 TensorFlow …

作者头像 李华