<Python>简易串口调试助手(优化版,功能和界面优化)

更新日志:
202210141442:优化功能(增加自动读取和单次读取可选)和界面美化。
202210141627:优化功能(添加菜单和读取优化,连接状态显示)
202210261446:优化功能(添加CRC校验工具)

Python简易串口调试助手

  • 1 软件配置
    • 1 使用软件
    • 2 安装方式
  • 2 需要的包
  • 3 程序

1 软件配置

1 使用软件

python有很多编辑器,pycharm、vscode都可以,本例用的是vscode
<Python>简易串口调试助手(优化版,功能和界面优化)_第1张图片
系统平台:windows10
<Python>简易串口调试助手(优化版,功能和界面优化)_第2张图片

2 安装方式

1、VS code:vscode是微软旗下的一款程序编写器,可以直接在其官网下载,免费,不过微软服务器下载有时候会很慢,可以搜索一下国内镜像下载,会快不少。
2、Pycharm:如果不想用vscode,可以用Pycharm,Pycharm是Jetbrain公司旗下的软件,也可以在其官网下载(有免费版,个人用足矣。)

2 需要的包

1、PyQt5
2、serial、pyserial
安装方式直接使用pip就可以:

pip install PyQt5

其他类似

3 程序

串口调试程序,我之前写过一个:
https://blog.csdn.net/normer123456/article/details/124402399
这次是在之前的基础上,增加了一下界面优化的内容和功能上优化。
先说界面优化:
界面优化使用了Qt提供的Qss样式:

qss格式大概是这样
<Python>简易串口调试助手(优化版,功能和界面优化)_第3张图片
可以看到,就是对窗口及其子控件进行美化,如字体、字体位置、背景色、等等,这是完全自定义的,自己调整到一个合适的界面配置即可,比如我自己设置的:
<Python>简易串口调试助手(优化版,功能和界面优化)_第4张图片
20221026新增:
<Python>简易串口调试助手(优化版,功能和界面优化)_第5张图片

qss文件调用:

 self.qss_style_file='E:\\100proworld2021\Python2022\style.qss'
 self.qss1=QssRead.readQSS(self.qss_style_file)

我这里是单独写了一个函数,用于读取qss文件,然后在串口的程序引用即可。

from qss_read import QssRead

qss_read.py内容如下:

class QssRead:
    @staticmethod
    def readQSS(style):
        with open(style, "r") as f:
            return f.read()

可以看到,就是读取qss文件的内容并返回。
调用完,还要设置主窗口的style:

self.setStyleSheet(self.qss1)

以下是完整程序:
20221026:增加crc校验工具函数

from asyncio.windows_events import NULL
from cmath import isclose
from ftplib import CRLF
from multiprocessing.connection import wait
from turtle import color
from numpy.lib.function_base import place
import serial
import serial.tools.list_ports
import sys
import re
import os
import threading
import datetime
import time
from PyQt5 import QtCore
from PyQt5.QtCore import *
from PyQt5.QtWidgets import *
from PyQt5.QtGui import *
from qss_read import QssRead

class new_win(QDialog):
    """新对话窗口"""
    def __init__(self,t):
        super().__init__()
        self.setWindowTitle(t)
        self.resize(400,200)
class new_win2(QWidget):
    """新窗口"""
    def __init__(self,t):
        super().__init__()
        self.tt=t
        self.initUI()
    def initUI(self):

        self.help_text=self.helpfile_read('S01_SerialDeviceComm_pro2022\helpfile_title.txt')
        self.label_1=QLabel(self)
        self.label_1.setGeometry(20,20,60,20)
        self.label_1.setText(self.help_text)
        self.label_1.setStyleSheet("background-color:yellow")
        self.label_1.adjustSize()

        self.help_text2=self.helpfile_read('S01_SerialDeviceComm_pro2022\help.txt')
        self.label_2=QLabel(self)
        self.label_2.setGeometry(20,60,60,20)
        self.label_2.setText(self.help_text2)
        self.label_2.setStyleSheet("background-color:aquamarine")
        self.label_2.adjustSize()

        self.setWindowTitle(self.tt)
        self.setWindowIcon(QIcon('S01_SerialDeviceComm_pro2022\cat.ico'))
        self.resize(600,200)

    def helpfile_read(self,f):
        with open(f,"r",encoding='utf-8') as f:
            text1=f.read()
            return text1

class crc_win(QWidget):
    """CRC窗口"""
    def __init__(self):
        super().__init__()
        self.initUI()
    def initUI(self):
        
        self.qss_style_file='S01_SerialDeviceComm_pro2022\qss_crc_win_style.qss'
        self.qss1=QssRead.readQSS(self.qss_style_file)
        
        self.btn_crc_cal=QPushButton('CRC计算',self)
        self.btn_crc_cal.setGeometry(100,60,100,20)
        self.btn_crc_cal.clicked.connect(self.crc_cal_func)
        
        self.label_crc_data=QLabel(self)
        self.label_crc_data.setGeometry(100,100,100,20)
        self.label_crc_data.setText('要校验的数据:')
        
        self.le_crc_data=QLineEdit(self)
        self.le_crc_data.setGeometry(200,100,300,20)
        self.le_crc_data.setText('')
        
        self.le_crc_result=QLineEdit(self)
        self.le_crc_result.setGeometry(520,100,80,20)
        self.le_crc_result.setText('')
        
        self.label_crc_resutl=QLabel(self)
        self.label_crc_resutl.setGeometry(520,130,100,20)
        self.label_crc_resutl.setText('CRC校验计算结果')
        
        self.le_crc_all=QLineEdit(self)
        self.le_crc_all.setGeometry(200,160,300,20)
        self.le_crc_all.setText('')
        
        self.label_crc_all=QLabel(self)
        self.label_crc_all.setGeometry(100,160,60,20)
        self.label_crc_all.setText('总的结果:')
        
        
        self.setWindowTitle('CRC校验计算工具')
        self.setWindowIcon(QIcon('S01_SerialDeviceComm_pro2022\cat.ico'))
        self.setStyleSheet(self.qss1)
        self.setGeometry(200,150,800,300)
        
    def crc_cal_func(self):
        """CRC计算函数"""
        #16进制元数据
        crc_source_data=self.le_crc_data.text()
        #去除空格
        crc_source_data_nospace=crc_source_data.replace(" ","")
        #print(crc_source_data_nospace)
        #每2个字符添加空格
        crc_source_data_spaceper2=re.sub(r"(?<=\w)(?=(?:\w\w)+$)", " ", crc_source_data_nospace)                         
        crc_source_data_list=crc_source_data_spaceper2.split(' ')      
        crc_datas=list(crc_source_data_list)
        #初始值
        crc16=0xFFFF
        #多项式
        poly=0xA001
        #计算过程
        for crc_temp_data in crc_datas:
            a=int(crc_temp_data,16)
            crc16=a ^ crc16         #异或
            for i in range(8):
                if 1 & crc16 ==1:
                    crc16 = crc16 >> 1
                    crc16=crc16 ^ poly
                else:
                    crc16=crc16 >> 1
        crc16=hex(int(crc16))
        crc16=crc16[2:].upper()
        length=len(crc16)
        crc16_high=crc16[0:length-2].zfill(2)
        crc16_high=str(crc16_high)
        crc16_low=crc16[length-2:length].zfill(2)
        crc16_low=str(crc16_low)
        #print(crc16_high,crc16_low)
        self.le_crc_result.setText(crc16_low + ' '+ crc16_high)
        self.le_crc_all.setText(crc_source_data_spaceper2+' '+crc16_low+' '+crc16_high)

class serial_win(QMainWindow):
    def __init__(self) -> None:
        super().__init__()
        self.initUI()
    def initUI(self):

        self.qss_style_file='S01_SerialDeviceComm_pro2022\qss_style.qss'
        self.qss1=QssRead.readQSS(self.qss_style_file)

        self.serial1=None

        self.read_data_flag=False

        self.port=''
        self.baud=0
        self.timeout=0.0

        self.send_data=bytes()
        self.read_data=''

        #添加菜单项
        self.menu1=self.menuBar()
        self.menu1.setGeometry(QtCore.QRect(0,0,100,40))
        self.menu1.setObjectName("menubar")
        #一级菜单
        self.file=self.menu1.addMenu('文件')
        #self.file_open=self.file.addMenu('打开')
        #self.file_exit=self.file.addMenu('退出')
        #二级菜单
        self.file_open=QAction('打开(&O)',self)
        self.file.addAction(self.file_open)
        self.file_exit=QAction('退出(&E)',self)
        self.file.addAction(self.file_exit)

        self.edit=self.menu1.addMenu('编辑')
        
        self.tool=self.menu1.addMenu('工具')
        self.tool_crc=QAction('CRC校验工具',self)
        self.tool.addAction(self.tool_crc)

        self.about=self.menu1.addMenu('关于')
        
        self.about_me=QAction('本程序(&M)',self)
        self.about.addAction(self.about_me)
        self.about_help=QAction('帮助&H',self)
        self.about.addAction(self.about_help)

        #为菜单添加响应
        self.file_open.triggered.connect(self.file_open_func)
        self.file_exit.triggered.connect(self.close)
        
        self.tool_crc.triggered.connect(self.crc_func)

        self.about_me.triggered.connect(self.about_me_show)
        self.about_help.triggered.connect(self.about_help_show)

        self.btn_plist=QPushButton('获取可用串口',self)          #新建按钮实例
        self.btn_plist.setGeometry(20,30,80,20)                 #按钮布局位置
        self.btn_plist.clicked.connect(self.get_serial_info)    #按钮链接函数
        #self.btn_plist.adjustSize()                             #按钮自适应文本

        self.btn_serial_init=QPushButton('串口初始化',self)
        self.btn_serial_init.setGeometry(20,60,80,20)
        self.btn_serial_init.clicked.connect(self.serial_init)
        #self.btn_serial_init.adjustSize()

        self.btn_serial_open=QPushButton('打开串口',self)
        self.btn_serial_open.setGeometry(20,90,80,20)
        self.btn_serial_open.clicked.connect(self.open_serial)
        #self.btn_serial_open.adjustSize()

        self.btn_serial_close=QPushButton('关闭串口',self)
        self.btn_serial_close.setGeometry(20,120,80,20)
        self.btn_serial_close.clicked.connect(self.close_serial)
        #self.btn_serial_close.adjustSize()

        self.btn_read_data=QPushButton('单次读取数据',self)
        self.btn_read_data.setGeometry(20,150,80,20)
        self.btn_read_data.clicked.connect(self.read_serial_data_th)
        #self.btn_read_data.adjustSize()
        #self.btn_read_data.setStyleSheet("background-color:red")

        self.btn_send_data=QPushButton('发送数据',self)
        self.btn_send_data.setGeometry(20,460,80,20)
        self.btn_send_data.clicked.connect(self.send_serial_data)
        #self.btn_send_data.adjustSize()

        self.btn_close_win=QPushButton('关闭窗口',self)
        self.btn_close_win.setGeometry(20,180,80,20)
        self.btn_close_win.clicked.connect(self.close)

        self.label_conn_status=QLabel(self)
        self.label_conn_status.setGeometry(140,60,60,20)
        self.label_conn_status.setText('连接状态')
        self.label_conn_status.setStyleSheet("background-color:gray;color:white")

        self.serial_port_set=QComboBox(self)
        self.serial_port_set.setGeometry(500,20,100,20)
        self.serial_port_set.addItems(['COM1'])

        self.label_port_set=QLabel(self)
        self.label_port_set.setGeometry(420,20,60,20)
        self.label_port_set.setText('串口号:')

        self.serial_baud_set=QComboBox(self)
        self.serial_baud_set.setGeometry(500,50,100,20)
        self.serial_baud_set.addItems(['9600','19200','38400','115200'])

        self.label_baud_set=QLabel(self)
        self.label_baud_set.setGeometry(420,50,60,20)
        self.label_baud_set.setText('波特率:')

        self.serial_stopbit_set=QComboBox(self)
        self.serial_stopbit_set.setGeometry(500,80,100,20)
        self.serial_stopbit_set.addItems(['0','1'])

        self.label_stopbit_set=QLabel(self)
        self.label_stopbit_set.setGeometry(420,80,60,20)
        self.label_stopbit_set.setText('停止位:')

        self.serial_parity_set=QComboBox(self)
        self.serial_parity_set.setGeometry(500,110,100,20)
        self.serial_parity_set.addItems(['无','奇校验','偶校验'])

        self.label_parity_set=QLabel(self)
        self.label_parity_set.setGeometry(420,110,60,20)
        self.label_parity_set.setText('校验位:')

        self.serial_databit_set=QComboBox(self)
        self.serial_databit_set.setGeometry(500,140,100,20)
        self.serial_databit_set.addItems(['8','7'])

        self.label_databit_set=QLabel(self)
        self.label_databit_set.setGeometry(420,140,60,20)
        self.label_databit_set.setText('数据位:')

        self.serial_timeout_set=QLineEdit(self)
        self.serial_timeout_set.setGeometry(500,170,100,20)
        self.serial_timeout_set.setText('1000')

        self.label_timeout_set=QLabel(self)
        self.label_timeout_set.setGeometry(420,170,60,20)
        self.label_timeout_set.setText('超时时间:')

        self.label_timeout_set2=QLabel(self)
        self.label_timeout_set2.setGeometry(610,170,60,20)
        self.label_timeout_set2.setText('ms')

        self.le_send_data=QLineEdit(self)                   #发送数据文本框
        self.le_send_data.setGeometry(120,460,700,20)
        self.le_send_data.setText('please input send_data here!')

        self.le_recv_data=QTextEdit(self)                                       #接收数据文本框
        self.le_recv_data.setGeometry(20,240,800,200)
        self.le_recv_data.setText('receive data in here!')
        
        self.label_recv_data=QLabel(self)
        self.label_recv_data.setGeometry(20,210,60,20)
        self.label_recv_data.setText('接收数据:')

        self.checkbox1=QCheckBox(self)
        self.checkbox1.setGeometry(120,210,70,20)
        self.checkbox1.setText('自动接收')
        #self.checkbox1.adjustSize()
        self.checkbox1.stateChanged.connect(self.cb1_check_status)

        self.label_auto_recdata_timeset=QLabel(self)
        self.label_auto_recdata_timeset.setGeometry(220,210,60,20)
        self.label_auto_recdata_timeset.setText('时间间隔:')

        self.le_auto_recdata_timeset=QLineEdit(self)
        self.le_auto_recdata_timeset.setGeometry(290,210,40,20)
        self.le_auto_recdata_timeset.setText('1000')
        #self.le_auto_recdata_timeset.adjustSize()

        self.label_auto_recdata_timeset_2=QLabel(self)
        self.label_auto_recdata_timeset_2.setGeometry(335,210,20,20)
        self.label_auto_recdata_timeset_2.setText('ms')

        self.label_recvdata_length=QLabel(self)
        self.label_recvdata_length.setGeometry(540,210,60,20)

        self.label_recvdata_length_2=QLabel(self)
        self.label_recvdata_length_2.setGeometry(485,210,60,20)
        self.label_recvdata_length_2.setText('字节数:')

        self.setGeometry(100,100,1000,600)
        self.setWindowIcon(QIcon('S01_SerialDeviceComm_pro2022\cat.ico'))     #为窗口添加ICON
        self.setWindowTitle('串口调试助手')
        self.setStyleSheet(self.qss1)
        self.show()

    def new_win_func(self):
        self.c_win=new_win()
        self.c_win.show()
        self.c_win.exec_()
    

    def file_open_func(self):
        """打开按钮"""
        path,_=QFileDialog.getOpenFileName(self,'open file','','txt file(*.txt)')
        if path:
            try:
                with open(path,"r",encoding='utf-8') as f:
                    text=f.read()
                    print(text)
            except Exception as e:
                QMessageBox.warning(self,'提示!',str(e),QMessageBox.Ok | QMessageBox.Cancel,QMessageBox.Ok)
    def crc_func(self):
        """CRC校验"""
        self.crc_win=crc_win()
        self.crc_win.show()
     
        
    def about_me_show(self):
        """本程序按钮"""
        QMessageBox.information(self,'关于本程序','本程序是用于实现与PLC等设备进行串口通讯!')

    def about_help_show(self):
        """帮助按钮"""
        self.help_window=new_win2("帮助?")
        self.help_window.show()

    def cb1_check_status(self):     
        if QAbstractButton.isChecked(self.checkbox1):       #判断复选框是否选中
            self.read_data_flag=True
            self.btn_read_data.setEnabled(False)
            self.btn_read_data.setStyleSheet("background-color:gray")
            self.read_serial_data_th()
        else:
            self.read_data_flag=False
            self.btn_read_data.setEnabled(True)
            self.btn_read_data.setStyleSheet("background-color:rgb(250, 250, 252)")

    
    def get_serial_info(self):
        """用于获取设备串口信息"""
        self.plist=list(serial.tools.list_ports.comports())
        if len(self.plist) <=0:
            print('无串口可用')
            qm=QMessageBox.warning(self,'提示','未找到串口,请检查接线')
            if qm == QMessageBox.Yes:
                print('Yes')
            else:
                print('no')
        else:
            for i in list(self.plist):
                self.serial_port_set.clear()
                self.serial_port_set.addItem(i.name)
                self.serial_port_set.currentText=i.name
    def serial_init(self):
        """用于串口初始化"""
        self.port=self.serial_port_set.currentText
        self.baud=int(self.serial_baud_set.currentText())
        self.timeout=float(self.serial_timeout_set.text())
        try:
            self.serial1=serial.Serial(port=self.port,baudrate=self.baud,bytesize=8,parity='N',stopbits=1)
            print(self.serial1)
            if self.serial1.isOpen: 
                self.label_conn_status.setText('连接成功')
                self.label_conn_status.setStyleSheet("background-color:lime")                             
                print('串口正常')
        except Exception as e:
            QMessageBox.warning(self,'提示!',str(e),QMessageBox.Ok | QMessageBox.Cancel,QMessageBox.Ok)
            print('异常:',e)
    def open_serial(self):
        """打开串口"""
        try:
            self.serial1.open()
        except Exception as e:
            QMessageBox.warning(self,'提示!',str(e),QMessageBox.Ok | QMessageBox.Cancel,QMessageBox.Ok)
            print('异常:',e)
    def close_serial(self):
        """关闭串口"""
        try:
            if self.serial1.isOpen:
                self.read_data_flag=False           
                self.serial1.close()
                if self.serial1.isOpen()==False:
                    QMessageBox.warning(self,'提示!','串口已关闭',QMessageBox.Ok | QMessageBox.Cancel,QMessageBox.Ok)
                    self.label_conn_status.setText('连接断开')
                    self.label_conn_status.setStyleSheet("background-color:gray")
            else:
                exit
        except Exception as e:
            QMessageBox.warning(self,'提示!',str(e),QMessageBox.Ok | QMessageBox.Cancel,QMessageBox.Ok)
            print('异常:',e)

    def read_serial_data_auto(self):
        """自动读取串口数据"""
        print('开始接收数据:')
        self.time_jiange=float(self.le_auto_recdata_timeset.text()) / 1000.0
        while self.read_data_flag:
            ct=datetime.datetime.now()
            ct_str=ct.strftime("%Y-%m-%d %H:%M:%S")
            try:           
                if self.serial1.isOpen:       
                    self.len=self.serial1.in_waiting
                    self.label_recvdata_length.setText(str(self.len))
                    #print(self.len)         
                    self.read_data=self.serial1.read(size=self.len)
                    self.read_data_str=self.read_data.hex()
                    self.read_data_str_fg=self.str_fen_ge(self.read_data_str)
                    self.le_recv_data.append('\n'+'['+ct_str+']'+' '+self.read_data_str_fg+'\n')
                    time.sleep(self.time_jiange)
                else:
                    break

            except Exception as e:
                QMessageBox.warning(self,'提示!',str(e),QMessageBox.Ok | QMessageBox.Cancel,QMessageBox.Ok)
                print('异常:',e)
                break
    def read_serial_data_manual(self):
        """手动获取串口数据"""
        print('开始接收数据:')
        ct2=datetime.datetime.now()
        ct_str2=ct2.strftime("%Y-%m-%d %H:%M:%S")
        try:           
                if self.serial1.isOpen:   
                    self.len2=self.serial1.in_waiting    
                    self.label_recvdata_length.setText(str(self.len2))         
                    self.read_data=self.serial1.read(size=self.len2)
                    self.read_data_str=self.read_data.hex()
                    self.read_data_str_fg=self.str_fen_ge(self.read_data_str)
                    self.le_recv_data.append('\n'+'['+ct_str2+']'+' '+self.read_data_str_fg+'\n')
                else:
                    print('串口未打开')
        except Exception as e:
                QMessageBox.warning(self,'提示!',str(e),QMessageBox.Ok | QMessageBox.Cancel,QMessageBox.Ok)
                print('异常:',e)


    def read_serial_data_th(self):
        #新建线程用于接收串口数据
        if self.serial1 is not NULL:
            if self.serial1.isOpen() == True:
                if self.read_data_flag:
                    self.th1=threading.Thread(name='t1',target=self.read_serial_data_auto)
                    self.th1.start()
                else:
                    self.read_serial_data_manual()
            else:
                QMessageBox.information(self,'提示','无串口可用!')

    def send_serial_data(self):
        """发送串口数据"""
        current_dt=datetime.datetime.now()                          #获取系统时间
        current_dt_str=current_dt.strftime("%Y-%m-%d %H:%M:%S")     #格式化时间

        try:
            self.send_data_str=self.le_send_data.text()
            self.send_data=bytes.fromhex(self.send_data_str)
            self.serial1.write(self.send_data)
        except Exception as e:
            QMessageBox.warning(self,'提示!',str(e),QMessageBox.Ok | QMessageBox.Cancel,QMessageBox.Ok)
            print('异常:',e)

    def str_fen_ge(self,a):
        """对字符进行分隔"""
        b=re.findall(r'.{2}',a)
        c=' '.join(b)
        return str(c)



if __name__ == '__main__':

    app=QApplication(sys.argv)
    ex=serial_win()
    
    sys.exit(app.exec_())    


主窗口样式文件:
qss_style.qss:

/*
*
{
color:red;
}
*/
QWidget
{
    background-color: rgb(142, 221, 235);
}
QPushButton
{
text-align:center;
color:rgb(14, 13, 13);
background-color:rgb(250, 250, 252);
}
QLabel
{
text-align: center;
font:bold;
font-size:13px;
background-color:rgba(228, 236, 228, 0);
color:black;

}
QComboBox
{
text-align: center;
background-color: rgb(248, 249, 250);
}
QLineEdit
{
    background-color: rgb(250, 250, 250);
}
QTextEdit
{
    background-color:antiquewhite;
}

crc窗口样式文件:
qss_crc_win_style.qss:

/*
*
{
color:red;
}
*/
QWidget
{
    background-color: #ebc7c7;
}
QPushButton
{
text-align:center;
color:rgb(14, 13, 13);
background-color:rgb(250, 250, 252);
}
QLabel
{
text-align: center;
font:lighter;
font-size:13px;
background-color:rgba(228, 236, 228, 0);
color:black;

}
QCheckBox
{
    text-align: center;
}
QComboBox
{
text-align: center;
background-color: rgb(248, 249, 250);
}
QLineEdit
{
    background-color: rgb(250, 250, 250);
}
QTextEdit
{
    background-color:antiquewhite;
}
QMessageBox
{   
    icon:'CoDeSys.ico';
    background-color: aquamarine;
}

样式文件的读取模块:
qss_read.py:

class QssRead:
    @staticmethod
    def readQSS(style):
        with open(style, "r") as f:
            return f.read()

你可能感兴趣的:(python,工业自动化编程,网络通信,python,开发语言,qt)