微信小程序+MQTT+ESP8266:DHT11温湿度监测与LED控制实战
2026/9/10 17:12:14 网站建设 项目流程

简介:ESP32微信小程序智能家居控制系统是一套面向物联网初学者的实用源码包,围绕MQTT消息协议串起温湿度采集、烟雾报警和LED远程控制等完整链路。压缩包共3个文件,分别为Arduino主程序(.ino)、MQTT客户端源文件(.cpp)及其头文件(.h);.ino负责ESP32初始化、Wi-Fi联网、MQTT配置与传感器数据读取,.cpp/.h则提供连接云服务器和收发消息所需的底层接口。整包仅7KB,体量非常精简,却完整覆盖硬件接入、云端通道与设备控制三块核心代码,适合课程设计、智能家居改造入门或毕业设计对照。已有1772人在CSDN学习下载,可直接参照其中逻辑理解微信小程序与ESP32通过MQTT双向通信的流程,进而掌握DHT11单总线温湿度读取、烟雾传感阈值报警以及LED远程开关的实现方法。哪怕是刚接触Arduino的读者,也能借此了解从设备端到云平台再到手机端的完整数据链路。

1. 微信、MQTT、DHT11 与 LED 的搭积木游戏:这个 zip 里到底装了什么

微信作为国民级应用,跟一块温湿度传感器和 LED 灯有什么关系?我第一次看到这个 zip 包名时,以为只是把几个流行词拼在一起。但仔细拆开链路:微信小程序做人机界面,MQTT 当消息总线,DHT11 负责感知环境,LED 作为执行反馈——这恰好构成了物联网里从“感知”到“控制”的最小闭环。这个项目特别适合放在宿舍或实验室里当环境监测报警器,也可以用来系统学习“设备 - Broker - 微信端”三层架构。我习惯把这类压缩包先解压,分清固件目录和小程序目录,把无关的打包文件删掉再跑条件编译。目标是让嵌入式工程师能复用这套通信协议,让前端开发也能看懂设备端的取舍;新手按步骤接线烧录,熟手直接改主题和回调逻辑就能接入自己的业务。

2. 先立住理论:MQTT 消息模型与 DHT11 温湿度数据格式

很多 zip 项目跑不起来,不是因为代码缺什么,而是因为对 MQTT 主题的“有状态”理解不够。DHT11 的数据是“读取型”,LED 的状态是“保持型”,两者在消息设计上完全不一样。只有把这一层想清楚,后面写固件和微信端才不会被回调函数绕晕。

2.1 MQTT 协议详解:发布订阅模型与 QoS 选择

MQTT 是基于发布订阅模型的轻量消息协议,它和 HTTP 的本质区别在于:HTTP 是请求-响应,客户端必须知道服务端地址;MQTT 则通过 Broker 中转,发布者只管发,订阅者只管收,两者时间上可以错开。这对传感器节点极其友好,因为设备可以“说一句就下线”,Broker 还能把最后一条消息留住,微信端上线后再补拿。

我一般会在本地或者云主机上先部署一个 Mosquitto 或 EMQX 作为 Broker。部署完成后,用命令行客户端先行验证链路,这里最常用的就是mosquitto_sub

mosquitto_sub -h 192.168.1.10 -p 1883 -u device1 -P device1_secret -t 'home/room1/#' -v

参数解释:-h指定 Broker 主机地址,-p是端口,-u-P是 MQTT 用户名和密码(如果 Broker 开了 ACL),-t是要订阅的主题,#通配符代表匹配该层级下所有子主题,-v会在输出里同时打印主题和载荷。我刚写固件时一定先开一个这样的终端,能直观看到设备有没有成功上报,载荷是不是合法 JSON。

MQTT 的 QoS 这里多说一点,选错会发生丢数据或者重复控制指令。下表是几个典型场景的对比:

QoS语义下发 LED 指令上报 DHT11 数据
0最多一次,不确认一般不选常用,丢了下次再报
1至少一次,有重传推荐,防止丢灯控可以用,但可能重复
2仅一次,握手重很少用不推荐

注意很多 Arduino 库的publish第三个参数是保留标记,不是 QoS。像 PubSubClient 的publish(topic, payload, retained)只能发 QoS 0。如果你需要 QoS 1,可以换用支持 QoS 的客户端,或者用 Mosquitto 的mosquitto_pub手动测试 ACL 链路。

2.2 温湿度传感器 DHT11 的单总线时序与数据解析

DHT11 是典型的单总线传感器,一条数据线既做输入又做输出。主机先拉低总线 18ms 以上,然后释放,DHT11 会回一个低电平响应信号,之后再输出 40 位数据。每一位的“0”和“1”靠高电平持续时长区分:大约 26-28us 是高电平视为 0,70us 左右视为 1。反正逻辑电平很快,我常见的误区是拿逻辑分析仪去抓波形,其实用示波器看一遍就记住了。

40 位数据排列是:湿度高 8 位、湿度低 8 位、温度高 8 位、温度低 8 位、校验和 8 位。校验和 = 前四字节相加取低 8 位。读取代码一般用现成库,但核心逻辑要能看懂:

uint8_t dht_data[5] = {0, 0, 0, 0, 0}; if (dht11_read(dht_data)) { float humidity = (dht_data[0] << 8 | dht_data[1]) / 10.0f; float temperature = ((dht_data[2] & 0x7F) << 8 | dht_data[3]) / 10.0f; if ((uint8_t)(dht_data[0] + dht_data[1] + dht_data[2] + dht_data[3]) != dht_data[4]) { // 校验失败,丢弃本次读取 } }

我在实际工程中一般不直接操作底层的精确延时,而是用 DHT sensor library 封装好的readTemperature()readHumidity()。尽管如此,还是要记住 DHT11 的采样周期至少 1 秒,连续快速读会导致返回NAN或固定值,黑客在论坛上说的“读取卡死”多半就是这个原因。

2.3 消息体设计:JSON 还是纯文本?

设备端能发一串26.5 45.0,但微信小程序那边解析会很难受。我通常把上报和控制都设计成 JSON,理由很简单:可扩展、可读、不用位运算。主题划分会直接影响 ACL 和调试,所以我把传感器和控制指令分开:

  • home/room1/sensor—— 设备定时上报的温湿度
  • home/room1/led—— 微信端下发的 LED 控制

传感器载荷示例:

{ "device": "esp8266-01", "type": "sensor", "temp": 26.4, "hum": 45.2, "ts": 1712330000 }

device字段允许同一主题下多设备共存,以后增加第二个传感器也不用改主题。ts是 Unix 时间戳,微信端拿它算数据新鲜度。LED 控制载荷保持简单,便于在 callback 里直接判断:

{"state": "on", "brightness": 128}

brightness预留了后续 PWM 调光,哪怕旧客户端不认识这个字段也不影响开关功能。这就是 JSON 最实在的好处:加字段不下滑兼容。

3. 固件端实现:ESP8266 采集 DHT11 并控制 LED

理论部分足够清了,现在可以动手烧录固件。我选择 ESP8266 作为主控,因为 Arduino 生态对 DHT11 和 MQTT 的支持非常成熟,入门成本低。如果你用的是 STM32,思路也是一样的:封装好 WiFi 模块和 MQTT 客户端,再对接 GPIO 逻辑。

3.1 硬件接线与元器件清单

准备一块 ESP8266 开发板、DHT11、直插型 LED、1kΩ 限流电阻、面包板和若干杜邦线。接线原则是避开 ESP8266 下载模式和串口引脚冲突,我一般把 DHT11 放在 GPIO4,LED 放在 GPIO5。

DHT11 接线表:

DHT11 引脚接 ESP8266 引脚说明
VCC3.3VDHT11 工作电压 3.3V-5V,直接 3.3V 即可
GNDGND跟 ESP8266 共地
DATAGPIO4 / D2这个引脚必须外接 10kΩ 上拉电阻
悬空脚不接不用管

LED 接线表:

LED 引脚接 ESP8266 引脚说明
正极GPIO5 / D1串联 220-470Ω 电阻
负极GND低电平点亮,也可以高电平但需要换接线

很多人上来就把 LED 直接接 3.3V,然后发现 GPIO 驱动能力不足,灯只亮一下就灭。限流电阻用欧姆定律算:R = (3.3 - LED 正向压降) / 工作电流,普通红色 LED 压降约 1.8V,取 5mA 工作电流,算出来约 300Ω,实际取 1kΩ 也能亮,只是亮度低一些。

3.2 Arduino 固件:连接 WiFi 与 MQTT

打开 Arduino IDE,安装DHT sensor libraryPubSubClient,然后新建工程粘贴最小代码。下面这份代码我尽可能压到最少行数,但保留身份验证和回调处理:

#include <ESP8266WiFi.h> #include <PubSubClient.h> #include <DHT.h> #define DHTPIN 4 #define DHTTYPE DHT11 #define LEDPIN 5 const char* ssid = "YOUR_WIFI"; const char* wifi_password = "YOUR_WIFI_PASS"; const char* mqtt_host = "192.168.1.10"; const int mqtt_port = 1883; const char* mqtt_user = "device1"; const char* mqtt_pass = "device1_secret"; const char* sensor_topic = "home/room1/sensor"; const char* led_topic = "home/room1/led"; WiFiClient espClient; PubSubClient client(espClient); DHT dht(DHTPIN, DHTTYPE); void connectMQTT() { while (!client.connected()) { if (client.connect("esp8266_room1", mqtt_user, mqtt_pass)) { client.subscribe(led_topic); } else { delay(2000); } } } void callback(char* topic, byte* payload, unsigned int length) { String message = ""; for (int i = 0; i < length; i++) { message += (char)payload[i]; } if (String(topic) == led_topic) { if (message.indexOf("\"on\"") >= 0) { digitalWrite(LEDPIN, HIGH); } else { digitalWrite(LEDPIN, LOW); } } } void setup() { pinMode(LEDPIN, OUTPUT); digitalWrite(LEDPIN, LOW); Serial.begin(115200); dht.begin(); WiFi.mode(WIFI_STA); WiFi.begin(ssid, wifi_password); while (WiFi.status() != WL_CONNECTED) { delay(500); } client.setServer(mqtt_host, mqtt_port); client.setCallback(callback); } void loop() { if (!client.connected()) { connectMQTT(); } client.loop(); static unsigned long lastMsg = 0; if (millis() - lastMsg > 5000) { float h = dht.readHumidity(); float t = dht.readTemperature(); if (!isnan(h) && !isnan(t)) { String payload = String("{\"device\":\"esp8266-01\",\"type\":\"sensor\",\"temp\":") + String(t) + String(",\"hum\":") + String(h) + String(",\"ts\":") + String(millis() / 1000) + String("}"); client.publish(sensor_topic, payload.c_str(), true); } else { Serial.println("Failed to read from DHT11"); } lastMsg = millis(); } }

代码说明:client.connect("esp8266_room1", mqtt_user, mqtt_pass)的三个参数分别是客户端 ID、用户名和密码。如果 Broker 没有开认证,后两个参数可以留空;但生产环境一定要开,否则谁都能控制你的 LED。client.subscribe(led_topic)在连接成功后调用,表示只关注 LED 控制指令。

publish(sensor_topic, payload.c_str(), true)的第三个参数是 retained 标记,不是 QoS。这个true让 Broker 缓存该主题的最新一条消息,微信端订阅时立刻就能收到最后的状态。温湿度上报周期我放在5000ms,DHT11 完全扛得住;如果你把它改成 500ms,大概率读到NAN

3.3 LED 控制回调与本地自动保护

callback里用message.indexOf("\"on\"")判断是否包含“on”,这是最快速的匹配方式,不用引入 JSON 解析库。如果微信端发给你的 JSON 字符串中间有空格,比如{"state": "on"},这里的indexOf("\"on\"")也能命中,因为"on"前后有引号作为边界。

但只有回调是不够的。我通常会加一层“本地优先”逻辑:比如温度超过 30°C 时 LED 自动点亮作为报警,此刻忽略远程关机指令。做法是在loop()里判断温度,而不是在callback里:

static bool overheated = false; void loop() { // ... if (!isnan(t)) { if (t > 30.0) { digitalWrite(LEDPIN, HIGH); overheated = true; } else if (overheated && t < 28.0) { overheated = false; } } }

注意在callback里加这样一段保护逻辑会造成状态覆盖,因为回调触发时t不一定是当前最新温度。所以我习惯把本地保护放在主循环里,只在最后让“远程控制”覆盖“本地状态”,这样可预测性更强。

4. 微信小程序通过 MQTT over WebSocket 远程控制

微信小程序不能直接发起 TCP 连接,所以要使用 MQTT over WebSocket 才能让小程序与 Broker 通信。一般做法是让 Broker 监听 WS/WSS 端口,小程序用 MQTT.js 连接。如果你不想折腾前端证书,还可以走“后端订阅 + 模板消息”的链路,但那种方案只能推送通知,不能反向控制。

4.1 小程序中使用 mqtt.js 连接 Broker

先在本地执行npm install mqtt,然后把dist/mqtt.min.js拷贝到小程序的utils目录。开发阶段在微信开发者工具里勾选“不校验合法域名”,正式发布前再去公众平台配置 Socket 域名。页面核心连接代码如下:

const mqtt = require('../../utils/mqtt.min.js') Page({ data: { temp: '--', hum: '--', ledOn: false, connected: false }, onLoad() { const client = mqtt.connect('wss://user:pass@192.168.1.10:8084/mqtt', { clientId: 'wx_' + Math.random().toString(16).substr(2, 8), cleanSession: true, reconnectPeriod: 4000 }) client.on('connect', () => { this.setData({ connected: true }) client.subscribe('home/room1/sensor') client.subscribe('home/room1/led') }) client.on('message', (topic, payload) => { const msg = JSON.parse(payload.toString()) if (topic === 'home/room1/sensor') { this.setData({ temp: msg.temp && msg.temp.toFixed(1), hum: msg.hum && msg.hum.toFixed(1) }) } else if (topic === 'home/room1/led') { this.setData({ ledOn: msg.state === 'on' }) } }) client.on('close', () => { this.setData({ connected: false }) }) this.client = client }, toggleLed() { const state = this.data.ledOn ? 'off' : 'on' this.client.publish('home/room1/led', JSON.stringify({ state }), { qos: 1 }) } })

这里wss://user:pass@192.168.1.10:8084/mqtt的地址写法是把用户名和密码直接放在 URL 里,MQTT.js 解析时会自动拆出来。路径/mqtt是很多 Broker 默认的 MQTT over WebSocket 路径,如果你的 Broker 用了自定义路径,这里必须改一致。clientId加随机后缀是为了避免多个用户使用同一个固定 ID 互踢。

reconnectPeriod建议设在 3000-5000ms。太短会导致弱网环境下疯狂重连,反而加重 Broker 负担。cleanSession: true意味着每次上线都是全新的会话,不保存遗嘱消息;如果你希望离线消息也能收到,则需要设为 false 并认真设计session expiry

4.2 WXML 页面绑定与事件处理

页面布局不复杂,放一个卡片显示温湿度,放一个 switch 控制 LED:

<view class="card"> <text>温度:{{temp}} °C</text> <text>湿度:{{hum}} %</text> <switch checked="{{ledOn}}" disabled="{{!connected}}" bindchange="toggleLed" /> <text wx:if="{{!connected}}">连接已断开</text> </view>

bindchange="toggleLed"在小程序 switch 组件里回传一个事件对象,但我们不关心它的detail.value,而是直接读data.ledOn取反。这样保证“先改界面再发指令”,避免快速点击多次发送一样的状态。如果你是资深前端,想用内置的wx.connectSocket自己封装全套 MQTT,那我建议别费那个劲,直接用 MQTT.js 省心得多。

4.3 没有小程序时:微信公众号模板消息桥接

如果你暂时不想注册小程序,也不要写客户端,还可以用公众号模板消息做单向告警。先写一个守护脚本订阅home/room1/sensor,温度超过阈值就调用公众号的接口把消息推给粉丝。这个方案不需要 WebSocket,代码量更小,但只能近实时推送,用户无法直接在微信里控制 LED。

实现方法很简单:Node-RED 里拖一个mqtt in节点,再接Function节点判断温度,最后接HTTP Request节点调用模板消息接口。后端脚本也可以使用 Python 的paho-mqttrequests,逻辑完全一致。这个思路适合做“告警通知”,而不是“远程控制”,两个场景别搞混。

5. 部署排错与进阶:把 zip 里的项目跑成稳定节点的 6 个细节

部署顺序建议是:先配 Broker,再烧录固件,最后改小程序。以下是容易出现问题的地方以及排查方法:

现象排查点参考命令/代码
温湿度数据不更新DHT11 接线不稳或采样间隔太短mosquitto_sub -h broker -t home/room1/sensor -v
MQTT 每隔几秒断开客户端 ID 冲突或 ACL 拒绝tail -f /var/log/mosquitto/mosquitto.log
小程序无法连接证书无效或不支持 wss开发者工具 Network 面板看状态码
LED 灯不执行主题不匹配,或载荷里没有"on"mosquitto_pub -t home/room1/led -m '{"state":"on"}'
灯亮一下就灭GPIO 驱动电流不足测量 LED 两端电压,确认不低于 1.8V
上报数据偶尔错乱DHT11 读取距离上次小于 1 秒delay改为 1500ms 以上

最后一颗进阶棋子,我指的是 PWM 调光。普通 LED 的驱动电路只需要一个限流电阻,但如果你用 ESP8266 的analogWrite(LEDPIN, brightness)输出 PWM,LED 的亮度就会变成平滑可调。微信端小程序发送{"state":"on","brightness":512},固件在 callback 里解析 brightness 然后执行analogWrite(LEDPIN, brightness)。PWM 频率默认在 1kHz 左右,不会产生人耳可闻噪声,大多数普通 LED 也扛得住。更复杂的大功率 LED 需要外置恒流驱动芯片,但原理仍是 PWM 占空比控制。

验证链路打通很快:先mosquitto_sub看到温湿度,再mosquitto_pub控制 LED,最后小程序点击 switch 观察 Broker 日志。稳定性调优时,记得给 ESP8266 加看门狗,ESP.wdtFeed()放在 WiFi 重连的 while 循环里,避免断网时重启卡死。

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

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询