2025-01-07 09:07:01 +08:00
|
|
|
|
import sys
|
|
|
|
|
import serial.tools.list_ports
|
|
|
|
|
from PySide6.QtWidgets import (QApplication, QMainWindow, QWidget, QVBoxLayout,
|
|
|
|
|
QHBoxLayout, QComboBox, QPushButton, QLabel,
|
|
|
|
|
QLineEdit, QMessageBox)
|
|
|
|
|
from PySide6.QtCore import Qt
|
|
|
|
|
from PySide6.QtGui import QIntValidator
|
|
|
|
|
from data_collector import SerialDataLogger
|
2025-01-10 22:54:25 +08:00
|
|
|
|
import os
|
2025-01-07 09:07:01 +08:00
|
|
|
|
|
|
|
|
|
|
2025-03-20 10:24:52 +08:00
|
|
|
|
CMD_ON = b'\x01\x00\x01\x02'
|
|
|
|
|
CMD_OFF = b'\x01\x00\x00\x01'
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
2025-01-07 09:07:01 +08:00
|
|
|
|
class SerialUI(QMainWindow):
|
|
|
|
|
def __init__(self):
|
|
|
|
|
super().__init__()
|
2025-03-20 10:24:52 +08:00
|
|
|
|
self.setWindowTitle("Signal Collector V1.0.6 BUILD202503041320")
|
2025-01-10 22:54:25 +08:00
|
|
|
|
self.resize(400, 300)
|
2025-01-07 09:07:01 +08:00
|
|
|
|
|
|
|
|
|
# 设置应用样式
|
|
|
|
|
self.setStyleSheet("""
|
|
|
|
|
QMainWindow {
|
|
|
|
|
background-color: #2B2B2B;
|
|
|
|
|
}
|
|
|
|
|
QWidget {
|
|
|
|
|
background-color: #2B2B2B;
|
|
|
|
|
color: #FFFFFF;
|
|
|
|
|
}
|
|
|
|
|
QLabel {
|
|
|
|
|
color: #CCCCCC;
|
|
|
|
|
font-size: 14px;
|
|
|
|
|
}
|
|
|
|
|
QComboBox {
|
|
|
|
|
background-color: #3C3F41;
|
|
|
|
|
border: 1px solid #646464;
|
|
|
|
|
border-radius: 3px;
|
|
|
|
|
color: #FFFFFF;
|
|
|
|
|
padding: 5px;
|
|
|
|
|
min-width: 100px;
|
|
|
|
|
}
|
|
|
|
|
QComboBox:hover {
|
|
|
|
|
border: 1px solid #FF8C00;
|
|
|
|
|
}
|
|
|
|
|
QComboBox::drop-down {
|
|
|
|
|
border: none;
|
|
|
|
|
padding-right: 8px;
|
|
|
|
|
}
|
|
|
|
|
QPushButton {
|
|
|
|
|
background-color: #3C3F41;
|
|
|
|
|
border: 1px solid #646464;
|
|
|
|
|
border-radius: 3px;
|
|
|
|
|
color: #FFFFFF;
|
|
|
|
|
padding: 5px 15px;
|
|
|
|
|
min-height: 25px;
|
|
|
|
|
}
|
|
|
|
|
QPushButton:hover {
|
|
|
|
|
background-color: #434749;
|
|
|
|
|
border: 1px solid #FF8C00;
|
|
|
|
|
}
|
|
|
|
|
QPushButton:pressed {
|
|
|
|
|
background-color: #333637;
|
|
|
|
|
}
|
|
|
|
|
QPushButton:checked {
|
|
|
|
|
background-color: #FF8C00;
|
|
|
|
|
border: 1px solid #FF8C00;
|
|
|
|
|
}
|
|
|
|
|
QLineEdit {
|
|
|
|
|
background-color: #3C3F41;
|
|
|
|
|
border: 1px solid #646464;
|
|
|
|
|
border-radius: 3px;
|
|
|
|
|
color: #FFFFFF;
|
|
|
|
|
padding: 5px;
|
|
|
|
|
min-width: 100px;
|
|
|
|
|
}
|
|
|
|
|
QLineEdit:focus {
|
|
|
|
|
border: 1px solid #FF8C00;
|
|
|
|
|
}
|
|
|
|
|
QLineEdit:disabled {
|
|
|
|
|
background-color: #2B2B2B;
|
|
|
|
|
color: #808080;
|
|
|
|
|
}
|
|
|
|
|
QStatusBar {
|
|
|
|
|
background-color: #3C3F41;
|
|
|
|
|
color: #CCCCCC;
|
|
|
|
|
}
|
|
|
|
|
#record_btn {
|
|
|
|
|
background-color: #FF8C00;
|
|
|
|
|
border: none;
|
|
|
|
|
font-weight: bold;
|
|
|
|
|
}
|
|
|
|
|
#record_btn:hover {
|
|
|
|
|
background-color: #FFA500;
|
|
|
|
|
}
|
|
|
|
|
#record_btn:checked {
|
|
|
|
|
background-color: #CD3333;
|
|
|
|
|
}
|
|
|
|
|
""")
|
|
|
|
|
|
|
|
|
|
# 创建中心部件和布局
|
|
|
|
|
central_widget = QWidget()
|
|
|
|
|
self.setCentralWidget(central_widget)
|
|
|
|
|
layout = QVBoxLayout(central_widget)
|
|
|
|
|
|
|
|
|
|
# 串口选择区域
|
|
|
|
|
port_layout = QHBoxLayout()
|
|
|
|
|
port_label = QLabel("串口:")
|
|
|
|
|
self.port_combo = QComboBox()
|
|
|
|
|
self.refresh_btn = QPushButton("刷新")
|
|
|
|
|
self.refresh_btn.clicked.connect(self.refresh_ports)
|
|
|
|
|
port_layout.addWidget(port_label)
|
|
|
|
|
port_layout.addWidget(self.port_combo)
|
|
|
|
|
port_layout.addWidget(self.refresh_btn)
|
|
|
|
|
layout.addLayout(port_layout)
|
|
|
|
|
|
|
|
|
|
# 波特率选择区域
|
|
|
|
|
baud_layout = QHBoxLayout()
|
|
|
|
|
baud_label = QLabel("波特率:")
|
|
|
|
|
self.baud_input = QLineEdit()
|
|
|
|
|
self.baud_input.setText("4000000")
|
|
|
|
|
self.baud_input.setValidator(QIntValidator(1200, 4000000))
|
|
|
|
|
self.baud_input.setPlaceholderText("请输入波特率")
|
|
|
|
|
self.connect_btn = QPushButton("连接端口")
|
|
|
|
|
self.connect_btn.setCheckable(True)
|
|
|
|
|
self.connect_btn.clicked.connect(self.toggle_connection)
|
|
|
|
|
baud_layout.addWidget(baud_label)
|
|
|
|
|
baud_layout.addWidget(self.baud_input)
|
|
|
|
|
baud_layout.addWidget(self.connect_btn)
|
|
|
|
|
layout.addLayout(baud_layout)
|
|
|
|
|
|
2025-03-20 10:24:52 +08:00
|
|
|
|
# 开关按钮区域
|
|
|
|
|
cmd_layout = QHBoxLayout()
|
|
|
|
|
cmd_label = QLabel("开关命令:")
|
|
|
|
|
self.cmd_btn_on = QPushButton("ON")
|
|
|
|
|
self.cmd_btn_on.clicked.connect(self.on_cmd_btn_on)
|
|
|
|
|
self.cmd_btn_off = QPushButton("OFF")
|
|
|
|
|
self.cmd_btn_off.clicked.connect(self.on_cmd_btn_off)
|
|
|
|
|
|
|
|
|
|
cmd_layout.addWidget(baud_label)
|
|
|
|
|
cmd_layout.addWidget(self.cmd_btn_on)
|
|
|
|
|
cmd_layout.addWidget(self.cmd_btn_off)
|
|
|
|
|
layout.addLayout(cmd_layout)
|
|
|
|
|
|
2025-01-07 09:07:01 +08:00
|
|
|
|
# 录制控制区域
|
|
|
|
|
record_layout = QHBoxLayout()
|
|
|
|
|
self.record_btn = QPushButton("开始录制")
|
|
|
|
|
self.record_btn.setCheckable(True)
|
|
|
|
|
self.record_btn.clicked.connect(self.toggle_recording)
|
|
|
|
|
record_layout.addWidget(self.record_btn)
|
|
|
|
|
layout.addLayout(record_layout)
|
|
|
|
|
|
2025-01-10 22:54:25 +08:00
|
|
|
|
channel_layout = QHBoxLayout()
|
|
|
|
|
self.channel_input = QLineEdit()
|
|
|
|
|
self.channel_input.setText("0") # 设置默认值为0
|
|
|
|
|
self.channel_input.setPlaceholderText("输入标签内容")
|
|
|
|
|
self.set_channel_btn = QPushButton("设置通道")
|
|
|
|
|
self.set_channel_btn.clicked.connect(self.confirm_channel_setting)
|
|
|
|
|
channel_layout.addWidget(self.channel_input)
|
|
|
|
|
channel_layout.addWidget(self.set_channel_btn)
|
|
|
|
|
layout.addLayout(channel_layout)
|
|
|
|
|
|
2025-01-07 09:07:01 +08:00
|
|
|
|
# 标签输入区域
|
|
|
|
|
tag_layout = QHBoxLayout()
|
|
|
|
|
self.tag_input = QLineEdit()
|
|
|
|
|
self.tag_input.setText("0") # 设置默认值为0
|
|
|
|
|
self.tag_input.setPlaceholderText("输入标签内容")
|
|
|
|
|
self.add_label_btn = QPushButton("增加标签")
|
|
|
|
|
self.add_label_btn.clicked.connect(self.add_tag)
|
|
|
|
|
tag_layout.addWidget(self.tag_input)
|
|
|
|
|
tag_layout.addWidget(self.add_label_btn)
|
|
|
|
|
layout.addLayout(tag_layout)
|
|
|
|
|
|
|
|
|
|
# 数据计数显示区域
|
|
|
|
|
count_layout = QHBoxLayout()
|
2025-01-10 22:54:25 +08:00
|
|
|
|
self.count_label = QLabel("当前记录数量:")
|
2025-01-07 09:07:01 +08:00
|
|
|
|
self.count_label.setAlignment(Qt.AlignCenter)
|
|
|
|
|
count_layout.addWidget(self.count_label)
|
2025-01-10 22:54:25 +08:00
|
|
|
|
|
|
|
|
|
self.count_value_label = QLabel("0")
|
|
|
|
|
self.count_value_label.setAlignment(Qt.AlignCenter)
|
|
|
|
|
count_layout.addWidget(self.count_value_label)
|
2025-01-07 09:07:01 +08:00
|
|
|
|
layout.addLayout(count_layout)
|
|
|
|
|
|
|
|
|
|
# 添加状态栏
|
|
|
|
|
self.statusBar().showMessage("就绪")
|
|
|
|
|
|
|
|
|
|
# 初始化串口列表
|
|
|
|
|
self.refresh_ports()
|
|
|
|
|
|
|
|
|
|
# 初始化录制状态
|
|
|
|
|
self.is_recording = False
|
|
|
|
|
self.serial_logger = None
|
|
|
|
|
|
|
|
|
|
# 设置对象名称以便样式表识别
|
|
|
|
|
self.record_btn.setObjectName("record_btn")
|
|
|
|
|
|
|
|
|
|
# 增加组件间距
|
|
|
|
|
layout.setSpacing(10)
|
|
|
|
|
layout.setContentsMargins(20, 20, 20, 20)
|
|
|
|
|
|
|
|
|
|
# 为所有水平布局添加间距
|
|
|
|
|
for layout_item in [port_layout, baud_layout, record_layout, tag_layout]:
|
|
|
|
|
layout_item.setSpacing(10)
|
|
|
|
|
|
|
|
|
|
# 设置窗口最小尺寸
|
|
|
|
|
self.setMinimumSize(450, 250)
|
|
|
|
|
|
|
|
|
|
# 初始化时禁用录制按钮
|
|
|
|
|
self.record_btn.setEnabled(False)
|
|
|
|
|
|
2025-01-10 22:54:25 +08:00
|
|
|
|
records_save_path = './records'
|
|
|
|
|
if not os.path.exists(records_save_path):
|
|
|
|
|
# 如果不存在,则创建文件夹
|
|
|
|
|
os.makedirs(records_save_path)
|
|
|
|
|
|
2025-01-07 09:07:01 +08:00
|
|
|
|
self.serial_logger = SerialDataLogger()
|
2025-01-10 22:54:25 +08:00
|
|
|
|
self.channel_input.setText(str(self.serial_logger.CH_NUM))
|
2025-01-07 09:07:01 +08:00
|
|
|
|
self.serial_logger.signal_some_frames_processed.connect(self.update_count_display)
|
|
|
|
|
|
|
|
|
|
# 添加退出时的确认标志
|
|
|
|
|
self.exit_confirmed = False
|
|
|
|
|
|
2025-03-20 10:24:52 +08:00
|
|
|
|
def on_cmd_btn_on(self):
|
|
|
|
|
self.serial_logger.send_cmd(CMD_ON)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def on_cmd_btn_off(self):
|
|
|
|
|
self.serial_logger.send_cmd(CMD_OFF)
|
|
|
|
|
|
2025-01-07 09:07:01 +08:00
|
|
|
|
def update_count_display(self):
|
|
|
|
|
"""更新数据计数显示"""
|
|
|
|
|
if self.serial_logger:
|
|
|
|
|
count = self.serial_logger.recorded_frames_count
|
2025-01-10 22:54:25 +08:00
|
|
|
|
self.count_value_label.setText(str(count))
|
2025-01-07 09:07:01 +08:00
|
|
|
|
|
|
|
|
|
def get_recorder_tag(self):
|
|
|
|
|
return self.label_input.text()
|
|
|
|
|
|
|
|
|
|
def refresh_ports(self):
|
|
|
|
|
"""刷新可用串口列表"""
|
|
|
|
|
self.port_combo.clear()
|
|
|
|
|
ports = serial.tools.list_ports.comports()
|
|
|
|
|
|
|
|
|
|
if ports:
|
|
|
|
|
for port in ports:
|
|
|
|
|
# 格式化串口信息:端口 - 设备名称 (设备ID)
|
|
|
|
|
port_info = f"{port.device} - {port.description}"
|
|
|
|
|
if port.serial_number:
|
|
|
|
|
port_info += f" ({port.serial_number})"
|
|
|
|
|
|
|
|
|
|
# 将完整信息显示在下拉框中,但在数据中保存实际的端口名
|
|
|
|
|
self.port_combo.addItem(port_info, port.device)
|
|
|
|
|
else:
|
|
|
|
|
self.statusBar().showMessage("未找到可用串口")
|
2025-01-10 22:54:25 +08:00
|
|
|
|
|
|
|
|
|
def confirm_channel_setting(self):
|
|
|
|
|
"""确认通道数设置"""
|
|
|
|
|
try:
|
|
|
|
|
channels = int(self.channel_input.text())
|
|
|
|
|
if 1 <= channels <= 64:
|
|
|
|
|
if self.serial_logger:
|
|
|
|
|
self.serial_logger.set_channels(channels)
|
|
|
|
|
self.statusBar().showMessage(f"已设置通道数: {channels}")
|
|
|
|
|
# 禁用通道设置
|
|
|
|
|
self.channel_input.setEnabled(False)
|
|
|
|
|
self.set_channel_btn.setEnabled(False)
|
|
|
|
|
# 启用录制按钮
|
|
|
|
|
self.record_btn.setEnabled(True)
|
|
|
|
|
else:
|
|
|
|
|
QMessageBox.warning(self, "警告", "请输入1-64之间的通道数")
|
|
|
|
|
except ValueError:
|
|
|
|
|
QMessageBox.warning(self, "警告", "请输入有效的通道数")
|
2025-01-07 09:07:01 +08:00
|
|
|
|
|
|
|
|
|
def toggle_connection(self):
|
|
|
|
|
"""切换端口连接状态"""
|
|
|
|
|
print("toggle_connection")
|
|
|
|
|
if self.exit_confirmed and self.connect_btn.isChecked():
|
|
|
|
|
self.connect_btn.setChecked(False)
|
|
|
|
|
return
|
|
|
|
|
|
|
|
|
|
if self.connect_btn.isChecked():
|
|
|
|
|
port = self.port_combo.currentData()
|
|
|
|
|
try:
|
|
|
|
|
baudrate = int(self.baud_input.text())
|
|
|
|
|
self.serial_logger.set_port(port, baudrate)
|
|
|
|
|
if self.serial_logger.test_connection():
|
|
|
|
|
self.connect_btn.setText("断开连接")
|
|
|
|
|
self.statusBar().showMessage("端口已连接")
|
|
|
|
|
# 禁用串口和波特率选择
|
|
|
|
|
self.port_combo.setEnabled(False)
|
|
|
|
|
self.baud_input.setEnabled(False)
|
|
|
|
|
self.refresh_btn.setEnabled(False)
|
|
|
|
|
# 启用录制按钮
|
|
|
|
|
self.record_btn.setEnabled(True)
|
|
|
|
|
self.is_connected = True
|
|
|
|
|
else:
|
|
|
|
|
raise Exception("端口连接失败")
|
|
|
|
|
except ValueError:
|
|
|
|
|
QMessageBox.critical(self, "错误", "请输入有效的波特率")
|
|
|
|
|
self.connect_btn.setChecked(False)
|
|
|
|
|
except Exception as e:
|
|
|
|
|
QMessageBox.critical(self, "错误", f"无法连接端口: {str(e)}")
|
|
|
|
|
self.connect_btn.setChecked(False)
|
|
|
|
|
else:
|
|
|
|
|
if self.serial_logger:
|
|
|
|
|
if self.serial_logger.disconnect():
|
|
|
|
|
self.connect_btn.setText("连接端口")
|
|
|
|
|
self.statusBar().showMessage("端口已断开")
|
|
|
|
|
# 启用串口和波特率选择
|
|
|
|
|
self.port_combo.setEnabled(True)
|
|
|
|
|
self.baud_input.setEnabled(True)
|
|
|
|
|
self.refresh_btn.setEnabled(True)
|
|
|
|
|
# 禁用录制按钮
|
|
|
|
|
self.record_btn.setEnabled(False)
|
|
|
|
|
self.is_connected = False
|
|
|
|
|
|
|
|
|
|
else:
|
|
|
|
|
QMessageBox.warning(self, "警告", "断开连接时发生错误")
|
|
|
|
|
|
|
|
|
|
def toggle_recording(self):
|
|
|
|
|
"""切换录制状态"""
|
|
|
|
|
if not self.is_connected:
|
|
|
|
|
QMessageBox.warning(self, "警告", "请先连接端口")
|
|
|
|
|
self.record_btn.setChecked(False)
|
|
|
|
|
return
|
|
|
|
|
|
|
|
|
|
# 如果正在退出,不允许开始新的录制
|
|
|
|
|
if self.exit_confirmed:
|
|
|
|
|
self.record_btn.setChecked(False)
|
|
|
|
|
return
|
|
|
|
|
|
|
|
|
|
if self.record_btn.isChecked():
|
|
|
|
|
self.serial_logger.test_connection()
|
|
|
|
|
if self.serial_logger.start():
|
2025-01-10 22:54:25 +08:00
|
|
|
|
count = self.serial_logger.recorded_frames_count
|
|
|
|
|
self.count_value_label.setText(str(count))
|
2025-01-07 09:07:01 +08:00
|
|
|
|
self.record_btn.setText("停止录制")
|
|
|
|
|
self.statusBar().showMessage("正在录制...")
|
|
|
|
|
# 禁用连接按钮
|
|
|
|
|
self.connect_btn.setEnabled(False)
|
|
|
|
|
else:
|
|
|
|
|
QMessageBox.critical(self, "错误", "启动数据记录失败")
|
|
|
|
|
self.record_btn.setChecked(False)
|
|
|
|
|
else:
|
|
|
|
|
if self.serial_logger.stop():
|
|
|
|
|
self.record_btn.setText("开始录制")
|
|
|
|
|
self.statusBar().showMessage("录制已停止")
|
|
|
|
|
self.record_btn.setEnabled(True)
|
|
|
|
|
# self.record_btn.setChecked(True)
|
|
|
|
|
# 启用连接按钮
|
|
|
|
|
self.connect_btn.setEnabled(True)
|
|
|
|
|
print(self.serial_logger)
|
|
|
|
|
else:
|
|
|
|
|
QMessageBox.warning(self, "警告", "停止录制时发生错误")
|
|
|
|
|
|
|
|
|
|
def add_tag(self):
|
|
|
|
|
"""添加标签"""
|
|
|
|
|
label_text = self.label_input.text().strip()
|
|
|
|
|
if not label_text:
|
|
|
|
|
return
|
|
|
|
|
|
|
|
|
|
if self.serial_logger and self.is_recording:
|
|
|
|
|
# 这里可以添加向文件写入标签的逻辑
|
|
|
|
|
# 例如:self.serial_logger.add_label(label_text)
|
|
|
|
|
self.serial_logger.RECORDER_TAG = int(self.tag_input.text())
|
|
|
|
|
self.statusBar().showMessage(f"已添加标签: {label_text}")
|
|
|
|
|
self.label_input.clear()
|
|
|
|
|
else:
|
|
|
|
|
QMessageBox.warning(self, "警告", "请先开始录制")
|
|
|
|
|
|
|
|
|
|
def closeEvent(self, event):
|
|
|
|
|
"""窗口关闭事件处理"""
|
|
|
|
|
if not self.exit_confirmed:
|
|
|
|
|
reply = QMessageBox.question(
|
|
|
|
|
self,
|
|
|
|
|
'确认退出',
|
|
|
|
|
'确定要退出程序吗?',
|
|
|
|
|
QMessageBox.Yes | QMessageBox.No,
|
|
|
|
|
QMessageBox.No
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
if reply == QMessageBox.Yes:
|
|
|
|
|
# 停止更新计时器
|
|
|
|
|
if hasattr(self, 'update_timer'):
|
|
|
|
|
self.update_timer.stop()
|
|
|
|
|
|
|
|
|
|
# 如果正在录制,先停止录制
|
|
|
|
|
if self.record_btn.isChecked():
|
|
|
|
|
self.record_btn.setChecked(False)
|
|
|
|
|
self.toggle_recording()
|
|
|
|
|
|
|
|
|
|
# 如果已连接,断开连接
|
|
|
|
|
if self.connect_btn.isChecked():
|
|
|
|
|
self.connect_btn.setChecked(False)
|
|
|
|
|
self.toggle_connection()
|
|
|
|
|
|
|
|
|
|
self.exit_confirmed = True
|
|
|
|
|
event.accept()
|
|
|
|
|
else:
|
|
|
|
|
event.ignore()
|
|
|
|
|
else:
|
|
|
|
|
event.accept()
|
|
|
|
|
|
|
|
|
|
def cleanup_resources(self):
|
|
|
|
|
"""清理所有资源"""
|
|
|
|
|
try:
|
|
|
|
|
# 清理串口记录器
|
|
|
|
|
if self.serial_logger:
|
|
|
|
|
try:
|
|
|
|
|
# 直接设置停止标志
|
|
|
|
|
self.serial_logger.is_running = False
|
|
|
|
|
|
|
|
|
|
# 关闭串口连接
|
|
|
|
|
if hasattr(self.serial_logger, 'ser') and self.serial_logger.ser:
|
|
|
|
|
self.serial_logger.ser.close()
|
|
|
|
|
|
|
|
|
|
# 快速清理处理队列
|
|
|
|
|
if hasattr(self.serial_logger, 'process_queue'):
|
|
|
|
|
while not self.serial_logger.process_queue.empty():
|
|
|
|
|
try:
|
|
|
|
|
self.serial_logger.process_queue.get_nowait()
|
|
|
|
|
except:
|
|
|
|
|
break
|
|
|
|
|
|
|
|
|
|
self.serial_logger = None
|
|
|
|
|
|
|
|
|
|
except Exception as e:
|
|
|
|
|
print(f"清理串口记录器时出错: {e}")
|
|
|
|
|
|
|
|
|
|
except Exception as e:
|
|
|
|
|
print(f"清理资源时出错: {e}")
|
|
|
|
|
|
|
|
|
|
if __name__ == '__main__':
|
|
|
|
|
app = QApplication(sys.argv)
|
|
|
|
|
window = SerialUI()
|
|
|
|
|
window.show()
|
|
|
|
|
sys.exit(app.exec())
|