1. 从一个温度传感器说起:这套方案到底在解决什么问题
夏天机房巡检,最怕的不是服务器宕机,而是空调半夜罢工。等运维人员早上发现时,机柜进风温度已经飙到四十多度,几台老设备直接热保护关机。后来我就在想,能不能让温度数据自己“说话”——温度一超标,制冷设备自动启动,不需要人盯着,也不依赖某个中心平台下发指令。
这就是MQTT Climate-Triggered Cooling要干的事:用 MQTT 协议把温度采集和制冷控制串起来,让制冷动作由气候数据(温度、湿度)直接触发。整套逻辑的核心不是“远程手动开空调”,而是边缘侧的自动联动——传感器发布温度,规则引擎或订阅端判断阈值,直接向制冷设备的控制主题发布指令。
MQTT 在这里扮演的角色,是设备之间那根“看不见的信号线”。它足够轻,跑在单片机上也不吃力;它支持发布/订阅模型,温度采集端和制冷控制端不需要知道对方的存在,只认主题。这种解耦带来的好处很实在:你后面想换温度传感器品牌,或者把制冷设备从风扇换成工业空调,只要主题和消息格式不变,另一头完全不用动。
这套方案适合谁?我梳理了三类人:一是做机房、配电室、温室大棚环境监控的运维或集成人员;二是玩智能家居、想用 ESP32 或树莓派做温控联动 DIY 的爱好者;三是刚接触 MQTT、想找一个“有实际闭环”的项目来练手的初学者。因为整个链路短、反馈直接,调通之后你能立刻看到“温度升高→制冷启动”的效果,比单纯点灯实验有成就感得多。
需要提前说明的是,下面涉及的具体阈值、主题命名、代码参数,都是基于常见工程实践给出的参考值。实际部署时你要根据自己的设备手册和现场环境调整,尤其是制冷设备的控制协议,有的走 MQTT 原生主题,有的需要网关转 Modbus 或红外码,这部分我会在实操章节展开。
2. 整体架构拆解:为什么这样设计而不是那样
2.1 发布/订阅模型是这套方案的骨架
很多人第一次接触 MQTT,会习惯性地拿它和 HTTP 对比。HTTP 是请求/响应,客户端问一次服务器答一次;MQTT 是发布/订阅,消息发出去之后,谁关心谁就来订阅。这个差异在温控场景里非常关键。
假设你用 HTTP 做温度联动:温度传感器得不断向服务器 POST 数据,服务器再判断,再向制冷设备发请求。传感器和制冷设备之间隔着一个中心服务,任何一方掉线,整条链路就断了。而 MQTT 的 broker 只负责转发,温度传感器只管往climate/room1/temperature这个主题发消息,制冷控制器只管订阅climate/room1/cooling/cmd。broker 挂了才是单点,但 broker 本身可以做集群,而且它不参与业务判断,压力小得多。
更妙的是“一对多”能力。同一个温度主题,可以同时被规则引擎、数据记录服务、手机 App 订阅。规则引擎负责触发制冷,记录服务负责存历史曲线,App 负责展示。三者互不干扰,加一个订阅者不需要改发布者的代码。这种扩展性在后期加功能时特别省心。
2.2 触发逻辑放在哪一层:三种常见选型对比
“Climate-Triggered”里的触发,可以放在不同位置。我实际试过三种做法,各有适用场景。
第一种是订阅端直接判断。写一个 Python 或 Node.js 脚本,订阅温度主题,收到消息后比较阈值,超过就向制冷主题发布指令。优点是逻辑透明、改起来快,适合原型验证和小规模部署。缺点是脚本挂了触发就停了,需要配合进程守护。
第二种是broker 内置规则引擎。EMQX、Mosquitto 的插件、HiveMQ 的 extension 都能做。规则写在 broker 侧,比如“当climate/+/temperature的值大于 30 时,向climate/+/cooling/cmd发ON”。优点是稳定,不依赖外部脚本;缺点是规则语法各平台不同,调试不如写代码直观。
第三种是边缘网关本地判断。温度采集和制冷控制接在同一个网关上,网关内部做逻辑,MQTT 只用于上报和远程覆盖。优点是断网也能工作,延迟最低;缺点是网关要写业务代码,灵活性稍差。
我个人的建议是:原型阶段用第一种,快速跑通;小规模生产用第二种,省运维;对可靠性要求高的现场用第三种,网络断了也不影响本地温控。下面实操部分我以第一种为主来演示,因为最容易复现,理解了之后再迁移到规则引擎就不难。
2.3 主题设计:别小看命名这件事
主题设计是 MQTT 项目里最容易被忽视、后期最让人头疼的部分。我见过有人用temp1、temp2这种命名,设备一多就完全分不清哪个是哪个房间。推荐用分层结构,从大到小:
climate/{site}/{room}/{metric} climate/{site}/{room}/cooling/cmd climate/{site}/{room}/cooling/state{site}是站点或楼层,{room}是具体房间,{metric}是温度、湿度等。这样通配符订阅很灵活:climate/+/+/temperature能拿到所有房间温度,climate/buildingA/#能拿到 A 栋所有消息。
还有一个细节:控制指令和状态反馈要用不同主题。cooling/cmd是“我希望你开”,cooling/state是“我实际开了”。很多新手只发指令不看状态,结果指令发出去了但设备没执行,还以为联动成功了。加上状态回传,才能形成真正的闭环。
3. 核心组件与实操环境搭建
3.1 MQTT Broker 选型与安装
Broker 是整个系统的消息中枢。常见选择有 Mosquitto、EMQX、NanoMQ。家用或小规模场景,Mosquitto 足够,资源占用小,配置简单。需要 Web 管理界面和规则引擎,EMQX 更合适。
Linux 上装 Mosquitto 很直接:
sudo apt update sudo apt install mosquitto mosquitto-clients sudo systemctl enable mosquitto sudo systemctl start mosquitto默认监听 1883 端口,只允许本地访问。要让局域网内其他设备连进来,需要改配置文件/etc/mosquitto/mosquitto.conf,加上:
listener 1883 0.0.0.0 allow_anonymous true注意:
allow_anonymous true只适合内网测试。生产环境一定要配用户名密码或客户端证书,否则任何人都能往你的制冷主题发指令。
Windows 上可以用安装包,装完在服务里启动。如果现场是麒麟 V10 ARM 这类国产系统且不能联网,需要提前在有网环境下载对应的 deb 包和依赖,用dpkg -i离线安装。这一步坑比较多,依赖缺失时apt --fix-broken install在离线环境用不了,建议用dpkg -i *.deb一次性把所有依赖包按顺序装完。
3.2 温度采集端:从传感器到 MQTT 消息
采集端我常用两种方案。一种是 ESP32 加 DHT22 或 DS18B20,直接跑 MQTT 客户端库,成本低、部署快。另一种是工业现场用 Modbus 温度变送器,通过采集网关转成 MQTT。后者就是热词里提到的“采集网关支持 MQTT 协议、支持 Modbus 645”那类设备,适合水表、电表、温控器混合采集的场景。
以 ESP32 为例,核心逻辑是:读传感器→格式化 JSON→发布到温度主题。伪代码结构如下:
float temp = readTemperature(); String payload = "{\"value\":" + String(temp) + ",\"unit\":\"C\"}"; client.publish("climate/site1/room1/temperature", payload.c_str());发布周期建议 10 到 30 秒一次。太频繁会增加 broker 和网络负担,太稀疏则触发不及时。温度这种缓变量,15 秒一次是比较平衡的选择。
3.3 制冷控制端:指令怎么变成动作
制冷设备千差万别。最简单的是一块继电器模块控制风扇或空调电源,MQTT 消息收到ON就吸合继电器。复杂一点的是通过红外发射管模拟空调遥控器,或者通过 Modbus 写寄存器控制工业空调。
如果制冷设备本身不支持 MQTT,就需要一个“协议转换”环节。常见做法是用 ESP32 或树莓派做网关:订阅cooling/cmd,收到指令后转成红外码或 Modbus 帧发给设备,再把设备状态发布到cooling/state。这个网关是整个链路里最需要针对现场调试的部分,因为每种空调的红外编码、每个 Modbus 设备的寄存器地址都不一样。
3.4 客户端工具:调试阶段的好帮手
调 MQTT 一定要有趁手的客户端。MQTT Explorer 是我用得最多的,图形界面能树状展示所有主题,实时看消息变化,还能手动发布测试消息。下载安装后填 broker 地址和端口就能连,订阅climate/#就能看到所有温控相关消息。
命令行工具mosquitto_sub和mosquitto_pub也必备,适合在服务器上快速验证:
mosquitto_sub -h 192.168.1.100 -t "climate/#" -v mosquitto_pub -h 192.168.1.100 -t "climate/site1/room1/cooling/cmd" -m "ON"调试顺序建议是:先用mosquitto_sub确认能收到温度消息,再用mosquitto_pub手动发一条制冷指令确认设备能响应,最后才把自动触发逻辑接上。这样出问题时能快速定位是哪一段断了。
4. 触发逻辑实现:从温度到制冷的完整闭环
4.1 阈值判断的基本写法
触发脚本的核心就是一个订阅回调。收到温度消息后,解析数值,和阈值比较,超过就发指令。用 Python 的 paho-mqtt 库写出来大概是这样:
import paho.mqtt.client as mqtt import json THRESHOLD_ON = 30.0 THRESHOLD_OFF = 27.0 cooling_on = False def on_message(client, userdata, msg): global cooling_on data = json.loads(msg.payload) temp = data["value"] if temp > THRESHOLD_ON and not cooling_on: client.publish("climate/site1/room1/cooling/cmd", "ON") cooling_on = True elif temp < THRESHOLD_OFF and cooling_on: client.publish("climate/site1/room1/cooling/cmd", "OFF") cooling_on = False client = mqtt.Client() client.on_message = on_message client.connect("192.168.1.100", 1883) client.subscribe("climate/site1/room1/temperature") client.loop_forever()这段代码里有两个阈值:30 度开,27 度关。为什么不用同一个阈值?因为温度在阈值附近波动时,如果只用 30 度判断,29.9 关、30.1 开、29.9 又关,制冷设备会疯狂启停,既费电又伤压缩机。这种“回差”设计是温控里的基本常识,回差一般取 2 到 3 度。
4.2 状态回传与防抖处理
上面的脚本有个隐患:它假设指令发出去设备就一定执行了。实际中可能继电器卡住、空调没收到红外码、网络丢包。所以必须订阅cooling/state,用实际状态来更新cooling_on变量,而不是用“我发过指令”来判断。
改进后的逻辑是:收到state为ON才把cooling_on置真,收到OFF才置假。同时加一个超时机制——发出指令后 5 秒内没收到状态回传,就重发一次,重发三次仍无响应则告警。
防抖还有一层是消息层面的。温度传感器偶尔会发出一条异常值,比如 85 度(DHT22 的经典错误值)。如果直接拿这个值判断,制冷会误启动。简单做法是连续收到三次超阈值才触发,或者对最近几次读数取平均。我在实际项目里用的是滑动窗口平均,取最近 5 个读数的均值来判断,能过滤掉大部分毛刺。
4.3 多房间与多设备的扩展
单房间跑通之后,扩展到多房间的思路是让主题带上房间标识,触发脚本用通配符订阅,从主题里提取房间名,再向对应房间的制冷主题发指令。这样一套脚本能管所有房间,不用每个房间跑一个进程。
def on_message(client, userdata, msg): parts = msg.topic.split("/") room = parts[2] temp = json.loads(msg.payload)["value"] cmd_topic = f"climate/site1/{room}/cooling/cmd" # 判断逻辑同上,向 cmd_topic 发布多设备时要注意的是,不同房间的阈值可能不同。机房要求严格,28 度就得开;仓库可能 32 度才需要。可以把每个房间的阈值配置放在一个字典或配置文件里,脚本启动时加载,避免硬编码。
4.4 断网与重连的处理
MQTT 客户端断线是常态,尤其是无线网络。paho-mqtt 有自动重连机制,但重连后订阅关系需要重新建立。稳妥的做法是在on_connect回调里统一做订阅,这样每次重连都会自动重新订阅。
def on_connect(client, userdata, flags, rc): client.subscribe("climate/site1/+/temperature") client.subscribe("climate/site1/+/cooling/state")另外,触发脚本本身要用 systemd 或 supervisor 守护,崩溃了自动拉起。我试过用nohup跑,结果服务器重启后脚本没起来,温控失效了好几天才发现。后来改成 systemd service,开机自启、崩溃重启,省心很多。
5. 常见问题与排查技巧实录
5.1 消息发出去了但设备没反应
这是最常见的问题,排查顺序建议从后往前。先确认制冷设备本身能不能手动控制——如果手动都开不了,那跟 MQTT 无关。再确认控制网关有没有收到 MQTT 消息,用 MQTT Explorer 订阅cooling/cmd看消息到没到。如果消息到了但设备不动,问题在网关到设备这一段,可能是红外码不对、Modbus 地址错、继电器接线松。
我遇到过一次是继电器模块的触发电平搞反了,MQTT 发ON继电器反而断开。这种问题看状态回传就能发现:指令发ON,状态回OFF,说明控制逻辑反了,改一下电平配置就行。
5.2 温度数据时有时无
无线传感器信号弱是主因。DHT22 这类传感器对时序要求高,线太长或干扰大就容易读失败。解决办法一是缩短传感器到主控的线,二是加屏蔽,三是读失败时重试几次再放弃。软件层面,如果连续多次读失败,可以发布一个null或错误标记,让触发端知道数据不可信,而不是拿旧值继续判断。
还有一种情况是 broker 的保留消息在作怪。如果温度主题设置了 retain,新订阅者一连上就会收到最后一条保留消息,可能是几小时前的旧数据。触发脚本如果不看消息时间戳,就会拿旧温度做判断。建议温度主题不要设 retain,或者消息体里带上时间戳,触发端检查时间戳是否在合理范围内。
5.3 制冷设备频繁启停
前面提到的回差没设好是主因。除此之外,还要检查是不是有多个触发脚本同时在跑。我有一次调试时忘了关掉一个测试脚本,结果两个脚本都在发指令,一个开一个关,设备就来回跳。用ps aux | grep python查一下有没有重复进程。
另外,如果温度采集周期太短,比如 1 秒一次,而制冷设备响应有延迟,也会造成判断依据滞后。把采集周期调到 15 秒以上,给设备足够的响应时间,情况会好很多。
5.4 常见问题速查表
| 现象 | 可能原因 | 排查方法 | 解决方向 |
|---|---|---|---|
| 设备不响应指令 | 网关未收到消息 | MQTT Explorer 订阅 cmd 主题 | 检查主题名和 broker 连接 |
| 设备不响应指令 | 协议转换错误 | 手动触发网关测试 | 核对红外码或 Modbus 地址 |
| 温度数据缺失 | 传感器读取失败 | 看串口日志 | 缩短线缆、加重试 |
| 温度数据异常 | 保留消息过期 | 检查消息时间戳 | 关闭 retain 或校验时间 |
| 设备频繁启停 | 回差过小 | 观察温度曲线 | 增大回差到 2-3 度 |
| 设备频繁启停 | 多脚本冲突 | 查进程列表 | 停掉多余脚本 |
| 断网后不恢复 | 订阅未重建 | 看重连日志 | on_connect 里重新订阅 |
| 脚本崩溃不重启 | 无守护进程 | 查 systemd 状态 | 配置自动重启 |
5.5 几个我踩过的坑
第一个坑是主题名用了大写字母。MQTT 主题是区分大小写的,Climate/Room1和climate/room1是两个不同主题。我一开始没注意,订阅端和发布端大小写不一致,调了半天才发现。建议统一用小写加斜杠,别用驼峰。
第二个坑是 QoS 设置。默认 QoS 0 是“最多一次”,网络不好时消息可能丢。温度数据丢一两条问题不大,但制冷指令丢了就麻烦。控制指令建议用 QoS 1,至少保证到达一次。不过 QoS 1 可能重复,所以控制端要做幂等处理,收到重复的ON不要重复动作。
第三个坑是 broker 的并发连接数。小规模没问题,设备上百之后 Mosquitto 默认配置可能不够用,需要调max_connections和文件描述符限制。这个在项目初期不用管,但扩容时要心里有数。
6. 从原型到落地:一些实用的扩展思路
6.1 加一层数据记录与可视化
触发逻辑跑通后,很自然的需求是看历史曲线。用 Node-RED 或 Telegraf 订阅温度主题,写入 InfluxDB,再用 Grafana 画图,整套都是现成组件。这样你能看到每天的温度变化,判断阈值设得合不合理,也能在事后追溯“为什么那天制冷启动了”。
Node-RED 的好处是可视化编排,拖几个节点就能把 MQTT 输入接到 InfluxDB 输出,不用写代码。对于不熟悉编程的运维人员,这是很友好的方案。而且 Node-RED 本身也能做触发逻辑,用 switch 节点判断阈值,比写脚本更直观。
6.2 远程覆盖与手动优先
自动触发虽然方便,但有时候人需要手动干预。比如设备维护时不想让制冷自动启动,或者想提前预冷。这就需要在触发逻辑里加一个“手动模式”开关。订阅一个climate/site1/room1/mode主题,值为auto时走自动判断,值为manual时忽略温度直接执行手动指令。
手动优先的优先级要高于自动,避免人刚关了空调,自动逻辑又给打开。实现上就是在触发判断前先检查模式,手动模式下直接跳过阈值判断。
6.3 多条件触发:不只看温度
“Climate-Triggered”里的 climate 不只有温度。湿度高了可能也需要除湿或通风,或者温度和湿度组合判断——温度没超但湿度很高时启动通风。消息体里可以带多个字段:
{"temp": 28.5, "humidity": 75, "ts": 1690000000}触发逻辑里用and或or组合条件。比如温度大于 30 或湿度大于 80 就启动。这样一套逻辑能覆盖更多场景,不用为每个指标单独写脚本。
6.4 边缘计算与离线自治
如果现场网络不稳定,可以考虑把触发逻辑下沉到边缘网关。网关本地采集温度、本地判断、本地控制,MQTT 只用于把数据同步到云端和接收远程覆盖指令。这样即使外网断了,本地温控照常工作。实现上可以用 Node-RED 跑在网关上,或者用 Python 脚本加本地 broker。
这种架构的代价是网关要承担更多计算,配置和更新也麻烦一些。但对于无人值守的机房、基站这类场景,离线自治是值得的。我见过因为网络故障导致温控失效、设备过热的案例,事后加边缘自治就再没出过问题。
6.5 安全加固不能省
最后强调一下安全。内网测试用匿名连接没问题,但只要涉及真实设备控制,就必须加认证。Mosquitto 支持用户名密码,配置password_file即可。更严格的是用 TLS 证书,消息加密传输,防止被截获篡改。
还有一点是主题权限控制。温度采集端只应该能发布温度主题,不应该能发布制冷指令。Mosquitto 的 ACL 可以按用户名限制主题读写权限,配置好之后即使某个设备被攻破,也影响不到其他设备。这部分配置稍微繁琐,但值得花时间做。
我在实际部署中的体会是,温控联动这类项目,难点往往不在 MQTT 本身,而在现场设备的对接和异常情况的处理。协议是标准的,但每台空调、每个传感器都有自己的脾气。把异常流程想清楚,比把正常流程跑通更重要。