UI界面如上图,停止位和校验没用到,可能也有些地方写得不够严谨,欢迎指正
"""
# @项目名:serialTest
# @文件名: A06_Serial_pyqt5
# @开发语言:python3.7.4
# @编译器:pycharm
# @创建者: zhouyuezhen
# @时间: 2022/04/03
# @描述:todo
"""
import binascii
import random
import sys
import threading
from time import sleep
import serial.tools.list_ports
from PyQt5.QtCore import QObject, pyqtSignal
from PyQt5.QtGui import QTextCursor, QIcon
from PyQt5.QtWidgets import QMainWindow, QPushButton, QApplication, QLineEdit, QTextBrowser, \
QHBoxLayout, QWidget, QFormLayout, QComboBox, \
QLabel, QMessageBox, QCheckBox, QTextEdit, QRadioButton, QButtonGroup, QGroupBox
class Stream(QObject):
newText = pyqtSignal(str)
def write(self, testdata):
self.newText.emit(str(testdata))
class Serial(QMainWindow):
def __init__(self):
super().__init__()
"""加载界面InitUI QWidget_UI Left_Window_UI 更新打印信息"""
self.InitUI()
self.QWidget_UI()
self.InitStyle()
sys.stdout = Stream(newText=self.onUpdateText)
def onUpdateText(self, text):
"""获取process(阅读器)文本光标对象textCursor(),往里插入文本数据text"""
cursor = self.process_output.textCursor()
cursor.insertText(text)
"""自动滚屏"""
self.process_output.moveCursor(QTextCursor.End)
def InitStyle(self):
self.setStyleSheet("""
QComboBox{
background-color:#fff;
color:#571601;
font-size:13px;
font-weight:700;
}
QPushButton{
background-color:#ddd;
color:black
}
QPushButton:hover{
background-color:#ccc;
color:#571601;
font-size:13px;
font-weight:700;
}
QPushButton:pressed{
background-color:rgba(255, 103, 0, .7);
}
QRadioButton:hover{
color:orange;
}
""")
def closeEvent(self, event):
"""关闭时关闭应用程序"""
sys.stdout = sys.__stdout__
super().closeEvent(event)
def InitUI(self):
# todo 主界面
self.resize(700, 500)
self.setWindowTitle("串口调试助手1.0.0")
self.setWindowIcon(QIcon("favicon1.ico"))
# todo 菜单栏设置
self.menuBar = self.menuBar()
self.menuBar.addMenu("文件")
self.menuBar.addMenu("说明")
"""添加一个中央组件:QWidget()"""
self.window = QWidget()
self.setCentralWidget(self.window)
"""版本信息"""
self.devicetype = QLineEdit('', self)
self.devicetype.hide()
# todo 状态栏设置
self.status_Bar = self.statusBar()
self.serialState = QLabel(self)
self.countSend = QLabel("发送:0", self)
self.countRead = QLabel("接收:0", self)
self.status_Bar.addPermanentWidget(self.serialState, 4)
self.status_Bar.addPermanentWidget(self.countSend, 1)
self.status_Bar.addPermanentWidget(self.countRead, 1)
self.status_Bar.setStyleSheet("QLabel{background-color:#FFFFFF;}")
def QWidget_UI(self):
"""中央组件QWidget的UI界面内有2个部件,为水平布局"""
self.left_window = QWidget()
self.right_window = QWidget()
windowLayout = QHBoxLayout()
windowLayout.addWidget(self.left_window, 2)
windowLayout.addWidget(self.right_window, 3)
self.left_window_UI()
self.right_window_UI()
""" 中央组件加载布局windowLayout """
self.window.setLayout(windowLayout)
""" 视图-浏览器背景颜色设置"""
def left_window_UI(self):
"""左边QWingt【快捷方式】布局:网格"""
Form_layout = QFormLayout()
self.bt_getSerial = QPushButton("获取电脑串口", self)
self.bt_getSerial.clicked.connect(self.getSerial)
'''打开串口'''
self.bt_openSerial = QPushButton('打开串口', self)
self.bt_openSerial.clicked.connect(self.openSerial)
QHboxLayout0 = QHBoxLayout()
QHboxLayout0.addWidget(self.bt_getSerial)
QHboxLayout0.addWidget(self.bt_openSerial)
Form_layout.addRow(QHboxLayout0)
'''串口'''
self.serialName = QLabel("串 口:", self)
# 串口内容(下拉框)
self.serialContent = QComboBox(self)
QHBoxLayout1 = QHBoxLayout()
QHBoxLayout1.addWidget(self.serialName, 1)
QHBoxLayout1.addWidget(self.serialContent, 3)
Form_layout.addRow(QHBoxLayout1)
'''波特率'''
self.serialBaud = QLabel("波特率:", self)
# 串口波特率内容(下拉框)
self.serialBaudContent = QComboBox(self)
self.serialBaudContent.addItem("115200")
self.serialBaudContent.addItem("57600")
self.serialBaudContent.addItem("38400")
self.serialBaudContent.addItem("19200")
self.serialBaudContent.addItem("9600")
QHBoxLayout2 = QHBoxLayout()
QHBoxLayout2.addWidget(self.serialBaud, 1)
QHBoxLayout2.addWidget(self.serialBaudContent, 3)
Form_layout.addRow(QHBoxLayout2)
'''数据位'''
self.serialBits = QLabel("数据位:", self)
# 数据位内容
self.serialBitsContent = QComboBox(self)
self.serialBitsContent.addItem("8")
QHBoxLayout3 = QHBoxLayout()
QHBoxLayout3.addWidget(self.serialBits, 1)
QHBoxLayout3.addWidget(self.serialBitsContent, 3)
Form_layout.addRow(QHBoxLayout3)
'''校验位'''
self.serialVerify = QLabel("校验位:", self)
# 校验位内容
self.serialVerifyContent = QComboBox(self)
self.serialVerifyContent.addItem("无")
QHBoxLayout4 = QHBoxLayout()
QHBoxLayout4.addWidget(self.serialVerify, 1)
QHBoxLayout4.addWidget(self.serialVerifyContent, 3)
Form_layout.addRow(QHBoxLayout4)
'''停止位'''
self.serialStopBit = QLabel("停止位:", self)
# 停止位内容
self.serialStopBitContent = QComboBox(self)
self.serialStopBitContent.addItem("1")
self.serialStopBitContent.addItem("0")
QHBoxLayout5 = QHBoxLayout()
QHBoxLayout5.addWidget(self.serialStopBit, 1)
QHBoxLayout5.addWidget(self.serialStopBitContent, 3)
Form_layout.addRow(QHBoxLayout5)
'''发送框'''
self.serialSend_date = QTextEdit(self)
QHBoxLayout6 = QHBoxLayout()
QHBoxLayout6.addWidget(self.serialSend_date)
Form_layout.addRow(QHBoxLayout6)
'''生成按钮,发送按钮'''
self.bt_ramDomDate = QPushButton('生成数据', self)
self.lenDate = QLineEdit("128", self) # 内容长度
self.bt_ramDomDate.clicked.connect(self.ramdomDate)
self.clearAll = QPushButton('清除所有', self)
self.clearAll.clicked.connect(self.Clear)
QHBoxLayout7 = QHBoxLayout()
QHBoxLayout7.addWidget(self.bt_ramDomDate, 3)
QHBoxLayout7.addWidget(self.lenDate, 1)
QHBoxLayout7.addWidget(self.clearAll, 3)
Form_layout.addRow(QHBoxLayout7)
"""自动重发"""
# 勾选框
self.QcheckRetry = QCheckBox(self)
self.TxQlabel1 = QLabel('自动重发', self)
self.Tx_time = QLineEdit('200', self)
self.TxQlabel2 = QLabel('ms', self)
QHBoxLayout8 = QHBoxLayout()
QHBoxLayout8.addWidget(self.QcheckRetry)
QHBoxLayout8.addWidget(self.TxQlabel1)
QHBoxLayout8.addWidget(self.Tx_time)
QHBoxLayout8.addWidget(self.TxQlabel2)
Form_layout.addRow(QHBoxLayout8)
# 读取方式:文本模式,Hex模式
self.sendFileShow = QRadioButton("文本模式", self)
self.sendHexShow = QRadioButton("Hex模式", self)
self.sendHexShow.setChecked(True)
"""发送"""
self.bt_SendDate = QPushButton('发送', self)
self.bt_SendDate.clicked.connect(self.sendDate)
self.bg1 = QButtonGroup(self)
self.bg1.addButton(self.sendFileShow, 11)
self.bg1.addButton(self.sendHexShow, 12)
QHBoxLayout9 = QHBoxLayout()
QHBoxLayout9.addWidget(self.sendFileShow)
QHBoxLayout9.addWidget(self.sendHexShow)
QHBoxLayout9.addWidget(self.bt_SendDate)
Form_layout.addRow(QHBoxLayout9)
groupBox = QGroupBox('发送设置', self)
groupBox.setLayout(Form_layout)
Form_layout_All = QFormLayout()
Form_layout_All.addRow(groupBox)
#
self.left_window.setLayout(Form_layout_All)
def right_window_UI(self):
groupBox = QGroupBox('接收模式', self)
# 网格布局
QForm_Layout = QFormLayout()
# 输出阅读框
self.process_output = QTextBrowser(self)
# 水平布局1
QHBox_Layout1 = QHBoxLayout()
QHBox_Layout1.addWidget(self.process_output)
QForm_Layout.addRow(QHBox_Layout1)
# 读取方式:文本模式,Hex模式
self.readFileShow = QRadioButton("文本模式", self)
self.readHexShow = QRadioButton("Hex模式", self)
self.readHexShow.setChecked(True)
# 按钮【清空接收】
self.readClear = QPushButton("清空接收区", self)
self.readClear.clicked.connect(self.readclear)
QHBox_Layout2 = QHBoxLayout()
QHBox_Layout2.addWidget(self.readFileShow, 22)
QHBox_Layout2.addWidget(self.readHexShow, 23)
QHBox_Layout2.addWidget(self.readClear)
# QForm_Layout.addRow(QHBox_Layout2)
groupBox.setLayout(QHBox_Layout2)
QForm_Layout.addWidget(groupBox)
self.right_window.setLayout(QForm_Layout)
# 开启一个检测勾选框的子线程
self.flag = 0
threading.Thread(target=self.CheckboxSendDate, daemon=True).start()
threading.Thread(target=self.getSerial, daemon=True).start()
# 清理数据
def Clear(self):
self.serialSend_date.clear()
self.process_output.clear()
self.countRead.setText('接收:0')
self.countSend.setText('发送:0')
def readclear(self):
self.process_output.clear()
# 获取串口
def getSerial(self):
self.serialContent.clear()
try:
port_list = list(serial.tools.list_ports.comports())
if len(port_list) <= 0:
pass
else:
for ser in list(port_list):
self.serialContent.addItem(ser[0])
except Exception as e:
self.serialState.setText("%s" % e)
# 打开串口
def openSerial(self):
serialPort = self.serialContent.currentText() # 当前串口
serialBaud = self.serialBaudContent.currentText() # 当前波特率
serialBits = self.serialBitsContent.currentText() # 当前数据位
serialVerify = self.serialVerifyContent.currentText() # 当前校验位
serialStopBit = self.serialStopBitContent.currentText() # 当前停止位
if serialPort:
if self.bt_openSerial.text() == "打开串口":
try:
self.ser = serial.Serial(serialPort, serialBaud, timeout=0.01)
self.bt_openSerial.setText("串口已打开")
self.serialState.setText("%s %s 打开成功 " % (serialPort, serialBaud))
# 开启读取结果的子线程
threading.Thread(target=self.readDate, daemon=True).start()
except Exception as e: # serial.serialutil.SerialException
self.bt_openSerial.setText("打开串口")
self.serialState.setText("%s 打开失败!%s " % (serialPort, e))
QMessageBox.critical(self, "错误", "%s 打开失败!请确认串口是否已打开?" % serialPort, QMessageBox.Ok)
else:
self.ser.close()
self.bt_openSerial.setText("打开串口")
self.serialState.setText("%s %s 关闭成功 " % (serialPort, serialBaud))
else:
# 如果串口为空则提示错误弹框
QMessageBox.critical(self, "错误", "请确认是否已获取串口?", QMessageBox.Ok)
# 随机生成内容
def ramdomDate(self):
Str = self.lenDate.text()
# print(lenStr)
listlen = []
# 返回需要获取的数据长度:list形式[20,20,20,20,....],因为长度超过Strlist(里面的内容为32-255的16进制)里的内容会导致报错
def getLenlist(len):
# 返回需要获取的数据长度list
while True:
if len > 20:
len = len - 20
listlen.append(20)
elif len <= 20:
listlen.append(len)
break
# 返回一个字符串list,里面的内容为32-255的16进制
mStrlist = []
for n in range(256):
if 32 <= n:
mStrlist.append(("%x" % n).upper())
return listlen, mStrlist
# 判断Str是不是数字
date = ''
if Str.isdigit():
if 0 < int(Str) <= 10000:
# 返回数据长度list,字符串list
lenStr, Strlist = getLenlist(int(Str))
for n in lenStr:
# 随机获取Strlist里面的内容,长度为n,并拼接在一起
date = "".join(random.sample(Strlist, n)) + date
# 把获取到的内容放到self.serialSend_date框内
self.serialSend_date.setText(date)
else:
QMessageBox.critical(self, "错误", "请输入0-10000之间的整数", QMessageBox.Ok)
else:
QMessageBox.critical(self, "错误", "请确认输入的数据长度是否为整数", QMessageBox.Ok)
# 检测勾选框
def CheckboxSendDate(self):
while True:
sleepTime = self.Tx_time.text()
if self.QcheckRetry.isChecked():
if self.serialSend_date.toPlainText() != '':
if self.bt_openSerial.text() == "串口已打开":
# 开启一条发送数据的子线程
threading.Thread(target=self.sendDate, daemon=True).start()
if sleepTime.isdigit():
sleep(float(sleepTime) / 1000)
# else:
# sleep(0.01)
# 检测格式读取数据
def readDate(self):
while True:
if self.bt_openSerial.text() == "打开串口":
break
else:
try:
# 十六进制读取串口数据
if self.readHexShow.isChecked():
date = self.ser.readline().hex()
if date:
# 打印数据,类型为大写
print(date.upper())
self.countSendOrRead(self.countRead, date)
else:
# 字符串形式读取串口数据
date = self.ser.readline().decode(encoding='utf-8', errors='ignore')
if date:
print(date)
self.countSendOrRead(self.countRead, date)
except:
pass
# 检测格式并发送数据
def sendDate(self):
if self.sendHexShow.isChecked():
self.sendHexDate()
else:
self.sendStrDate()
# 字符串形式发送
def sendStrDate(self):
if self.serialSend_date.toPlainText() == '':
QMessageBox.critical(self, "错误", "发送内容为空!", QMessageBox.Ok)
return
if self.bt_openSerial.text() == "打开串口":
QMessageBox.critical(self, "错误", "请先打开串口!", QMessageBox.Ok)
return
else:
def runStrSend():
string = self.serialSend_date.toPlainText()
# 把内容当字符串发送
self.ser.write(bytes(string, encoding='utf-8'))
self.countSendOrRead(self.countSend, string)
threading.Thread(target=runStrSend).start()
# Hex形式发送
def sendHexDate(self):
if self.serialSend_date.toPlainText() == '':
QMessageBox.critical(self, "错误", "发送内容为空!", QMessageBox.Ok)
return
if self.bt_openSerial.text() == "打开串口":
QMessageBox.critical(self, "错误", "请先打开串口!", QMessageBox.Ok)
return
else:
string = self.serialSend_date.toPlainText()
# 检查长度,长度不够就补充(需要为偶数)
def replenish(s):
listS = []
for n in range(len(s)):
if n % 2 == 0:
listS.append(s[n] + s[n + 1])
else:
s = s + "0"
# 判断 listS的内容是否全是16进制
try:
bytes.fromhex("".join(listS))
except:
# 如果报错则遍历listS的内容,一个个检查
for n in range(len(listS)):
# print(listS[n])
# 如果单个listS的内容报错,则替换成字符串“00”
try:
bytes.fromhex(listS[n])
except:
listS[n] = listS[n].replace(listS[n], "00")
return "".join(listS)
def runHexSend(string):
string = replenish(string)
self.ser.write(bytes.fromhex(string))
self.countSendOrRead(self.countSend, string)
threading.Thread(target=runHexSend, args=(string,)).start()
# Hex形式显示
def readHexShow(self):
try:
while True:
str_line = self.ser.in_waiting
if str_line:
data = str(binascii.b2a_hex(self.ser.read(str_line)))[2:-1]
self.countSendOrRead(self.countRead, data)
print(data)
if self.bt_openSerial.text() == "打开串口":
break
sleep(0.01)
except:
pass
# 字符串形式显示
def readStrShow(self):
try:
while True:
str_line = self.ser.readlines()
if str_line:
print(str_line)
self.countSendOrRead(self.countRead, str_line)
if self.bt_openSerial.text() == "打开串口":
break
sleep(0.01)
except:
pass
# 计算输入或者输出的数量
def countSendOrRead(self, state, str):
read = state.text()
countRead = int(read.split(":")[1])
flag = self.sendHexShow.isChecked()
newCount = int(len(str) / 2 + countRead) if flag else int(len(str) + countRead)
self.countSend.setText(f"发送:{newCount}") if "发送" in read else self.countRead.setText(f"接收:{newCount}")
if __name__ == '__main__':
"""运行应用程序"""
app = QApplication(sys.argv)
gui = Serial() # 主程序入口
gui.show() # 显示主界面
sys.exit(app.exec_())