858 lines
35 KiB
Python
858 lines
35 KiB
Python
![]() |
# -*- coding: utf-8 -*-
|
|||
|
"""
|
|||
|
Created on Mon May 27 14:31:38 2024
|
|||
|
|
|||
|
@author: WANGXIBAO
|
|||
|
"""
|
|||
|
|
|||
|
import sys,os
|
|||
|
import serial,re
|
|||
|
import serial.tools.list_ports
|
|||
|
import time,datetime
|
|||
|
|
|||
|
from PyQt5 import QtWidgets
|
|||
|
from PyQt5.Qt import QPainter
|
|||
|
from PyQt5.QtWidgets import QMessageBox ,QFileDialog,QInputDialog, QFrame
|
|||
|
from PyQt5.QtCore import QTimer
|
|||
|
from serial import Serial
|
|||
|
|
|||
|
from PyUartUi import Ui_UartAssistant
|
|||
|
from UartDataPolt import QChartViewPlot,UpdateDataThread,GetDataTF
|
|||
|
from PyQt5.QtChart import QChartView
|
|||
|
from PyQt5.QtGui import QIcon
|
|||
|
from configparser import ConfigParser
|
|||
|
from BigData_Plot import ChartDialog
|
|||
|
|
|||
|
|
|||
|
|
|||
|
class PyQt5Serial(QtWidgets.QWidget,Ui_UartAssistant):
|
|||
|
ser: Serial | Serial
|
|||
|
|
|||
|
# %%初始化程序
|
|||
|
def __init__(self):
|
|||
|
super(PyQt5Serial, self).__init__()
|
|||
|
self.setupUi(self)
|
|||
|
self.quick_num = 99
|
|||
|
self.IniPath = "PyUart.ini"
|
|||
|
self.log_time = 10
|
|||
|
self.log_buffer = {} #定义成一个字典,键--文件名,值--对于收到的字符串列表
|
|||
|
self.qxCfg=()
|
|||
|
self.tfCfg=()
|
|||
|
self.otCfg=()
|
|||
|
self.current_lines = [] #定义一个空列表,存储待显示数据
|
|||
|
|
|||
|
self.update_data_thread = UpdateDataThread() # 创建更新波形数据线程
|
|||
|
self.get_data_tf = GetDataTF()
|
|||
|
self.get_data_tf.IndOfReturn(0) #根据数据特点给一个初始值
|
|||
|
|
|||
|
self.chart_dialog = ChartDialog()
|
|||
|
|
|||
|
self.init()
|
|||
|
self.CheckCfgIniData() #更改按钮的文字
|
|||
|
|
|||
|
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.setPlainText("")
|
|||
|
self.textEditReceive.setMaximumBlockCount(5000)
|
|||
|
self.textEditSend.setText("")
|
|||
|
# %%第二个TAB 初始化画图页面表格
|
|||
|
self.pushButtonStopPlot.setEnabled(False)
|
|||
|
self.pushButtonStartPlot.setEnabled(True)
|
|||
|
self.radioButtonCH4QX.setEnabled(True)
|
|||
|
self.radioButtonCH4TF.setEnabled(True)
|
|||
|
self.checkBoxAutoSaveCsv.setEnabled(False)
|
|||
|
self.pushButton_expend.setEnabled(False)
|
|||
|
|
|||
|
# 加载Qchart波形界面
|
|||
|
self.plot_qchart = QChartViewPlot()
|
|||
|
#设置外边框为0
|
|||
|
self.plot_qchart.setBackgroundRoundness(0)
|
|||
|
self.plot_qchart.layout().setContentsMargins(0, 0, 0, 0)
|
|||
|
self.plot_view.setChart(self.plot_qchart)
|
|||
|
self.plot_view.setRenderHint(QPainter.Antialiasing) # 抗锯齿
|
|||
|
self.plot_view.setRubberBand(QChartView.RectangleRubberBand)
|
|||
|
# 移除外部边框
|
|||
|
# self.plot_view.setFrameShape(QFrame.NoFrame)
|
|||
|
# self.plot_view.setStyleSheet("border: none;")
|
|||
|
|
|||
|
|
|||
|
|
|||
|
#加载快捷指令
|
|||
|
self.widget_6.hide()
|
|||
|
#加载快捷指令的按键值
|
|||
|
|
|||
|
# 用于暂存接收的串口数据
|
|||
|
self.buffer = b''
|
|||
|
# 用于暂存解码数据
|
|||
|
self.lineUtf8 = ""
|
|||
|
# 用于标志是否开始存CSV
|
|||
|
self.flag_draw = 0
|
|||
|
#用于暂存大数据
|
|||
|
self.bigdata=[]
|
|||
|
#开启大数据模式标志位
|
|||
|
self.flag_bigdata =0
|
|||
|
|
|||
|
|
|||
|
|
|||
|
self.double_click_timers = {} # 存储双击定时器
|
|||
|
# =============================================================================
|
|||
|
# 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(self.quick_num): # 假设有20个按钮
|
|||
|
index_str = f"{i:02}" # 确保编号为两位数字形式
|
|||
|
lineEditName = f"lineEditQuick_{index_str}"
|
|||
|
buttonName = f"pushButtonQuick_{index_str}" # 格式化按钮名称,确保两位数
|
|||
|
button_name = f"button{index_str}"
|
|||
|
set_text = getattr(self,lineEditName).text()+ "|" + getattr(self,buttonName).text()
|
|||
|
self.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)
|
|||
|
|
|||
|
# hexRecevie与savCsv的按键关联
|
|||
|
self.checkBoxHexReceive.stateChanged.connect(self.hex_link_savCsv)
|
|||
|
|
|||
|
# 清除发送按钮
|
|||
|
self.pushButtonClearSend.clicked.connect(self.send_data_clear)
|
|||
|
|
|||
|
# 清除接收按钮
|
|||
|
self.pushButtonClearReceive.clicked.connect(self.receive_data_clear)
|
|||
|
|
|||
|
# 开始绘图
|
|||
|
self.pushButtonStartPlot.clicked.connect(self.btn_start_clicked)
|
|||
|
# 关闭绘图
|
|||
|
self.pushButtonStopPlot.clicked.connect(self.btn_stop_clicked)
|
|||
|
#线程信号发射
|
|||
|
self.update_data_thread._signal_update.connect(self.update_data_thread_slot)
|
|||
|
# 选择绘图
|
|||
|
self.comboBoxPlot.currentIndexChanged.connect(self.plot_item_changed)
|
|||
|
# 重置绘图
|
|||
|
self.pushButtonResetPlot.clicked.connect(self.plot_reset)
|
|||
|
# 快捷指令扩展区域
|
|||
|
self.pushButton_expend.clicked.connect(self.adjust_sidebar)
|
|||
|
#大数据模式
|
|||
|
self.pushButton_bigdata.clicked.connect(self.bigdata_show)
|
|||
|
# 创建一个通用的槽函数来处理所有按钮
|
|||
|
# 动态创建控件并存储引用
|
|||
|
for i in range(self.quick_num): # 从0到20
|
|||
|
index_str = f"{i:02}" # 确保编号为两位数字形式
|
|||
|
horizontalLayoutName = f"horizontalLayoutQuick_{index_str}"
|
|||
|
horizontalLayout = QtWidgets.QHBoxLayout()
|
|||
|
horizontalLayout.setObjectName(horizontalLayoutName)
|
|||
|
|
|||
|
# 创建 QLineEdit 并设置动态属性
|
|||
|
lineEditName = f"lineEditQuick_{index_str}"
|
|||
|
setattr(self, lineEditName, QtWidgets.QLineEdit(self.layoutWidget1))
|
|||
|
getattr(self, lineEditName).setObjectName(lineEditName)
|
|||
|
horizontalLayout.addWidget(getattr(self, lineEditName))
|
|||
|
# 创建 QPushButton 并设置动态属性
|
|||
|
buttonName = f"pushButtonQuick_{index_str}"
|
|||
|
setattr(self, buttonName, QtWidgets.QPushButton(self.layoutWidget1))
|
|||
|
getattr(self, buttonName).setObjectName(buttonName)
|
|||
|
horizontalLayout.addWidget(getattr(self, buttonName))
|
|||
|
self.verticalLayout_8.addLayout(horizontalLayout)
|
|||
|
# 连接按钮点击事件到槽函数
|
|||
|
button = getattr(self, buttonName)
|
|||
|
if button:
|
|||
|
button.clicked.connect(lambda checked, idx=i: self.onButtonClick(idx))
|
|||
|
|
|||
|
# %% 串口检测
|
|||
|
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.001
|
|||
|
|
|||
|
|
|||
|
|
|||
|
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"))
|
|||
|
|
|||
|
# 获取到text光标
|
|||
|
textCursor = self.textEditReceive.textCursor()
|
|||
|
# 滚动到底部
|
|||
|
textCursor.movePosition(textCursor.End)
|
|||
|
# 设置光标到text中去
|
|||
|
self.textEditReceive.setTextCursor(textCursor)
|
|||
|
if self.checkBoxAddDate.isChecked():
|
|||
|
nowTime = datetime.datetime.now().strftime("%Y-%m-%d %H:%M:%S.%f")
|
|||
|
nowTime = nowTime[:-3]
|
|||
|
self.textEditReceive.insertPlainText(nowTime + " ")
|
|||
|
#self.add_line_to_textedit(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)
|
|||
|
#self.add_line_to_textedit(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'))
|
|||
|
#self.add_line_to_textedit(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')
|
|||
|
#self.add_line_to_textedit('\r\n')
|
|||
|
|
|||
|
|
|||
|
|
|||
|
# 统计接收字符的数量
|
|||
|
self.data_num_received += num
|
|||
|
self.lineEditReceiveNum.setText(str(self.data_num_received))
|
|||
|
|
|||
|
# 自动保存日志
|
|||
|
if self.checkBoxAutoSaveLog.isChecked():
|
|||
|
self.AutoSaveLog()
|
|||
|
if self.flag_bigdata == 1:
|
|||
|
self.BigDataPlot()
|
|||
|
|
|||
|
|
|||
|
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("重连成功")
|
|||
|
self.textEditReceive.insertPlainText("重连成功\r\n")
|
|||
|
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'(?<!\r)\n', b'\r\n', input_s)
|
|||
|
|
|||
|
# HEX接收显示
|
|||
|
if self.checkBoxHexReceive.isChecked():
|
|||
|
out_s = ''
|
|||
|
for i in range(0, len(input_s)):
|
|||
|
out_s = out_s + '{:02X}'.format(input_s[i]) + ' '
|
|||
|
|
|||
|
self.textEditReceive.insertPlainText(out_s)
|
|||
|
# ASCII接收显示
|
|||
|
else:
|
|||
|
self.textEditReceive.insertPlainText(input_s.decode('utf-8',errors='replace'))
|
|||
|
|
|||
|
# 接收换行
|
|||
|
if self.checkBoxCRLF.isChecked():
|
|||
|
self.textEditReceive.insertPlainText('\r\n')
|
|||
|
|
|||
|
# 获取到Text光标
|
|||
|
textCursor = self.textEditReceive.textCursor()
|
|||
|
# 滚动到底部
|
|||
|
textCursor.movePosition(textCursor.End)
|
|||
|
# 设置光标到Text中去
|
|||
|
self.textEditReceive.setTextCursor(textCursor)
|
|||
|
|
|||
|
# 统计发送字符数量
|
|||
|
num = self.ser.write(input_s)
|
|||
|
self.data_num_sended += num
|
|||
|
self.lineEditSendNum.setText(str(self.data_num_sended))
|
|||
|
|
|||
|
else:
|
|||
|
pass
|
|||
|
|
|||
|
# %%保存日志
|
|||
|
def savefiles(self):
|
|||
|
dlg = QFileDialog()
|
|||
|
filenames = dlg.getSaveFileName(None, "保存日志文件", None, "Txt files(*.txt)")
|
|||
|
|
|||
|
try:
|
|||
|
with open(file = filenames[0], mode='w', encoding='utf-8') as file:
|
|||
|
file.write(self.textEditReceive.toPlainText())
|
|||
|
except:
|
|||
|
QMessageBox.critical(self, '日志异常', '保存日志文件失败!')
|
|||
|
|
|||
|
#%% 加载日志
|
|||
|
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.textEditSend.setPlainText(file.read())
|
|||
|
except:
|
|||
|
QMessageBox.critical(self, '日志异常', '加载日志文件失败!')
|
|||
|
|
|||
|
# %%打开博客链接和公众号二维码
|
|||
|
|
|||
|
#webbrowser.open('https://blog.csdn.net/m0_38106923')
|
|||
|
|
|||
|
# %%清除发送数据显示
|
|||
|
# 自动保存日志 log he csv
|
|||
|
def AutoSaveLog(self):
|
|||
|
|
|||
|
try:
|
|||
|
lines = self.buffer.split(b'\n') # 或者使用 b'\r\n' 根据你的需要
|
|||
|
# 最后一个元素可能不包含完整的行,所以将其保留作为新的缓存
|
|||
|
if len(lines)>1:
|
|||
|
self.buffer = lines.pop()
|
|||
|
else:
|
|||
|
self.buffer = b''
|
|||
|
|
|||
|
|
|||
|
# 处理每一行数据
|
|||
|
for line in lines:
|
|||
|
# 注意:每行数据可能不包含结尾的换行符,所以在处理前检查一下
|
|||
|
if line.endswith(b'\r'):
|
|||
|
line = line[:-1] # 移除回车
|
|||
|
if self.checkBoxHexReceive.checkState():
|
|||
|
lineUtf8 = line.hex()
|
|||
|
out_s = ' '.join(['{:02X}'.format(b) for b in bytes.fromhex(lineUtf8)])
|
|||
|
saveData = datetime.datetime.now().strftime("%Y-%m-%dT%H:%M:%S.%f")[:23] +" " +out_s +"\r\n"
|
|||
|
else:
|
|||
|
lineUtf8 = line.decode('utf-8',errors='replace')
|
|||
|
saveData = (time.strftime("%Y-%m-%d %H:%M:%S", time.localtime())) + " " + lineUtf8 + "\r\n"
|
|||
|
|
|||
|
# 将日志信息添加到缓冲区
|
|||
|
if self.filename not in self.log_buffer:
|
|||
|
self.log_buffer[self.filename] = []
|
|||
|
self.log_buffer[self.filename].append(saveData)
|
|||
|
|
|||
|
# 当缓冲区中的记录达到10条时,写入文件
|
|||
|
if len(self.log_buffer[self.filename]) >= self.log_time:
|
|||
|
with open(self.filename, mode='a', newline='',encoding='utf-8', errors='replace') as file:
|
|||
|
file.writelines(self.log_buffer[self.filename])
|
|||
|
self.log_buffer[self.filename].clear()
|
|||
|
|
|||
|
|
|||
|
|
|||
|
|
|||
|
#判断选择的何种格式数据
|
|||
|
if self.radioButtonCH4QX.isChecked():
|
|||
|
self.get_data_tf.SetConfig(self.qxCfg)
|
|||
|
if self.radioButtonCH4TF.isChecked():
|
|||
|
self.get_data_tf.SetConfig(self.tfCfg)
|
|||
|
if self.radioButtonOtherData.isChecked():
|
|||
|
self.get_data_tf.SetConfig(self.otCfg)
|
|||
|
|
|||
|
print(lineUtf8)
|
|||
|
|
|||
|
if self.flag_draw:
|
|||
|
|
|||
|
dataSplit = self.get_data_tf.Transdata(lineUtf8)
|
|||
|
#print("dataSplit",type(dataSplit))
|
|||
|
if isinstance(dataSplit, (float, int)):
|
|||
|
|
|||
|
self.filenameCsv= self.file + ".csv"
|
|||
|
if self.checkBoxAutoSaveCsv.isChecked(): #写入CSV文件
|
|||
|
try:
|
|||
|
self.get_data_tf.SaveCsv(self.filenameCsv,self.log_time)
|
|||
|
except:
|
|||
|
print("写入CSV失败")
|
|||
|
pass
|
|||
|
|
|||
|
self.update_data_thread.SetFlag(1)
|
|||
|
#print("dataSplit",dataSplit)
|
|||
|
self.update_data_thread.SetReceiveData(dataSplit)
|
|||
|
#更新当前数据
|
|||
|
self.lineEditCurrentValue.setText(str(round(dataSplit,3)))
|
|||
|
self.lineEditWindowMean.setText (str(round(self.plot_qchart.windowAverage,3)))
|
|||
|
self.lineEditWindowMSE.setText (str(round(self.plot_qchart.windowStd,3)))
|
|||
|
else:
|
|||
|
print("Data split failed, dataSplit type:",type(dataSplit))
|
|||
|
|
|||
|
except Exception as e:
|
|||
|
print(f"Error reading configuration: {e}")
|
|||
|
print("自动保存日志失败")
|
|||
|
pass
|
|||
|
|
|||
|
def BigDataPlot(self):
|
|||
|
|
|||
|
try:
|
|||
|
lines = self.buffer.split(b'\n') # 或者使用 b'\r\n' 根据你的需要
|
|||
|
# 最后一个元素可能不包含完整的行,所以将其保留作为新的缓存
|
|||
|
self.buffer = lines.pop()
|
|||
|
# 处理每一行数据
|
|||
|
for line in lines:
|
|||
|
# 注意:每行数据可能不包含结尾的换行符,所以在处理前检查一下
|
|||
|
if line.endswith(b'\r'):
|
|||
|
line = line[:-1] # 移除回车
|
|||
|
lineUtf8 = line.decode('utf-8')
|
|||
|
if "end" in lineUtf8.lower() or len(self.bigdata) > 4999:
|
|||
|
tansData = (range(len(self.bigdata)), self.bigdata)
|
|||
|
self.chart_dialog.tans_data(tansData)
|
|||
|
self.chart_dialog.draw_figure()
|
|||
|
|
|||
|
self.bigdata = []
|
|||
|
else:
|
|||
|
try:
|
|||
|
self.bigdata.append(float(lineUtf8))
|
|||
|
# print(f'bigdata length:{len(self.bigdata)}')
|
|||
|
except:
|
|||
|
pass
|
|||
|
|
|||
|
|
|||
|
except:
|
|||
|
return None
|
|||
|
|
|||
|
def send_data_clear(self):
|
|||
|
self.textEditSend.setText("")
|
|||
|
|
|||
|
self.data_num_sended = 0
|
|||
|
self.lineEditSendNum.setText(str(self.data_num_sended))
|
|||
|
|
|||
|
# 清除接收数据显示
|
|||
|
def receive_data_clear(self):
|
|||
|
self.textEditReceive.setPlainText("")
|
|||
|
|
|||
|
self.data_num_received = 0
|
|||
|
self.lineEditSendNum.setText(str(self.data_num_received))
|
|||
|
# 关联hex接收与保存csv,hex下不保存csv
|
|||
|
def hex_link_savCsv(self):
|
|||
|
if self.checkBoxHexReceive.isChecked():
|
|||
|
self.checkBoxAutoSaveCsv.setChecked(False)
|
|||
|
else:
|
|||
|
self.checkBoxAutoSaveCsv.setChecked(True)
|
|||
|
|
|||
|
#设置接受区域显示条数
|
|||
|
def add_line_to_textedit(self, new_line):
|
|||
|
# 获取当前文本并按行分割
|
|||
|
#current_lines = self.textEditReceive.toPlainText().splitlines()
|
|||
|
# 添加新的行到列表中
|
|||
|
self.current_lines.append(new_line)
|
|||
|
# 如果行数超过100,则删除最早的行,直到剩下100行
|
|||
|
if len(self.current_lines) > 100:
|
|||
|
self.current_lines = self.current_lines[-100:] # 只保留最新的100行
|
|||
|
# 将更新后的行列表转换回字符串并设置为textEdit的内容
|
|||
|
self.textEditReceive.setPlainText(''.join(self.current_lines))
|
|||
|
#self.textEditReceive.setPlainText(self.current_lines)
|
|||
|
# 关闭串口
|
|||
|
def port_close(self):
|
|||
|
try:
|
|||
|
self.timer.stop()
|
|||
|
self.timer_send.stop()
|
|||
|
self.btn_stop_clicked() #执行停止绘图操作
|
|||
|
self.ser.close()
|
|||
|
except:
|
|||
|
QMessageBox.critical(self, '串口异常', '关闭串口失败,请重启程序!')
|
|||
|
return None
|
|||
|
|
|||
|
# 切换开关串口按钮使能状态和定时发送使能状态
|
|||
|
self.pushButtonOpenSerial.setEnabled(True)
|
|||
|
self.pushButtonCloseSerial.setEnabled(False)
|
|||
|
self.lineEditTime.setEnabled(True)
|
|||
|
self.comboBoxBaudrate.setEnabled(True)
|
|||
|
self.comboBoxSerial.setEnabled(True)
|
|||
|
self.pushButton_expend.setEnabled(False)
|
|||
|
# 发送数据和接收数据数目置零
|
|||
|
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.formGroupBox1.setTitle("串口状态(关闭)")
|
|||
|
#开始绘图
|
|||
|
def btn_start_clicked(self):
|
|||
|
#开启按钮
|
|||
|
|
|||
|
self.update_data_thread.start()
|
|||
|
self.update_data_thread.restart()
|
|||
|
|
|||
|
self.pushButtonStartPlot.setEnabled(False)
|
|||
|
self.pushButtonStopPlot.setEnabled(True)
|
|||
|
self.radioButtonCH4QX.setEnabled(False)
|
|||
|
self.radioButtonCH4TF.setEnabled(False)
|
|||
|
self.radioButtonOtherData.setEnabled(False)
|
|||
|
self.checkBoxAutoSaveCsv.setEnabled(True)
|
|||
|
|
|||
|
iterm = self.get_data_tf.rowTitle[1:]
|
|||
|
print("iterm",iterm)
|
|||
|
self.comboBoxPlot.addItems(iterm)
|
|||
|
|
|||
|
self.update_data_thread.SetPlotItem(0)
|
|||
|
|
|||
|
self.update_data_thread.restart()
|
|||
|
self.flag_draw = 1
|
|||
|
|
|||
|
|
|||
|
|
|||
|
#停止绘图
|
|||
|
def btn_stop_clicked(self):
|
|||
|
self.update_data_thread.stop()
|
|||
|
self.pushButtonStartPlot.setEnabled(True)
|
|||
|
self.pushButtonStopPlot.setEnabled(False)
|
|||
|
self.radioButtonCH4QX.setEnabled(True)
|
|||
|
self.radioButtonCH4TF.setEnabled(True)
|
|||
|
self.radioButtonOtherData.setEnabled(True)
|
|||
|
self.checkBoxAutoSaveCsv.setEnabled(False)
|
|||
|
self.comboBoxPlot.clear()
|
|||
|
self.flag_draw = 0
|
|||
|
|
|||
|
def update_data_thread_slot(self, data):
|
|||
|
# 线程回调函数
|
|||
|
#data = json.loads(data)
|
|||
|
self.plot_qchart.handle_update(float(data))
|
|||
|
#print("thread ",data)
|
|||
|
|
|||
|
def plot_item_changed(self,index):
|
|||
|
print(index)
|
|||
|
self.plot_qchart.clearSeries()
|
|||
|
self.get_data_tf.IndOfReturn(index)
|
|||
|
|
|||
|
def plot_reset(self):
|
|||
|
self.plot_qchart.zoomReset()
|
|||
|
#开关快捷指令栏
|
|||
|
def adjust_sidebar(self):
|
|||
|
if self.widget_6.isHidden():
|
|||
|
self.widget_6.show()
|
|||
|
else:
|
|||
|
self.widget_6.hide()
|
|||
|
def onPushButtonQuickClicked(self, line_edit):
|
|||
|
text = getattr(self, line_edit).text()
|
|||
|
#print(f"Button clicked: {text}")
|
|||
|
if self.checkBox_return.isChecked():
|
|||
|
text = text + "\r\n"
|
|||
|
self.data_send(text)
|
|||
|
|
|||
|
|
|||
|
def CheckCfgIniData(self):
|
|||
|
if not os.path.exists(self.IniPath):
|
|||
|
config = ConfigParser()
|
|||
|
#数据处理正则表达
|
|||
|
config.add_section('QX_config')
|
|||
|
config.set('QX_config', 'regular', '\+?-?\d+(?:\.\d+)?')
|
|||
|
config.set('QX_config', 'headStr', 'A+')
|
|||
|
config.set('QX_config', 'rowTitle', 'time,Methane,Air Temp,Laser Temp,Laser Intensity')
|
|||
|
|
|||
|
config.add_section('TF_config')
|
|||
|
config.set('TF_config', 'regular', '\+?-?\d+(?:\.\d+)?')
|
|||
|
config.set('TF_config', 'headStr', 'A+')
|
|||
|
config.set('TF_config', 'rowTitle', 'time,Methane,Air Temp,Laser Temp,Laser Intensity,amplification,NL,ND,Sinal,SNR,PEAK,Best Piont')
|
|||
|
|
|||
|
config.add_section('OtherData_config')
|
|||
|
config.set('OtherData_config', 'regular', '\+?-?\d+(?:\.\d+)?')
|
|||
|
# config.set('TF_config', 'regular', '(A\+|B\+|\s)+')
|
|||
|
config.set('OtherData_config', 'headStr', 'A+')
|
|||
|
config.set('OtherData_config', 'rowTitle', 'time,DATA1,DATA2')
|
|||
|
|
|||
|
#按键配置
|
|||
|
config.add_section('Quick_config')
|
|||
|
config.set('Quick_config', 'log_time', '10')
|
|||
|
for i in range(self.quick_num):
|
|||
|
idx = f'{i:02}'
|
|||
|
button_name = f'Button{idx}' # 格式化按钮名称,确保两位数
|
|||
|
config.set('Quick_config', button_name, '')
|
|||
|
with open(self.IniPath, 'w' ,encoding='utf-8') as f:
|
|||
|
config.write(f)
|
|||
|
|
|||
|
config = ConfigParser()
|
|||
|
config.read(self.IniPath, encoding='utf-8')
|
|||
|
try:
|
|||
|
|
|||
|
# 创建一个空字典来存储按钮名称和对应的配置
|
|||
|
self.log_time = int(config.get('Quick_config', 'log_time'))
|
|||
|
self.qxCfg = (config.get('QX_config','regular'),config.get('QX_config','headStr'),config.get('QX_config','rowTitle'))
|
|||
|
print('Quick_config:',self.qxCfg)
|
|||
|
self.tfCfg = (config.get('TF_config','regular'),config.get('TF_config','headStr'),config.get('TF_config','rowTitle'))
|
|||
|
print('TF_config:',self.tfCfg)
|
|||
|
self.otCfg = (config.get('OtherData_config','regular'),config.get('OtherData_config','headStr'),config.get('OtherData_config','rowTitle'))
|
|||
|
print('OtherData_config:',self.otCfg)
|
|||
|
|
|||
|
# 循环遍历按钮编号,从0到19
|
|||
|
for i in range(self.quick_num):
|
|||
|
idx = f'{i:02}'
|
|||
|
button_name = f'Button{idx}' # 格式化按钮名称,确保两位数
|
|||
|
# 使用 get 方法安全地获取配置,如果不存在则返回空字符串
|
|||
|
config_value = config.get('Quick_config', button_name, fallback='')
|
|||
|
config_value_split = config_value.split('|')
|
|||
|
# 将按钮名称和配置信息存储在字典中
|
|||
|
if len(config_value_split) == 2:
|
|||
|
# 打印字典查看结果,并赋值
|
|||
|
print(button_name,config_value_split[0],config_value_split[1])
|
|||
|
getattr(self, f'pushButtonQuick_{idx}').setText(config_value_split[1])
|
|||
|
getattr(self, f'lineEditQuick_{idx}').setText(config_value_split[0])
|
|||
|
|
|||
|
except Exception as e:
|
|||
|
print(f"Error reading configuration: {e}")
|
|||
|
|
|||
|
|
|||
|
def SetCfgIniData(self,button_name,set_text):
|
|||
|
config = ConfigParser()
|
|||
|
config.read(self.IniPath, encoding='utf-8')
|
|||
|
config.set('Quick_config', button_name, set_text)
|
|||
|
with open(self.IniPath, 'w' ,encoding='utf-8') as f:
|
|||
|
config.write(f)
|
|||
|
|
|||
|
|
|||
|
def onButtonClick(self, idx):
|
|||
|
# 槽函数处理按钮点击事件
|
|||
|
|
|||
|
if idx not in self.double_click_timers:
|
|||
|
self.double_click_timers[idx] = None
|
|||
|
|
|||
|
if self.double_click_timers[idx] is None:
|
|||
|
self.double_click_timers[idx] = True
|
|||
|
QTimer.singleShot(200, lambda: self.onButtonSigleClick(idx))
|
|||
|
else:
|
|||
|
self.onButtonDoubleClick(idx)
|
|||
|
|
|||
|
def onButtonSigleClick(self, idx):
|
|||
|
index_str = f"{idx:02}"
|
|||
|
if self.double_click_timers[idx]:
|
|||
|
self.double_click_timers[idx] = None
|
|||
|
lineEdit = getattr(self, f"lineEditQuick_{index_str}")
|
|||
|
print(f"Text in lineEdit_{idx}: {lineEdit.text()}")
|
|||
|
text = lineEdit.text()
|
|||
|
if self.checkBox_return.isChecked():
|
|||
|
text = text + "\r\n"
|
|||
|
self.data_send(text)
|
|||
|
|
|||
|
def onButtonDoubleClick(self, idx):
|
|||
|
# 槽函数处理按钮双击事件
|
|||
|
self.double_click_timers[idx] = None
|
|||
|
index_str = f"{idx:02}"
|
|||
|
print(f"Double click detected on Button {index_str}.")
|
|||
|
button = getattr(self, f"pushButtonQuick_{index_str}")
|
|||
|
new_name, ok = QInputDialog.getText(self, 'Button Rename', 'Enter new button name:')
|
|||
|
if ok and new_name:
|
|||
|
button.setText(new_name)
|
|||
|
|
|||
|
|
|||
|
def bigdata_show(self):
|
|||
|
if self.flag_bigdata == 0:
|
|||
|
self.chart_dialog.show()
|
|||
|
self.flag_bigdata = 1
|
|||
|
self.checkBoxAutoSaveLog.setChecked(False)
|
|||
|
else:
|
|||
|
self.chart_dialog.close()
|
|||
|
self.flag_bigdata = 0
|
|||
|
self.checkBoxAutoSaveLog.setChecked(True)
|
|||
|
#执行
|
|||
|
if __name__ == '__main__':
|
|||
|
app = QtWidgets.QApplication(sys.argv)
|
|||
|
myshow = PyQt5Serial()
|
|||
|
myshow.show()
|
|||
|
sys.exit(app.exec_())
|