[原创]PyQT基于Modbus_tk开发Modbus上位机

一句废话:之前说不搞PyQT了,可是python的库实在太强大,QT的界面实在太方便,二者结合,实在太牛逼,我还是继续踏实地往下做吧


Pycharm新建工程,就不细表了。安装一些依赖库,WIN+R调出cmd,输入
pip3 install modbus_tk 就OK了
但是有个问题,pip安装的库默认在python安装路径下,而Pycharm新建工程则默认去识别当前工程所在的目录包含的库。因此,会出现无法import库的情况,更改如下
[原创]PyQT基于Modbus_tk开发Modbus上位机_第1张图片
选择Python路径作为project interpreter,一切OK

PyQT界面设计

开工,在Qt Designer下绘制UI,如下图
[原创]PyQT基于Modbus_tk开发Modbus上位机_第2张图片
此时在工程目录下,会出现相应的ui文件,比如我的是MBcomQT.ui,右键选择PyUIC,将会在当前工程目录下,生成 MBcomQT.py:
[原创]PyQT基于Modbus_tk开发Modbus上位机_第3张图片
大致看一下生成的文件内容:

# -*- coding: utf-8 -*-

# Form implementation generated from reading ui file 'MBcomQT.ui'
#
# Created by: PyQt5 UI code generator 5.6
#
# WARNING! All changes made in this file will be lost!

from PyQt5 import QtCore, QtGui, QtWidgets

class Ui_MainWindow(object):
    def setupUi(self, MainWindow):
        MainWindow.setObjectName("MainWindow")
        MainWindow.setEnabled(True)
        MainWindow.resize(800, 600)
        #还有很多

该py文件由ui文件自动生成,每次QT的Ui改动,记得生成一次py文件就好。
然后,在主文件中包含文件,新建窗口类包含Ui类就好。如下:

from MBcomQT import Ui_MainWindow
###
#此处省略N行无关代码
###

class MyWindow(QMainWindow,Ui_MainWindow):
    def __init__(self):
        super(MyWindow,self).__init__()
        self.inspectData = {'adcValue':0,'adcRefValue':0,'pTomValue':0,'meaPlsCnt':0,'refPlsCnt':0,'meaTRatio':0}
###
#此处再省略N行无关代码
###

最简单的测试代码可以这样写,

import sys
from MBcomQT import Ui_MainWindow
from PyQt5.QtWidgets import *
from PyQt5.QtGui import *
from PyQt5.QtCore import *
from PyQt5 import QtCore, QtGui

class MyWindow(QMainWindow,Ui_MainWindow):
    def __init__(self):
        super(MyWindow,self).__init__()
        self.setupUi(self)
        
if __name__ == "__main__":
    app = QApplication(sys.argv)
    myshow = MyWindow()
    myshow.show()

    print('程序终止')

    sys.exit(app.exec_())
  • 出错:
    这个时候会报错,原因是在Ui生成的py文件末尾,有rc文件的引用,而这个文件并不存在,即缺少相应的rc文件

  • 办法:
    在PyCharm的Terminal下,输入
    pyrcc5 -o MBcomSrc_rc.py MBcomSrc.qrc
    [原创]PyQT基于Modbus_tk开发Modbus上位机_第4张图片

  • 关于UI文件中用到图标资源的几点注意(本处非必要,可略过不看)
    [原创]PyQT基于Modbus_tk开发Modbus上位机_第5张图片
    所有的图标资源都在这里管理,点开铅笔
    [原创]PyQT基于Modbus_tk开发Modbus上位机_第6张图片
    这几个小图标分别是:新建资源文件、打开资源文件、移除资源文件;添加前缀、添加文件、删除文件。见名知意,不再赘述。只是想说的是,这里的前缀决定了图标的索引,举个例子:
    [原创]PyQT基于Modbus_tk开发Modbus上位机_第7张图片
    我的文件结构是,主py文件与icons文件夹并列,所有ico和png都在icons文件夹下。因此,前缀应该是./,文件路径是icons/logo.ico即从工程目录开始,以 ./icons/logo.ico来索引资源,以此类推
    到这里,软件的基本操作就算告一段落了,下面讲点业务上的事情


Modbus Python库

1. modbus_tk的基本使用
可以参考另一篇文章
python下通过modbus_tk实现modbus主机

2.业务逻辑
如前面UI所示,有端口选择框和一些pushbutton,主要有 建立连接BuidLink、观测数据InspectData、清除数据ClearData和远端加热FarHeat。这些pushbutton都连接了相应的槽函数,分别如下:

self.timeThread.oneSecondTriger.connect(self.timeUpdate)
self.pushButton_BuidLink.clicked.connect(self.MBusBuidLink)
self.pushButton_InspectData.clicked.connect(self.InspectData)
self.pushButton_ClearData.clicked.connect(self.TextBrowserClear)
self.pushButton_FarHeat.clicked.connect(self.FarHeatCtrl)

列出几个关键的函数:

def InspectData(self):
    try:
        x = self.MBusMaster.execute(35, cst.READ_HOLDING_REGISTERS, 25, 11)
        self.inspectData['adcValue']    = AddrIntToFloat(x[0],x[1])
        self.inspectData['adcRefValue'] = AddrIntToFloat(x[2],x[3])
        self.inspectData['pTomValue']   = AddrIntToFloat(x[4],x[5])
        self.inspectData['meaPlsCnt']   = (x[6] << 8)| (x[7])
        self.inspectData['refPlsCnt']   = (x[8] << 8)| (x[9])
        self.inspectData['meaTRatio']   = x[10]
        print(self.inspectData)
        self.TextBrowserAdd()
    except modbus_tk.modbus_rtu.ModbusInvalidResponseError as err:
        print(err)

以及

def FarHeatCtrl(self):
    if self.pushButton_FarHeat.text() == '远端加热':
        self.pushButton_FarHeat.setText('中断加热')
        self.label_PortHeatInfo.setText('加热中')
        self.graphicsView_Heat.setStyleSheet("background-color: rgb(255, 0, 0);")
        try:
            print(self.MBusMaster.execute(35, cst.WRITE_MULTIPLE_REGISTERS, 9, output_value=[1]))
        except modbus_tk.modbus_rtu.ModbusInvalidResponseError as err:
            print(err)
    else:
        self.pushButton_FarHeat.setText('远端加热')
        self.label_PortHeatInfo.setText('未加热')
        self.graphicsView_Heat.setStyleSheet("background-color: rgb(184, 134, 11);")
        try:
            print(self.MBusMaster.execute(35, cst.WRITE_MULTIPLE_REGISTERS, 9, output_value=[0]))
        except modbus_tk.modbus_rtu.ModbusInvalidResponseError as err:
            print(err)

着重说一下,多线程的刷新问题,关于Python多线程以及PyQT多线程,可参考这篇文章:Python多线程与PyQT多线程实例讲解
在UI中,有checkbox,这个是否勾选将决定线程是否向从机读数据。代码如下:

class MBThread(QThread):
    oneSecondTriger = pyqtSignal()

    def __init__(self):
        super(MBThread,self).__init__()

    def run(self):
        while True:
            self.oneSecondTriger.emit()
            time.sleep(1)

槽函数定义

def timeUpdate(self):
    self.label_Time.setText(time.strftime('%H:%M:%S'))
    if self.checkBox_Refresh.isChecked() == True:
        try:
            x = self.MBusMaster.execute(35, cst.READ_HOLDING_REGISTERS, 4, 2)  
            self.rh = AddrIntToFloat(x[0], x[1])
            x = self.MBusMaster.execute(35, cst.READ_HOLDING_REGISTERS, 5, 2)  
            self.t = AddrIntToFloat(x[0], x[1])
        except modbus_tk.modbus_rtu.ModbusInvalidResponseError as err:
            print(err)
    else:
        self.rh = 0
        self.t = 0

    self.label_RHValue.setText(str(round(self.rh,2)))
    self.label_TValue.setText(str(round(self.t,2)))

当然,在类内部构造函数,应该做相应的初始化工作的

self.timeThread = MBThread()
self.timeThread.oneSecondTriger.connect(self.timeUpdate)
self.timeThread.start()

好的,运行测试

细节功能就不一一展示了。

  • 收尾
    将上述工程文件打包生成exe,也是一堆坑,可参考下面这篇文章
    生成exe

好,本篇结束

你可能感兴趣的:(PyQT,Python)