PyQt5 基于paho-mqtt库 实现MQTT通信
MQTT简介
MQTT(Message Queuing Telemetry Transport)是一种基于发布/订阅模式的轻量级通讯协议,构建于TCP/IP协议之上。 它由IBM在1999年开发,旨在为硬件性能有限的远程设备以及网络状况不佳的环境下提供实时可靠的消息服务。MQTT的最大优点在于其以极少的代码和有限的带宽就能实现这一功能。
MQTT协议的核心特点包括:
- 轻量级:MQTT协议设计为资源受限设备设计,因此代码量小,带宽占用低。
- 发布/订阅模式:MQTT支持一对多的通信方式,发布者发送消息给所有订阅该主题的订阅者。
- 消息代理:MQTT使用一个消息代理(Broker)来转发消息,发布者和订阅者通过代理进行通信。
- 主题分层结构:MQTT使用主题来组织消息,支持使用“#”和“+”符号进行主题的模糊匹配,以便更灵活地组织消息。
MQTT协议广泛应用于各种场景,包括:
- 物联网(IoT):在智能家居、工业控制、智能城市等领域,MQTT用于设备间的通信。
- 机器与机器通信(M2M):在远程监控、智能交通、环境监测等应用中,MQTT用于设备与服务器之间的通信。
- 移动应用:在需要低功耗、低带宽的环境中,MQTT提供有效的消息传递服务。
由于这些特点,MQTT在全球化的企业应用中也得到了广泛应用。例如,EMQX企业版通过提供全球范围的MQTT消息分发和集群连接功能,支持企业构建更灵活、安全、可靠的物联网应用。
MQTT Broker
EMQX官方文档
安装EMQX MQTT Broker
- 下载EMQX
- 解压文件
- 进入emqx-5.1.0-windows-amd64\bin
- 启动emqx:命令行执行emqx.cmd start
- 停止emqx:命令行执行emqx.cmd stop
启动emqx之后浏览器打开:http://127.0.0.1:18083
默认账号:admin
默认密码:public 或 admin
第一次登录可能会提示修改密码,在设置里可以修改语言为中文。
MQTTX 客户端
安装paho-mqtt库
pip install paho-mqtt
- mqtt loop处理方式
import time
import paho.mqtt.client as mqtt
client = mqtt.Client(client_id="mqtt_client_id", protocol=mqtt.MQTTv311)
# 方式1:阻塞loop
client.loop_forever() # 阻塞方式,代码会阻塞在这里
# 方式2:循环调用loop函数
while True:
client.loop() # 手动调用loop
time.sleep(1)
# 方式3:开始/停止 loop
client.loop_start() # 开始loop
for i in range(5):
print(i)
client.loop_stop() # 停止loop
订阅消息演示
import paho.mqtt.client as mqtt
mqtt_host = '127.0.0.1'
mqtt_port = 1883
mqtt_username = 'admin'
mqtt_password = 'admin123456'
mqtt_topic_msg = 'topic/msg'
mqtt_client_id = 'device_sn_112233'
def on_connect(client, userdata, falgs, rc):
print("Connected with result code:" + str(rc))
def on_message(client, userdata, msg):
print("on message" + msg.topic + " " + str(msg.payload))
# 订阅回调
def on_subscribe(self, client, userdata, mid):
print("on subscribe: qos =%d" % mid)
pass
# 取消订阅回调
def on_unsubscribe(self, client, userdata, mid):
print("on unsubscribed: qos=%d" % mid)
pass
# 发布消息回调
def on_publish(self, client, userdata, mid):
print("on publish: qos=%d" %mid)
pass
# 端口连接回调
def on_disconnect(self, client, userdata):
print("on disconnect ")
pass
client = mqtt.Client(client_id=mqtt_client_id, protocol=mqtt.MQTTv311)
client.username_pw_set(mqtt_username, mqtt_password)
client.on_connect = on_connect
client.on_message = on_message
client.on_subscribe = on_subscribe
client.on_unsubscribe = on_unsubscribe
client.on_publish = on_publish
client.on_disconnect = on_disconnect
client.connect(mqtt_host, mqtt_port, 60) # 60为keepalive时间
client.subscribe(mqtt_topic_msg, qos=0)
client.loop_forever() # 保持连接
# client.disconnect() # 断开连接
发布消息演示
import time
import paho.mqtt.client as mqtt
mqtt_host = '127.0.0.1'
mqtt_port = 1883
mqtt_username = 'admin'
mqtt_password = 'admin123456'
mqtt_topic_msg = 'topic/msg'
mqtt_client_id = 'device_sn_112244'
def on_connect(client, userdata, falgs, rc):
print("Connected with result code:" + str(rc))
def on_message(client, userdata, msg):
print("on message" + msg.topic + " " + str(msg.payload))
# 订阅回调
def on_subscribe(self, client, userdata, mid):
print("on subscribe: qos =%d" % mid)
pass
# 取消订阅回调
def on_unsubscribe(self, client, userdata, mid):
print("on unsubscribed: qos=%d" % mid)
pass
# 发布消息回调
def on_publish(self, client, userdata):
print("on publish: qos=%d")
pass
# 端口连接回调
def on_disconnect(self, client, userdata):
print("on disconnect ")
pass
client = mqtt.Client(client_id=mqtt_client_id, protocol=mqtt.MQTTv311)
client.username_pw_set(mqtt_username, mqtt_password)
client.on_connect = on_connect
client.on_message = on_message
client.on_subscribe = on_subscribe
client.on_unsubscribe = on_unsubscribe
client.on_publish = on_publish
client.on_disconnect = on_disconnect
client.connect(mqtt_host, mqtt_port, 600) # 600为keepalive时间
client.publish(mqtt_topic_msg, payload='hello', qos=0)
# client.loop_forever() # 保持连接
# client.disconnect() # 断开连接
while True:
client.loop()
client.publish(mqtt_topic_msg, payload='hello', qos=0)
time.sleep(1)
EMQX Broker:
综合示例(PyQt5)
- 封装MQTT类
- 订阅消息
- 发布消息
- 信号方式接收处理MQTT消息
import paho.mqtt.client as mqtt
import sys
import json
from PyQt5.QtGui import *
from PyQt5.QtCore import *
from PyQt5.QtWidgets import *
# 填写实际的MQTT相关参数
mqtt_host = "127.0.0.1"
mqtt_port = 1883
mqtt_client_id="sn_864423065869616"
mqtt_username = "admin"
mqtt_password = "admin123456"
mqtt_sub_topic = "topic/sub"
mqtt_pub_topic = "topic/pub"
# 封装一个MQTT客户端
class MqttClient(QObject):
# 创建信号用于UI更新数据
message_signal = pyqtSignal(str, str)
def __init__(self, broker, port, client_id, protocol=mqtt.MQTTv311):
super(MqttClient, self).__init__()
self.broker = broker
self.port = port
self.client_id = client_id
self.client = mqtt.Client(mqtt.CallbackAPIVersion.VERSION2, client_id)
def connect(self, username=None, password=None, keepalive=60):
self.client.username_pw_set(username, password)
self.client.connect(self.broker, self.port, keepalive)
def subscribe(self, topic, qos=0):
self.client.subscribe(topic, qos)
def publish(self, topic, paylaod, qos=0, retain=False):
self.client.publish(topic, paylaod, qos, retain)
def on_connect(self, client, userdata, flags, reason_code, properties):
if reason_code == 0:
print("Connected to MQTT Broker!")
else:
print("Failed to connect, return code %\n", reason_code)
def on_disconnect(self, client, userdata, flags, reason_code, properties):
if reason_code == 0:
# success disconnect
print("Disconnect to MQTT Broker!")
if reason_code > 0:
# error processing
print("Failed to disconnect, return code %\n", reason_code)
def on_message(self, client, userdata, message):
print("Received message: ", str(message.payload.decode("utf-8")))
self.message_signal.emit(message.topic, message.payload.decode()) # 发射信号UI线程里处理更新数据
def on_subscribe(self, client, userdata, mid, reason_codes, properties):
for sub_result in reason_codes:
if sub_result == 1:
# process QoS == 1
print("on_subscribe process QoS == 1")
# Any reason code >= 128 is a failure.
if sub_result >= 128:
# error processing
print("on_subscribe error processing。")
def on_unsubscribe(client, userdata, mid, reason_codes, properties):
# In NEW version, reason_codes is always a list. Empty for MQTTv3
for unsub_result in reason_codes:
# Any reason code >= 128 is a failure.
if reason_codes[0] >= 128:
# error processing
print("on_unsubscribe error processing.")
def on_publish(self, client, userdata, mid, reason_codes, properties):
print('Public reason_codes %\n', reason_codes)
def on_log(self, client, userdata, level, buf):
print(buf)
def start(self):
self.client.on_connect = self.on_connect
self.client.on_disconnect = self.on_disconnect
self.client.on_subscribe = self.on_subscribe
self.client.on_unsubscribe = self.on_unsubscribe
self.client.on_publish = self.on_publish
self.client.on_message = self.on_message
self.client.on_log = self.on_log
self.client.loop_start()
def stop(self):
self.client.loop_stop()
self.client.disconnect()
class MainWindow(QMainWindow):
def __init__(self, parent=None):
super(MainWindow, self).__init__(parent)
self.initUI()
self.client = MqttClient(broker=mqtt_host, port=mqtt_port, client_id=mqtt_client_id)
self.client.connect(username=mqtt_username, password=mqtt_password, keepalive=60)
self.client.subscribe(topic=mqtt_sub_topic, qos=0)
self.client.message_signal.connect(self.update_ui)
self.client.start()
def initUI(self):
self.setWindowTitle("MQTT测试工具")
self.resize(800, 480)
self.center() # 窗口居中显示
self.label_show = QLabel(self)
self.label_show.setText("...")
self.label_show.setStyleSheet("color:blue; font-size:20px;")
self.btn_mqttpub = QPushButton(self)
self.btn_mqttpub.setText("发布消息")
self.btn_mqttpub.clicked.connect(lambda: self.publish_message())
root = QVBoxLayout()
root.addWidget(self.label_show)
root.addWidget(self.btn_mqttpub)
mwidget = QWidget()
mwidget.setLayout(root)
self.setCentralWidget(mwidget)
def center(self):
screen = QDesktopWidget().screenGeometry()
size = self.geometry()
self.move((int)((screen.width()-size.width())/2), (int)((screen.height()-size.height())/2))
def update_ui(self, topic, message):
print('接收到的消息更新到UI显示')
print(topic)
print(message)
self.label_show.setText(topic +" "+ message)
def publish_message(self):
print("发布消息")
self.client.publish(topic=mqtt_pub_topic, paylaod="hello world", qos=0, retain=False)
def closeEvent(self, event):
# 重写closeEvent方法
print('窗口关闭前执行的操作')
self.client.stop() # 停止MQTT
# 调用基类的closeEvent方法来执行关闭事件
super().closeEvent(event)
if __name__ == "__main__":
app = QApplication(sys.argv)
win = MainWindow()
win.show()
sys.exit(app.exec_())
错误处理
- 报错信息:
Unsupported callback API version: version 2.0 added a callback_api_version, see docs/migrations.rst for details
不支持的回调 API 版本:2.0 版本添加了一个callback_api_version,详情请参阅docs/migrations.rst
参考官方文档
-
原因:回调参数不一致,2.0 版本更改了传递给用户回调的参数。回调的版本1已弃用,但在版本 2.x 中仍受支持。
-
解决方法:
- 方法1:
使用旧版本的回调。需告诉 paho-mqtt 您选择此版本即可,修改如下代码:
但是这种方法每次运行的时候,会出现以下警告:from paho.mqtt import client as mqtt # OLD code client = mqtt.Client(client_id) # NEW code client = mqtt.Client(mqtt.CallbackAPIVersion.VERSION1, client_id)
DeprecationWarning: Callback API version 1 is deprecated, update to latest version - 方法2:需要修改两处
1)在创建client对象时,新增参数:mqtt_client.CallbackAPIVersion.VERSION2
2)在on_connect()函数中,新增参数:propertiesfrom paho.mqtt import client as mqtt # OLD code client = mqtt.Client(client_id) # NEW code client = mqtt.Client(mqtt.CallbackAPIVersion.VERSION2, client_id) def on_connect(client, userdata, flags, rc, properties): if rc == 0: print("Connected to MQTT Broker!") else: print("Failed to connect,return code {}".format(rc)) client.on_connect = on_connect
- 方法3:降低paho-mqtt版本号到1.x版本
$ pip install paho-mqtt==1.6.1
- 方法1: