基于python+pyqt5的串口助手

基于python+pyqt5的串口助手

环境: pycharm、python3.8,pyqt5,pyserial。(需要该节的工程文件 请私信)

说明: 通过python3.8、pyqt5和pyserial自制串口助手,可以实现基础ascll码,hex数据的收发以及定时发送,还可以实现接收数据的动态波形显示。

功能展示

在工程应用中串口助手的其他功能因项目而异,因此学会基础功能的搭建,在改进的时候就方便很多。

请添加图片描述

文章目录

  • 1.环境的安装(很重要)
  • 1.1.所需包的安装
  • 1.2. 链接QT
  • 1.2.1 pycharm如何调用QT设计师
  • 1.2.2 pycharm中如何更新QT对应的py代码
  • 2.串口助手代码
  • 2.1 主代码
  • 2.2 链接QT的py代码
  • 3.虚拟环境下生成exe(含图标)
  • 3.1任务栏和exe左上角的图标添加
  • 3.2 虚拟环境下生成EXE
  • 3.3 功能演示

  • 1.环境的安装(很重要)

       Python3.8和pycharm进行常规安装即可(pycharm需要破解一下)。(如果需要安装包可以私信)

    1.1.所需包的安装

    所需的包

    说明
    pyqt5 链接QT
    pyqt5-tools 链接QT
    pyserial 串口库
    matplotlib 绘图所需的matlib包
    pyinstaller 生成EXE所需的包
    py2exe 生成exe所需的包(关系到exe任务栏和左上角图标能否正常显示

    安装方法(下面三个选一个就可以):

    ①默认方法(安装比较慢):

    pip install  pack    (pack是指上面的包)
    

    ②推荐使用清华源:

    pip install -i https://pypi.tuna.tsinghua.edu.cn/simple/ pack     (pack是指上面的包)
    

    ③也可以使用其他源

    阿里云: https://mirrors.aliyun.com/pypi/simple/  pack     (pack是指上面的包)
    
    豆瓣:http://pypi.douban.com/simple/ pack     (pack是指上面的包)
    

    安装好包之后

    在Terminal下执行pip list可以看到安装的包

    (有些包是上面总结的包附带的)

    还有下面这个包

    pyqt5-tools
    

    1.2. 链接QT

    首先我们GUI是通过QT搭建的,界面如下所示:

    我们通过两个步骤进行python和qt的关联,也就是设置下面两个外部工具进行 QT设计师的打开QT代码的更新

    1.2.1 pycharm如何调用QT设计师

    我们要通过pycharm去调用QT设计师去设计GUI界面,但是在pycharm中如何直接调用QT设计师呢?

    前提条件是已经安装上面所述的pyqt5的包。

    接下来就配置 QtDesigner 外部工具,用来调用QT界面:


    file→settings


    Settings→Tools→External Tools(扩展包)

    里面的QtDesigner和pyuic5是我已经配置好的,没有配置里面是空白的。

    ③配置QtDesigner

    点击“+”号就会出现以下界面


    ④开始填写

    Name: QtDesigner

    Program: 选择designer.exe的目录

    (一般来说,在你Python安装目录下的\Lib\site-packages\pyqt5-tools\designer.exe。安装的第三方库一般都在Lib文件夹下site-packages文件夹中。装完PyQt5,和PyQt-tools后,该目录下至少会有这俩个文件夹)。

    注意 新版本的python下designer.exe的目录在安装Pyqt5-tools后,designer.exe所在路径是python\Lib\site-packages\qt5_applications\Qt\bin\designer.exe(安装目录下找)

    我的路径是:

    D:\software\python\Anaconda3\Lib\site-packages\qt5_applications\Qt\bin\designer.exe
    


    Arguments: $FileName$ (复制吧,也可以自己选择insert插入,这样保证已经生成的ui文件直接用designer直接打开)

    Working directory: $FileDir$ (路径设置,同样可以直接复制,选完program后会把它当默认路径而不是工程路径,这个参数会让路径转为工程路径)

    配置完界面如下:

    配置完成

    在Tools→External Tools中点击QtDesigner就可以打开QT设计师


    1.2.2 pycharm中如何更新QT对应的py代码

    Qt设计师中,界面设计完毕后怎么转换为py代码呢?

    同样需要配置一个外部工具(Pyuic5):


    file→settings


    Settings→Tools→External Tools(扩展包)

    里面的QtDesigner和pyuic5是我已经配置好的,没有配置里面是空白的。

    ③配置pyuic5

    Name: Pyuic5(其实名字随你,你自己懂就行了)

    Program: 这里选的是你Python安装目录,找到python.exe(例如我的是D:\software\python\Anaconda3\python.exe)

    Arguments: -m PyQt5.uic.pyuic $FileName$ -o $FileNameWithoutExtension$.py
    Working directory: $FileDir$ (这样保证转换的文件是在你的工程目录下的,否则是其他目录)

    配置好的界面如下

    配置完成

    点击Tools→External Tools→pyuic5就可以将QT设计师中GUI界面转换为py文件

    2.串口助手代码

    2.1 主代码

    需要工程文件,私信。

    import sys
    import serial
    import serial.tools.list_ports
    
    
    from PyQt5.QtWidgets import QMessageBox
    from PyQt5.QtCore import QTimer
    from ui_demo_1 import Ui_Form
    
    from PyQt5.QtGui import QIcon
    
    from PyQt5.QtWidgets import QFileDialog
    from matplotlib.backends.backend_qt5agg import FigureCanvasQTAgg as FigureCanvas, NavigationToolbar2QT as NavigationToolbar
    
    from matplotlib.figure import Figure
    
    import images
    
    import ctypes
    ctypes.windll.shell32.SetCurrentProcessExplicitAppUserModelID("myappid")
    
    from PyQt5 import QtWidgets                     #包含构建界面的UI元素组件
    
    
    
    
    class Pyqt5_Serial(QtWidgets.QWidget, Ui_Form):
        def __init__(self):                         #开头都这么写
            super(Pyqt5_Serial, self).__init__()    #开头都这么写
            self.setupUi(self)                      #开头都这么写
            self.init()                             #调用下面的init函数
            self.setWindowTitle("Ucom v1.2")        #设置标题
            self.ser = serial.Serial()
            self.port_check()                       #串口检测
            self.setWindowIcon(QIcon(':/Ucom.ico'))   #设置图标
    
            # 接收数据和发送数据数目置零
            self.data_num_received = 0              #接收数据清零
            self.lineEdit.setText(str(self.data_num_received))
            self.data_num_sended = 0                #发送数据清零
            self.lineEdit_2.setText(str(self.data_num_sended))
            self.ReceNumForClear = 0                # 接收框的数据个数用于自动清除  放到初始化只会初始化一次
    
            # 设置选项卡的名称
            self.tabWidget.setTabText(0, '数据接收')
            self.tabWidget.setTabText(1, '波形显示')
            # index = self.tabWidget.currentIndex()     #可以读取选项卡的状态
    
    
    
    
            # 添加图像显示的画布
            self.static_canvas = FigureCanvas(Figure())                     # 画布、渲染器
            layout = QtWidgets.QVBoxLayout(self.groupBox)                   # 添加垂直布局类groupBox
            layout.addWidget(self.static_canvas)                            # 向布局groupBox_1中添加渲染器
            tool_bar = NavigationToolbar(self.static_canvas, self.groupBox)  # 生成画布相关联的工具栏
            layout.addWidget(tool_bar)                                      # 向布局groupBox_2中添加工具栏
            self._static_ax1 = self.static_canvas.figure.subplots(1, 1)     # 从渲染器中的画布figure中,获取子布,也就是Axes(1行1列)
    
    
        def init(self):
            # 串口检测按钮
            self.s1__box_1.clicked.connect(self.port_check)             # 单击 检测串口 按钮链接到port_check函数
    
            # 串口信息显示
            self.s1__box_2.currentTextChanged.connect(self.port_imf)    # 当前文本改变(串口选择) 链接到串口信息函数
    
            # 打开串口按钮
            self.open_button.clicked.connect(self.port_open)            # 单击 打开串口 按钮链接到打开串口函数
    
            # 关闭串口按钮
            self.close_button.clicked.connect(self.port_close)          # 单击 关闭串口 按钮链接到关闭串口函数
    
            # 发送数据按钮
            self.s3__send_button.clicked.connect(self.data_send)        # 单击 发送 按钮链接到数据发送函数
    
    
            # 定时发送数据
            self.timer_send = QTimer()
            self.timer_send.timeout.connect(self.data_send)
            self.timer_send_cb.stateChanged.connect(self.data_send_timer)
    
            # 定时器接收数据
            self.tit = 0
            self.timer = QTimer(self)
            self.timer.timeout.connect(self.data_receive)              # 串口中断是2ms进一次
    
            # 清除发送窗口
            self.s3__clear_button.clicked.connect(self.send_data_clear)     #单击 清除 按钮链接到清除显示函数(清除发送区)
    
            # 清除接收窗口
            self.s2__clear_button.clicked.connect(self.receive_data_clear)  #单击 清除 按钮链接到清除显示函数(清除接收区)
    
            # 打开文件
            self.s3__openfile_button.clicked.connect(self.openFile)         # 单击 打开文件
    
            self.timer1 = QTimer()                                          # 调用QT的定时器,用于波形显示
    
    
        # ------------------按下检测串口按钮
        def port_check(self):
            # 检测所有存在的串口,将信息存储在字典中
            self.Com_Dict = {}                                              #创建一个字典,字典是可变的容器
            port_list = list(serial.tools.list_ports.comports())            #list是序列,一串数据,可以追加数据
            self.s1__box_2.clear()                                          #s1__box_2为串口选择列表
            for port in port_list:
                self.Com_Dict["%s" % port[0]] = "%s" % port[1]
                self.s1__box_2.addItem(port[0])                             #将检测到的串口放置到s1__box_2串口选择列表
            if len(self.Com_Dict) == 0:
                self.state_label.setText(" 无串口")
    
        # ------------------串口选择下拉框选择com口
        def port_imf(self):
            # 显示选定的串口的详细信息
            imf_s = self.s1__box_2.currentText()                            #当前显示的com口
            if imf_s != "":
                self.state_label.setText(self.Com_Dict[self.s1__box_2.currentText()])#state_label显示窗口显当前串口
    
        # -------------------打开串口
        def port_open(self):
            self.ser.port = self.s1__box_2.currentText()                    #串口选择框
            self.ser.baudrate = int(self.s1__box_3.currentText())           #波特率输入框
            self.ser.bytesize = int(self.s1__box_4.currentText())           #数据位输入框
            self.ser.stopbits = int(self.s1__box_6.currentText())           #停止位输入框
            self.ser.parity = self.s1__box_5.currentText()                  #校验位输入框
            try:
                self.ser.open()
            except:
    	        QMessageBox.critical(self, "Port Error", "此串口不能被打开!")
    	        return None
    
    
            self.timer.start(2)                                             #打开串口接收定时器,周期为2ms
    
            if self.ser.isOpen():                                           #打开串口按下,禁用打开按钮,启用关闭按钮
                self.open_button.setEnabled(False)                          #禁用打开按钮
                self.close_button.setEnabled(True)                          #启用关闭按钮
                self.formGroupBox1.setTitle("串口状态(已开启)")              #GroupBox1控件在串口打开的时候显示(已开启)
    
        # --------------------关闭串口
        def port_close(self):
            self.timer.stop()                                               # 停止计时器
            self.timer_send.stop()                                          # 停止定时发送
            self.timer1.stop()                                              # 停止图形显示计时器
            try:
                self.ser.close()
            except:
    	        pass
            self.open_button.setEnabled(True)                               #启用打开按钮
            self.close_button.setEnabled(False)                             #禁用停止按钮
            self.lineEdit_3.setEnabled(True)                                #启用定时发送时间框
            # 接收数据和发送数据数目置零
            self.data_num_received = 0
            self.lineEdit.setText(str(self.data_num_received))              #接收数目置零
            self.data_num_sended = 0
            self.lineEdit_2.setText(str(self.data_num_sended))              #发送数目清零
            self.formGroupBox1.setTitle("串口状态(已关闭)")                  #GroupBox1控件在串口打开的时候显示(已关闭)
    
        # ----------------------发送数据
        def data_send(self):
            if self.ser.isOpen():                                           #判断串口是否打开 ↓
                input_s = self.s3__send_text.toPlainText()                  #(发送区)获取文本内容
                if input_s != "":                                           # 非空字符串 ↓
                    # hex发送
                    if self.hex_send.isChecked():                           #(勾选Hex发送) ↓(复选框选中返回Ture)
                        input_s = input_s.strip()                           #移除字符串头尾指定的字符(默认为空格或换行符)
                        send_list = []                                      #创建一个列表
                        while input_s != '':                                #发送框不是空的说明有数据,等发送完毕跳出循环
                            try:                                            #捕获异常
                                num = int(input_s[0:2], 16)                 #十六进制(取前两个字节数据)
                            except ValueError:
                                QMessageBox.critical(self, 'wrong data', '请输入十六进制数据,以空格分开!')
                                return None
                            input_s = input_s[2:].strip()                   #移除字符串头尾指定的字符(默认为空格或换行符)
                            send_list.append(num)                           #列表末尾添加新的对象
                        input_s = bytes(send_list)                          #返回字节对象
                    # ascii发送
                    else:
                        input_s = (input_s + '\r\n').encode('utf-8')        #
    
                    num = self.ser.write(input_s)                           #串口写,返回的写入的数据数
                    self.data_num_sended += num                             #发送数据统计
                    self.lineEdit_2.setText(str(self.data_num_sended))      #已发送数据显示框
            else:
    	        pass #空语句 保证结构的完整性
    
        # ----------------------接收数据
        def data_receive(self):
            try:
                num = self.ser.inWaiting()                                  # 获取接受缓存中的字符数
            except:
                self.port_close()
                return None
    
            if num > 0:                                                     # 如果收到数据 数据以十六进制的形式放到data里面     ,接收一次数据进一次(也就是发送端点一次发送进一次)
                data = self.ser.read(num)                                   # 从串口读取指定字节大小的数据
                num = len(data)                                             # 等到收到数据的长度
                self.recevive_num = num
                # hex显示
                if self.hex_receive.checkState():                           # 如果勾选HEX接收
                    out_s = ''                                              # 如果是空的,显示空格
                    self.display = []                                       # 创建一个列表,用于暂存波形波形显示
                    self.display2 = []                                      # 创建一个列表,用于图形绘制
                    for i in range(0, len(data)):
                        out_s = out_s + '{:02X}'.format(data[i]) + ' '      # 字符串操作显示到接收框(格式为2位十六进制)
                        self.display.append(data[i])
                    self.s2__receive_text.insertPlainText(out_s)            # 接收的数据显示至接收区
    
                    # 调用定时器实现图形绘制
                    self.timer1.start(100)  # 单位为ms,10ms刷新一次
    
                    self.i = 0  # 新建变量
                    self.t = []  # 新建一个列表
                    self.s = []  # 新建一个列表
    
                    self.timer1.timeout.connect(self.refresh_plot)  # 定时器计数达到后调用函数refresh_plot
    
    
                # 如果不勾选HEX接收
                else:
                    # 串口接收到的字符串为b'123',要转化成unicode字符串才能输出到窗口中去
                    self.s2__receive_text.insertPlainText(data.decode('iso-8859-1'))    #接收区
    
                # 统计接收字符的数量
                self.data_num_received += num                               # 更新接收到所有数据的数目
                self.ReceNumForClear += num                                 # 更新接收框里面的数据的数据
                self.lineEdit.setText(str(self.data_num_received))          # 显示接收数据个数
                if self.SetAutoClear.checkState():                          # 如果勾选自动清除
                    ValueClearNumSet = int(self.ClearNumSet.text())         # 获取设定的自动清除的num值
                    if self.ReceNumForClear >= ValueClearNumSet:            # 如果收到的数据大于等于ValueClearNumSet 有缺陷必须是等于才会清掉
                        self.s2__receive_text.setText("")                   # 清楚接收框 不清计数
                        self.ReceNumForClear = 0                            # ReceNumForClear清零用于下一次统计接收框接收数据个数
                        self.ReceNumForClear = self.ReceNumForClear         # 调试用
    
                # 获取到text光标
                textCursor = self.s2__receive_text.textCursor()             #获取接收区光标
                # 滚动到底部
                textCursor.movePosition(textCursor.End)                     #
                # 设置光标到text中去
                self.s2__receive_text.setTextCursor(textCursor)
            else:
                pass
    
    
        # -------------------------接收定时器达到时被调用
        def refresh_plot(self):                                             # 定时器数值达到时被调用
                                                                 #
            if(self.i == self.recevive_num):                                # 画完图后关闭定时器
                self.timer1.stop()
    
            else:
                self.t.append(self.i)                                           # 列表后面添加i,生成横坐标
                self.display2.append(self.display[self.i])                      # 生成纵坐标
                self._static_ax1.cla()                                          # 清空子图
                self._static_ax1.plot(self.t, self.display2)                    # 绘制新的图
                self.static_canvas.draw()                                       # 更新字画布的渲染器
                self.i += 1
    
        # -------------------------定时发送数据
        def data_send_timer(self):
            if self.timer_send_cb.isChecked():                              # 勾选定时发送
                self.timer_send.start(int(self.lineEdit_3.text()))          # 勾选定时发送  lineEdit_3为定时时间(设置定时周期)
                self.lineEdit_3.setEnabled(False)                           # 禁用时间输入框
            else:
                self.timer_send.stop()                                      # 不勾选定时发送时,将发送定时器关闭
                self.lineEdit_3.setEnabled(True)                            # 不勾选定时发送时,使能时间输入框
    
        # ------------------------清除发送
        def send_data_clear(self):
            self.s3__send_text.setText("")                                  # 发送区
            self.data_num_sended = 0                                        # 清除的时候发送计数归零
            self.lineEdit_2.setText(str(self.data_num_sended))              # 已发送计数清零
        #  ------------------------清除接收
        def receive_data_clear(self):
            self.data_num_received = 0                                      # 清除的时候接收计数归零
            self.lineEdit.setText(str(self.data_num_received))              # 定时发送时间清除
            self.s2__receive_text.setText("")                               # 接收区清零
    
    
    
        # ------------------------打开文件
        def openFile(self):
            fname = QFileDialog.getOpenFileName(self, '打开文件', './')      # 打开文件
            if fname[0]:                                                    # fname[0]就是要打开的文件
                with open(fname[0], 'r', encoding='gb18030', errors='ignore') as f:
                    self.s3__send_text.setText(f.read())                    # 将读取的文件放到发送框里面
    
    
    if __name__ == '__main__':
        app = QtWidgets.QApplication(sys.argv)
        myshow = Pyqt5_Serial()     #调用class
        myshow.show()
        sys.exit(app.exec_())
    
    
    

    2.2 链接QT的py代码

    链接QT的代码(该代码是QT的UI生成的代码,不是敲出来的哈):

    # -*- coding: utf-8 -*-
    
    # Form implementation generated from reading ui file 'ui_demo_1.ui'
    #
    # Created by: PyQt5 UI code generator 5.9.2
    #
    # WARNING! All changes made in this file will be lost!
    
    from PyQt5 import QtCore, QtGui, QtWidgets
    
    class Ui_Form(object):
        def setupUi(self, Form):
            Form.setObjectName("Form")
            Form.setEnabled(True)
            Form.resize(727, 535)
            self.formGroupBox = QtWidgets.QGroupBox(Form)
            self.formGroupBox.setGeometry(QtCore.QRect(20, 20, 167, 301))
            self.formGroupBox.setObjectName("formGroupBox")
            self.formLayout = QtWidgets.QFormLayout(self.formGroupBox)
            self.formLayout.setContentsMargins(10, 10, 10, 10)
            self.formLayout.setSpacing(10)
            self.formLayout.setObjectName("formLayout")
            self.s1__lb_1 = QtWidgets.QLabel(self.formGroupBox)
            self.s1__lb_1.setObjectName("s1__lb_1")
            self.formLayout.setWidget(0, QtWidgets.QFormLayout.LabelRole, self.s1__lb_1)
            self.s1__box_1 = QtWidgets.QPushButton(self.formGroupBox)
            self.s1__box_1.setAutoRepeatInterval(100)
            self.s1__box_1.setDefault(True)
            self.s1__box_1.setObjectName("s1__box_1")
            self.formLayout.setWidget(0, QtWidgets.QFormLayout.FieldRole, self.s1__box_1)
            self.s1__lb_2 = QtWidgets.QLabel(self.formGroupBox)
            self.s1__lb_2.setObjectName("s1__lb_2")
            self.formLayout.setWidget(1, QtWidgets.QFormLayout.LabelRole, self.s1__lb_2)
            self.s1__box_2 = QtWidgets.QComboBox(self.formGroupBox)
            self.s1__box_2.setObjectName("s1__box_2")
            self.formLayout.setWidget(1, QtWidgets.QFormLayout.FieldRole, self.s1__box_2)
            self.s1__lb_3 = QtWidgets.QLabel(self.formGroupBox)
            self.s1__lb_3.setObjectName("s1__lb_3")
            self.formLayout.setWidget(3, QtWidgets.QFormLayout.LabelRole, self.s1__lb_3)
            self.s1__box_3 = QtWidgets.QComboBox(self.formGroupBox)
            self.s1__box_3.setObjectName("s1__box_3")
            self.s1__box_3.addItem("")
            self.s1__box_3.addItem("")
            self.s1__box_3.addItem("")
            self.s1__box_3.addItem("")
            self.s1__box_3.addItem("")
            self.s1__box_3.addItem("")
            self.s1__box_3.addItem("")
            self.s1__box_3.addItem("")
            self.s1__box_3.addItem("")
            self.s1__box_3.addItem("")
            self.s1__box_3.addItem("")
            self.s1__box_3.addItem("")
            self.formLayout.setWidget(3, QtWidgets.QFormLayout.FieldRole, self.s1__box_3)
            self.s1__lb_4 = QtWidgets.QLabel(self.formGroupBox)
            self.s1__lb_4.setObjectName("s1__lb_4")
            self.formLayout.setWidget(4, QtWidgets.QFormLayout.LabelRole, self.s1__lb_4)
            self.s1__box_4 = QtWidgets.QComboBox(self.formGroupBox)
            self.s1__box_4.setObjectName("s1__box_4")
            self.s1__box_4.addItem("")
            self.s1__box_4.addItem("")
            self.s1__box_4.addItem("")
            self.s1__box_4.addItem("")
            self.formLayout.setWidget(4, QtWidgets.QFormLayout.FieldRole, self.s1__box_4)
            self.s1__lb_5 = QtWidgets.QLabel(self.formGroupBox)
            self.s1__lb_5.setObjectName("s1__lb_5")
            self.formLayout.setWidget(5, QtWidgets.QFormLayout.LabelRole, self.s1__lb_5)
            self.s1__box_5 = QtWidgets.QComboBox(self.formGroupBox)
            self.s1__box_5.setObjectName("s1__box_5")
            self.s1__box_5.addItem("")
            self.formLayout.setWidget(5, QtWidgets.QFormLayout.FieldRole, self.s1__box_5)
            self.open_button = QtWidgets.QPushButton(self.formGroupBox)
            self.open_button.setObjectName("open_button")
            self.formLayout.setWidget(7, QtWidgets.QFormLayout.SpanningRole, self.open_button)
            self.close_button = QtWidgets.QPushButton(self.formGroupBox)
            self.close_button.setObjectName("close_button")
            self.formLayout.setWidget(8, QtWidgets.QFormLayout.SpanningRole, self.close_button)
            self.s1__lb_6 = QtWidgets.QLabel(self.formGroupBox)
            self.s1__lb_6.setObjectName("s1__lb_6")
            self.formLayout.setWidget(6, QtWidgets.QFormLayout.LabelRole, self.s1__lb_6)
            self.s1__box_6 = QtWidgets.QComboBox(self.formGroupBox)
            self.s1__box_6.setObjectName("s1__box_6")
            self.s1__box_6.addItem("")
            self.formLayout.setWidget(6, QtWidgets.QFormLayout.FieldRole, self.s1__box_6)
            self.state_label = QtWidgets.QLabel(self.formGroupBox)
            self.state_label.setText("")
            self.state_label.setTextFormat(QtCore.Qt.AutoText)
            self.state_label.setScaledContents(True)
            self.state_label.setAlignment(QtCore.Qt.AlignRight|QtCore.Qt.AlignTrailing|QtCore.Qt.AlignVCenter)
            self.state_label.setObjectName("state_label")
            self.formLayout.setWidget(2, QtWidgets.QFormLayout.SpanningRole, self.state_label)
            self.verticalGroupBox = QtWidgets.QGroupBox(Form)
            self.verticalGroupBox.setGeometry(QtCore.QRect(210, 20, 401, 321))
            self.verticalGroupBox.setObjectName("verticalGroupBox")
            self.verticalLayout = QtWidgets.QVBoxLayout(self.verticalGroupBox)
            self.verticalLayout.setContentsMargins(10, 10, 10, 10)
            self.verticalLayout.setObjectName("verticalLayout")
            self.tabWidget = QtWidgets.QTabWidget(self.verticalGroupBox)
            self.tabWidget.setObjectName("tabWidget")
            self.tab = QtWidgets.QWidget()
            self.tab.setObjectName("tab")
            self.s2__receive_text = QtWidgets.QTextBrowser(self.tab)
            self.s2__receive_text.setGeometry(QtCore.QRect(0, 0, 371, 261))
            self.s2__receive_text.setObjectName("s2__receive_text")
            self.tabWidget.addTab(self.tab, "")
            self.tab2 = QtWidgets.QWidget()
            self.tab2.setObjectName("tab2")
            self.groupBox = QtWidgets.QGroupBox(self.tab2)
            self.groupBox.setGeometry(QtCore.QRect(0, 0, 371, 261))
            self.groupBox.setTitle("")
            self.groupBox.setObjectName("groupBox")
            self.tabWidget.addTab(self.tab2, "")
            self.verticalLayout.addWidget(self.tabWidget)
            self.verticalGroupBox_2 = QtWidgets.QGroupBox(Form)
            self.verticalGroupBox_2.setGeometry(QtCore.QRect(210, 350, 401, 141))
            self.verticalGroupBox_2.setObjectName("verticalGroupBox_2")
            self.verticalLayout_2 = QtWidgets.QVBoxLayout(self.verticalGroupBox_2)
            self.verticalLayout_2.setContentsMargins(10, 10, 10, 10)
            self.verticalLayout_2.setObjectName("verticalLayout_2")
            self.s3__send_text = QtWidgets.QTextEdit(self.verticalGroupBox_2)
            self.s3__send_text.setObjectName("s3__send_text")
            self.verticalLayout_2.addWidget(self.s3__send_text)
            self.s3__send_button = QtWidgets.QPushButton(Form)
            self.s3__send_button.setGeometry(QtCore.QRect(620, 450, 61, 31))
            self.s3__send_button.setObjectName("s3__send_button")
            self.s3__clear_button = QtWidgets.QPushButton(Form)
            self.s3__clear_button.setGeometry(QtCore.QRect(620, 370, 61, 31))
            self.s3__clear_button.setObjectName("s3__clear_button")
            self.formGroupBox1 = QtWidgets.QGroupBox(Form)
            self.formGroupBox1.setGeometry(QtCore.QRect(20, 390, 171, 101))
            self.formGroupBox1.setObjectName("formGroupBox1")
            self.formLayout_2 = QtWidgets.QFormLayout(self.formGroupBox1)
            self.formLayout_2.setContentsMargins(10, 10, 10, 10)
            self.formLayout_2.setSpacing(10)
            self.formLayout_2.setObjectName("formLayout_2")
            self.label = QtWidgets.QLabel(self.formGroupBox1)
            self.label.setObjectName("label")
            self.formLayout_2.setWidget(0, QtWidgets.QFormLayout.LabelRole, self.label)
            self.label_2 = QtWidgets.QLabel(self.formGroupBox1)
            self.label_2.setObjectName("label_2")
            self.formLayout_2.setWidget(1, QtWidgets.QFormLayout.LabelRole, self.label_2)
            self.lineEdit = QtWidgets.QLineEdit(self.formGroupBox1)
            self.lineEdit.setObjectName("lineEdit")
            self.formLayout_2.setWidget(0, QtWidgets.QFormLayout.FieldRole, self.lineEdit)
            self.lineEdit_2 = QtWidgets.QLineEdit(self.formGroupBox1)
            self.lineEdit_2.setObjectName("lineEdit_2")
            self.formLayout_2.setWidget(1, QtWidgets.QFormLayout.FieldRole, self.lineEdit_2)
            self.hex_send = QtWidgets.QCheckBox(Form)
            self.hex_send.setGeometry(QtCore.QRect(620, 350, 71, 16))
            self.hex_send.setChecked(False)
            self.hex_send.setObjectName("hex_send")
            self.hex_receive = QtWidgets.QCheckBox(Form)
            self.hex_receive.setEnabled(True)
            self.hex_receive.setGeometry(QtCore.QRect(620, 140, 71, 16))
            self.hex_receive.setTabletTracking(False)
            self.hex_receive.setAcceptDrops(False)
            self.hex_receive.setAutoFillBackground(False)
            self.hex_receive.setChecked(False)
            self.hex_receive.setAutoRepeat(False)
            self.hex_receive.setAutoExclusive(False)
            self.hex_receive.setTristate(False)
            self.hex_receive.setObjectName("hex_receive")
            self.s2__clear_button = QtWidgets.QPushButton(Form)
            self.s2__clear_button.setGeometry(QtCore.QRect(620, 160, 61, 31))
            self.s2__clear_button.setObjectName("s2__clear_button")
            self.timer_send_cb = QtWidgets.QCheckBox(Form)
            self.timer_send_cb.setGeometry(QtCore.QRect(260, 500, 71, 16))
            self.timer_send_cb.setObjectName("timer_send_cb")
            self.lineEdit_3 = QtWidgets.QLineEdit(Form)
            self.lineEdit_3.setGeometry(QtCore.QRect(350, 500, 61, 20))
            self.lineEdit_3.setAlignment(QtCore.Qt.AlignRight|QtCore.Qt.AlignTrailing|QtCore.Qt.AlignVCenter)
            self.lineEdit_3.setObjectName("lineEdit_3")
            self.dw = QtWidgets.QLabel(Form)
            self.dw.setGeometry(QtCore.QRect(420, 500, 54, 20))
            self.dw.setObjectName("dw")
            self.label_62 = QtWidgets.QLabel(Form)
            self.label_62.setGeometry(QtCore.QRect(630, 0, 91, 20))
            self.label_62.setObjectName("label_62")
            self.label_63 = QtWidgets.QLabel(Form)
            self.label_63.setGeometry(QtCore.QRect(630, 20, 41, 16))
            self.label_63.setObjectName("label_63")
            self.SetAutoClear = QtWidgets.QCheckBox(Form)
            self.SetAutoClear.setEnabled(True)
            self.SetAutoClear.setGeometry(QtCore.QRect(620, 200, 71, 16))
            self.SetAutoClear.setTabletTracking(False)
            self.SetAutoClear.setAcceptDrops(False)
            self.SetAutoClear.setAutoFillBackground(False)
            self.SetAutoClear.setChecked(True)
            self.SetAutoClear.setAutoRepeat(False)
            self.SetAutoClear.setAutoExclusive(False)
            self.SetAutoClear.setTristate(False)
            self.SetAutoClear.setObjectName("SetAutoClear")
            self.ClearNumSet = QtWidgets.QLineEdit(Form)
            self.ClearNumSet.setGeometry(QtCore.QRect(650, 220, 41, 20))
            self.ClearNumSet.setAlignment(QtCore.Qt.AlignRight|QtCore.Qt.AlignTrailing|QtCore.Qt.AlignVCenter)
            self.ClearNumSet.setObjectName("ClearNumSet")
            self.dw_2 = QtWidgets.QLabel(Form)
            self.dw_2.setGeometry(QtCore.QRect(620, 220, 31, 20))
            self.dw_2.setObjectName("dw_2")
            self.s3__openfile_button = QtWidgets.QPushButton(Form)
            self.s3__openfile_button.setGeometry(QtCore.QRect(620, 410, 61, 31))
            self.s3__openfile_button.setObjectName("s3__openfile_button")
            self.verticalGroupBox.raise_()
            self.verticalGroupBox_2.raise_()
            self.formGroupBox.raise_()
            self.s3__send_button.raise_()
            self.s3__clear_button.raise_()
            self.formGroupBox.raise_()
            self.hex_send.raise_()
            self.hex_receive.raise_()
            self.s2__clear_button.raise_()
            self.timer_send_cb.raise_()
            self.lineEdit_3.raise_()
            self.dw.raise_()
            self.label_62.raise_()
            self.label_63.raise_()
            self.SetAutoClear.raise_()
            self.ClearNumSet.raise_()
            self.dw_2.raise_()
            self.s3__openfile_button.raise_()
    
            self.retranslateUi(Form)
            self.tabWidget.setCurrentIndex(0)
            QtCore.QMetaObject.connectSlotsByName(Form)
    
        def retranslateUi(self, Form):
            _translate = QtCore.QCoreApplication.translate
            Form.setWindowTitle(_translate("Form", "Form"))
            self.formGroupBox.setTitle(_translate("Form", "串口设置"))
            self.s1__lb_1.setText(_translate("Form", "串口检测:"))
            self.s1__box_1.setText(_translate("Form", "检测串口"))
            self.s1__lb_2.setText(_translate("Form", "串口选择:"))
            self.s1__lb_3.setText(_translate("Form", "波特率:"))
            self.s1__box_3.setItemText(0, _translate("Form", "115200"))
            self.s1__box_3.setItemText(1, _translate("Form", "2400"))
            self.s1__box_3.setItemText(2, _translate("Form", "4800"))
            self.s1__box_3.setItemText(3, _translate("Form", "9600"))
            self.s1__box_3.setItemText(4, _translate("Form", "14400"))
            self.s1__box_3.setItemText(5, _translate("Form", "19200"))
            self.s1__box_3.setItemText(6, _translate("Form", "38400"))
            self.s1__box_3.setItemText(7, _translate("Form", "57600"))
            self.s1__box_3.setItemText(8, _translate("Form", "76800"))
            self.s1__box_3.setItemText(9, _translate("Form", "12800"))
            self.s1__box_3.setItemText(10, _translate("Form", "230400"))
            self.s1__box_3.setItemText(11, _translate("Form", "460800"))
            self.s1__lb_4.setText(_translate("Form", "数据位:"))
            self.s1__box_4.setItemText(0, _translate("Form", "8"))
            self.s1__box_4.setItemText(1, _translate("Form", "7"))
            self.s1__box_4.setItemText(2, _translate("Form", "6"))
            self.s1__box_4.setItemText(3, _translate("Form", "5"))
            self.s1__lb_5.setText(_translate("Form", "校验位:"))
            self.s1__box_5.setItemText(0, _translate("Form", "N"))
            self.open_button.setText(_translate("Form", "打开串口"))
            self.close_button.setText(_translate("Form", "关闭串口"))
            self.s1__lb_6.setText(_translate("Form", "停止位:"))
            self.s1__box_6.setItemText(0, _translate("Form", "1"))
            self.verticalGroupBox.setTitle(_translate("Form", "接收区"))
            self.tabWidget.setTabText(self.tabWidget.indexOf(self.tab), _translate("Form", "Tab 1"))
            self.tabWidget.setTabText(self.tabWidget.indexOf(self.tab2), _translate("Form", "Tab 2"))
            self.verticalGroupBox_2.setTitle(_translate("Form", "发送区"))
            self.s3__send_text.setHtml(_translate("Form", "<!DOCTYPE HTML PUBLIC \"-//W3C//DTD HTML 4.0//EN\" 
            self.s3__send_button.setText(_translate("Form", "发送"))
            self.s3__clear_button.setText(_translate("Form", "清除发送"))
            self.formGroupBox1.setTitle(_translate("Form", "串口状态"))
            self.label.setText(_translate("Form", "已接收:"))
            self.label_2.setText(_translate("Form", "已发送:"))
            self.hex_send.setText(_translate("Form", "Hex发送"))
            self.hex_receive.setText(_translate("Form", "Hex接收"))
            self.s2__clear_button.setText(_translate("Form", "清除接收"))
            self.timer_send_cb.setText(_translate("Form", "定时发送"))
            self.lineEdit_3.setText(_translate("Form", "20"))
            self.dw.setText(_translate("Form", "ms/次"))
            self.label_62.setText(_translate("Form", "Author:Crazzy_M"))
            self.label_63.setText(_translate("Form", "V1.2"))
            self.SetAutoClear.setText(_translate("Form", "自动清除"))
            self.ClearNumSet.setText(_translate("Form", "10000"))
            self.dw_2.setText(_translate("Form", "超过"))
            self.s3__openfile_button.setText(_translate("Form", "打开文件"))
    

    3.虚拟环境下生成exe(含图标)

    3.1任务栏和exe左上角的图标添加

    生成exe之前我们先给exe加个图标

    ①图标对应的图片自己选择哈,格式是ico,随便找个网站就能转换。


    新建.qrc文件
    名字自己命名

    我命名为images.qrc

    ③images.qrc中添加代码

    <RCC>
        <qresource prefix="/">
            <file>Ucom.ico</file>
         </qresource>
    </RCC>
    

    ④生成images.py文件

    执行

    pyrcc5 -o images.py images.qrc
    

    首先保证Terminal中的路径是在刚才新建的images.qrc文件下

    不然会提示错误

    通过cd指令将目录转至images.qrc文件下再执行

    pyrcc5 -o images.py images.qrc
    

    执行命令之前是没有images.py

    执行命令之后

    images.py文件中是什么呢,其实就是将图片转换为了数据

    ⑤将转换后的images.py引入主程序

    import images
    

    ⑥在程序中调用

    self.setWindowIcon(QIcon(':/Ucom.ico'))   #设置图标
    

    至此图标设置完成(注意要安装py2exe的包,不然有可能打包出来的图标会不显示)。

    3.2 虚拟环境下生成EXE

    如果你在运行环境下没装过其他的包,可以直接执行

    pyinstaller -F --icon=Ucom.ico pyserial_demo.py -w
    

    但是如果你安装的多余包过多,或者是以前项目安装了很多包就会导致打包的exe文件异常大,动辄300M+。

    所以我们需要创建虚拟环境进行exe的打包,也就是为exe创建一个纯净的包环境。

    ①安装pipenv

    pip install --user pipenv
    

    ②创建虚拟环境

    pipenv shell
    

    进入虚拟环境,操作环境就会变化

    ③查看虚拟环境下有哪些包

    pip list
    

    下面是已经安装过包的list

    ④安装所需要的包
    所需的包

    说明
    pyqt5 链接QT
    pyqt5-tools 链接QT
    pyserial 串口库
    matplotlib 绘图所需的matlib包
    pyinstaller 生成EXE所需的包
    py2exe 生成exe所需的包(关系到exe任务栏和左上角图标能否正常显示

    我是用的清华源比较快一点。

     pip install -i https://pypi.tuna.tsinghua.edu.cn/simple/ pyqt5
     ...
    

    ⑤执行打包代码

    虚拟环境下要安装pyinstaller 包,不然还是利用python默认的环境打包,打包出来的文件还是很大。

    pyinstaller -F --icon=Ucom.ico pyserial_demo.py -w
    

    ⑥完成

    在dist文件中可以找到生成的exe文件

    打开:

    3.3 功能演示

    功能展示

    ★★★如有错误,欢迎指导!!!

    物联沃分享整理
    物联沃-IOTWORD物联网 » 基于python+pyqt5的串口助手

    发表评论