news 2026/9/7 20:02:43

ESP8266+DHT11+MQTT+OneNet远程温湿度监控实战指南

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
ESP8266+DHT11+MQTT+OneNet远程温湿度监控实战指南

简介:本资源是一套基于ESP8266与DHT11传感器实现远程温湿度监控的嵌入式物联网实践方案,面向嵌入式初学者、IoT开发者及高校课程设计者,解决环境参数采集、Wi-Fi联网、MQTT协议接入云平台等典型物联网开发痛点。压缩包共46个文件,含12个XML配置文件(用于OneNet设备注册与数据流定义)、10幅WebP格式界面截图(展示OneNet仪表板效果)、4个Java源码(配套Android端简易监控客户端)、3份README文档(涵盖硬件连接、编译说明与部署流程),以及核心C++/Arduino代码(main.cpp、mqtt_device_info.h等),整体仅167KB,轻量易导入。已有336人学习下载,资源结构清晰分层:include目录封装MQTT与传感器驱动头文件,src/main.cpp实现温湿度周期采集、Wi-Fi连接、JSON数据组包与MQTT发布逻辑,platformio.ini与CMakeLists.txt支持多环境编译,适合快速复现、调试并拓展报警、OTA升级等进阶功能。 把 ESP8266、DHT11、MQTT 和 OneNet 这四个词拆开看,每一个都是物联网入门圈里最常见的熟面孔,但把它们串起来做一个远程温湿度监控,这套组合直到今天依然是低成本 IoT 项目里最值得复刻的那个。我前后帮朋友做过好几版类似的监控终端,从最开始在面包板上用杜邦线飞线,到后来自己画 PCB 打样,踩过的坑比写过的代码还多。这篇就把整个方案的来龙去脉、硬件接线、云端配置、代码实现和排障经验一次说透,保证你照着做就能把数据稳定送上云端。

这个项目本质上解决的是一个很朴素的痛点:你想知道某个地方现在热不热、潮不潮,但你又不能一直蹲在那儿盯着。无论是蔬菜大棚、机房、仓库、孵化箱,还是家里养的多肉花房,温湿度都是最基础也最关键的环境参数。用 ESP8266 做终端采集,通过 MQTT 协议上报到 OneNet 平台,你就能在手机、电脑上随时看到数据曲线,甚至设置告警。整个过程没有复杂的前端后端开发,一块十几块的开发板加一个几块钱的传感器就能跑起来,非常适合刚入门物联网的朋友用来打通“采集-传输-上云”整条链路。

我写这篇文章时尽量把原理和代码拆开讲,不仅告诉你“怎么接”“怎么写”,还会解释“为什么这么接”“为什么这么写”。因为你只有理解了背后的逻辑,以后换传感器、换平台、换通信协议时才不会抓瞎。下面进入正题。

1. 项目整体设计与方案选型

1.1 核心架构拆解

这个项目的完整数据链路是:DHT11 传感器采集温度和湿度 → ESP8266 通过 GPIO 引脚读取数据 → 数据按照 MQTT 协议封装成报文 → 通过 WiFi 网络发送到 OneNet 平台的 MQTT Broker → 平台解析报文并存入数据库 → 用户在 OneNet 控制台查看实时数据或历史曲线。

用一张图来表示就是“传感器 → 主控 → 网络 → 云平台 → 用户端”五层结构。每一层都有可替换的方案,但我在这个项目里选的都是成熟度高、资料多、成本低的组合。比如传感器可以用 DHT22、SHT30,主控可以用 ESP32,平台可以用阿里云、巴法云、华为云,但 DHT11 + ESP8266 + OneNet 这套组合对新手最友好,踩坑成本最低。

MQTT 在里面扮演的角色是“运输工”。它不关心你的数据是温湿度还是开关状态,只负责把消息从一个节点搬运到另一个节点。ESP8266 作为发布者,把温湿度数据发布到指定的主题(Topic),OneNet 平台作为订阅者接收这些数据。整个过程是异步的、解耦的,设备端不需要关心云端处理逻辑,云端下发指令时也不需要关心设备当前是否在线,这种设计正是物联网场景下最常用的模式。

1.2 为什么选这套方案:ESP8266 + DHT11 + MQTT + OneNet

先说 ESP8266。市面上做 WiFi 模块的方案不少,但 ESP8266 几乎是入门首选,原因很简单:便宜、功耗可控、生态成熟。NodeMCU 开发板带 USB 转串口芯片,插上电脑就能烧录调试,不需要额外的下载器。而且 Arduino 社区对 ESP8266 的支持非常完善,库函数丰富到什么程度呢?你基本不需要关心底层 WiFi 协议栈,几行代码就能连上网。

再说 DHT11。这个传感器的精度其实一般,温度 ±2℃,湿度 ±5%RH,但它的优势是便宜且在绝大多数场景下够用。你要监控的是“变化趋势”和“异常报警”,不是做精密计量,所以没必要上 SHT30 这种高精度传感器。当然,如果你预算充足、对精度有要求,后面换 DHT22 也只是改一行代码的事,两个传感器的库是同一套。

MQTT 协议的选择就更明确了。HTTP 是请求-响应模式,设备要主动上报数据,必须由设备发请求,服务器才能收到数据,服务器想主动推消息给设备还得靠轮询或长连接。而 MQTT 基于发布/订阅模式,设备连接服务器后保持长连接,双方都能随时收发消息,实时性和流量开销都比 HTTP 好。具体对比放到后面第 5 章详细说。

OneNet 平台属于国内老牌的物联网云平台,对个人开发者很友好。它的 MQTT 接入文档写得清楚,控制台能看到实时数据流和历史曲线,还支持 HTTP API 调用。更关键的一点是,OneNet 允许你在产品里创建多个设备,意味着将来你可以在同一个项目里接入多个监控点,比如一个放在客厅、一个放在卧室、一个放在厨房,统一管理非常方便。

1.3 方案选型时避开的几个坑

这套方案看起来顺理成章,但我在选型阶段也走了一些弯路,这里把经验写出来帮你避开。第一,不要选裸的 ESP-01 模块做第一版。ESP-01 虽然便宜,但引出引脚太少,你接传感器就占掉两个 GPIO,还得额外接 TTL 转串口模块才能烧录,调试体验非常痛苦。建议直接上 NodeMCU 或 D1 Mini 这类带 USB 接口的开发板。

第二,DHT11 的国产兼容版质量参差不齐。我在淘宝买过一批几块钱的,有的读出来湿度偏差特别大,有的连续读取几次就超时。后来我发现关键不是品牌,而是模块上有没有焊接上拉电阻。如果你买的是四针独立封装的裸传感器,数据线上必须自己加一个 4.7kΩ 左右的上拉电阻到 VCC,否则信号不稳定,经常读到 0 或者超时。这个细节在后文接线部分还会详细讲。

第三,OneNet 平台有旧版 MQTT 和新版 OneNET Studio 的区别,两者的接入域名、端口、Topic 格式都不一样。你在网上搜到的很多教程是旧版的,如果你注册时默认进了新版控制台,照着旧版教程配会连不上。我建议注册时仔细看一下产品类型,选“多协议接入”还是“OneNET Studio”要想清楚,最好跟着官方文档走,别只抄教程里的 IP 地址和端口。具体参数在第 4 章会给出。

2. 硬件准备与接线实操

2.1 物料清单与选购建议

这个项目的硬件成本极低,我把清单列出来供你参考。ESP8266 开发板我推荐 NodeMCU 或者 D1 Mini,都是带 USB 口、可以直接用 Arduino IDE 烧录的型号,价格在 10 到 20 元之间。DHT11 传感器模块选蓝色四针小板就行,板载了上拉电阻和滤波电容,比自己买裸传感器再搭外围电路省事很多。接线用的杜邦线公对公、公对母各准备几根,面包板一个用来快速验证。如果要做成成品,还需要一个 3.3V 稳压模块和电池盒或者旧手机充电头供电。

采购时注意区分 DHT11 模块的引脚顺序。蓝色模块一般标注 VCC、DATA、NC、GND,但也有个别厂家把 GND 和 VCC 做成相反的,上电前一定要对一下丝印,别凭经验接。我在实际项目里就遇到过一次模块引脚顺序和网上教程相反的情况,差点烧掉传感器。另外建议一次买 2 到 3 个 DHT11 备用,这玩意儿便宜,但也确实容易在焊接或插拔时损坏。

2.2 接线原理图与电源注意事项

接线其实非常简单,整个电路只有三个连接点。DHT11 模块的 VCC 接 ESP8266 的 3.3V 引脚,GND 接 GND 引脚,DATA 接 GPIO2 引脚(D4)。这里有一个容易踩的坑:DHT11 模块的电源要求是 3.3V 到 5.5V 都能工作,但 ESP8266 的逻辑电平是 3.3V,所以建议都用 3.3V 供电,避免电平不匹配造成读数异常。

DHT11 模块引脚接 ESP8266(NodeMCU)引脚
VCC3.3V
DATAGPIO2(D4)
NC悬空
GNDGND

这里说明一下为什么要接 GPIO2 而不是其他引脚。GPIO2 在 NodeMCU 上对应 D4,这个引脚在板载 LED 附近,默认被拉高,用做单总线数据读取是稳定的。有的教程用 GPIO0(D3)或 GPIO4(D2),也能跑通,但 GPIO0 同时是启动模式选择引脚,如果你接线不当,可能会影响模块上电启动。新手我建议直接抄 GPIO2 这个方案,减少不确定性。

电源是很多人忽略的重灾区。ESP8266 在 WiFi 发射瞬间电流能达到 300mA 左右,如果你用一个输出能力不足的 USB 口供电,模块会反复重启,表现为串口监视器里“重新连接 WiFi”刷屏。解决方法是:第一,优先用电脑 USB 口或者手机充电头供电,不要用 Arduino 板的 5V 引脚转接,除非你的稳压模块输出能力足够;第二,供电线尽量短,质量差的长线会有明显压降;第三,如果传感器离开发板比较远,超过 20cm 建议用屏蔽线或者加长线径,否则 DHT11 的单总线信号会失真。

2.3 硬件点亮与连接验证

硬件接好后,第一次上电不要急着写完整程序,建议先用一个最简单的 Arduino 示例程序验证板子能不能正常启动。在 Arduino IDE 里选择 NodeMCU 对应的开发板,烧录一个 Blink 示例,让板载 LED 闪烁。这一步能确认三件事:驱动是否装好、板卡是否选对、烧录链路是否通畅。

确认板子能跑后,再用下面这段代码验证 DHT11 能否正常读取数据。把 DHT 库和 Adafruit Unified Sensor 库安装好后,编译上传,然后打开串口监视器,波特率设 9600,如果能看到温度和湿度数值输出,就说明硬件链路已经通了。

#include <DHT.h> #define DHTPIN 2 #define DHTTYPE DHT11 DHT dht(DHTPIN, DHTTYPE); void setup() { Serial.begin(9600); dht.begin(); } void loop() { float h = dht.readHumidity(); float t = dht.readTemperature(); Serial.print("Humidity: "); Serial.print(h); Serial.print("% Temperature: "); Serial.print(t); Serial.println("C"); delay(2000); }

注意 Arduino 里的数字 2 对应的是 GPIO2,如果你用的是 NodeMCU 开发板,板子上丝印写的 D4 就是 GPIO2,代码里写 2 即可。如果你换成 D1 Mini,同样也是 D4。这个对应关系建议记下来,我在实际项目中经常看到有人把板子丝印的“D4”当成代码里的数字 4,结果一直读不到数据,排查半天才发现是引脚号写错了。

3. 开发环境搭建:Arduino IDE 与 ESP8266 离线包

3.1 为什么推荐 Arduino IDE 而不是其他框架

ESP8266 可以用的开发环境很多:官方 ESP-IDF、PlatformIO、MicroPython、NodeMCU Lua 等等。但对于做“数据采集 + 上云”这种偏应用的场景,Arduino 框架是最快的路径。因为你需要的大部分功能,WiFi 连接、MQTT 客户端、传感器驱动,社区都已经封装成库了,你要做的是把它们组合起来,而不是从零写协议栈。

ESP-IDF 是乐鑫官方的开发框架,功能最强,但对新手来说门槛偏高,配置工程、编译系统、分区表这些概念足以劝退很多人。MicroPython 开发效率高,但运行时开销大,对 GPIO 的时序控制不如 Arduino 精确,尤其 DHT11 这种单总线协议对时序要求敏感,用 MicroPython 读有时候会不稳定。所以我个人的建议是:第一版用 Arduino IDE,跑通后再根据需求决定要不要换框架。

3.2 安装 Arduino IDE 与 ESP8266 离线安装包

Arduino IDE 的安装本身没什么好说的,官网下载对应系统的版本,一路下一步即可。真正麻烦的是给 IDE 添加 ESP8266 开发板支持。国内网络访问 Arduino 官方板卡管理器地址经常超时,所以更推荐用离线安装包的方式。

具体步骤是:先安装好 Arduino IDE,打开“文件 -> 首选项”,在“附加开发板管理器网址”里填入 ESP8266 社区维护的 JSON 地址。这个地址在 GitHub 上搜 ESP8266 Arduino 就能找到。如果你网络状况不好,下载离线包更方便。离线包的原理是把你需要的 esp8266 工具链和编译器预先打包好,放进 Arduino 的硬件目录里,不同操作系统的路径不同,Windows 一般在C:\Users\你的用户名\AppData\Local\Arduino15或者 IDE 安装目录下的 hardware 文件夹里。

用离线包时,如果你之前已经添加过 JSON 地址,建议先删掉这个地址,否则 Arduino IDE 启动时还是会去尝试联网检查更新,导致打开板卡管理器时卡住。我遇到过好几次这个情况,最后直接把 JSON 填进去又删掉,用离线包安装完,反而省事。

安装完成后,在“工具 -> 开发板 -> 开发板管理器”里搜索 esp8266,能看到“esp8266 by ESP8266 Community”选项,点安装。如果你是用离线包的方式,安装是自动完成的,不需要走这个界面。装完后在“工具 -> 开发板”菜单里选择“NodeMCU 1.0 (ESP-12E Module)”或者你手头板子对应的型号。

3.3 CH340 驱动与开发板参数设置

NodeMCU 和大多数国产 ESP8266 开发板用的 USB 转串口芯片是 CH340,Windows 系统一般会自动识别,但如果你的电脑没装上驱动,串口列表里就看不到端口。这种情况去搜 CH340 驱动,下载对应系统版本装一下即可。装完后在“设备管理器 -> 端口(COM 和 LPT)”里能看到一个 CH340 开头的 COM 口,记住这个 COM 号,在 Arduino IDE 的“工具 -> 端口”里选择它。

开发板参数里有几个关键选项需要说明。CPU 频率默认 80MHz,你不用去动,跑这个项目完全够。Flash Size 一般选 4M(1M SPIFFS),后面做 OTA 升级或网页配网时需要足够大的 Flash 分区才会涉及这个参数。上传速度可以选 115200 或 921600,后者更快,但如果你的 USB 线质量不好,可能出现烧录失败,降到 115200 反而更稳。

还有一个容易忽略的设置是“内置 LED”,NodeMCU 的板载 LED 接在 GPIO2 上,和 DHT11 的数据线是同一个引脚。这意味着烧录完成后,你可能会看到 LED 轻微闪烁或行为怪异,这是正常的,不用管它。但如果你的程序里用了 GPIO2 做其他事,就会和传感器冲突,报错或者读数异常,记住这个引脚不要重复使用。

4. OneNet 平台侧配置:产品、设备与 MQTT 接入参数

4.1 注册账号与创建产品

OneNet 平台的配置是整个项目的“云端一半”,很多人在这一步卡住,原因主要是平台改版导致新旧教程对不上。我的经验是:无论页面怎么变,核心要搞清楚三样东西——产品、设备、API Key,以及它们之间的关系。

登录 OneNet 官网,用手机号注册并实名认证后,进入控制台。找到“产品开发”或“多协议接入”入口,不同版本叫法不同。点击创建产品时,会让你填产品名称、选择产品分类、选择接入协议。这里就是新旧版分叉的地方:如果你用的是新版 OneNET Studio,创建的是“项目”维度,接入方式里选 MQTT;如果你用的是旧版多协议接入,需要选择独立产品并记录产品 ID。

我个人建议直接使用新版的 OneNET Studio,因为它的数据流模板、设备影子、API 调用设计更合理,官方文档也更完备。创建产品时,设备接入协议选 MQTT,数据格式可以选 JSON,其他选项按默认来。创建之后,系统会生成一个产品 ID,这个 ID 在设备鉴权时要用到。

4.2 创建设备并获取鉴权信息

在产品详情页里找到“设备管理”,点击添加设备。设备名称可以随便起,比如“study-room-sensor”,但最好别用中文和特殊字符,避免 MQTT 报文编码问题。添加完成后,设备列表里会出现你的设备,状态是“未激活”,等设备第一次成功上报数据后才会变成“在线”或“已激活”。

点开设备详情,你需要记下几样东西:设备 ID、鉴权信息(Access Token)、产品 ID。在 OneNet 新版里,MQTT 连接的 UserName 一般是产品 ID,Password 一般是设备的 Access Token,ClientID 一般是设备名称。不同平台版本的鉴权字段可能叫法不一样,但核心思路就是这样:服务器通过这三元组确认“你是谁”“你属于哪个产品”“你有没有权限接入”。网上经常有人搜“mqtt 华为云三元组”,说的就是同一个概念。

另外,在产品详情里你还需要配置数据流模板(Data Stream)。这个不是必须的,但强烈建议配置。数据流模板定义了设备上传的数据结构,比如温度字段叫什么、湿度字段叫什么、单位是什么。配置好了以后,OneNet 控制台能自动解析数据并生成曲线,否则光传上来一串 JSON,平台不知道哪个值是温度,哪个是湿度,展示体验会差很多。

4.3 MQTT 接入参数与 Topic 规则

设备接入 OneNet 的 MQTT 服务器,核心参数如下。Broker 地址:mqtts.heclouds.com 或根据你创建产品时页面提示的地址来填,端口一般是 1883(明文)或 8883(TLS)。如果你用的是新版 OneNET Studio,域名和端口以官方文档为准,不同地域可能不同。

Topic 的规则是这个项目的关键。OneNet 支持设备通过发布消息到指定 Topic 来上报数据,一般格式是$sys/{产品ID}/{设备名称}/thing/property/post。这里$sys开头的表示系统级 Topic,不能随意修改。发布消息的 payload 需要按照平台要求的 JSON 结构来组织,常见格式是:

{ "id": "123", "version": "1.0", "params": { "Temperature": 25.6, "Humidity": 60.2 } }

注意这里的TemperatureHumidity字段名必须和数据流模板里定义的一致,否则平台解析不了。我在实际测试中遇到过数据能发到 Topic、但控制台曲线不更新的情况,排查到最后就是 field 名称大小写不一致。

MQTT 接入还有一个概念是订阅 Topic。设备可以订阅$sys/{产品ID}/{设备名称}/thing/property/set,这个 Topic 用于接收平台下发的属性设置指令。对于纯粹的温湿度监控,你可以不订阅,但如果你想做远程控制,比如通过平台下发指令让设备打开继电器,就必须把订阅逻辑加进去。

5. MQTT 协议核心机制与代码实践

5.1 MQTT 核心概念:Broker、Topic、QoS 与保留消息

MQTT 的全称是 Message Queuing Telemetry Transport,翻译过来叫消息队列遥测传输,是物联网场景的事实标准协议。它最大的特点是轻量,报文头很小,非常省流量,很适合 ESP8266 这种资源受限的设备。

理解 MQTT 最直观的方式是类比“微信群”。群主(Broker)管理一个群,群成员(客户端)可以往群里发消息(发布),也可以接收别人发的消息(订阅)。每次发消息必须指定说给谁听,这就是 Topic。比如所有人都在“温湿度/客厅”这个群里聊客厅的温湿度,传感器设备发消息到这个 Topic,你的手机 App 订阅了这个 Topic,就能收到消息。

QoS(Quality of Service)是消息服务质量,共分 3 级。QoS 0 是“发出去就不管了”,消息可能丢失;QoS 1 是“至少一次”,发送方会等接收方确认,但可能重复;QoS 2 是“恰好一次”,性能开销最大。对于温湿度这种周期性上报的数据,用 QoS 0 完全够用,丢一两条没关系,下一周期又会补上。但如果是控制开关、报警这类关键消息,建议至少用 QoS 1。

Retained Message(保留消息)也是一个实用功能。当设备发布一条带 retain 标志的消息后,Broker 会保存这条消息的最新值,新的订阅者一上线就能立刻收到,不用等设备再发一次。这个功能非常适合显示“最新状态”的场景,比如一个显示面板,它订阅 Topic 时立刻就能拿到上一次上报的温湿度值。

5.2 MQTT 与 HTTP、Socket 的对比:为什么 IoT 场景不选 HTTP

很多人会问:我直接用 HTTP POST 上传数据到平台不就行了吗?为什么还要学 MQTT?答案是:HTTP 和 MQTT 解决问题的场景不同。

HTTP 是典型的请求-响应模式,客户端主动发起请求,服务器返回响应。你的 ESP8266 要上传数据,可以每隔 10 秒 POST 一次,平台也能收到。但问题在于:服务器无法主动给设备推送消息,除非设备自己频繁轮询。轮询间隔短,流量和功耗高;间隔长,实时性差。这就像你每隔 10 秒去收发室看一次有没有你的信,有信要等,没信也要跑一趟,效率很低。

而 MQTT 是长连接 + 发布/订阅模式,设备连接 Broker 后保持通路,双方都能随时发送消息,实时性好,而且协议头部开销极小。同样一条消息,HTTP 请求头可能有几百字节,MQTT 固定报文头只有 2 字节。在移动网络或 WiFi 环境下,设备可能休眠后重连,MQTT 的心跳机制也能很好地维持连接状态。

Socket 和 MQTT 的区别就更明显了。Socket 是一种通用的通信方式,你定义了二进制协议自己解析;而 MQTT 在 Socket 之上定义了完整的消息格式、会话状态、QoS、主题路由等机制,相当于你直接用一个现成的高层协议,不需要自己造轮子。自己做 Socket 协议,前期开发量大,后期维护也麻烦,除非有特殊的二进制私有协议需求,否则物联网项目里直接用 MQTT 基本不会错。

5.3 ESP8266 上的 MQTT 客户端库选型

Arduino 生态里最常用的 MQTT 客户端库是 PubSubClient,由 Nick O'Leary 开发。这个库非常轻量,完美适配 ESP8266。另一个选择是 EspMQTTClient,它在 PubSubClient 基础上封装了 WiFi 连接和重连逻辑,使用起来更省心,但自由度稍低,也引入了更多依赖。

我个人的建议是,如果你希望理解 MQTT 连接的全过程,用 PubSubClient 自己写连接和重连逻辑;如果你想快速出活,用 EspMQTTClient。下面章节的代码示例基于 PubSubClient,因为它的使用量最大、网上资料最多,遇到问题也更容易搜到答案。

代码里你需要处理几个关键生命周期:连接 WiFi、连接 MQTT Broker、循环处理 MQTT 消息、断线重连。断线重连尤其重要,因为 WiFi 和 MQTT 都可能因为网络波动断开,如果程序不处理重连,设备就会变成“僵尸节点”,看起来程序还在跑,但数据已经不上传了。实际的温度湿度数据不丢了,但云端曲线连续性断了,你说影响大不大?所以我一般在loop()里先做重连检查,再做发布。

6. 核心代码实现:从读取传感器到 MQTT 上报

6.1 代码整体结构与全局配置

写完前面这些准备工作,现在可以进入最核心的部分了。整个程序分为这么几个模块:全局变量和配置参数、WiFi 连接函数、MQTT 连接函数、DHT11 数据读取函数、数据组装与发布函数、主循环调度。下面先看全局配置:

#include <ESP8266WiFi.h> #include <PubSubClient.h> #include <DHT.h> // WiFi 配置 const char* ssid = "你的WiFi名称"; const char* password = "你的WiFi密码"; // OneNet MQTT 配置 const char* mqtt_server = "mqtts.heclouds.com"; const int mqtt_port = 1883; const char* mqtt_user = "产品ID"; const char* mqtt_password = "设备AccessToken"; const char* mqtt_clientId = "设备名称"; // DHT11 配置 #define DHTPIN 2 #define DHTTYPE DHT11 DHT dht(DHTPIN, DHTTYPE); // MQTT 主题 const char* pubTopic = "$sys/产品ID/设备名称/thing/property/post"; WiFiClient espClient; PubSubClient client(espClient); unsigned long lastPublishTime = 0; const unsigned long publishInterval = 10000; // 10秒上报一次

这些配置里最需要注意的是 mqtt_user、mqtt_password、mqtt_clientId 三者的对应关系,一定要和 OneNet 平台设备详情里的信息一致。我见过很多新手把这几个参数搞混,导致反复出现“MQTT connect failed, rc=-2”,其实就是 Username 和 Password 填反了。

WiFi 配置没有太多好说的,直接写死SSID和密码。如果你希望程序更灵活,能适配不同场所的 WiFi,后面可以考虑用 WiFiManager 库来做网页配网,我在第 7 章的扩展部分会提到这个方案。

6.2 WiFi 连接与 MQTT 连接函数

连接 WiFi 的代码比较标准。需要注意的一点是 WiFi 模式要设为 STA 模式,不要保留 AP 模式,防止 ESP8266 默认开启的 AP 热点占用资源。连接 WiFi 后等待直到 IP 地址拿到为止,这种阻塞式等待在项目里是可以接受的,因为设备上电时本来就需要等网络就绪。

void setupWiFi() { delay(10); Serial.println(); Serial.print("Connecting to "); Serial.println(ssid); WiFi.mode(WIFI_STA); WiFi.begin(ssid, password); while (WiFi.status() != WL_CONNECTED) { delay(500); Serial.print("."); } Serial.println(""); Serial.println("WiFi connected"); Serial.println("IP address: "); Serial.println(WiFi.localIP()); }

MQTT 连接函数里,核心是设置服务器地址、设置回调函数,然后在loop()里循环调用client.loop()以维持心跳。PubSubClient 的连接函数是client.connect(clientId, user, password),返回布尔值表示是否连接成功。连接失败时,client.state()可以拿到错误码,这个对排查问题很有用。

void setupMQTT() { client.setServer(mqtt_server, mqtt_port); client.setCallback(callback); } void reconnectMQTT() { while (!client.connected()) { Serial.print("Attempting MQTT connection..."); if (client.connect(mqtt_clientId, mqtt_user, mqtt_password)) { Serial.println("connected"); client.subscribe(subTopic); } else { Serial.print("failed, rc="); Serial.print(client.state()); Serial.println(" try again in 5 seconds"); delay(5000); } } }

这里的回调函数callback用于处理平台下发的消息,在纯粹的温度监控项目里可以先留空,但定义好回调是一个好习惯,因为后续你要做远程控制时会用到。回调函数会接收三个参数:订阅的 Topic 字符串、消息内容、消息长度,你可以在里面解析 JSON,执行对应操作。

6.3 DHT11 数据读取与容错处理

DHT11 的数据读取在代码里是最简单的,但在实际项目中却是最容易出问题的。问题的根源在于 DHT11 是单总线协议,时序要求较严格。不过既然我们用的是 Arduino 的 DHT 库,底层时序已经被封装好了,我们需要关注的是上层的数据有效性判断。

float readDHTTemperature() { float t = dht.readTemperature(); if (isnan(t)) { Serial.println("Failed to read temperature from DHT11"); return -255.0; } return t; } float readDHTHumidity() { float h = dht.readHumidity(); if (isnan(h)) { Serial.println("Failed to read humidity from DHT11"); return -255.0; } return h; }

isnan检查是必须的。DHT 库在读取失败时会返回 NaN(Not a Number),如果不做处理直接拼 JSON 字符串,OneNet 平台解析时会出错,或者在控制台显示一个很大的负数。我在代码里把失败值设为 -255.0,这样云端曲线会显示一个明显异常的值,提示你设备端有问题需要处理。

DHT11 的读取频率不能太快。数据手册建议两次读取间隔至少 1 秒,实际上我测下来,如果你每 100ms 读一次,偶尔会遇到稳定的超时。我的经验是 2 秒以上没问题,本项目设置的 10 秒上报间隔完全避开了这个限制。读取间隔太短不仅浪费电量,还可能让传感器内部电容放电不充分,导致后续读数漂移。

6.4 MQTT 数据发布与 JSON 组包

MQTT 发布的核心是把温湿度数据按 OneNet 要求的 JSON 格式组织好,然后publish到指定的 Topic。OneNet 新版要求的 JSON 结构是:

{ "id": "123", "version": "1.0", "params": { "Temperature": 25.6, "Humidity": 60.2 } }

在 Arduino 代码里拼 JSON 字符串很不方便,容易出错,推荐用 ArduinoJson 库。这个库是 ESP8266 和 Arduino 开发中几乎绕不开的依赖,安装后在“工具 -> 管理库”里搜索 ArduinoJson,选择最新版安装即可。下面是用 ArduinoJson 组织消息的代码:

void publishDHTData() { float temperature = readDHTTemperature(); float humidity = readDHTHumidity(); if (temperature == -255.0 || humidity == -255.0) { return; // 读取失败,不发布 } StaticJsonDocument<256> doc; doc["id"] = "123"; doc["version"] = "1.0"; JsonObject params = doc.createNestedObject("params"); params["Temperature"] = temperature; params["Humidity"] = humidity; char buffer[256]; serializeJson(doc, buffer); if (client.connected()) { boolean result = client.publish(pubTopic, buffer); if (result) { Serial.print("Publish success: "); Serial.println(buffer); } else { Serial.println("Publish failed"); } } else { Serial.println("MQTT not connected, skipping publish"); } }

注意client.publish的返回值表示发布是否成功。如果返回 false,可能是 Broker 连接已断开,也可能是消息体积超过限制。OneNet 对单条消息大小有限制,通常 256 字节足够。数字精度方面,保存两位小数就够展示和判断告警了,完整的25.6789上传没有任何意义,还占用带宽。

6.5 主循环调度与定时发布

主循环里的核心逻辑是:维护 MQTT 连接、按固定间隔发布数据。你需要一个简单的时间调度机制,而不是在loop()里用delay(),因为delay()会阻塞整个程序,导致 MQTT 心跳无法及时发送,连接容易被 Broker 断开。

void loop() { if (!client.connected()) { reconnectMQTT(); } client.loop(); unsigned long now = millis(); if (now - lastPublishTime >= publishInterval) { lastPublishTime = now; publishDHTData(); } }

这个模式是 Arduino 开发里非常经典的“非阻塞调度”。millis()返回的是开盘以来的毫秒数,用它来判断时间间隔,既不会阻塞,也不会因为loop()执行频率不恒定而影响时间准确性。如果你的程序以后要加更多功能,比如按键响应、LED 闪烁、OTA 升级监听,都用这种模式来写,程序会一直保持流畅。

整个代码编译烧录后,打开串口监视器,0 波特率 115200,你会看到类似这样的输出:WiFi 连接成功、MQTT 连接成功、每 10 秒一条发布成功的日志。然后到 OneNet 控制台看设备状态是不是“在线”,数据流里有没有温度、湿度两条曲线。到这里,一个完整的远程温湿度监控就真正跑通了。

7. 常见问题与排查技巧实录

7.1 典型问题速查表

我在做这个项目的过程中积累了一份问题速查表,这里整理成表格分享给你,按“现象 -> 可能原因 -> 解决办法”的格式来查,效率最高。

现象可能原因解决办法
串口监视器无输出波特率设置错误;串口选择错误;板子没有进入烧录模式检查波特率是否和代码一致;设备管理器重新确认 COM 口;按板子的 BOOT/FLASH 按键再烧录一次
DHT11 读数恒为 0 或 NaN数据线没接对;缺上拉电阻;供电不足检查 GPIO 引脚号;模块上接 4.7k 上拉电阻到 VCC;换一个 USB 口供电
MQTT 连接失败 rc=-2服务器地址或端口不对;客户端 ID 重复;网络不通检查 Broker 域名和端口;确保 ClientId 唯一;用手机热点测试排除网络问题
MQTT 连接失败 rc=-4UserName 或 Password 错误对比 OneNet 设备详情,确认产品 ID、Access Token、设备名称填对
连接正常但控制台无数据Topic 写错;payload 格式不对;字段名不匹配逐字核对 Topic 和 JSON 字段名;对比平台数据流模板定义
设备频繁掉线重连WiFi 信号弱;供电不稳;KeepAlive 设置过短调整设备位置或增加中继;检查电源输出能力;在客户端配置合理的 KeepAlive 间隔
温度湿度明显不准传感器离热源太近;传感器本身质量问题换位置测试;换一个 DHT11 模块做对比

这里的 rc 错误码是 PubSubClient 库返回的 MQTT 连接状态码,-2 表示网络连接失败,-4 表示用户名密码验证失败,3 表示服务器不可用。遇到问题先看串口打印的状态码,缩小排查范围比盲目改代码高效得多。

7.2 DHT11 读数异常的深度排查思路

DHT11 读数异常是新手遇到最多的问题,我把排查思路再详细展开一下。先确认代码里引脚号是不是和硬件接线对应。NodeMCU 的丝印“D4”对应 GPIO2,但如果你用的是 D1 Mini,丝印 D2 对应 GPIO4,D4 才对应 GPIO2。不同板子的丝印和 GPIO 编号映射不一样,千万别想当然。

然后检查传感器类型定义是否正确的。DHT 库用DHT dht(DHTPIN, DHTTYPE)初始化时,第二参数 DHTTYPE 如果写错了,比如用了 DHT11 模块却写 DHT22,读取出来的数据可能出现极其荒谬的值,比如温度 300 度,湿度 10000%。

如果硬件和代码都确认无误还是读不到数据,可以用逻辑分析仪或示波器看波形,但新手通常没有这些工具。我教你一个土办法:用另一块已知正常的开发板跑同样的代码,交叉测试。如果传感器在别的板子上能正常读取,说明传感器没问题,问题在你这块 ESP8266 的供电或引脚上。如果传感器换到别的板子上也读不到,那基本可以断定传感器坏了,换一个新的。

还有一个非常容易忽略的细节:DHT11 通电后需要 1 到 2 秒的稳定时间,如果你在setup()里上电后紧接着就读取,大概率第一次读出的值是 0 或者 NaN。正确的做法是让程序先运行一两秒再去读。我们的程序里loop()每隔 10 秒才发一次数据,第一次数据要等 10 秒,这无形中避开了这个问题。

7.3 MQTT 连接不稳定的几个隐藏原因

MQTT 连接不稳定,排查起来比传感器更隐蔽,因为原因往往不在你写的代码里。我遇到过的最典型的一种情况是:设备刚连上 WiFi 就去连接 MQTT,而路由器还没有完全分配好 IP,导致 TCP 连接失败。解决方法是连接 WiFi 后加一个短暂延时,确认拿到 IP 后再连 MQTT。

另一种情况是 ESP8266 本地时钟乱跳导致 TLS 证书验证失败。如果你用的是 8883 加密端口,Broker 会校验设备时钟,而 ESP8266 没有 RTC 电池,刚上电时时间是 1970 年,TLS 握手会失败。如果你用了加密连接而连接一直失败,可以先换成 1883 明文端口测试,排除 TLS 时间问题。实际生产环境才需要认真处理证书和时间的同步,个人项目用明文就够了。

还有一个网络层面的大坑:有些路由器开启了 AP 隔离或客户端隔离,导致接入设备之间、设备与外部网络之间通信受限。我在朋友家调试时就遇到过,ESP8266 能连上 WiFi,但访问外网一直失败,后来才发现是路由器开了“访客网络隔离”。这个问题无法从设备端解决,只能改路由器设置。

最后是 KeepAlive 参数。PubSubClient 默认的连接心跳周期是 15 秒,如果你所在网络环境不稳定,可以把 KeepAlive 调低到 10 秒甚至 5 秒,让 Broker 更快感知到设备失联,减少“假在线”时间。不过 KeepAlive 太短会增加网络流量和功耗,需要权衡。

7.4 安全与合规提示

做物联网项目绕不开安全话题,虽然这个项目是个人学习和实验性质,但我还是建议你从第一天就养成好习惯。第一,不要把 WiFi 密码、平台 Access Token 硬编码在代码里之后又传到公开的 GitHub 仓库。我见过太多人把真实凭据直接写到博客示例里,这是非常危险的行为。建议你把配置写在单独的头文件里,并加入.gitignore,或者从环境变量读取。

第二,如果你计划把这个设备部署到真实的公共环境,比如办公室、机房,建议在 MQTT 连接时使用 TLS 加密,并且为设备配置独立的、权限最小的 Access Token,不要使用产品管理员级别的密钥。OneNet 平台里你可以为每个设备生成独立的 Access Token,这样即使某一个设备的凭据泄露,也不会影响其他设备。

第三,不要用默认密码和默认凭据。OneNet 平台在创建设备时会生成默认的 Access Token,你可以在设备详情里手动改成强随机字符串。WiFi 密码也不要使用弱口令。这些看似基础的习惯,能在未来避免很多麻烦。

8. 功能扩展方向与个人体会

8.1 网页配网:用 WiFiManager 告别硬编码 WiFi

当你的设备要交给别人使用,或者你自己需要经常切换 WiFi 环境时,硬编码 WiFi 用户名密码的方式就变得非常痛苦。每次换地方都要改代码重新烧录,来回折腾很费时间。WiFiManager 库专门解决这个问题。

它的原理是:设备上电后如果检测不到预先配置的 WiFi,就自动开启一个 AP 热点,你拿手机连上这个热点,浏览器会自动弹出配置页面,选择你要连接的 WiFi 并输入密码。设备保存配置后自动重启并连接到目标 WiFi。这个库还支持自定义参数,比如你可以让用户同时配置 MQTT 服务器地址和设备名称,对做小批量产品非常实用。结合热搜词里的“esp8266 安装wifimanager”和“esp8266 网页配置wifi”,这个方向确实值得深入研究。

引入 WiFiManager 后,代码结构会有一个大的变化:setup()里不再直接WiFi.begin(),而是先初始化 WiFiManager,然后调用autoConnect()。需要注意的是 WiFiManager 一次配置后会把信息存到 Flash 里,下次上电直接连接,不再自动开启 AP。如果你需要重新配置,需要在程序里留一个按键触发resetSettings(),这也是项目里常见的“长按按键恢复默认设置”功能的实现方式。

8.2 远程控制与告警:从单向上报到双向交互

目前这个项目是单向的:设备采集数据,主动上报到云平台。但实际应用场景中,我们往往需要反向控制。比如监测到一个花房的温度过高,你想远程打开风扇;或者监测到机房湿度太大,你想启动除湿机。这时就需要用到 MQTT 的双向通信能力。

实现方式是在代码里订阅一个下行 Topic(在 OneNet 中通常是$sys/{产品ID}/{设备名称}/thing/property/set),然后在回调函数里解析平台下发的 JSON 指令,根据指令控制 GPIO 输出,进而控制继电器、风扇、加热器等外围设备。这个扩展可以让项目从“监控”升级为“监控 + 控制”,真正实现远程管理。

告警功能同样可以通过 OneNet 平台的消息推送实现。你可以在平台侧配置规则引擎,当温度超过某个阈值时,通过短信、邮件或微信推送告警。这一部分的实现不涉及设备端代码,完全在 OneNet 控制台配置,但它的价值非常大:以前你必须打开 App 才能看到数据是否异常,现在系统主动告诉你设备出问题了,这是从“被动查看”到“主动监控”的质变。

8.3 本地显示与数据持久化:把数据留一份在自己的系统里

如果你不想完全依赖第三方平台,或者希望数据保存在自己的服务器上,可以考虑在树莓派、NAS、或者云服务器上搭建自己的 MQTT Broker,比如 Mosquitto,再用 Node-RED、Grafana 等工具做数据展示和存储。这种方案的优点是数据完全自主可控,可以自己做后期分析,比如统计某个月的平均温度,判断设备健康状况等。

很多人在网上搜“mqtt服务器搭建docker”,就是想把 Mosquitto 用 Docker 跑起来。用 Docker 搭建 MQTT Broker 确实是现在最主流的方式,一个docker run命令就能搞定,不需要手动编译安装依赖。你甚至可以在此基础上用 Node-RED 做一套可视化大屏,配合 InfluxDB 时序数据库存储历史数据,做成一个相当完整的数据采集系统。

不过这一套方案的学习曲线比 OneNet 高不少,涉及 Linux 基础、Docker 使用、数据库配置、前端展示等多个技术点。我的建议是:第一版先用 OneNet 跑通,等确认传感器数据稳定、理解了 MQTT 的整个机制之后,再考虑自建服务器方案。循序渐进比一步到位靠谱得多。

8.4 低功耗改造:电池供电的远程监控节点

实话说,ESP8266 在低功耗方面并不擅长,它的 WiFi 模块工作电流较大,如果直接电池供电,几节 AA 电池撑不过一周。但如果你要部署一个没有电源插座的地方做长期监控,就需要考虑低功耗改造。

基本的思路是让设备大部分时间处于休眠状态,定时唤醒采集数据并上传。ESP8266 支持 Deep Sleep 模式,功耗可以降到几十微安,配合一个定时器唤醒,比如每 30 分钟醒来一次,上传完数据继续睡。这种模式下,一节 18650 锂电池理论上可以撑几周甚至一两个月。不过 DHT11 从睡眠唤醒到稳定读取需要时间,所以唤醒后先延迟一两秒再读取传感器,否则读数会不准。

代码层面,使用ESP.deepSleep(microseconds)进入休眠,唤醒后程序从setup()重新开始执行,所以数据上报逻辑要写在setup()里,上报完成后立即进入休眠,而不是放在loop()。这个模式设计好了,你的远程监控节点就真的可以摆脱电源线束缚,部署到温室大棚、果园、仓库阁楼这些不方便拉电线的角落里长期运行了。

我个人在这套方案上花费的时间最多的地方不是代码,而是排查各种环境问题。用了一段时间后,你慢慢的就能理解为什么物联网项目没有“一招通吃”的解法:传感器有适用场景的差异,网络环境各不相同,平台版本也在不断更新。项目最有价值的部分从来不是那一两行核心代码,而是你对整条链路的理解和排错时练出来的感觉。

最后分享一个我常用的调试技巧:如果设备连不上网、连不上平台,先把收发数据的链路拆开测试,用手机开热点给 ESP8266 用,确认能不能正常上报。如果手机热点能上报、家里的 WiFi 不行,问题多半在路由器;如果手机热点也不行,问题多半在设备端或者平台配置。这种逐段排查的方式,能帮你快速定位问题,而不是漫无目的地乱改代码。希望这篇文章能帮你少走一些弯路,早日把自己的第一套远程监控跑起来。

本文还有配套的精品资源,点击获取

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

让AI自动生成流程图:从手搓到Skill封装实战

在日常开发和方案设计里&#xff0c;流程图往往是先于代码出现的第一份资料。很多开发者对画流程图这件事并不陌生&#xff0c;但真正动起手来&#xff0c;却常常卡在“图形绘制”环节&#xff1a;逻辑其实已经想清楚了&#xff0c;可打开绘图工具后&#xff0c;方框、箭头、对…

作者头像 李华
网站建设 2026/9/2 22:51:25

机器人空翻之后:从“会翻”到“该翻”的决策智能跃迁

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

作者头像 李华
网站建设 2026/9/4 17:07:34

IndexTTS2本地部署实战:对比GPT-SoVITS更省事的语音克隆方案

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

作者头像 李华
网站建设 2026/9/4 17:09:10

TensorFlow与PyTorch深度学习框架选型指南:从原理到实践

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

作者头像 李华
网站建设 2026/9/6 7:11:08

实验室预约小程序开发实战:数据库设计与并发冲突处理

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

作者头像 李华
网站建设 2026/9/5 7:02:42

汇川AM600+Codesys多轴控制实战:从电子齿轮到指针应用

简介&#xff1a;本资源是一套面向自动化工程师与PLC进阶学习者的新能源领域实战案例&#xff0c;聚焦汇川中大型PLC在多轴协同控制中的Codesys编程实现&#xff0c;解决复杂运动控制逻辑设计、实时同步、指针动态寻址及HMI交互集成等核心问题。压缩包共80个文件&#xff0c;含…

作者头像 李华