侧边栏壁纸
  • 累计撰写 20 篇文章
  • 累计收到 1 条评论

Python对接EMQX踩坑:订阅全主题为啥收不到设备离线消息?

2026-9-7 / 0 评论 / 15 阅读

Python对接EMQX踩坑:订阅全主题为啥收不到设备离线消息?

你有没有遇到过用Python写MQTT消费端,明明订阅了全量#主题,却死活收不到设备离线事件的情况?上周做园区智能电表的边缘监控模块,我就卡了快2小时在这个破问题上。

当时用的是paho-mqtt 2.0版本对接EMQX 5.0,代码逻辑很简单:连接成功后订阅#主题,收到消息就推到监控平台。结果模拟设备断电断网,监控平台永远等不到离线告警,换MQTTX用完全相同的账号、Broker地址连接,断网后1秒就能收到离线消息。

排查绕了三个无效弯

  • 一开始以为是QoS等级不对,把订阅QoS从0改到2,关了Clean Session保留会话,消息还是收不到;
  • 后来怀疑是ACL权限问题,把账号换成超级管理员,重启服务依旧没反应;
  • 甚至抓了网卡包看MQTT报文,确认连接、订阅报文都正常发出去了,就是没见离线事件的报文回来。

最后偶然在on_subscribe回调里打了全量日志,才发现$SYS开头的主题订阅返回码是128——也就是订阅被拒绝了。

根因是个MQTT协议冷知识

翻了EMQX官方文档才反应过来,MQTT协议明确规定,通配符#默认不会匹配$开头的主题。不管是$SYS开头的Broker系统主题,还是$share开头的共享订阅主题,都必须显式单独订阅,不能靠#通配符覆盖。

我之前一直以为#是匹配所有主题,而MQTTX能收到消息纯粹是因为之前测试时手动订阅过$SYS主题,客户端自动记住了订阅配置,根本不是全量订阅的功劳。

修复代码很简单

只要在原有订阅逻辑下,加一行针对系统主题的显式订阅就行:

# 原有业务主题全量订阅
client.subscribe("#", qos=1)
# 单独订阅客户端上下线事件的系统主题
client.subscribe("$SYS/brokers/+/clients/#", qos=1)
几个容易踩的坑提醒下:
1. 写paho-mqtt代码一定要实现on_subscribe回调,判断granted_qos返回值,128就是订阅被拒绝,别闷头等消息;
2. 要是用设备自定义遗嘱消息做离线检测,别把will_retain设为True,不然新上线的消费端会收到历史保留消息,误判设备当前状态;
3. EMQX默认普通账号没有$SYS主题的订阅权限,用之前要先在ACL里加规则。

评论一下?

OωO
取消