> 文章列表 > 基于PyQt5的图形化界面开发——自制MQTT客户端软件

基于PyQt5的图形化界面开发——自制MQTT客户端软件

基于PyQt5的图形化界面开发——自制MQTT客户端软件

基于 PyQt5 的图形化界面开发——自制MQTT客户端

  • 0. 前言
  • 1. 第三方库的安装及注意事项
  • 2. Editor.py
    • 2.1 配置界面效果演示:
  • 3. Publish.py
    • 3.1 消息发布界面演示
  • 4. Subcribe.py
    • 4.1 订阅消息效果演示:
  • 界面切换——main.py
  • 5. 写在最后

0. 前言

使用 PyQt5 练习写图形化界面,目标是写出一个使用MQTT协议通信的界面,包含 编辑配置、消息发布、消息订阅 三个功能。

操作系统:windows10 专业版
开发环境:Pycharm Conmunity 2022.3
Python版本:Python3.8
第三方库:PyQt5、paho

1. 第三方库的安装及注意事项

需要安装第三方库 PyQt5paho

如果你还不会安装第三方库,你可以参考我的这篇文章学习
Python第三方库安装——使用vscode、pycharm安装Python第三方库

注意!!! 工程结构必须如下:

基于PyQt5的图形化界面开发——自制MQTT客户端软件

2. Editor.py

编辑配置界面代码如下:

# -*- coding: utf-8 -*-# Form implementation generated from reading ui file 'Editor.ui'
#
# Created by: PyQt5 UI code generator 5.15.4
#
# WARNING: Any manual changes made to this file will be lost when pyuic5 is
# run again.  Do not edit this file unless you know what you are doing.from PyQt5 import QtCore, QtGui, QtWidgetsclass Ui_MainWindow(object):def setupUi(self, MainWindow):MainWindow.setObjectName("MainWindow")MainWindow.resize(641, 409)self.centralwidget = QtWidgets.QWidget(MainWindow)self.centralwidget.setObjectName("centralwidget")self.pushButton = QtWidgets.QPushButton(self.centralwidget)self.pushButton.setGeometry(QtCore.QRect(490, 290, 93, 28))self.pushButton.setObjectName("pushButton")self.lineEdit = QtWidgets.QLineEdit(self.centralwidget)self.lineEdit.setGeometry(QtCore.QRect(260, 100, 171, 21))self.lineEdit.setObjectName("lineEdit")self.lineEdit_2 = QtWidgets.QLineEdit(self.centralwidget)self.lineEdit_2.setGeometry(QtCore.QRect(260, 130, 91, 21))self.lineEdit_2.setObjectName("lineEdit_2")self.label = QtWidgets.QLabel(self.centralwidget)self.label.setGeometry(QtCore.QRect(180, 100, 51, 20))self.label.setObjectName("label")self.label_2 = QtWidgets.QLabel(self.centralwidget)self.label_2.setGeometry(QtCore.QRect(190, 130, 51, 20))self.label_2.setObjectName("label_2")self.label_3 = QtWidgets.QLabel(self.centralwidget)self.label_3.setGeometry(QtCore.QRect(170, 160, 81, 20))self.label_3.setObjectName("label_3")self.lineEdit_3 = QtWidgets.QLineEdit(self.centralwidget)self.lineEdit_3.setGeometry(QtCore.QRect(260, 160, 171, 21))self.lineEdit_3.setObjectName("lineEdit_3")self.splitter = QtWidgets.QSplitter(self.centralwidget)self.splitter.setGeometry(QtCore.QRect(170, 20, 279, 28))self.splitter.setOrientation(QtCore.Qt.Horizontal)self.splitter.setObjectName("splitter")self.pushButton_3 = QtWidgets.QPushButton(self.splitter)self.pushButton_3.setObjectName("pushButton_3")self.pushButton_4 = QtWidgets.QPushButton(self.splitter)self.pushButton_4.setObjectName("pushButton_4")self.pushButton_5 = QtWidgets.QPushButton(self.splitter)self.pushButton_5.setObjectName("pushButton_5")self.label_4 = QtWidgets.QLabel(self.centralwidget)self.label_4.setGeometry(QtCore.QRect(230, 220, 131, 20))self.label_4.setText("")self.label_4.setObjectName("label_4")MainWindow.setCentralWidget(self.centralwidget)self.menubar = QtWidgets.QMenuBar(MainWindow)self.menubar.setGeometry(QtCore.QRect(0, 0, 641, 26))self.menubar.setObjectName("menubar")MainWindow.setMenuBar(self.menubar)self.statusbar = QtWidgets.QStatusBar(MainWindow)self.statusbar.setObjectName("statusbar")MainWindow.setStatusBar(self.statusbar)self.retranslateUi(MainWindow)QtCore.QMetaObject.connectSlotsByName(MainWindow)def retranslateUi(self, MainWindow):_translate = QtCore.QCoreApplication.translateMainWindow.setWindowTitle(_translate("MainWindow", "编辑配置"))self.pushButton.setText(_translate("MainWindow", "保存配置"))self.label.setText(_translate("MainWindow", "Broker"))self.label_2.setText(_translate("MainWindow", "Port"))self.label_3.setText(_translate("MainWindow", "Client ID"))self.pushButton_3.setText(_translate("MainWindow", "消息订阅"))self.pushButton_4.setText(_translate("MainWindow", "消息发布"))self.pushButton_5.setText(_translate("MainWindow", "编辑配置"))

2.1 配置界面效果演示:

基于PyQt5的图形化界面开发——自制MQTT客户端软件

点击保存配置后将生成一个config.json文件,下一次将不需要重新配置,将默认匹配填充配置文件中的字段。

注意,此时最上方的界面切换函数并没有添加切换功能

3. Publish.py

发布消息界面代码如下:

# -*- coding: utf-8 -*-# Form implementation generated from reading ui file 'Publish.ui'
#
# Created by: PyQt5 UI code generator 5.15.4
#
# WARNING: Any manual changes made to this file will be lost when pyuic5 is
# run again.  Do not edit this file unless you know what you are doing.from PyQt5 import QtCore, QtGui, QtWidgetsclass Ui_MainWindow(object):def setupUi(self, MainWindow):MainWindow.setObjectName("MainWindow")MainWindow.resize(641, 409)self.centralwidget = QtWidgets.QWidget(MainWindow)self.centralwidget.setObjectName("centralwidget")self.pushButton = QtWidgets.QPushButton(self.centralwidget)self.pushButton.setGeometry(QtCore.QRect(490, 290, 93, 28))self.pushButton.setObjectName("pushButton")self.lineEdit = QtWidgets.QLineEdit(self.centralwidget)self.lineEdit.setGeometry(QtCore.QRect(260, 100, 171, 21))self.lineEdit.setObjectName("lineEdit")self.lineEdit_2 = QtWidgets.QLineEdit(self.centralwidget)self.lineEdit_2.setGeometry(QtCore.QRect(260, 130, 171, 21))self.lineEdit_2.setObjectName("lineEdit_2")self.label = QtWidgets.QLabel(self.centralwidget)self.label.setGeometry(QtCore.QRect(200, 100, 41, 20))self.label.setObjectName("label")self.label_2 = QtWidgets.QLabel(self.centralwidget)self.label_2.setGeometry(QtCore.QRect(200, 130, 51, 20))self.label_2.setObjectName("label_2")self.splitter = QtWidgets.QSplitter(self.centralwidget)self.splitter.setGeometry(QtCore.QRect(170, 20, 279, 28))self.splitter.setOrientation(QtCore.Qt.Horizontal)self.splitter.setObjectName("splitter")self.pushButton_3 = QtWidgets.QPushButton(self.splitter)self.pushButton_3.setObjectName("pushButton_3")self.pushButton_4 = QtWidgets.QPushButton(self.splitter)self.pushButton_4.setObjectName("pushButton_4")self.pushButton_5 = QtWidgets.QPushButton(self.splitter)self.pushButton_5.setObjectName("pushButton_5")self.label_4 = QtWidgets.QLabel(self.centralwidget)self.label_4.setGeometry(QtCore.QRect(230, 220, 211, 20))self.label_4.setText("")self.label_4.setObjectName("label_4")self.pushButton_2 = QtWidgets.QPushButton(self.centralwidget)self.pushButton_2.setGeometry(QtCore.QRect(490, 70, 93, 28))self.pushButton_2.setObjectName("pushButton_2")MainWindow.setCentralWidget(self.centralwidget)self.menubar = QtWidgets.QMenuBar(MainWindow)self.menubar.setGeometry(QtCore.QRect(0, 0, 641, 26))self.menubar.setObjectName("menubar")MainWindow.setMenuBar(self.menubar)self.statusbar = QtWidgets.QStatusBar(MainWindow)self.statusbar.setObjectName("statusbar")MainWindow.setStatusBar(self.statusbar)self.retranslateUi(MainWindow)QtCore.QMetaObject.connectSlotsByName(MainWindow)def retranslateUi(self, MainWindow):_translate = QtCore.QCoreApplication.translateMainWindow.setWindowTitle(_translate("MainWindow", "消息发布"))self.pushButton.setText(_translate("MainWindow", "发布"))self.label.setText(_translate("MainWindow", "Topic"))self.label_2.setText(_translate("MainWindow", "Info"))self.pushButton_3.setText(_translate("MainWindow", "消息订阅"))self.pushButton_4.setText(_translate("MainWindow", "消息发布"))self.pushButton_5.setText(_translate("MainWindow", "编辑配置"))self.pushButton_2.setText(_translate("MainWindow", "连接服务器"))

3.1 消息发布界面演示

使用前需要先点击连接服务器与mqtt服务器进行连接

基于PyQt5的图形化界面开发——自制MQTT客户端软件

点击发布,将提示发布成功

基于PyQt5的图形化界面开发——自制MQTT客户端软件

使用MQTT客户端查看消息是否发布成功,如下,服务器有收到helloworld:

基于PyQt5的图形化界面开发——自制MQTT客户端软件
注意,此时最上方的界面切换函数并没有添加切换功能

4. Subcribe.py

# -*- coding: utf-8 -*-# Form implementation generated from reading ui file '.\\Subscribe.ui'
#
# Created by: PyQt5 UI code generator 5.15.4
#
# WARNING: Any manual changes made to this file will be lost when pyuic5 is
# run again.  Do not edit this file unless you know what you are doing.from PyQt5 import QtCore, QtGui, QtWidgetsclass Ui_MainWindow(object):def setupUi(self, MainWindow):MainWindow.setObjectName("MainWindow")MainWindow.resize(641, 409)self.centralwidget = QtWidgets.QWidget(MainWindow)self.centralwidget.setObjectName("centralwidget")self.pushButton = QtWidgets.QPushButton(self.centralwidget)self.pushButton.setGeometry(QtCore.QRect(490, 290, 93, 28))self.pushButton.setObjectName("pushButton")self.lineEdit = QtWidgets.QLineEdit(self.centralwidget)self.lineEdit.setGeometry(QtCore.QRect(230, 100, 141, 21))self.lineEdit.setObjectName("lineEdit")self.lineEdit_2 = QtWidgets.QLineEdit(self.centralwidget)self.lineEdit_2.setGeometry(QtCore.QRect(230, 140, 171, 21))self.lineEdit_2.setObjectName("lineEdit_2")self.label = QtWidgets.QLabel(self.centralwidget)self.label.setGeometry(QtCore.QRect(170, 100, 51, 20))self.label.setObjectName("label")self.label_2 = QtWidgets.QLabel(self.centralwidget)self.label_2.setGeometry(QtCore.QRect(170, 140, 71, 20))self.label_2.setObjectName("label_2")self.splitter = QtWidgets.QSplitter(self.centralwidget)self.splitter.setGeometry(QtCore.QRect(170, 20, 279, 28))self.splitter.setOrientation(QtCore.Qt.Horizontal)self.splitter.setObjectName("splitter")self.pushButton_3 = QtWidgets.QPushButton(self.splitter)self.pushButton_3.setObjectName("pushButton_3")self.pushButton_4 = QtWidgets.QPushButton(self.splitter)self.pushButton_4.setObjectName("pushButton_4")self.pushButton_5 = QtWidgets.QPushButton(self.splitter)self.pushButton_5.setObjectName("pushButton_5")self.label_4 = QtWidgets.QLabel(self.centralwidget)self.label_4.setGeometry(QtCore.QRect(230, 220, 211, 20))self.label_4.setText("")self.label_4.setObjectName("label_4")self.pushButton_2 = QtWidgets.QPushButton(self.centralwidget)self.pushButton_2.setGeometry(QtCore.QRect(490, 70, 93, 28))self.pushButton_2.setObjectName("pushButton_2")MainWindow.setCentralWidget(self.centralwidget)self.menubar = QtWidgets.QMenuBar(MainWindow)self.menubar.setGeometry(QtCore.QRect(0, 0, 641, 26))self.menubar.setObjectName("menubar")MainWindow.setMenuBar(self.menubar)self.statusbar = QtWidgets.QStatusBar(MainWindow)self.statusbar.setObjectName("statusbar")MainWindow.setStatusBar(self.statusbar)self.retranslateUi(MainWindow)QtCore.QMetaObject.connectSlotsByName(MainWindow)def retranslateUi(self, MainWindow):_translate = QtCore.QCoreApplication.translateMainWindow.setWindowTitle(_translate("MainWindow", "消息发布"))self.pushButton.setText(_translate("MainWindow", "订阅"))self.label.setText(_translate("MainWindow", "Topic"))self.label_2.setText(_translate("MainWindow", "Receive"))self.pushButton_3.setText(_translate("MainWindow", "消息订阅"))self.pushButton_4.setText(_translate("MainWindow", "消息发布"))self.pushButton_5.setText(_translate("MainWindow", "编辑配置"))self.pushButton_2.setText(_translate("MainWindow", "连接服务器"))

订阅消息部分用到了多线程,这样做的目的是防止界面卡死,因为界面是一直在死循环中接收消息的,使用线程的好处是不会引发线程冲突,我们最后的完整代码也依旧可以自由进行界面切换。

4.1 订阅消息效果演示:

同样的需要先连接服务器,我们使用现有的MQTT客户端进行测试:

基于PyQt5的图形化界面开发——自制MQTT客户端软件

可以看到我们自己编写的客户端收到了消息:

基于PyQt5的图形化界面开发——自制MQTT客户端软件
完成子功能测试后,我们可以开始添加界面切换功能了。

界面切换——main.py

界面切换的原理其实就是在显示当前界面时,关闭或者是隐藏其他的所有界面。

例如,我们在发布消息的界面时,订阅消息和编辑配置这两个界面是隐藏起来的

上传视频有点麻烦,这里不上传演示视频了,直接上完整代码:

# code:utf-8
# Create by Maxtang
# 2023/4/21import sys
from PyQt5.QtWidgets import QApplication, QMainWindow, QTableWidgetItem
from functools import partial
from PyQt5 import QtCore, QtWidgets
from PyQt5.QtCore import Qt
import timefrom paho.mqtt import client as mqtt_clientimport Editor
import Publish
import Subscribeclass UI_Editor(QMainWindow,Editor.Ui_MainWindow):def __init__(self):super(UI_Editor, self).__init__()self.setupUi(self)self.pushButton.clicked.connect(lambda: {self.save()})self.pushButton_3.clicked.connect(lambda: {self.hide(), self.change(3)})self.pushButton_4.clicked.connect(lambda: {self.hide(), self.change(4)})self.pushButton_5.clicked.connect(lambda: {self.hide(), self.change(5)})try:with open("config.json","r+") as f:config = eval(f.read())self.lineEdit.setText(config["Broker"])self.lineEdit_2.setText(config["Port"])self.lineEdit_3.setText(config["Client_ID"])except:print("[!] read config error")passdef change(self,i):edit = UI_Editor()sub = UI_Subscribe()pub = UI_Publish()if i == 3:sub.show()if i == 4:pub.show()if i == 5:edit.show()def save(self):try:Broker = self.lineEdit.text()Port = self.lineEdit_2.text()Client_ID = self.lineEdit_3.text()with open("config.json","w+") as f:f.write(str({"Broker":Broker,"Port":Port,"Client_ID":Client_ID}))f.close()self.label_4.setText("已保存配置!")except:self.label_4.setText("保存失败!")class UI_Publish(QMainWindow,Publish.Ui_MainWindow):def __init__(self):super(UI_Publish, self).__init__()self.setupUi(self)self.pushButton.clicked.connect(lambda: {self.publish(client)})self.pushButton_2.clicked.connect(lambda: {self.connect()})self.pushButton_3.clicked.connect(lambda: {self.hide(), self.change(3)})self.pushButton_4.clicked.connect(lambda: {self.hide(), self.change(4)})self.pushButton_5.clicked.connect(lambda: {self.hide(), self.change(5)})def change(self,i):edit = UI_Editor()sub = UI_Subscribe()pub = UI_Publish()if i == 3:sub.show()if i == 4:pub.show()if i == 5:edit.show()def publish(self,client):try:topic = self.lineEdit.text()msg = self.lineEdit_2.text()result = client.publish(topic, msg)# result: [0, 1]status = result[0]if status == 0:print(f"Send `{msg}` to topic `{topic}`")self.label_4.setText("[√]发送成功")else:print(f"Failed to send message to topic {topic}")self.label_4.setText("[×]发送失败")except:self.label_4.setText("[!]请先连接服务器!")def connect(self):try:def on_connect(client, userdata, flags, rc):if rc == 0:print("Connected to MQTT Broker!")else:print("Failed to connect, return code %d\\n", rc)with open("config.json", "r+") as f:config = eval(f.read())Broker = config["Broker"]Port = int(config["Port"])Client_ID = config["Client_ID"]print(config)f.close()global clientclient = mqtt_client.Client(Client_ID)client.on_connect = on_connectclient.connect(Broker, Port)self.label_4.setText("[√]连接成功")except:print("[!]Connect error")self.label_4.setText("[×]连接失败")passclass UI_Subscribe(QMainWindow,Subscribe.Ui_MainWindow):def __init__(self):from threading import Threadsuper(UI_Subscribe, self).__init__()self.setupUi(self)self.pushButton.clicked.connect(lambda: {self.subscribe(client),Thread(target=client.loop_forever).start()})self.pushButton_2.clicked.connect(lambda: {self.connect()})self.pushButton_3.clicked.connect(lambda: {self.hide(), self.change(3)})self.pushButton_4.clicked.connect(lambda: {self.hide(), self.change(4)})self.pushButton_5.clicked.connect(lambda: {self.hide(), self.change(5)})def change(self,i):edit = UI_Editor()sub = UI_Subscribe()pub = UI_Publish()if i == 3:sub.show()if i == 4:pub.show()if i == 5:edit.show()def subscribe(self,client):def on_message(client, userdata, msg):print("ok")self.lineEdit_2.setText(msg.payload.decode())self.label_4.setText("[√]接收成功")time.sleep(1)topic = self.lineEdit.text()client.subscribe(topic)client.on_message = on_messageself.label_4.setText("[√]订阅成功")def connect(self):try:def on_connect(client, userdata, flags, rc):if rc == 0:print("Connected to MQTT Broker!")else:print("Failed to connect, return code %d\\n", rc)with open("config.json", "r+") as f:config = eval(f.read())Broker = config["Broker"]Port = int(config["Port"])Client_ID = config["Client_ID"]print(config)f.close()global clientclient = mqtt_client.Client(Client_ID)client.on_connect = on_connectclient.connect(Broker, Port)self.label_4.setText("[√]连接成功")except:print("[!]Connect error")self.label_4.setText("[×]连接失败")passapp = QtWidgets.QApplication(sys.argv)
main = UI_Subscribe()
main.show()
sys.exit(app.exec_())

5. 写在最后

如果你的代码运行报错:

1. 请检查是否安装第三方库 PyQt5 和 paho
2. 工程结构是否与我一致

基于PyQt5的图形化界面开发——自制MQTT客户端软件