PyQt5 基于paho-mqtt库 实现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 客户端

MQTTX下载
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

安装paho-mqtt库

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()函数中,新增参数:properties
      from 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

### 使用 PyQt5 实现 MQTT 消息接收 为了在 PyQt5实现 MQTT 消息的接收,可以基于 `paho-mqtt` 来构建应用程序。下面是一个详细的说明以及代码示例。 #### 导入必要的模块 首先需要导入用于图形界面设计的 PyQt5 和负责网络通信paho-mqtt 的客户端类[^1]。 ```python import sys from PyQt5.QtWidgets import QApplication, QWidget, QVBoxLayout, QTextEdit, QPushButton import paho.mqtt.client as mqtt ``` #### 创建主窗口类 定义一个继承自 `QWidget` 的类作为程序的主要窗口,在其中初始化布局并设置按钮和文本框组件以便展示收到的消息内容。 ```python class MainWindow(QWidget): def __init__(self): super().__init__() self.initUI() def initUI(self): layout = QVBoxLayout() self.text_edit = QTextEdit(readOnly=True) layout.addWidget(self.text_edit) btn_connect = QPushButton('Connect') btn_connect.clicked.connect(on_button_click) layout.addWidget(btn_connect) self.setLayout(layout) self.setWindowTitle('MQTT Receiver') def on_button_click(): client.loop_start() # 启动循环以保持连接状态 client.subscribe("test/topic") # 订阅主题 ``` #### 设置 MQTT 客户端回调函数 当有新消息到达时触发特定的方法更新 GUI 上的信息显示区域;另外还需要指定连接成功的响应逻辑[^3]。 ```python # 当成功建立到代理服务器之间的 TCP 连接之后调用此方法 def on_connect(client, userdata, flags, rc): if rc == 0: print("Connected with result code " + str(rc)) else: print(f"Bad connection Returned code={rc}") # 接受到发布者发出的数据包后执行该操作 def on_message(client, userdata, msg): message = f"{msg.topic}: {str(msg.payload.decode())}" window.text_edit.append(message) # 将获取的内容追加至文本编辑区 ``` #### 初始化 MQTT 客户端实例并与事件绑定 最后一步是在全局范围内声明变量 `client`, 并将其与之前定义好的两个重要钩子关联起来——即每当发生连接或接收到数据的时候都会自动激活相应的处理器。 ```python app = QApplication(sys.argv) window = MainWindow() client = mqtt.Client() client.on_connect = on_connect client.on_message = on_message broker_address = "localhost" port = 1883 client.connect(broker_address, port=port) window.show() sys.exit(app.exec_()) ``` 通过上述步骤就可以完成一个简单的使用 PyQt5 来监听来自某个特定主题下的所有 MQTT 数据流的应用程序了。这不仅能够帮助理解两者之间的工作原理,同时也提供了一个良好的起点去探索更多关于这两项技术的知识点。
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

SongYuLong的博客

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值