更新日志:
202210141442:优化功能(增加自动读取和单次读取可选)和界面美化。
202210141627:优化功能(添加菜单和读取优化,连接状态显示)
202210261446:优化功能(添加CRC校验工具)
python有很多编辑器,pycharm、vscode都可以,本例用的是vscode
系统平台:windows10
1、VS code:vscode是微软旗下的一款程序编写器,可以直接在其官网下载,免费,不过微软服务器下载有时候会很慢,可以搜索一下国内镜像下载,会快不少。
2、Pycharm:如果不想用vscode,可以用Pycharm,Pycharm是Jetbrain公司旗下的软件,也可以在其官网下载(有免费版,个人用足矣。)
1、PyQt5
2、serial、pyserial
安装方式直接使用pip就可以:
pip install PyQt5
其他类似
串口调试程序,我之前写过一个:
https://blog.csdn.net/normer123456/article/details/124402399
这次是在之前的基础上,增加了一下界面优化的内容和功能上优化。
先说界面优化:
界面优化使用了Qt提供的Qss样式:
qss格式大概是这样
可以看到,就是对窗口及其子控件进行美化,如字体、字体位置、背景色、等等,这是完全自定义的,自己调整到一个合适的界面配置即可,比如我自己设置的:
20221026新增:
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()