# -*- coding: utf-8 -*- """ Created on Mon May 27 14:31:38 2024 @author: WANGXIBAO """ import sys import serial,re import serial.tools.list_ports import time,datetime from PyQt5 import QtWidgets from PyQt5.QtWidgets import QMessageBox ,QFileDialog,QInputDialog from PyQt5.QtCore import QTimer from PyUartUi import Ui_UartAssistant from PyQt5.QtGui import QIcon class PyQt5Serial(QtWidgets.QWidget,Ui_UartAssistant): # %%初始化程序 def __init__(self): super(PyQt5Serial, self).__init__() self.setupUi(self) self.init() self.ser = serial.Serial() #创建一个空对象 self.port_check() # 设置Logo和标题 self.setWindowIcon(QIcon('./favicon.ico')) self.setWindowTitle("调试助手") # 设置禁止拉伸窗口大小 #self.setFixedSize(self.width(), self.height()) # 发送数据和接收数据数目置零 self.data_num_sended = 0 self.lineEditSendNum.setText(str(self.data_num_sended)) self.data_num_received = 0 self.lineEditReceiveNum.setText(str(self.data_num_received)) # 串口关闭按钮使能关闭 self.pushButtonCloseSerial.setEnabled(False) # 发送框、文本框清除 self.textEditReceive.setText("") self.textEditSend.setText("") # %%第二个TAB 初始化画图页面表格 # 加载Qchart波形界面 #加载快捷指令 self.widget_6.hide() #加载快捷指令的按键值 # 用于暂存接收的串口数据 self.buffer = b'' # 用于暂存解码数据 self.lineUtf8 = "" # 用于标志是否开始存CSV self.CsvFlag = 0 # ============================================================================= # def wheelEvent(self, event): # if self.plot_view.underMouse: # # 鼠标滚轮:缩放Qchart波形 # if event.angleDelta().y() >= 0: # # 鼠标滚轮向上 # if event.x() < ( # self.plot_view.width() + self.plot_view.x()) and event.x() > self.plot_view.x(): # if event.y() < ( # self.plot_view.height() + self.plot_view.y()) and event.y() > self.plot_view.y(): # self.plot_qchart.zoomIn() # else: # # 鼠标滚轮向下 # if event.x() < ( # self.plot_view.width() + self.plot_view.x()) and event.x() > self.plot_view.x(): # if event.y() < ( # self.plot_view.height() + self.plot_view.y()) and event.y() > self.plot_view.y(): # self.plot_qchart.zoomOut() # ============================================================================= #%% 重写关闭按钮 def closeEvent(self, event): self.update_data_thread.stop() # 可选:加入超时判断,防止线程未能正常退出导致程序无法关闭 if not self.update_data_thread.wait(1000): # 等待5秒 print("线程未在指定时间内退出,可能需要进一步处理") # 如果串口已经打开,则关闭串口 if self.ser.is_open: self.port_close() #关闭界面前保存快捷区域的命令和名称 for i in range(20): # 假设有20个按钮 button_name = f'Button{i:02}' # 格式化按钮名称,确保两位数 set_text = getattr(self, f'lineEditQuick_{i+1}').text()+ "|" + getattr(self, f'pushButtonQuick_{i+1}').text() self.get_data_tf.SetCfgIniData(button_name, set_text) # 调用父类的关闭事件处理函数 super().closeEvent(event) # %%建立控件信号与槽关系 def init(self): # 串口检测按钮 self.pushButtonTestSerial.clicked.connect(self.port_check) # 串口打开按钮 self.pushButtonOpenSerial.clicked.connect(self.port_open) # 串口关闭按钮 self.pushButtonCloseSerial.clicked.connect(self.port_close) # 定时发送数据 self.timer_send = QTimer() self.timer_send.timeout.connect(self.data_send) self.checkBoxReapitSend.stateChanged.connect(self.data_send_timer) # 发送数据按钮 self.pushButtonSend.clicked.connect(lambda:self.data_send(text_quick= None)) # 保存日志 self.pushButtonLogSave.clicked.connect(self.savefiles) # 加载日志 self.pushButtonLogLoad.clicked.connect(self.openfiles) # 跳转链接 #self.commandLinkButton1.clicked.connect(self.link) # 清除发送按钮 self.pushButtonClearSend.clicked.connect(self.send_data_clear) # 清除接收按钮 self.pushButtonClearReceive.clicked.connect(self.receive_data_clear) # 快捷指令扩展区域 self.pushButton_expend.clicked.connect(self.adjust_sidebar) # 创建一个通用的槽函数来处理所有按钮 # 例如,使用lambda表达式传递额外的参数 for i in range(1, 21): # 假设有20个按钮 # getattr(self, f'pushButtonQuick_{i}').clicked.connect( # lambda checked, line_edit=f'lineEditQuick_{i}': self.onPushButtonQuickClicked(line_edit) # ) # 连接左键点击事件 button = getattr(self, f'pushButtonQuick_{i}') line_edit = f'lineEditQuick_{i}' button.clicked.connect(lambda checked, line_edit=line_edit: self.onPushButtonQuickClicked(line_edit)) # 连接右键点击事件 # button_id = f'pushButtonQuick_{i}' # button.setContextMenuPolicy(Qt.CustomContextMenu) # button.customContextMenuRequested.connect(lambda position, button_id=button_id: self.onButtonRightClicked(button_id)) # %% 串口检测 def port_check(self): # 检测所有存在的串口,将信息存储在字典中 self.Com_Dict = {} port_list = list(serial.tools.list_ports.comports()) self.comboBoxSerial.clear() for port in port_list: self.Com_Dict["%s" % port[0]] = "%s" % port[1] self.comboBoxSerial.addItem(port[0]) # 无串口判断 if len(self.Com_Dict) == 0: self.comboBoxSerial.addItem("无串口") # %%打开串口 def port_open(self): self.ser.port = self.comboBoxSerial.currentText() # 串口号 self.ser.baudrate = int(self.comboBoxBaudrate.currentText()) # 波特率 self.ser.timeout = 0.01 flag_data = int(self.comboBoxDataBits.currentText()) # 数据位 if flag_data == 5: self.ser.bytesize = serial.FIVEBITS elif flag_data == 6: self.ser.bytesize = serial.SIXBITS elif flag_data == 7: self.ser.bytesize = serial.SEVENBITS else: self.ser.bytesize = serial.EIGHTBITS flag_data = self.comboBoxCheckBit.currentText() # 校验位 if flag_data == "None": self.ser.parity = serial.PARITY_NONE elif flag_data == "Odd": self.ser.parity = serial.PARITY_ODD else: self.ser.parity = serial.PARITY_EVEN flag_data = int(self.comboBoxStopBit.currentText()) # 停止位 if flag_data == 1: self.ser.stopbits = serial.STOPBITS_ONE else: self.ser.stopbits = serial.STOPBITS_TWO flag_data = self.comboBoxFlow.currentText() # 流控 if flag_data == "No Ctrl Flow": self.ser.xonxoff = False #软件流控 self.ser.dsrdtr = False #硬件流控 DTR self.ser.rtscts = False #硬件流控 RTS elif flag_data == "SW Ctrl Flow": self.ser.xonxoff = True #软件流控 else: if self.checkBoxDTR.isChecked(): self.ser.dsrdtr = True #硬件流控 DTR if self.checkBoxRTS.isChecked(): self.ser.rtscts = True #硬件流控 RTS try: time.sleep(0.1) if self.ser.is_open: self.ser.close() self.ser.open() except: QMessageBox.critical(self, "串口异常", "此串口不能被打开!") return None # 串口打开后,切换开关串口按钮使能状态,防止失误操作 if self.ser.isOpen(): self.pushButtonOpenSerial.setEnabled(False) self.pushButtonCloseSerial.setEnabled(True) self.comboBoxBaudrate.setEnabled(False) self.comboBoxSerial.setEnabled(False) self.pushButton_expend.setEnabled(True) #self.formGroupBox1.setTitle("串口状态(开启)") #日志保存 # 格式化日期时间字符串,用于文件名 # 例如:2024-05-28_12-34-56.txt self.file = self.ser.port+"-"+time.strftime("%Y%m%d%H%M%S", time.localtime()) self.filename = self.file + ".txt" # 定时器接收数据 self.timer = QTimer() self.timer.timeout.connect(self.data_receive) # 打开串口接收定时器,周期为1ms self.timer.start(20) # %%接收数据 def data_receive(self): try: num = self.ser.inWaiting() if num>0 : #print("接收数据",datetime.datetime.now().strftime("%Y-%m-%d %H:%M:%S.%f")) try: data = self.ser.read(num) except: QMessageBox.critical(self, '串口异常') self.buffer+=data #print("接收完成",datetime.datetime.now().strftime("%Y-%m-%d %H:%M:%S.%f")) if self.checkBoxAddDate.isChecked(): nowTime = datetime.datetime.now().strftime("%Y-%m-%d %H:%M:%S.%f") nowTime = nowTime[:-3] self.textEditReceive.insertPlainText(nowTime + " ") # HEX显示数据 if self.checkBoxHexReceive.checkState(): out_s = '' for i in range(0, len(data)): out_s = out_s + '{:02X}'.format(data[i]) + ' ' self.textEditReceive.insertPlainText(out_s) # ASCII显示数据 else: #print("解码前",datetime.datetime.now().strftime("%Y-%m-%d %H:%M:%S.%f")) self.textEditReceive.insertPlainText(data.decode('utf-8',errors='replace')) #print("解码数据",datetime.datetime.now().strftime("%Y-%m-%d %H:%M:%S.%f")) # 接收换行 if self.checkBoxCRLF.isChecked(): self.textEditReceive.insertPlainText('\r\n') # 获取到text光标 textCursor = self.textEditReceive.textCursor() # 滚动到底部 textCursor.movePosition(textCursor.End) # 设置光标到text中去 self.textEditReceive.setTextCursor(textCursor) # 统计接收字符的数量 self.data_num_received += num self.lineEditReceiveNum.setText(str(self.data_num_received)) # 自动保存日志 if self.checkBoxAutoSaveLog.isChecked(): self.AutoSaveLog() except: # QMessageBox.critical(self, '串口异常', '串口接收数据异常,请重新连接设备!') # 获取到text光标 textCursor = self.textEditReceive.textCursor() # 滚动到底部 textCursor.movePosition(textCursor.End) # 设置光标到text中去 self.textEditReceive.setTextCursor(textCursor) self.textEditReceive.insertPlainText("串口断开,重连中...\r\n") self.ser.close() try: print("重连中...") time.sleep(1) if not self.ser.is_open: self.ser.open() print("重连成功") else: print("串口已连接,无需重连") except Exception as e: print(f"重连失败{e}") # %%定时发送数据 def data_send_timer(self): try: if 1<= int(self.lineEditTime.text()) <= 30000: # 定时时间1ms~30s内 if self.checkBoxReapitSend.isChecked(): self.timer_send.start(int(self.lineEditTime.text())) self.lineEditTime.setEnabled(False) else: self.timer_send.stop() self.lineEditTime.setEnabled(True) else: QMessageBox.critical(self, '定时发送数据异常', '定时发送数据周期仅可设置在30秒内!') except: QMessageBox.critical(self, '定时发送数据异常', '请设置正确的数值类型!') # %%发送数据 def data_send(self,text_quick = None): if self.ser.isOpen(): if text_quick== None: input_s = self.textEditSend.toPlainText() else: input_s = text_quick # 判断是否为非空字符串 if input_s != "": # 时间显示 if self.checkBoxAddDate.isChecked(): self.textEditReceive.insertPlainText((time.strftime("%Y-%m-%d %H:%M:%S", time.localtime())) + " ") # HEX发送 if self.checkBoxHexSend.isChecked(): input_s = input_s.strip() send_list = [] while input_s != '': try: num = int(input_s[0:2], 16) except ValueError: QMessageBox.critical(self, '串口异常', '请输入规范十六进制数据,以空格分开!') return None input_s = input_s[2:].strip() send_list.append(num) input_s = bytes(send_list) # ASCII发送 else: input_s = (input_s).encode('utf-8') input_s = re.sub(b'(?