基于 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. 第三方库的安装及注意事项
需要安装第三方库 PyQt5 和 paho
如果你还不会安装第三方库,你可以参考我的这篇文章学习
Python第三方库安装——使用vscode、pycharm安装Python第三方库
注意!!! 工程结构必须如下:
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, QtWidgets
class 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.translate
MainWindow.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 配置界面效果演示:
点击保存配置后将生成一个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, QtWidgets
class 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.translate
MainWindow.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服务器进行连接
点击发布,将提示发布成功
使用MQTT客户端查看消息是否发布成功,如下,服务器有收到helloworld:
注意,此时最上方的界面切换函数并没有添加切换功能
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, QtWidgets
class 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.translate
MainWindow.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客户端进行测试:
可以看到我们自己编写的客户端收到了消息:
完成子功能测试后,我们可以开始添加界面切换功能了。
界面切换——main.py
界面切换的原理其实就是在显示当前界面时,关闭或者是隐藏其他的所有界面。
例如,我们在发布消息的界面时,订阅消息和编辑配置这两个界面是隐藏起来的
上传视频有点麻烦,这里不上传演示视频了,直接上完整代码:
# code:utf-8
# Create by Maxtang
# 2023/4/21
import sys
from PyQt5.QtWidgets import QApplication, QMainWindow, QTableWidgetItem
from functools import partial
from PyQt5 import QtCore, QtWidgets
from PyQt5.QtCore import Qt
import time
from paho.mqtt import client as mqtt_client
import Editor
import Publish
import Subscribe
class 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")
pass
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 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 client
client = mqtt_client.Client(Client_ID)
client.on_connect = on_connect
client.connect(Broker, Port)
self.label_4.setText("[√]连接成功")
except:
print("[!]Connect error")
self.label_4.setText("[×]连接失败")
pass
class UI_Subscribe(QMainWindow,Subscribe.Ui_MainWindow):
def __init__(self):
from threading import Thread
super(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_message
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 client
client = mqtt_client.Client(Client_ID)
client.on_connect = on_connect
client.connect(Broker, Port)
self.label_4.setText("[√]连接成功")
except:
print("[!]Connect error")
self.label_4.setText("[×]连接失败")
pass
app = QtWidgets.QApplication(sys.argv)
main = UI_Subscribe()
main.show()
sys.exit(app.exec_())
5. 写在最后
如果你的代码运行报错:
1. 请检查是否安装第三方库 PyQt5 和 paho
2. 工程结构是否与我一致