超级好用的Python QT GUI串口调试助手


目录

前言

一、软件概要:

二、软件界面:

1.App动态演示

2.其他扩展展示

三、main.py源码:

1.PyQt5_Serial_Debug_Assistant_V1.0源码

四、获取 >> 源码以及Git记录:

总结


前言

        Python串口调试助手支持常用的50bps - 10Mbps波特率,能设置校验、数据位和停止位,能以ASCII码或十六进制接收或发送任何数据或字符,可以任意设定自动发送周期,并能将接收数据实时保存成文本文件,能发送任意大小的数据或字符。

备注: V1.0为简单Demo,适合初级用户使用,V1.1可用于工程应用英文名:PyQt5_Serial_Debug_Assistant_V1.1
支持:常用的50bps ~ 10Mbps波特率 类型:串口调试助手
软件大小:18M
软件版本:V1.1
软件下载&更新:百度网盘链接_提取码 6666

一、软件概要:

(默认按每小时分包保存日志,100M自动清空接收窗口,防止UI阻塞)。

二、软件界面:

1.App动态演示


2.其他扩展展示

Pyqt 串口demo pyqt5串口助手_Pyqt 串口demo

PyQt5_Serial_Debug_Assistant_V1.1

Pyqt 串口demo pyqt5串口助手_Pyqt 串口demo_02

 PyQt5_Serial_Debug_Assistant_V1.1_串口UI布局

Pyqt 串口demo pyqt5串口助手_python_03

PyQt5_Serial_Debug_Assistant_V1.0

三、main.py源码:

1.PyQt5_Serial_Debug_Assistant_V1.0源码

代码如下(示例):

import sys
import serial  # 导入模块  #安装: pip3 install pyserial
import serial.tools.list_ports
import webbrowser
import time
import datetime

# 导入Ui设计
from PyQt5.QtWidgets import QApplication, QMainWindow # 串口Ui文件
from PyQt5.QtWidgets import QMessageBox, QLabel, QFileDialog
from PyQt5.QtCore import QTimer
from PyQt5.QtGui import QIcon

import Serial_Ui_Designer  # 串口UI文件

# PyQt5程序打包
# 1.使用PyInstaller来打包:
# 安装:pip3 install PyInstaller
# 打包:pyinstaller -F -w -i=Com.ico main.py  #单文件打包-优缺点:只生成exe文件,但文件大,打开软件时加载时间长
# 打包:pyinstaller -D -w -i=Com.ico main.py  #多文件打包-优缺点:生成exe关联包,可删除无效库,文件小,运行顺畅

# 主窗口
class PyQt5_Serial(QMainWindow, Serial_Ui_Designer.Ui_MainWindow):
    # 初始化程序
    def __init__(self):
        super(PyQt5_Serial, self).__init__()
        self.setupUi(self)
        self.Init()
        self.Qt5_Ui_Init()

    # 初始化
    def Init(self):
        self.ser = serial.Serial()
        self.port_check()
        self.baudrateBox.setCurrentIndex(5)  # 921600-20 9600-5
        self.dataBitsBox.setCurrentIndex(3)   # 8

        # 设置Logo和标题
        self.setWindowIcon(QIcon('Com.ico'))
        self.setWindowTitle("PyQt5_串口调试助手_V1.0")
        #self.setWindowTitle("PyQt5_Serial_Debug_Assistant_V1.0")

        # 发送数据和接收数据数目置零
        self.data_num_sended = 0
        self.label_Tx.setText(str(self.data_num_sended))
        self.data_num_received = 0
        self.label_Rx.setText(str(self.data_num_received))

        # 串口关闭按钮使能关闭
        self.sendButton.setEnabled(0)
        self.checkBox_autoSend.setEnabled(0)

        # 发送框、文本框清除
        self.sendTextEdit.setText("")
        self.recvTextEdit.setText("")

    # 建立控件信号与槽关系
    def Qt5_Ui_Init(self):
        # 串口检测按钮
        self.pushButton_Refresh.clicked.connect(self.port_check)
        # 串口打开按钮
        self.pushButton_Open_Close.clicked.connect(self.port_open_close)
        # 定时发送数据
        self.timer_send = QTimer()
        self.timer_send.timeout.connect(self.data_send)
        self.checkBox_autoSend.stateChanged.connect(self.data_send_timer)
        # 发送数据按钮
        self.sendButton.clicked.connect(self.data_send)
        # 保存日志
        self.pushButton_saveLog.clicked.connect(self.savefiles)
        # 加载文件
        self.pushButton_openLog.clicked.connect(self.openfiles)
        # 跳转链接
        self.commandLinkButton.clicked.connect(self.link)
        # 清除发送按钮
        self.pushButton_ClearSend.clicked.connect(self.send_data_clear)
        # 清除接收按钮
        self.pushButton_ClearRecive.clicked.connect(self.receive_data_clear)
		# RTS
        self.checkBox_RTS.clicked.connect(self.rts_handle)
		# DTR
        self.checkBox_DTR.clicked.connect(self.dtr_handle)

    # 串口检测
    def port_check(self):
        # 检测所有存在的串口,将信息存储在字典中
        self.Com_Dict = {}
        port_list = list(serial.tools.list_ports.comports())

        self.portNameBox.clear()
        for port in port_list:
            self.Com_Dict["%s" % port[0]] = "%s" % port[1]
            self.portNameBox.addItem(port[0])

        # 无串口判断
        if len(self.Com_Dict) == 0:
            self.portNameBox.addItem("无串口")

    # 打开/关闭串口
    def port_open_close(self):
        if self.pushButton_Open_Close.text() == "打开串口":
            self.port_open()
        else:
            self.port_close()

    # 打开串口
    def port_open(self):
        port = self.portNameBox.currentText()
        # print("port:", port)

        baudrate = int(self.baudrateBox.currentText())
        # print("baudrate:", baudrate)

        bytesize = int(self.dataBitsBox.currentText()) # 数据位
        # print("bytesize:", bytesize)

        parity = self.ParityBox.currentText()  # 校验位
        # print("parity:", parity)

        stopbits = self.stopBitsBox.currentText() # 停止位
        # print("stopbits:", stopbits)

        flowctrl = self.flowControlBox.currentText()  # 流控

        self.ser.port = port
        self.ser.baudrate = baudrate

        # print("bytesize:", bytesize)
        if bytesize == 5:
            self.ser.bytesize = serial.FIVEBITS
        elif bytesize == 6:
            self.ser.bytesize = serial.SIXBITS
        elif bytesize == 7:
            self.ser.bytesize = serial.SEVENBITS
        elif bytesize == 8:
            self.ser.bytesize = serial.EIGHTBITS
        else:
            self.ser.bytesize = serial.EIGHTBITS

        # print("parity:", parity)
        if parity == "None":
            self.ser.parity = serial.PARITY_NONE
        elif parity == "Even":
            self.ser.parity = serial.PARITY_EVEN
        elif parity == "Odd":
            self.ser.parity = serial.PARITY_ODD
        elif parity == "Space":
            self.ser.parity = serial.PARITY_SPACE
        elif parity == "Mark":
            self.ser.parity = serial.PARITY_MARK
        else:
            self.ser.parity = serial.PARITY_NONE

        # print("stopbits:", stopbits)
        if stopbits == "1":
            self.ser.stopbits = serial.STOPBITS_ONE
        elif stopbits == "1.5":
            self.ser.parity = serial.STOPBITS_ONE_POINT_FIVE
        elif stopbits == "2":
            self.ser.parity = serial.STOPBITS_TWO
        else:
            self.ser.stopbits = serial.STOPBITS_ONE

        self.ser.xonxoff = False  # 软件流控
        self.ser.rtscts  = False  # 硬件流控 RTS
        self.ser.dsrdtr  = False  # 硬件流控 DTR
        # print("flowctrl:", flowctrl)
        if flowctrl == "OFF":
            self.ser.xonxoff = False  # 软件流控
            self.ser.rtscts  = False  # 硬件流控 RTS
            self.ser.dsrdtr  = False  # 硬件流控 DTR
        elif flowctrl == "Hardware":
            if self.checkBox_DTR.isChecked():
                self.ser.dsrdtr = True  #硬件流控 DTR
            if self.checkBox_RTS.isChecked():
                self.ser.rtscts = True  #硬件流控 RTS
        elif flowctrl == "Software":
            self.ser.xonxoff = True   # 软件流控
            self.ser.rtscts  = False  # 硬件流控 RTS
            self.ser.dsrdtr  = False  # 硬件流控 DTR

        # print(self.ser)
        # Serial < id = 0x4883040, open = False > (port='COM1', baudrate=9600, bytesize=8, parity='N', stopbits=1, timeout=None, xonxoff=False, rtscts=False, dsrdtr=False)

        try:
            self.ser.open()
        except:
            QMessageBox.critical(self, "串口异常", "串口打开失败! 错误: 拒绝访问(被占用).")
            return None

        # 串口打开后,切换开关串口按钮使能状态,防止失误操作
        if self.ser.isOpen():
            self.pushButton_Open_Close.setText("关闭串口")
			
            self.portNameBox.setEnabled(0)
            self.baudrateBox.setEnabled(0)
            self.dataBitsBox.setEnabled(0)
            self.ParityBox.setEnabled(0)
            self.stopBitsBox.setEnabled(0)
            self.flowControlBox.setEnabled(0)
						
            self.pushButton_Refresh.setEnabled(0)
            self.sendButton.setEnabled(1)
            self.checkBox_autoSend.setEnabled(1)

            # 定时器接收数据
            self.timer = QTimer()
            self.timer.timeout.connect(self.data_receive)
            # 打开串口接收定时器,周期为1ms
            self.timer.start(1)

    # 关闭串口
    def port_close(self):
        try:
            self.timer.stop()
            self.timer_send.stop()
            self.ser.close()
            self.pushButton_Open_Close.setText("打开串口")
			
            self.portNameBox.setEnabled(1)
            self.baudrateBox.setEnabled(1)
            self.dataBitsBox.setEnabled(1)
            self.ParityBox.setEnabled(1)
            self.stopBitsBox.setEnabled(1)
            self.flowControlBox.setEnabled(1)

            self.pushButton_Refresh.setEnabled(1)
            self.sendButton.setEnabled(0)
            self.checkBox_autoSend.setEnabled(0)
        except:
            QMessageBox.critical(self, '串口异常', '关闭串口失败,请重启程序!')
            return None

    # 定时发送数据
    def data_send_timer(self):
        try:
            if 1<= int(self.spinBox_timeDly.text()) <= 300000:  # 定时时间1ms~30s内
                if self.checkBox_autoSend.isChecked():
                    self.timer_send.start(int(self.spinBox_timeDly.text()))
                    self.spinBox_timeDly.setEnabled(False)
                else:
                    self.timer_send.stop()
                    self.spinBox_timeDly.setEnabled(True)
            else:
                QMessageBox.critical(self, '定时发送数据异常', '定时发送数据周期仅可设置在300秒内!')
        except:
            QMessageBox.critical(self, '定时发送数据异常', '请设置正确的数值类型!')

    # 发送数据
    def data_send(self):
        if self.ser.isOpen():
            input_s = self.sendTextEdit.toPlainText()

            # 判断是否为非空字符串
            if input_s != "":
                # 时间显示
                if self.checkBox_displayTime.isChecked():
                    if self.checkBox_displaySend.isChecked():
                        self.recvTextEdit.insertPlainText(self.get_datetime())

                # HEX发送
                if self.checkBox_hexSend.isChecked():
                    #input_s = input_s.strip() #strip() 方法用于移除字符串头尾指定的字符(默认为空格或换行符)或字符序列。注意:该方法只能删除开头或是结尾的字符,不能删除中间部分的字符。
                    input_s = input_s.replace(" ", "")
                    send_list = []
                    while input_s != '':
                        try:
                            num = int(input_s[0:2], 16) # 没有步长的简单切片
                            #print( hex(num) )
                        except ValueError:
                            QMessageBox.critical(self, '数据异常', '请输入规范的十六进制数据!')
                            return None

                        input_s = input_s[2:]
                        send_list.append(num)

                    if self.checkBox_CR_LF.isChecked():
                        send_list.append(0x0D)
                        send_list.append(0x0A)

                    input_s = bytes(send_list)
                # ASCII发送
                else:
                    if self.checkBox_CR_LF.isChecked():
                        input_s += '\r\n'

                    input_s = (input_s).encode('utf-8')

                # HEX接收显示
                if self.checkBox_hexReceive.isChecked():
                    out_s = ''
                    for i in range(0, len(input_s)):
                        out_s = out_s + '{:02X}'.format(input_s[i]) + ' '

                    if self.checkBox_displaySend.isChecked():
                        self.recvTextEdit.insertPlainText(out_s)
                # ASCII接收显示
                else:
                    if self.checkBox_displaySend.isChecked():
                        self.recvTextEdit.insertPlainText(input_s.decode('utf-8'))

                    # 接收换行
                if self.checkBox_AutoLineBreak.isChecked():
                    if self.checkBox_displaySend.isChecked():
                        self.recvTextEdit.insertPlainText('\r\n')

                # 获取到Text光标
                textCursor = self.recvTextEdit.textCursor()
                # 滚动到底部
                textCursor.movePosition(textCursor.End)
                # 设置光标到Text中去
                self.recvTextEdit.setTextCursor(textCursor)

                # 统计发送字符数量
                num = self.ser.write(input_s)
                self.data_num_sended += num
                self.label_Tx.setText(str(self.data_num_sended))
        else:
            pass

    # 接收数据
    def data_receive(self):
        try:
            num = self.ser.inWaiting()
            # if num > 0:
            #     time.sleep(0.1) #100ms
            #     num = self.ser.inWaiting()  # 延时,再读一次数据,确保数据完整性
        except:
            # QMessageBox.critical(self, '串口异常', '串口接收数据异常,请重新连接设备!')
            # self.port_close()
            return None

        if num > 0:
            data = self.ser.read(num)
            num = len(data)

            # HEX显示数据
            if self.checkBox_hexReceive.checkState():
                # 时间显示
                if self.checkBox_displayTime.isChecked():
                    self.recvTextEdit.insertPlainText(self.get_datetime())
                out_s = ''
                for i in range(0, len(data)):
                    out_s = out_s + '{:02X}'.format(data[i]) + ' '

                self.recvTextEdit.insertPlainText(out_s)
                # 接收换行
                if self.checkBox_AutoLineBreak.isChecked():
                    self.recvTextEdit.insertPlainText('\r\n')
            # ASCII显示数据
            else:
                try:
                    if self.checkBox_displayTime.isChecked():
                        displayStr = self.get_datetime()
                        displayStr += data.decode('utf-8',"ignore")
                        displayStr = displayStr.replace("\n", "\n" + self.get_datetime())
                        # 接收换行
                        if self.checkBox_AutoLineBreak.isChecked():
                            displayStr += "\r\n"  # 接收换行
                        self.recvTextEdit.insertPlainText(displayStr)
                    else:
                        self.recvTextEdit.insertPlainText(data.decode('utf-8',"ignore"))
                        # 接收换行
                        if self.checkBox_AutoLineBreak.isChecked():
                            self.recvTextEdit.insertPlainText('\r\n')
                except Exception as e:
                    print("接收数据异常,波特率错误,请重新配置!\n", e)

            # 获取到text光标
            textCursor = self.recvTextEdit.textCursor()
            # 滚动到底部
            textCursor.movePosition(textCursor.End)
            # 设置光标到text中去
            self.recvTextEdit.setTextCursor(textCursor)

            # 统计接收字符的数量
            self.data_num_received += num
            self.label_Rx.setText(str(self.data_num_received))
        else:
            pass

    # 保存日志
    def savefiles(self):
        dlg = QFileDialog()
        filename = self.portNameBox.currentText() + time.strftime("_%Y-%m-%d_%H_%M_%S", time.localtime())
        filenames = dlg.getSaveFileName(None, "保存日志文件", filename, "Txt files(*.txt)")

        try:
            with open(file = filenames[0], mode='w', encoding='utf-8') as file:
                file.write(self.recvTextEdit.toPlainText())
        except:
            #QMessageBox.critical(self, '日志异常', '保存日志文件失败!')
            pass
        
    # 加载日志
    def openfiles(self):
        dlg = QFileDialog()
        filenames = dlg.getOpenFileName(None, "加载日志文件", None, "Txt files(*.txt)")

        try:
            with open(file = filenames[0], mode='r', encoding='utf-8') as file:
                self.sendTextEdit.setPlainText(file.read())
        except:
            # QMessageBox.critical(self, '日志异常', '加载日志文件失败!')
            pass
				
    # 打开博客链接
    def link(self):
        webbrowser.open('')

    # 清除发送数据显示
    def send_data_clear(self):
        self.sendTextEdit.setText("")

        self.data_num_sended = 0
        self.label_Tx.setText(str(self.data_num_sended))

    # 清除接收数据显示
    def receive_data_clear(self):
        self.recvTextEdit.setText("")

        self.data_num_received = 0
        self.label_Rx.setText(str(self.data_num_received))

        self.data_num_sended = 0
        self.label_Tx.setText(str(self.data_num_sended))

    # 时间格式
    def get_datetime(self):
        time_now = datetime.datetime.now()
        # print(str(time_now)[:-3])
        time_now = "[" + str(time_now)[:-3] + "] "  # 转为字符串后切片
        return time_now

    # RTS
    def rts_handle(self):
        if self.ser.isOpen():
            if self.checkBox_RTS.isChecked():
                self.ser.setRTS(1)
            else:
                self.ser.setRTS(0)
    # DTR
    def dtr_handle(self):
        if self.ser.isOpen():
            if self.checkBox_DTR.isChecked():
                self.ser.setDTR(1)
            else:
                self.ser.setDTR(0)

# 主函数
def main():
    print("Hello, I'm PyQt5_Serial_Debug_Assistant_V1.0")
    # 1、创建QApplication类的实例对象
    app = QApplication(sys.argv)
    # 2、创建一个 PyQt5_Serial 实例对象
    myMainWindow = PyQt5_Serial()
    # 3、显示主窗口
    myMainWindow.show()
    # 4、进入程序的主循环、并通过exit函数确保主循环安全结束
    sys.exit(app.exec_())

if __name__ == '__main__':
    main()

四、获取 >> 源码以及Git记录:

PyQt5_Serial_Debug_Assistant_V1.0&V1.1