1. 从一个温控风扇说起MQTT Climate-Triggered Cooling 到底在做什么夏天最让人头疼的事情之一就是家里某个角落温度悄悄升上去等人发现的时候设备已经烫得不行。尤其是弱电箱、机柜、阳台储物柜、宠物屋这类封闭空间空调吹不到、通风又差靠人盯着温度计显然不现实。我最早做这个项目的动机特别朴素家里弱电箱里塞了光猫、路由器、交换机、NAS 电源夏天箱内温度能比室温高出七八度摸上去烫手。于是就想能不能让温度传感器自己判断一旦超过阈值就自动开风扇温度降下来再关掉全程不用我管。这个项目的核心就是MQTT Climate-Triggered Cooling翻译成大白话就是“基于 MQTT 协议、由环境温度触发的自动降温系统”。它要解决的问题非常明确让温度数据驱动执行器实现无人值守的闭环温控。整套系统由三部分组成——负责采集温度并上报的传感节点、负责转发消息的 MQTT 代理Broker、负责订阅温度并控制风扇的執行节点。三者之间不直接通信全部通过 MQTT 的发布/订阅机制解耦这也是它比“传感器直接连风扇”更值得折腾的地方。适合谁来参考这篇内容如果你玩过 Arduino、ESP32 这类开发板听说过 MQTT 但没真正跑通一个完整项目或者你已经在用 KingSCADA、TLINK 这类平台采集数据想把“数据”变成“动作”那这篇就是写给你的。我会把选型逻辑、接线、代码、阈值调参、踩坑记录全部摊开讲尽量做到你照着做就能复现。哪怕你只是想给鱼缸、孵化箱、3D 打印机舱室做个自动散热这套思路也能直接搬过去。2. 整体架构设计与方案选型为什么是 MQTT 而不是别的2.1 为什么温控场景特别适合 MQTT先说说为什么这个项目我坚持用 MQTT而不是用 HTTP 轮询或者蓝牙直连。温控场景有几个天然特点数据量小、上报频率稳定、需要一对多分发、执行端可能不止一个。温度值就是一个浮点数几秒钟发一次用 HTTP 每次都要重新建立连接、带一堆请求头纯属浪费。MQTT 是长连接消息头最小只有两个字节天生为这种“小包高频”场景设计。更关键的是发布/订阅模型带来的解耦。传感器只管往home/workshop/temperature这个主题Topic发消息它根本不知道谁在听。风扇控制器订阅这个主题SCADA 平台也订阅这个主题手机上的监控 App 还能订阅同一个主题。以后我想加一个“温度过高发通知”的功能只需要再起一个订阅者传感器和执行端一行代码都不用改。这种“加功能不动老代码”的特性在真实项目里省下的维护成本远超你的想象。提示MQTT 的 QoS 等级选择很关键。温控这种场景用 QoS 0最多一次就够了丢一两条温度数据不影响判断但如果是控制指令比如“开风扇”建议用 QoS 1至少一次确保指令不丢。别一上来就无脑用 QoS 2握手开销大温控场景没必要。2.2 三种典型架构的取舍在实际落地时我试过三种架构各有适用场景这里做个对比方便你按自己的条件选。架构方案组成优点缺点适用场景直连式传感器继电器一体结构最简单无需网络无法远程监控阈值改不了单点、临时使用本地 Broker 式传感器本地MQTT执行器局域网内低延迟数据不出户出门就看不到数据家庭、小型工作室云端 Broker 式传感器云MQTT执行器平台随时随地可监控可对接SCADA依赖外网有延迟多点位、需远程管理我自己的弱电箱项目用的是本地 Broker 为主、云端为辅的混合方案本地 Broker 保证断网也能自动降温这点极其重要断网时风扇不能罢工同时把数据桥接到云端方便我在外面用手机看曲线。如果你用的是 KingSCADA 或 TLINK 这类平台它们通常自带 MQTT 接入能力可以直接作为云端订阅者省去自己搭可视化界面的功夫。2.3 硬件选型的几个关键考量主控我选的是 ESP32而不是更便宜的 ESP8266 或 Arduino 网口模块。原因有三第一ESP32 自带 Wi-Fi 和蓝牙省一个模块第二双核处理器一个核跑 MQTT 心跳一个核跑传感器采样互不阻塞第三ADC 精度和引脚数量都够用后面想加个继电器、OLED 屏都不捉襟见肘。Arduino UNO 加网口扩展板也能做但成本和体积反而更高除非你手头正好有。温度传感器我用的是DS18B20 防水探头而不是 DHT11/DHT22。DHT 系列测的是空气温湿度精度一般±2℃而且响应慢、容易受潮漂移。DS18B20 是单总线数字传感器精度 ±0.5℃防水封装可以直接贴在被测物体表面或者伸进机柜深处一根线能串好几个特别适合多点测温。如果你要测的是空气温度而非表面温度DHT22 也能用但记得加个防潮处理。执行端就是最普通的5V 继电器模块 12V 风扇。这里有个细节风扇不要直接接在 ESP32 的 GPIO 上GPIO 输出电流只有几十毫安带不动风扇必须通过继电器或者 MOS 管驱动。继电器模块选“低电平触发”还是“高电平触发”要看你的代码逻辑我习惯用低电平触发因为很多模块上电默认高电平低电平触发能避免上电瞬间风扇误转。3. 核心细节拆解从温度采集到风扇转动的完整链路3.1 温度采集与数据格式设计DS18B20 的读取依赖 OneWire 总线协议Arduino 生态里有现成的OneWire和DallasTemperature库几行代码就能读出摄氏度。但真正要设计的是上报的数据格式。我见过太多人直接发一个裸浮点数26.5结果后面想加个湿度、加个设备 ID 就得推倒重来。正确的做法是从一开始就用结构化格式我推荐 JSON虽然比纯文本多几个字节但可读性和扩展性好太多。{ device: workshop-node-01, temp: 26.5, unit: C, ts: 1718000000 }这个格式里device用于区分多个节点temp是温度值unit明确单位避免华氏摄氏混淆ts是时间戳。时间戳很重要因为 MQTT 消息本身不带时间如果 Broker 转发有延迟或者你事后分析历史数据没有时间戳就抓瞎。ESP32 可以连 NTP 服务器同步时间成本几乎为零。注意JSON 里的温度值建议保留一位小数即可。DS18B20 分辨率可以设到 0.0625℃但上报两位小数会让消息体积翻倍而温控场景根本不需要这么高的精度。我一般设成 12 位分辨率0.0625℃但上报时四舍五入到 0.1℃。3.2 MQTT 主题Topic的命名规范主题命名是 MQTT 项目里最容易被忽视、但后期最影响维护的部分。我踩过的坑是一开始随便起了个temp后来设备多了根本分不清哪个是哪个。后来我固定用一套分层命名法{场所}/{设备类型}/{设备ID}/{数据类型}比如workshop/sensor/node01/temperature、workshop/actuator/fan01/state、workshop/actuator/fan01/command。这样设计的好处是你可以用通配符批量订阅。比如 SCADA 平台想订阅所有传感器数据直接订阅workshop/sensor//temperature就行是单层通配符。想订阅 workshop 下所有消息用workshop/##是多层通配符。这里有个实操心得控制指令和状态上报一定要用不同的主题。风扇的开关指令发到.../command风扇实际状态回报到.../state。为什么因为指令发出去了不代表执行成功继电器可能卡住、风扇可能断电。只有订阅state主题你才知道风扇到底转没转。这个“指令与状态分离”的原则是所有可靠控制系统的通用做法。3.3 阈值判断逻辑迟滞比较是灵魂这是整个项目最核心、也最容易被做错的地方。新手最常见的写法是if (temp 30) 开风扇; else 关风扇;。这个逻辑有个致命问题——抖动。当温度在 30℃ 上下浮动时风扇会疯狂地开开关关继电器“哒哒哒”响个不停寿命急剧缩短风扇电机也受不了。正确的做法是引入迟滞Hysteresis也叫回差。设定两个阈值开启阈值 30℃关闭阈值 28℃。逻辑变成温度超过 30℃ 才开开了之后要等温度降到 28℃ 以下才关。中间这 2℃ 的“死区”就是迟滞带它吸收了温度的自然波动让风扇动作变得干净利落。const float TEMP_ON 30.0; const float TEMP_OFF 28.0; bool fanState false; void evaluate(float temp) { if (!fanState temp TEMP_ON) { fanState true; digitalWrite(FAN_PIN, LOW); // 低电平触发 } else if (fanState temp TEMP_OFF) { fanState false; digitalWrite(FAN_PIN, HIGH); } }迟滞带设多大合适我的经验是1.5℃ 到 3℃之间。太小了防不住抖动太大了温度控制不精准。如果你的空间很小、升温快可以取小一点空间大、热惯性大就取大一点。这个值没有标准答案需要根据你的实际环境实测调整。3.4 断网与异常情况的兜底设计真实环境里网络会断、Broker 会挂、传感器会掉线。一个合格的温控系统必须考虑这些。我的做法是本地判断为主MQTT 上报为辅。也就是说阈值判断逻辑跑在 ESP32 本地即使 MQTT 完全断开风扇照样能根据温度自动开关。MQTT 只负责把数据传出去和接收远程指令不参与实时控制决策。这个设计原则叫“边缘决策”在工业物联网里是标配。你想想如果控制逻辑放在云端网一断风扇就失控弱电箱分分钟过热。所以记住安全相关的控制逻辑永远放在离执行器最近的地方。远程指令可以作为“覆盖”手段比如你手动想强制开风扇发个指令过去但本地逻辑要有优先级判断避免远程和本地打架。另外传感器掉线也要处理。DS18B20 读不到数据时会返回DEVICE_DISCONNECTED或者 -127℃ 这种异常值。代码里必须判断如果连续几次读不到有效温度就进入“安全模式”——要么保持风扇常开宁可多吹要么报警。我一般选择常开因为过热比多耗点电危险得多。4. 完整实操过程从零搭起一套可用的温控系统4.1 硬件接线与供电规划先把硬件连起来。ESP32 的 GPIO4 接 DS18B20 的数据脚DQDQ 和 VCC 之间要接一个4.7kΩ 上拉电阻这是 OneWire 总线的硬性要求不接的话读出来的全是 85℃ 或者 -127℃。DS18B20 三根线VCC 接 3.3VGND 接 GNDDQ 接 GPIO4。如果你用防水探头线序通常是红 VCC、黑 GND、黄 DQ但不同厂家可能不一样接之前一定用万用表确认。继电器模块的 IN 脚接 ESP32 的 GPIO5VCC 接 5V注意是 5V 不是 3.3V很多继电器模块 3.3V 驱动不了GND 共地。风扇的电源走继电器的常开触点NO 和 COM12V 电源正极接 COM风扇正极接 NO风扇负极直接回电源负极。这样继电器吸合时电路才通。供电这块要特别注意ESP32 和继电器最好分开供电或者用足够功率的 5V 电源。我一开始用一个 5V 1A 的充电头同时带 ESP32 和继电器结果继电器一吸合ESP32 就重启——典型的电源跌落。后来换成 5V 2A问题消失。风扇的 12V 电源独立走不要和逻辑电源混在一起避免电机启动时的干扰串进来。4.2 ESP32 端代码实现代码分几个模块Wi-Fi 连接、MQTT 客户端、传感器读取、控制逻辑。我用的是PubSubClient库做 MQTTDallasTemperature读温度。核心代码如下关键地方我都加了注释。#include WiFi.h #include PubSubClient.h #include OneWire.h #include DallasTemperature.h #define ONE_WIRE_BUS 4 #define FAN_PIN 5 #define TEMP_ON 30.0 #define TEMP_OFF 28.0 OneWire oneWire(ONE_WIRE_BUS); DallasTemperature sensors(oneWire); WiFiClient espClient; PubSubClient mqtt(espClient); const char* ssid 你的WiFi; const char* password 你的密码; const char* broker 192.168.1.100; // 本地Broker地址 const int port 1883; bool fanState false; unsigned long lastPub 0; void setup() { pinMode(FAN_PIN, OUTPUT); digitalWrite(FAN_PIN, HIGH); // 上电默认关风扇 Serial.begin(115200); sensors.begin(); WiFi.begin(ssid, password); while (WiFi.status() ! WL_CONNECTED) { delay(500); } mqtt.setServer(broker, port); } void reconnect() { while (!mqtt.connected()) { if (mqtt.connect(esp32-node01)) { mqtt.subscribe(workshop/actuator/fan01/command); } else { delay(2000); } } } void loop() { if (!mqtt.connected()) reconnect(); mqtt.loop(); sensors.requestTemperatures(); float t sensors.getTempCByIndex(0); // 异常值处理 if (t -50 || t 100) { // 传感器异常保持当前状态并报警 return; } // 迟滞控制 if (!fanState t TEMP_ON) { fanState true; digitalWrite(FAN_PIN, LOW); } else if (fanState t TEMP_OFF) { fanState false; digitalWrite(FAN_PIN, HIGH); } // 每5秒上报一次 if (millis() - lastPub 5000) { char payload[128]; snprintf(payload, sizeof(payload), {\device\:\node01\,\temp\:%.1f,\fan\:%d}, t, fanState ? 1 : 0); mqtt.publish(workshop/sensor/node01/temperature, payload); mqtt.publish(workshop/actuator/fan01/state, fanState ? ON : OFF); lastPub millis(); } }这段代码里reconnect()函数保证了断线自动重连这是 MQTT 长连接必须做的。上报频率我设的 5 秒一次这个值你可以调。太频繁了浪费带宽和电量太慢了曲线不连续。温控场景 5 到 10 秒都合理。4.3 Broker 的搭建与配置Broker 我用的是Mosquitto轻量、稳定、跨平台树莓派、旧笔记本、NAS 上都能跑。安装很简单Linux 下apt install mosquitto mosquitto-clients就完事。默认配置只监听本地要让局域网设备连上需要改配置文件。# /etc/mosquitto/mosquitto.conf listener 1883 0.0.0.0 allow_anonymous trueallow_anonymous true是允许匿名连接方便调试。但正式使用一定要开认证否则同网络下任何人都能订阅你的数据、发指令控制你的风扇。开认证需要配置密码文件mosquitto_passwd -c /etc/mosquitto/passwd myuser然后在配置文件里加上password_file /etc/mosquitto/passwd和allow_anonymous false。ESP32 端连接时用mqtt.connect(node01, myuser, 密码)带上凭据。提示如果你想让外网也能访问 Broker不要直接把 1883 端口暴露出去。正确做法是用 TLS 加密8883 端口加上强密码或者干脆用云端 Broker 服务。安全这块省不得我见过太多人裸奔的 MQTT 被扫到后乱发指令。4.4 对接 KingSCADA / TLINK 获取数据如果你已经在用 KingSCADA 或 TLINK 这类组态平台它们通常内置了 MQTT 客户端功能可以直接订阅你的主题。以常见的配置流程为例在平台的“设备管理”里新建一个 MQTT 连接填入 Broker 地址、端口、用户名密码然后添加“数据点”把数据点绑定到具体的 MQTT 主题上。比如新建一个变量叫“车间温度”绑定主题workshop/sensor/node01/temperature解析方式选 JSON字段填temp。配置好之后你就能在平台的画面上实时看到温度曲线还能做历史存储、报警推送。TLINK 这类平台还支持反向控制你可以在画面上放个按钮点击后往workshop/actuator/fan01/command发ON或OFF实现远程手动干预。这就把本地自动控制和远程监控完美结合起来了。这里有个实操心得平台订阅主题时尽量用通配符订阅比如workshop/sensor//temperature这样以后加新节点平台侧不用改配置新数据自动就进来了。但要注意通配符订阅会增加 Broker 的匹配开销节点特别多的时候要权衡。5. 常见问题与排查技巧实录5.1 温度读数异常排查表调试阶段最容易卡在传感器读数上我把遇到过的问题整理成表方便你对照排查。现象可能原因排查方法解决读数恒为 85℃上拉电阻缺失或接触不良万用表测 DQ 对 VCC 阻值补焊 4.7kΩ 电阻读数恒为 -127℃传感器未连接或线序错检查三根线是否接对重新确认线序读数跳变剧烈线太长或受干扰缩短线长加屏蔽加 0.1uF 滤波电容多个传感器只读到一个地址冲突或总线负载用扫描程序列出地址逐个接入确认上电后读数正常几分钟后漂移电源不稳或发热测 VCC 电压换独立稳定电源DS18B20 最常见的就是 85℃ 这个“默认值”它其实是芯片上电后的初始寄存器值说明根本没读到有效数据。九成情况是上拉电阻的问题别怀疑代码。5.2 MQTT 连接不上的排查思路MQTT 连不上按这个顺序查网络通不通 → Broker 在不在 → 认证对不对 → 主题权限够不够。先用ping确认 ESP32 和 Broker 在同一网段再用mosquitto_sub -h broker地址 -t # -v在电脑上测试能不能收到消息。如果电脑能收到、ESP32 收不到那就是 ESP32 的 Wi-Fi 或客户端配置问题。一个隐蔽的坑是Client ID 冲突。MQTT 要求每个客户端的 Client ID 唯一如果你复制代码时忘了改两个设备用同一个 IDBroker 会把先连的踢掉表现为“连上又断、断了又连”。所以每个节点的 Client ID 一定要带唯一标识比如用 MAC 地址后缀。5.3 风扇误动作与继电器保护风扇误动作通常有两个来源一是前面说的阈值抖动靠迟滞解决二是上电瞬间的 GPIO 状态不确定。ESP32 上电时 GPIO 有个短暂的不确定期如果继电器是高电平触发可能瞬间吸合一下。解决办法是在setup()里第一件事就把 FAN_PIN 设为输出并写高电平关闭状态抢在继电器响应之前锁定状态。继电器本身也有寿命机械继电器一般十万次左右。按迟滞逻辑一天最多动作几十次用几年没问题。但如果你发现继电器频繁动作说明迟滞带设小了回去调大。另外继电器触点断开感性负载风扇电机时会产生反向电动势长期可能打火。讲究的话在风扇两端并联一个续流二极管或者 RC 吸收电路能显著延长继电器寿命。5.4 数据上报丢失与 QoS 选择如果你发现平台上的温度曲线有断点先确认是网络问题还是 QoS 问题。QoS 0 在网络抖动时会丢消息这是设计如此。温控数据丢一两条无所谓但如果你要做精确的历史分析可以把上报主题的 QoS 提到 1。改法很简单mqtt.publish(topic, payload, 1)第三个参数就是 QoS。不过 QoS 1 会带来重复消息的可能至少一次所以你的数据处理端要能容忍重复。对于温度曲线来说重复点无所谓但如果是计数类的数据就要做去重。这个权衡在做项目前要想清楚。6. 阈值调参与长期运行的经验总结6.1 如何科学地确定你的阈值阈值不是拍脑袋定的我建议你先跑一周纯监测模式——只上报温度不控制风扇把数据存下来看曲线。观察几个关键点日常温度范围是多少、峰值出现在什么时段、升温速率有多快。比如你发现弱电箱日常 28℃、下午峰值 35℃、从 30℃ 升到 35℃ 只要 20 分钟那开启阈值设 30℃、迟滞带设 2℃ 就比较合理风扇有足够时间在温度失控前介入。如果空间热惯性大比如大机柜升温慢阈值可以设高一点迟滞带也可以大一点减少动作次数。反之小空间升温快阈值要设低、迟滞带要小响应更灵敏。这个没有万能公式数据说话最靠谱。6.2 长期运行的稳定性维护系统跑起来之后别就不管了。我建议做几件事第一给 ESP32 加看门狗Watchdog代码里定期esp_task_wdt_reset()防止程序卡死第二Broker 端开启日志定期看看有没有异常断连第三风扇和继电器每年清一次灰机械部件会积尘第四传感器探头如果测的是潮湿环境注意防水封装老化。还有个小技巧在 ESP32 里加一个“心跳”主题每分钟发一次alive消息。平台侧如果超过 3 分钟没收到心跳就报警提示节点离线。这样你不用天天盯着出问题平台会告诉你。6.3 这套方案的扩展方向跑通基础版之后这套架构能扩展的地方很多。想加湿度控制就在 JSON 里加个humidity字段执行端加个加湿器继电器逻辑照搬。想做多节点联动比如三个传感器取平均温度再决策就在 Broker 端或者用一个专门的“聚合节点”订阅所有温度算完平均值再发控制指令。想接入语音助手就在平台侧做个桥接把 MQTT 主题映射成语音指令。我个人在实际操作中的体会是MQTT 这套东西真正的价值不在于“能连上”而在于它让设备之间的耦合变得极低。你今天加个传感器、明天加个执行器、后天换个平台彼此都不用改代码。这种松耦合带来的灵活性是传统点对点方案给不了的。所以别把它当成一个“温控小项目”把它当成一套可以复用的物联网骨架后面你想做的任何“数据触发动作”的场景都能往这个骨架里塞。