「Python」结合PyQt5和pyserial实现串口助手

「Python」结合PyQt5和PySerial实现串口助手

一、概要

1.主要模块介绍

PyQt5

PyQt5是一个用于创建 GUI应用程序的跨平台的工具包,它将 Python编程语言和Qt库成功融合在一起(Qt库是目前最强大的GUI库之一)。PyQt5可以运行在所有主流的操作系统上,包括UNIX、Windows和Mac OS。
特点:

  • 基于高性能的Qt的GUI控件集。
  • 能够跨平台运行在Windows、Linux和Mac OS等系统上。
  • 使用信号/槽(signal/slot)机制进行通信。
  • 对Qt库的完全封装。
  • 可以使用Qt成熟的IDE(如Qt Designer)进行图形界面设计,并自动生成可执行的Python代码。
  • 提供了一整套种类繁多的窗口控件。

pyqt5的类别分为几个模块,包括以下:
将会详细列出本案例所用到的模块和组件,下同

  • QtCore(包含了核心的非GUI功能。此模块用于处理时间、文件和目录、各种数据类型、流、URL、MIME类型、线程或进程。)
    • QDateTime(日期操作)、QTimer(定时器)、QRegExp(正则表达式类)
  • QtGui(包含类窗口系统集成、事件处理、二维图形、基本成像、字体和文本)
    • QIcon(图像)、QRegExpValidator(内置正则表达式检验器)、QColor(颜色)、QTextCursor(操作指针)
  • QtWidgets(包含创造经典桌面风格的用户界面提供了一套UI元素的类)
    • QWidget(基础窗口控件)、QApplication(控制流和主要设置)、QDesktopWidget(提供屏幕信息的访问)、QMessageBox(弹出式对话框)、QFileDialog(文件目录对话框)
    • QGroupBox(组合框)、QGridLayout(表格布局)、QVBoxLayout(垂直布局)、QHBoxLayout(水平布局)、QFormLayout(表单布局)
    • QLabel(标签)、QPushButton(按钮)、QComboBox(下拉列表)、QCheckBox(复选框)、QTextEdit(多行文本框)、QLineEdit(单行文本框)、QTextBrowser(富文本浏览器)
  • QtMultimedia、QtBluetooth、QtNetwork、QtPositioning、Enginio、QtWebSockets、QtWebKit、QtWebKitWidgets、QtXml、QtSvg、QtSql、QtTest

一些组件的基本用法
QLabel(标签)

方法名用途
setText()设置QLabel的文本内容
text()获得QLabel的文本内容

QPushButton(按钮)

方法名用途
setIcon()设置按钮上的图标
setEnabled()设置按钮是否可用 False不可用 点击不会发射信号
setText()设置按钮的显示文本
text()返回按钮的显示文本

QComboBox(下拉列表)

方法名用途
addItem()添加一个下拉选项
addItems()从列表中添加下拉选项
clear()删除下拉列表中所有的选项
currentText()返回选中选项的文本
currentIndex()返回选中项的索引
setCurrentText()设置当前选择的文本

QCheckBox(复选框)

方法用途
setChecked()设置复选框的状态,设置为True表示选中,False表示取消选中的复选框
setText()设置复选框的显示文本
text()返回复选框的显示文本
isChecked()检查复选框是否被选中

QLineEdit(单行文本框)

方法名用途
setText()设置文本框的文本内容
text()获得文本框的文本内容
clear()清除文本框内容

QTextEdit(多行文本框)

方法名用途
setPlainText()设置多行文本框的文本内容
toPlainText()返回文本框的文本内容
clear()清除文本框内容

QTextBrowser(富文本浏览器)

方法名用途
append()追加文本,添加新行
insertPlainText()添加什么就显示什么

QMessageBox(弹出式对话框)

方法名用途
information(QWidget parent, title, text, buttons, defaultButton)弹出消息对话框:QWidget parent指定的父窗口控件;title对话框标题;text度花开文本;buttons多个标准按钮,默认为OK按钮;defaultButton默认选中的标准按钮,默认是第一个标准按钮
question(QWidget parent, title, text, buttons, defaultButton)弹出问答对话框
warning(QWidget parent, title, text, buttons, defaultButton)弹出警告对话框
critical(QWidget parent, title, text, buttons, defaultButton)弹出严重错误对话框
about(QWidget parent, title, text, buttons, defaultButton)弹出关于对话框
setTitle()设置标题
setText()
setIcon()设置弹出对话框的图片

QTimer(定时器)

方法名用途
start(milliseconds)启动或重新启动定时器,时间间隔为毫秒,如果定时器已经运行,他将停止并重新启动,如果singleSlot信号为真,定时器仅被激活一次
stop()停止定时器

QFileDialog(文件目录对话框)

方法名用途
getOpenFileName()返回用户所选择文件的名称,并打开该文件
getSaveFileName()使用用户选择的文件名保存文件
setFileMode()可以选择的文件类型,枚举常量是:
QFileDialog.AnyFile:任何文件
QFileDialog.ExistingFile:已存在的文件
QFileDialog.Directory:文件目录
QFileDialog.ExistingFiles:已经存在的多个文件
setFilter()设置过滤器,只显示过滤器允许的文件类型

信号与槽
PyQt5有一个独一无二的信号和槽机制来处理事件。信号和槽用于对象之间的通信。当指定事件发生,一个事件信号会被发射。槽可以被任何Python脚本调用。当和槽连接的信号被发射时,槽会被调用。
PyQt中所有继承自QWidget的控件(这些都是QObject的子对象)都支持信号与槽机制。当信号发射时,连接的槽函数将会自动执行。在PyQt 5中信号与槽通过object.signal.connect()方法连接。
PyQt的窗口控件类中有很多内置信号,开发者也可以添加自定义信号。信号与槽具有如下特点。

  • 一个信号可以连接多个槽。
  • 一个信号可以连接另一个信号。
  • 信号参数可以是任何Python类型。
  • 一个槽可以监听多个信号。
  • 信号与槽的连接方式可以是同步连接,也可以是异步连接。
  • 信号与槽的连接可能会跨线程。
  • 信号可能会断开。

PySerial

PySerial模块封装了对串口的访问。
特性:

  • 为多平台提供了统一的接口。
  • 通过python属性访问串口设置。
  • 支持不同的字节大小、停止位、校验位和流控设置。
  • 可以有或者没有接收超时。
  • 类似文件的API,例如read和write,也支持readline等。

初始化及对象常用方法

ser = serial.Serial("com1",9600,timeout=0.5)   winsows系统使用com1口连接串行口
ser.isOpen()   查看端口是否被打开。
ser.open()   打开端口‘。
ser.close()   关闭端口。
ser.read()   从端口读字节数据。默认1个字节。
ser.read_all()   从端口接收全部数据。
ser.write("hello")   向端口写数据。
ser.readline()   读一行数据。
ser.readlines()   读多行数据。
in_waiting()   返回接收缓存中的字节数。

configparser

ConfigParser是python中一个用来读取配置文件的模块。该模块适用于配置文件的格式与windows ini文件类似,可以包含一个或多个节(section),每个节可以有多个参数(键=值)。
配置文件一般格式:

[db]
db_host = 127.0.0.1
db_port = 69
db_user = root
db_pass = root
host_port = 69

[concurrent]
thread = 10
processor = 20

括号“[ ]”内包含的为section。紧接着section 为类似于key-value的options的配置内容。

配置文件的一般使用:

使用ConfigParser 首选需要初始化实例,并读取配置文件:
import configparser
config = configparser.ConfigParser()

1、获取指定section下指定option的值
config.read("ini", encoding="utf-8")
r = config.get("db", "db_host")
print(r)  # 运行结果 127.0.0.1

2、添加section 和 option
if not config.has_section("default"):  # 检查是否存在section
    config.add_section("default")
if not config.has_option("default", "db_host"):  # 检查是否存在该option
    config.set("default", "db_host", "1.1.1.1")

3、写入文件
config.write(open("ini", "w"))

2.主要功能规划区分

串口助手主要有五个功能区域,串口设置、串口状态、单条发送、多条发送、接收区。

  • 串口设置:主要用于刷新/检测串口,显示串口名称,选择串口,设置串口的波特率、数据位、校验位、停止位等,打开和关闭串口,以及设置接收窗口的文字及背景颜色。
  • 串口状态:显示当前串口已接收和已发送的字节数,实时显示当前时间,以及串口助手版本号及作者姓名。
  • 单条发送:单条发送数据,单条循环发送,清除窗口及保存窗口,设置是否以HEX进行接收和发送。
  • 多条发送:多条发送数据,多条循环发送,清除接收窗口。
  • 接收区:实时动态显示接收到的数据。

其余具体的设计参照下图:
在这里插入图片描述

3.原型图设计

利用任意原型图设计网站设计原型图,可以根据自己的喜好调整。
在这里简单设计了一下串口助手大致的布局,大小等,方便一会儿创建界面。
在这里插入图片描述

二、界面实现

1.总体布局

在这里插入图片描述

通过设计原型图,大致将串口助手的五大功能区划分为了五个组合框/分组框GroupBox,组合框/分组框的好处在于每个分组框都自带标题和边框,方便区域划分,可以在每个组合框中设置想要的布局结构,各个组合框内部的分组结构互不影响,在一定程度上简化了整个界面的布局。
每个组合框按照表格布局进行排列。首先创建一个用于编写界面的类SerialUi类继承QWidget,在init方法中会调用unit_ui方法来进行初始化UI,在unit_ui方法中对整个窗口进行表格布局,设置窗口大小、图标、名称和位置:

在表格布局中要按照位置关系将每个组合框添加进去
set_serial_setting_groupbox()、set_serial_state_groupbox()、set_receive_groupbox()、set_mul_sent_groupbpx()、set_single_sent_groupbox()都是稍后会写的方法,均会返回一个QGroupBox对象

class SerialUi(QWidget):
    def __init__(self):
        super().__init__()
        # 初始化UI
        self.unit_ui()
    
    # 初始化UI
    def unit_ui(self):
        grid_layout = QGridLayout()  # 设置总体布局为表格布局 2行3列
        # addWidget用于添加组件
        grid_layout.addWidget(self.set_serial_setting_groupbox(), 0, 0)
        grid_layout.addWidget(self.set_serial_state_groupbox(), 1, 0)
        grid_layout.addWidget(self.set_receive_groupbox(), 0, 1)
        grid_layout.addWidget(self.set_mul_sent_groupbpx(), 0, 2)
        grid_layout.addWidget(self.set_single_sent_groupbox(), 1, 1, 1, 2)
        # 设置布局为grid_layout
        self.setLayout(grid_layout)
        # resize()方法调整窗口的大小
        self.resize(760, 450)
        # 设置窗口的图标
        self.setWindowIcon(QIcon('title_icon.png'))
        # 将窗口显示到中心
        self.center()
        # 设置窗口名
        self.setWindowTitle('串口助手')
        # 显示
        self.show()

将窗口显示到屏幕中间的方法:

# 控制窗口显示在屏幕中心的方法
def center(self):
    # 获得窗口
    qr = self.frameGeometry()
    # 获得屏幕中心点
    cp = QDesktopWidget().availableGeometry().center()
    # 显示到屏幕中心
    qr.moveCenter(cp)
    self.move(qr.topLeft())

2.串口设置区

在串口设置的界面实现中,根据需要,每一个下拉菜单或者按钮都有一个对应的标签,鉴于这种情况,可以使用表单布局(QFormLayout),QFormLayout是以表单的形式管理界面组件,其中表单中的标签和组件是相对应的关系:
在这里插入图片描述

首先创建串口设置区的分组框serial_setting_gb,并且将其中的布局管理器设置为表单布局:

# 串口设置 区
def set_serial_setting_groupbox(self):
    # 设置一个 串口设置 分组框
    serial_setting_gb = QGroupBox('串口设置')
    # 创建 串口设置 分组框内的布局管理器
    serial_setting_formlayout = QFormLayout()

创建“检测串口”按钮,addRow是添加表单项的方法,第一个参数为标签Label,第二个参数是组件Field:

# 检测串口 按钮
self.sset_btn_detect = QPushButton('检测串口')
serial_setting_formlayout.addRow('串口选择:', self.sset_btn_detect)

创建后“串口选择”标签,对应“检测串口”组件:

在这里插入图片描述

创建串口下拉菜单,将其添加到表单布局的第二行,没有标签则省略第一个参数,直接添加组件即可:

# 选择串口 下拉菜单
self.sset_cb_choose = QComboBox(serial_setting_gb)
# 添加一个下拉列表 由于没有标签 直接省略即可
serial_setting_formlayout.addRow(self.sset_cb_choose)

创建完成后:
在这里插入图片描述
创建“波特率”、“数据位”、“校验位”、“停止位”、“窗口配色”下拉菜单及对应的标签:

# 波特率 下拉菜单
self.sset_cb_baud = QComboBox(serial_setting_gb)
self.sset_cb_baud.addItems(['100', '300', '600', '1200', '2400', '4800', '9600', '14400', '19200',
'38400', '56000', '57600', '115200', '128000', '256000'])
serial_setting_formlayout.addRow('波特率:', self.sset_cb_baud)

# 数据位 下拉菜单
self.sset_cb_data = QComboBox(serial_setting_gb)
self.sset_cb_data.addItems(['8', '7', '6', '5'])
serial_setting_formlayout.addRow('数据位:', self.sset_cb_data)

# 校验位 下拉菜单
self.sset_cb_parity = QComboBox(serial_setting_gb)
self.sset_cb_parity.addItems(['N', 'E', 'O'])  # 校验位N-无校验,E-偶校验,O-奇校验
serial_setting_formlayout.addRow('校验位:', self.sset_cb_parity)

# 停止位 下拉菜单
self.sset_cb_stop = QComboBox(serial_setting_gb)
self.sset_cb_stop.addItems(['1', '1.5', '2'])
serial_setting_formlayout.addRow('停止位:', self.sset_cb_stop)

# 窗口配色 下拉菜单
self.sset_cb_color = QComboBox(serial_setting_gb)
self.sset_cb_color.addItems(['whiteblack', 'blackwhite', 'blackgreen'])
serial_setting_formlayout.addRow('窗口配色:', self.sset_cb_color)

创建后:
在这里插入图片描述

最后创建打开/关闭串口的按钮:

# 打开串口 按钮
self.sset_btn_open = QPushButton('打开串口')
self.sset_btn_open.setIcon(QIcon('open_button.png'))
self.sset_btn_open.setEnabled(False)
serial_setting_formlayout.addRow(self.sset_btn_open)

在这里插入图片描述

最后调整表单布局里每项之间的间距,将组合框serial_setting_gb的布局设置为表单布局serial_setting_formlayout:

serial_setting_formlayout.setSpacing(11)

serial_setting_gb.setLayout(serial_setting_formlayout)

最终串口设置组合框的效果如下:

在这里插入图片描述

3.串口状态区

串口状态区和之前的串口设置区类似,首先创建“串口状态”组合框,并设置布局为表单布局:
在这里插入图片描述

# 串口状态区
def set_serial_state_groupbox(self):
    self.serial_state_gb = QGroupBox('串口状态', self)
    serial_state_formlayout = QFormLayout()

创建“已发送”和“已接收”标签,初始显示值均为0:

# 已发送 标签
self.sent_count_num = 0
self.ssta_lb_sent = QLabel(str(self.sent_count_num))
serial_state_formlayout.addRow('已发送:', self.ssta_lb_sent)

# 已接收 标签
self.receive_count_num = 0
self.ssta_lb_receive = QLabel(str(self.receive_count_num))
serial_state_formlayout.addRow('已接收:', self.ssta_lb_receive)

在这里插入图片描述

添加当前时间标签,这里创建了一个timer定时器对象,用于动态显示时间,将它的timeout()信号连接到showtime函数上,然后调用其start()函数开启定时器:

# 当前时间 标签
self.ssta_lb_timer = QLabel(self)
timer = QTimer(self)
timer.timeout.connect(self.showtime)
timer.start()
serial_state_formlayout.addRow(self.ssta_lb_timer)

在showtime函数中,获取当前的系统时间并转化为适当的格式的字符串,将当前时间的标签设置为该字符串:

def showtime(self):
    time_display = QDateTime.currentDateTime().toString('yyyy-MM-dd hh:mm:ss dddd')
    self.ssta_lb_timer.setText(time_display)

在这里插入图片描述

添加版本及作者标签:

# 版本标签
ssta_lb_version = QLabel('version:V1.0.0')
serial_state_formlayout.addRow(ssta_lb_version)
ssta_lb_coder = QLabel('author:wong')
serial_state_formlayout.addRow(ssta_lb_coder)

最后设置行间距和布局:

serial_state_formlayout.setSpacing(13)
self.serial_state_gb.setLayout(serial_state_formlayout)

在这里插入图片描述

4.接收区

接收区的布局较之前的布局较为简单,只需使用垂直布局,在接收区组合框中添加一个富文本浏览器QTextBrowser即可:
在这里插入图片描述

# 接收区
def set_receive_groupbox(self):
    # 设置一个接收区分组框
    receive_gb = QGroupBox('接收区', self)
    # 添加显示接收日志的文本框
    self.receive_log_view = QTextBrowser(receive_gb)
    # 在这里设置了最小宽度 防止接收窗口因拉动伸缩而变换的过小
    self.receive_log_view.setMinimumWidth(350)  
    self.receive_log_view.append('Hello,欢迎使用串口助手!\n')
    # 设置布局并添加文本框
    vbox = QVBoxLayout()
    vbox.addWidget(self.receive_log_view)
    # 设置接收区分组框的布局
    receive_gb.setLayout(vbox)
    return receive_gb

在这里插入图片描述

5.单条发送区

单条发送区的结构较为复杂,在组合框中总体布局为水平布局,要在其中嵌套一个垂直布局和一个表格布局,垂直布局中添加单条发送文本框,表格布局中添加其余组件,之后将两种布局嵌套进入水平布局中:
在这里插入图片描述

# 单条发送区
def set_single_sent_groupbox(self):
    single_sent_gb = QGroupBox('单条发送', self)
    sins_overall_hlayout = QHBoxLayout(single_sent_gb)
    vlayout_1 = QVBoxLayout()
    glayout_1 = QGridLayout()

创建单条发送文本框,并添加到垂直布局中:

# 单条命令 文本框
self.sins_te_send = QTextEdit()
self.sins_te_send.setMinimumWidth(350)
vlayout_1.addWidget(self.sins_te_send)

创建循环发送复选框及输入文本框、HEX发送复选框、HEX接收复选框、清除发送、保存窗口、发送按钮,都添加在表格布局中:

# 循环发送 HEX发送 HEX接收 复选框
self.sins_cb_loop_send = QCheckBox('循环发送')
self.sins_le_loop_text = QLineEdit('1000')
reg = QRegExp('^(?!0)(?:[0-9]{1,6}|1000000)$')  # ^(?!0)(?:[0-9]{1,4}|10000)$
reg_validator = QRegExpValidator(reg)
self.sins_le_loop_text.setValidator(reg_validator)
sins_lb_loop_label = QLabel('ms/次')
self.sins_cb_hex_receive = QCheckBox('HEX接收')
self.sins_cb_hex_send = QCheckBox('HEX发送')

glayout_1.addWidget(self.sins_cb_loop_send, 0, 0)
glayout_1.addWidget(self.sins_le_loop_text, 0, 1)
glayout_1.addWidget(sins_lb_loop_label, 0, 2)
glayout_1.addWidget(self.sins_cb_hex_receive, 1, 0)
glayout_1.addWidget(self.sins_cb_hex_send, 2, 0)

# 保存窗口 清除发送 发送 按钮
self.sins_btn_save = QPushButton('保存窗口')
self.sins_btn_clear = QPushButton('清除发送')
self.sins_btn_send = QPushButton('发送')
glayout_1.addWidget(self.sins_btn_save, 1, 1, 1, 2)
glayout_1.addWidget(self.sins_btn_clear, 2, 1, 1, 2)
glayout_1.addWidget(self.sins_btn_send, 3, 0, 1, 3)

其中,在循环时间输入文本框中,使用了一个校验器来对输入的文字进行校验,文本框中可输入的有效时间范围为1-1000000的数字,如果不符合输入的范围,例如输入了汉字、字母、特殊符号等,则不显示,或者输入了超出范围的数字,例如0、负数、大于1000000的数字,也不予显示。校验用的是正则表达式:

reg = QRegExp('^(?!0)(?:[0-9]{1,6}|1000000)$')  # 创建一个正则表达式
reg_validator = QRegExpValidator(reg)  # QRegExpValidator类用于根据正则表达式检查字符串,创建一个根据reg正则表达式检查字符串的对象reg_validator
self.sins_le_loop_text.setValidator(reg_validator)  # 将文本框的输入限制设置为reg_validator校验器来进行校验

# 其中正则表达式解释如下
^(?!0)(?:[0-9]{1,6}|1000000)$
^   # 匹配字符串的开始
(?!0)  # 匹配不是0开头的任意数字  零宽度负预测先行断言(?!exp),断言此位置的后面不能匹配表达式exp
(?:[0-9]{1,6}|1000000)   # (?:exp) 匹配exp表达式但不获取结果   匹配 0-999999 或者 1000000 这些数字
[0-9]{1,6} 0-9  # 这几个数字可以出现至少1次 至多6次 
|  # 或者
$  # 匹配字符串的结束

最后将垂直布局和表格布局添加到水平布局中,再将组合框的布局设置为水平布局:

sins_overall_hlayout.addLayout(vlayout_1)
sins_overall_hlayout.addLayout(glayout_1)
single_sent_gb.setLayout(sins_overall_hlayout)

在这里插入图片描述

6.多条发送区

多条发送中的布局也较为复杂,多条发送组合框总体的布局方式为垂直布局,但是在其中要嵌套两个表格布局,第一个表格布局中放置“清除接收”按钮和“多条循环”复选框、输入文本框和标签,第二个表格布局中放置7组由复选框、单行文本框、按钮组成的多条发送界面:

在这里插入图片描述

# 多条发送区
def set_mul_sent_groupbpx(self):
    mul_send_gb = QGroupBox('多条发送', self)
    mul_send_vlayout = QVBoxLayout()
    mul_send_gridlayout1 = QGridLayout()
    mul_send_gridlayout2 = QGridLayout()

在第一个表格布局中添加“清除接收”和“多条循环”复选框及单行文本框和“ms”标签,其中时间单行输入文本框的输入限制校验器的设置同上:

# 清除接收 按钮
self.muls_btn_clear = QPushButton('清除接收')
mul_send_gridlayout1.addWidget(self.muls_btn_clear, 0, 0, 1, 3)

# 多条循环发送
self.mul_cb_loop_send = QCheckBox('多条循环')
mul_send_gridlayout1.addWidget(self.mul_cb_loop_send, 1, 0)
self.mul_le_loop_text = QLineEdit('1000')
reg = QRegExp('^(?!0)(?:[0-9]{1,6}|1000000)$')
reg_validator = QRegExpValidator(reg)
self.mul_le_loop_text.setValidator(reg_validator)
mul_send_gridlayout1.addWidget(self.mul_le_loop_text, 1, 1)
self.mul_lb_loop_lable = QLabel('ms/次')
mul_send_gridlayout1.addWidget(self.mul_lb_loop_lable, 1, 2)

在第二个表格布局中为了使代码简洁,利用两组for循环添加复选框、文本框和按钮,由于后续的使用需要,给每个复选框、文本框和按钮添加唯一的对象名称objectName,在之后功能实现的过程中如果需要利用这7组组件,则可以直接用findChild通过objectName来查找:

# 多条发送 区域
    self.mul_btn_list = []
    for i in range(1, 8):
        for j in range(3):
            if j == 0:
                self.checkbox = QCheckBox()
                self.checkbox.setObjectName('mul_cb_{}'.format(i))
                mul_send_gridlayout2.addWidget(self.checkbox, i, j)
            elif j == 1:
                self.textedit = QLineEdit()
                self.textedit.setObjectName('mul_le_{}'.format(i))
                mul_send_gridlayout2.addWidget(self.textedit, i, j)
            else:
                self.button = QPushButton(str(i))
                self.button.setFixedSize(25, 22)
                self.button.setObjectName('mul_btn_{}'.format(i))
                self.mul_btn_list.append(self.button.objectName())
                mul_send_gridlayout2.addWidget(self.button, i, j)

最后将两组表格布局嵌套进垂直布局中:

mul_send_vlayout.addLayout(mul_send_gridlayout1)
mul_send_vlayout.addLayout(mul_send_gridlayout2)
mul_send_gb.setLayout(mul_send_vlayout)
mul_send_gb.setFixedWidth(180)

在这里插入图片描述

最终完成效果:

在这里插入图片描述

三、功能实现

首先创建一个SerialAssistant类继承SerialUi,在这个类里实现串口助手所需的功能,在init方法里初始化串口通信的serial对象,初始化串口配置文件以及给各个信号绑定的槽函数,由于显示的调用了父类的init方法,所以在实例化的时候会自动生成UI界面,不用再次调用生成函数:

class SerialAssistant(SerialUi):
    def __init__(self):
        super().__init__()
        # 初始化serial对象 用于串口通信
        self.ser = serial.Serial()
        # 初始化串口配置文件
        self.serial_cfg()
        # 初始化串口 绑定槽
        self.unit_serial()

检测串口

首先在unit_serial()方法中给串口检测按钮点击clicked信号绑定槽函数port_detect,当点击按钮时就会调用port_detect方法:

# 初始化串口 给各个信号绑定槽
def unit_serial(self):
    # 串口检测按钮
    self.sset_btn_detect.clicked.connect(self.port_detect)

在port_detect方法中,调用serial.tools.list_ports.comports()接口可以返回计算机上所有的port口信息,将其储存在列表中并显示在下拉列表中。

返回的列表信息是ListPortInfo类的列表,存储类似于 [ [ COM1, xxxx ], [ COM2, yyyy ] ] 利用for循环读取每个串口的COM口数和串口名称,将其添加到下拉列表中,如果读取到的列表长度为0,则显示无串口:

# 串口检测
def port_detect(self):
    # 检测所有存在的串口 将信息存在字典中
    self.port_dict = {}
    # serial.tools.list_ports.comports()返回计算机上所有的port口信息
    # 将其存在列表中
    port_list = list(serial.tools.list_ports.comports())  
    # 清除下拉列表中已有的选项
    self.sset_cb_choose.clear()
    for port in port_list:
    	# 添加到字典里
        self.port_dict["%s" % port[0]] = "%s" % port[1]
        # 添加到下拉列表选项
        self.sset_cb_choose.addItem(port[0] + ':' + port[1])
    if len(self.port_dict) == 0:
        self.sset_cb_choose.addItem('无串口')
	self.sset_btn_open.setEnabled(True)

检测串口-无串口
在这里插入图片描述
检测串口-有串口
在这里插入图片描述

打开/关闭串口

首先在unit_serial()方法中给串口打开/关闭按钮点击clicked信号绑定槽函数port_open_close,当点击按钮时就会调用port_open_close方法:

# 初始化串口 给各个信号绑定槽
def unit_serial(self):
    # 打开/关闭串口 按钮
	self.sset_btn_open.clicked.connect(self.port_open_close)

在port_open_close方法中,对按钮文字和是否有串口进行判断,如果打开串口无异常且串口状态是打开,则改变按钮的文字和图标,将串口的波特率、校验位、数据位、停止位下拉列表设置为不可修改:

# 打开/关闭 串口
    def port_open_close(self):
    	# 按打开串口按钮 且 串口字典里有值 
        if (self.sset_btn_open.text() == '打开串口') and self.port_dict:
            self.ser.port = self.get_port_name()  # 设置端口
            self.ser.baudrate = int(self.sset_cb_baud.currentText())  # 波特率
            self.ser.bytesize = int(self.sset_cb_data.currentText())  # 数据位
            self.ser.parity = self.sset_cb_parity.currentText()  # 校验位
            self.ser.stopbits = int(self.sset_cb_stop.currentText())  # 停止位
            # 捕获 串口无法打开的异常
            try:
                self.ser.open()
            except serial.SerialException:
                QMessageBox.critical(self, 'Open Port Error', '此串口不能正常打开!')
                return None

            # 打开串口接收定时器 周期为2ms
            self.serial_receive_timer.start(2)

            # 判断 串口的打开状态
            if self.ser.isOpen():
                self.sset_btn_open.setText('关闭串口')
                self.sset_btn_open.setIcon(QIcon('close_button.png'))
                self.serial_state_gb.setTitle('串口状态(已开启)')
                self.set_setting_enable(False)

        # 按打开串口按钮 但 没有读取到串口
        elif (self.sset_btn_open.text() == '打开串口') and (self.sset_cb_choose.currentText() == '无串口'):
            QMessageBox.warning(self, 'Open Port Warning', '没有可打开的串口!')
            return None
        # 点击关闭串口按钮
        elif self.sset_btn_open.text() == '关闭串口':
            # 停止定时器
            self.serial_receive_timer.stop()
            try:
                self.ser.close()
            except:
                QMessageBox.critical(self, 'Open Port Error', '此串口不能正常关闭!')
                return None
            self.sset_btn_open.setText('打开串口')
            self.sset_btn_open.setIcon(QIcon('open_button.png'))
            self.serial_state_gb.setTitle('串口状态')
            self.set_setting_enable(True)

            # 更改已发送和已接收标签
            self.sent_count_num = 0
            self.ssta_lb_sent.setText(str(self.sent_count_num))
            self.receive_count_num = 0
            self.ssta_lb_receive.setText(str(self.receive_count_num))

获取端口号的方法:

# 获取端口号(串口选择界面想显示完全 但打开串口只需要串口号COMX)
def get_port_name(self):
	full_name = self.sset_cb_choose.currentText()
 	# rfind会找到:的位置
 	com_name = full_name[0:full_name.rfind(':')]
 	return com_name

使能的设置串口设置的可用与禁用:

# 设置 串口设置区 可用与禁用
def set_setting_enable(self, enable):
 	self.sset_cb_choose.setEnabled(enable)
 	self.sset_cb_baud.setEnabled(enable)
 	self.sset_cb_data.setEnabled(enable)
 	self.sset_cb_parity.setEnabled(enable)
 	self.sset_cb_stop.setEnabled(enable)

打开串口-有串口
在这里插入图片描述

打开串口-无串口
在这里插入图片描述

改变窗口颜色

根据窗口配色下拉列表中选择的选项的不同,改变窗口配色,在unit_serial()方法中给根据下拉列表文字改变的信号绑定槽函数change_color,当点选择不同的下拉列表选项时就会调用change_color方法:

def unit_serial(self):
    # 更改窗口颜色下拉菜单
    self.sset_cb_color.currentTextChanged.connect(self.change_color)

在change_color方法中通过if判断和设置styleSheet即可更改窗口颜色:
使用setStyleSheet可以设置图形界面的外观,setStyleSheet("QPushButton{color:red}") 设定前景颜色,就是字体颜色,setStyleSheet("QPushButton{background-color:yellow}")设定背景颜色为红色,setStyleSheet("QPushButton{color:rgb(255,255,255);background-color:black}")同时设定字体和背景颜色

# 改变窗口颜色
def change_color(self):
	if self.sset_cb_color.currentText() == 'whiteblack':
		self.receive_log_view.setStyleSheet("QTextEdit {color:black;background-color:white}")
	elif self.sset_cb_color.currentText() == 'blackwhite':
		self.receive_log_view.setStyleSheet("QTextEdit {color:white;background-color:black}")
	elif self.sset_cb_color.currentText() == 'blackgreen':
		self.receive_log_view.setStyleSheet("QTextEdit {color:rgb(0,255,0);background-color:black}")

改变窗口颜色
在这里插入图片描述

发送

分析发送的基本实现代码可知,单条发送和多条发送在基础实现上是差不多的,为了提高代码复用率,先定一个公用的发送函数,根据是否勾选了HEX发送来判断是否要转换为16进制发送:

# 发送
    def send_text(self, send_string):
        if self.ser.isOpen():
            # 非空字符串
            if send_string != '':
                # 如果勾选了HEX发送 则以HEX发送 String到Int再到Byte
                if self.sins_cb_hex_send.isChecked():
                    # 移除头尾的空格或换行符
                    send_string = send_string.strip()
                    sent_list = []
                    while send_string != '':
                        # 检查是否是16进制 如果不是则抛出异常
                        try:
                            # 将send_string前两个字符以16进制解析成整数
                            num = int(send_string[0:2], 16)
                        except ValueError:
                            QMessageBox.critical(self, 'Wrong Data', '请输入十六进制数据,以空格分开!')
                            self.sins_cb_hex_send.setChecked(False)
                            return None
                        else:
                            send_string = send_string[2:].strip()
                            # 将需要发送的字符串保存在sent_list里
                            sent_list.append(num)
                    # 转化为byte
                    single_sent_string = bytes(sent_list)
                # 否则ASCII发送
                else:
                    single_sent_string = (send_string + '\r\n').encode('utf-8')

                # 获得发送字节数
                sent_num = self.ser.write(single_sent_string)
                self.sent_count_num += sent_num
                self.ssta_lb_sent.setText(str(self.sent_count_num))

        else:
            QMessageBox.warning(self, 'Port Warning', '没有可用的串口,请先打开串口!')
            return None

单条发送

单条发送要通过点击“发送”按钮来触发,在unit_serial()方法中给“发送”按钮的点击信号绑定槽函数single_send,当点选择不同的下拉列表选项时就会调用single_send方法:

def unit_serial(self):
    # 单行发送数据 按钮
    self.sins_btn_send.clicked.connect(self.single_send)

单条发送就是从单条发送文本框中获取到需要发送的文本后调用send_text方法:

# 单条发送
    def single_send(self):
        # 获取已输入的字符串
        single_sent_string = self.sins_te_send.toPlainText()
        self.send_text(single_sent_string)

单条发送-ASCII
在这里插入图片描述

单条发送-HEX
在这里插入图片描述

多条发送

多条发送要根据所点击命令对应的按钮来触发,在unit_serial方法中根据唯一的objectname来循环给每个多条发送按钮绑定对应的槽函数multi_send_general:

def unit_serial(self):
    # 多行发送数据 按钮
    for i in range(1, 8):
        self.child_button = self.findChild(QPushButton, 'mul_btn_{}'.format(i))
        self.child_button.clicked.connect(self.multi_send_general)

在multi_send_general中首先要找到发送点击信号的发送者(Sender),判断是哪个按钮发送的信号,然后再获取按钮对应的文本框中的文本,之后调用send_text方法:

# 多行发送————普通 涉及sender 点击数字按钮发送对应的命令
def multi_send_general(self):
    # 获取到发送信号的按钮序号
    sent = self.sender().text()
    # 找到对应的文本框
    mul_te_sent = self.findChild(QLineEdit, 'mul_le_{}'.format(sent))
    # 获取内容
    multi_sent_string = mul_te_sent.text()
    self.send_text(multi_sent_string)

多条发送
在这里插入图片描述

接收数据

接收数据需要定时刷新串口接收缓存区,所以需要定义一个定时器,在打开串口后定时刷新self.serial_receive_timer.start(2),timeout代表计时结束后调用data_receive,也就是每2ms调用一次data_receive:

def unit_serial(self):
    # 定时器接受数据
    self.serial_receive_timer = QTimer(self)
    self.serial_receive_timer.timeout.connect(self.data_receive)

接收函数中获取接收缓存中的字节数后根据是否勾选HEX接收来判断如何显示接收数据:

# 接收数据
    def data_receive(self):
        try:
            # inWaiting():返回接收缓存中的字节数
            num = self.ser.inWaiting()
        except:
            pass
        else:
        	# 接收缓存中有数据
            if num > 0:
            	# 读取所有的字节数
                data = self.ser.read(num)
                receive_num = len(data)
                # HEX显示
                if self.sins_cb_hex_receive.isChecked():
                    receive_string = ''
                    for i in range(0, len(data)):
                        # {:X}16进制标准输出形式 02是2位对齐 左补0形式
                        receive_string = receive_string + '{:02X}'.format(data[i]) + ' '
                    self.receive_log_view.append(receive_string)
                    # 让滚动条随着接收一起移动
                    self.receive_log_view.moveCursor(QTextCursor.End)
                else:
                    self.receive_log_view.insertPlainText(data.decode('utf-8'))
                    self.receive_log_view.moveCursor(QTextCursor.End)

                # 更新已接收字节数
                self.receive_count_num += receive_num
                self.ssta_lb_receive.setText(str(self.receive_count_num))
            else:
                pass

循环发送

单条循环发送

单条循环发送需要创建一个定时器来充当循环的时钟,由定时器控制循环时长,根据循环发送复选框是否被勾选的状态信号链接到single_loop_send方法:

def unit_serial(self):
    # 循环发送数据——单条
    self.loop_single_send_timer = QTimer()
    self.loop_single_send_timer.timeout.connect(self.single_send)
    self.sins_cb_loop_send.stateChanged.connect(self.single_loop_send)

当单条循环复选框的勾选状态改变之后,调用single_loop_send方法后,进行相应的判断“单条输入文本框是否有内容”→“串口是否处于打开状态”→“如果单条循环复选框被勾选”→“打开定时器”:

# 循环发送——单条
    def single_loop_send(self):
        # 单挑输入框中有内容
        if self.sins_te_send.toPlainText():
            # 串口打开
            if self.ser.isOpen():
            	# 循环发送复选框被勾选
                if self.sins_cb_loop_send.isChecked():
                    # 循环时间文本框中输入了内容
                    if self.sins_le_loop_text.text():
                    	# 打开定时器 按输入的数字计时
                        self.loop_single_send_timer.start(int(self.sins_le_loop_text.text()))
                        # 将循环时间文本框和打开串口按钮设为不可选中
                        self.sins_le_loop_text.setEnabled(False)
                        
                        self.sset_btn_open.setEnabled(False)
                    else:
                        QMessageBox.warning(self, 'Value Error', '请输入1-1000000的值!')
                        self.sins_le_loop_text.setText('1000')
                        self.sins_cb_loop_send.setChecked(False)
                else:
                	# 停止定时器
                    self.loop_single_send_timer.stop()
                    self.sins_le_loop_text.setEnabled(True)
                    self.sset_btn_open.setEnabled(True)
            else:
                QMessageBox.warning(self, 'Port Warning', '没有可用的串口,请先打开串口!')
                self.sins_cb_loop_send.setChecked(False)

打开定时器后,定时器计时结束,会调用单行发送的方法single_send将数据发送出去:

# 单行发送
def single_send(self):
    # 获取已输入的字符串
    single_sent_string = self.sins_te_send.toPlainText()
    self.send_text(single_sent_string)

单条循环发送
在这里插入图片描述

多条循环发送(功能尚不完善)

多条循环发送与单纯的多条发送不一样,多条发送只需要判断发送信号的按钮是哪个,然后调用multi_send_general即可,但是多条循环发送需要根据是否勾选多条循环复选框,是否勾选多条发送输入框前的复选框,勾选的多条发送输入框是否由内容来判断能否实施发送操作。首先在unit_serial中给信号绑定槽函数,这里的逻辑可以理解为,多条循环复选框的勾选状态改变后将会调用mul_loop_send方法,在mul_loop_send方法中当判断的先决条件都成立时则打开start定时器loop_mul_sent_timer,定时器multi_send_special计时结束timeout后会调用multi_send_special方法发送多条数据,每当定时器multi_send_special计时结束后都会调用multi_send_special方法,以此来形成多条循环:

def unit_serial(self):
    # 循环发送数据——多条
    self.loop_mul_sent_timer = QTimer()
    self.loop_mul_sent_timer.timeout.connect(self.multi_send_special)
    self.mul_cb_loop_send.stateChanged.connect(self.mul_loop_send)

当勾选/取消勾选了多条输入复选框后,会进入mul_loop_send方法,在其中进行判断,然后打开定时器,定时时间与多条循环文本框中输入的一致:

	# 循环发送——多条
    def mul_loop_send(self):
        # 串口状态打开
        if self.ser.isOpen():
        	# 多条循环复选框被勾选
            if self.mul_cb_loop_send.isChecked():
                # 多条循环时间输入框由内容
                if self.mul_le_loop_text.text():
                	# 打开定时器
                    self.loop_mul_sent_timer.start(int(self.mul_le_loop_text.text()))
                    self.mul_le_loop_text.setEnabled(False)
                    self.sset_btn_open.setEnabled(False)
                else:
                    QMessageBox.warning(self, 'Value Error', '请输入1-1000000的值!')
                    self.mul_le_loop_text.setText('1000')
                    self.mul_cb_loop_send.setChecked(False)
            else:
                self.loop_mul_sent_timer.stop()
                self.mul_le_loop_text.setEnabled(True)
                self.sset_btn_open.setEnabled(True)
        else:
            QMessageBox.warning(self, 'Port Warning', '1没有可用的串口,请先打开串口!')
            self.mul_cb_loop_send.setChecked(False)

定时器loop_mul_sent_timer计时结束timeout后会调用multi_send_special方法,在其中获取到发送命令列表后利用send_text进行发送:

# 多行发送————特殊 不用点击按钮 根据勾选发送
    def multi_send_special(self):
    	# 将待发送列表中的每一条循环发送出去
        self.send_list = self.get_mul_send_list()
        for string in self.send_list:
            self.send_text(string)

其中get_mul_send_list方法用于获取一个多条发送命令的列表,里面包含了所有被勾选的命令:

# 获取一个多条发送列表 里面包含所有打钩的命令
def get_mul_send_list(self):
    self.mul_send_list = []
    for i in range(1, 8):
        mul_te_sent = self.findChild(QLineEdit, 'mul_le_{}'.format(i))
        multi_sent_string = mul_te_sent.text()
        mul_cb_check = self.findChild(QCheckBox, 'mul_cb_{}'.format(i))
        if multi_sent_string and mul_cb_check.isChecked():
            self.mul_send_list.append(multi_sent_string)
    return self.mul_send_list

多条循环
在这里插入图片描述
缺陷
目前多条循环发送,只能一次性同时发送多条,不能定时发送一条后再发送一条,这里仍需改进。

保存窗口

保存窗口用到了QFileDialog.getSaveFileName,这个方法不会帮你创建文件,只返回一个元组。元组第一项为你的文件路径(包括你给的文件名),第二项为该文件的类型。

# 保存窗口
def save_receive_to_file(self):
    file_name = QFileDialog.getSaveFileName(self, '保存窗口为txt文件','SaveWindow' +
                                     time.strftime('%Y_%m_%d_%H-%M-%S', time.localtime()) + '.txt')
    if file_name[1]:
        with open(file_name[0], 'w') as file:
            my_text = self.receive_log_view.toPlainText()
            file.write(my_text)
	else:
		pass

保存窗口

在这里插入图片描述

清除

清除接收

当“清除接收”按钮被点击之后,执行清除操作:

def unit_serial(self):
    # 清除接收按钮
    self.muls_btn_clear.clicked.connect(self.clear_receive)

清除窗口就是直接将接收文本框中的文本设置为空:

# 清除接收
def clear_receive(self):
    self.receive_log_view.setText('')

清除接收
在这里插入图片描述

清除发送

当“清除发送”按钮被点击之后,执行清除操作:

def unit_serial(self):
	# 清除发送按钮
	self.sins_btn_clear.clicked.connect(self.clear_send)

清除发送就是直接将发送文本框中的文本设置为空:

# 清除发送
def clear_send(self):
	self.sins_te_send.setText('')

清除发送
在这里插入图片描述

读写配置文件

有效利用配置文件可以帮助我们记忆串口设置和输入的命令,减少重复工作,提高串口助手的效率。

每次使用配置文件都要在SerialAssistant类中的init方法中对串口配置进行初始化,包括初始化一个用于保存串口配置信息的字典,一个用于保存多条命令的字典,一个用于保存单条命令的字典,获取当前的目录用于保存配置文件,以及配置文件所在的目录名和配置文件的名称,之后要初始化一个configparser对象用于后续的配置操作:

# 初始化串口配置 定义串口设置信息 保存和读取
    def serial_cfg(self):
        self.cfg_serial_dic = {}  # 用于保存串口设置信息的字典
        self.cfg_command_dic = {}
        self.cfg_single_dic = {}
        self.current_path = os.path.dirname(os.path.realpath(__file__))  # 当前目录
        self.cfg_path = ''  # 配置文件的路径
        self.cfg_dir = 'settings'  # 配置文件目录
        self.cfg_file_name = 'cfg.ini'  # 配置文件名
        self.conf_parse = configparser.ConfigParser()  # 配置文件解析ConfigParser对象

        # 读取串口配置
        self.read_cfg()
        # 将读取到的串口配置信息显示到界面上
        self.display_cfg()

读取串口配置的方法read_cfg是要查看配置文件和所需的section是否存在,如果不存在则需要新建目录setting和目录中的文件cfg.ini,如果文件cfg.ini已存在但其中所需要的的section不存在则需要新建section:

	# 读取串口配置————配置文件和section是否存在
    def read_cfg(self):
        self.cfg_path = os.path.join(self.current_path, self.cfg_dir, self.cfg_file_name)  # 获取配置文件路径 join用于连接两个或更多的路径
        # 判断读取配置文件是否正常 如果读取文件正常
        if self.conf_parse.read(self.cfg_path):
            # 判断读取section是否正常
            try:
                # 获取serial_setting section  返回一个配置字典
                serial_items = self.conf_parse.items('serial_setting')
                self.cfg_serial_dic = dict(serial_items)
                print(self.cfg_serial_dic)
            # 如果没有找到section
            except configparser.NoSectionError:
                self.conf_parse.add_section('serial_setting')  # 添加section
                self.conf_parse.write(open(self.cfg_path, 'w'))  # 保存到配置文件
            try:
                command_items = self.conf_parse.items('mul_sent_command')
                self.cfg_command_dic = dict(command_items)
                print(self.cfg_command_dic)
            except configparser.NoSectionError:
                self.conf_parse.add_section('mul_sent_command')
                self.conf_parse.write(open(self.cfg_path, 'w'))
            try:
                command_items = self.conf_parse.items('single_sent_command')
                self.cfg_single_dic = dict(command_items)
                print(self.cfg_single_dic)
            except configparser.NoSectionError:
                self.conf_parse.add_section('single_sent_command')
                self.conf_parse.write(open(self.cfg_path, 'w'))
        # 读取文件异常
        else:
            # 判断setting目录是否存在 不存在的话新建目录
            if not os.path.exists(os.path.join(self.current_path, self.cfg_dir)):
                os.mkdir(os.path.join(self.current_path, self.cfg_dir))
            self.conf_parse.add_section('serial_setting')  # 添加section
            self.conf_parse.add_section('mul_sent_command')
            self.conf_parse.add_section('single_sent_command')
            self.conf_parse.write(open(self.cfg_path, 'w'))  # 保存到配置文件

保存串口配置信息的方法save_cfg将每个需要保存的信息保存在配置文件中,供下一次启动时读取,每次在退出串口助手是调用保存方法,保存此次串口助手的配置:

	# 保存串口配置信息
    def save_cfg(self):
    	# 保存每一项到配置文件
        self.conf_parse.set('serial_setting', 'baudrate', str(self.ser.baudrate))
        self.conf_parse.set('serial_setting', 'data', str(self.ser.bytesize))
        self.conf_parse.set('serial_setting', 'stopbits', str(self.ser.stopbits))
        self.conf_parse.set('serial_setting', 'parity', self.ser.parity)

        for i in range(1, 8):
            self.conf_parse.set('mul_sent_command', 'command_{}'.format(i),
                                self.findChild(QLineEdit, 'mul_le_{}'.format(i)).text())

        self.conf_parse.set('single_sent_command', 'command', self.sins_te_send.toPlainText())
        self.conf_parse.write(open(self.cfg_path, 'w'))

每次启动串口助手时,需要读取串口配置信息然后将其显示在串口助手中对应的位置上:

	# 将读取到的串口配置信息显示到界面上
    def display_cfg(self):
        self.sset_cb_baud.setCurrentText(self.conf_parse.get('serial_setting', 'baudrate'))
        self.sset_cb_data.setCurrentText(self.conf_parse.get('serial_setting', 'data'))
        self.sset_cb_stop.setCurrentText(self.conf_parse.get('serial_setting', 'stopbits'))
        self.sset_cb_parity.setCurrentText(self.conf_parse.get('serial_setting', 'parity'))

        for i in range(1, 8):
            command = self.conf_parse.get('mul_sent_command', 'command_{}'.format(i))
            if command:
                self.findChild(QLineEdit, 'mul_le_{}'.format(i)).setText(command)

        self.sins_te_send.setText(self.conf_parse.get('single_sent_command', 'command'))

配置文件格式
在这里插入图片描述
结合配置文件启动程序
在这里插入图片描述

关闭窗口

关闭窗口需要重写closeEvent方法,在点击窗口右上角的×以后,发出询问对话框,询问是否确认退出,一旦选择是则保存当前串口的配置后退出串口助手,否则不做任何操作:

def closeEvent(self, event):
    reply = QMessageBox.question(self, 'Message', "确定要退出吗?",
                                 QMessageBox.Yes | QMessageBox.No, QMessageBox.No)
    # 判断返回值,如果点击的是Yes按钮,我们就关闭组件和应用,否则就忽略关闭事件
    if reply == QMessageBox.Yes:
        self.save_cfg()
        event.accept()
	else:
		event.ignore()

关闭窗口
在这里插入图片描述

main函数

在main函数中实例化应用程序和对象即可运行串口助手:

if __name__ == '__main__':
	# 每一PyQt5应用程序必须创建一个应用程序对象
    app = QApplication(sys.argv)
    # 创建一个SerialAssistant对象
    su = SerialAssistant()
    # 系统exit()方法确保应用程序干净的退出
	# exec_()方法的作用是“进入程序的主循环直到exit()被调用”,
	# 如果没有这个方法,运行的时候窗口会闪退
    sys.exit(app.exec_())

四、其他

依赖库版本

python——3.8.3
PyQt5——5.15.0
pyserial——3.5
configparser——5.0.1
以上模块除python外都可以通过pip install 模块名来下载

目录结构

在这里插入图片描述

  • 39
    点赞
  • 158
    收藏
    觉得还不错? 一键收藏
  • 20
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 20
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值