diff --git a/data_20241103_185036.png b/data_20241103_185036.png new file mode 100644 index 0000000..981cf74 Binary files /dev/null and b/data_20241103_185036.png differ diff --git a/data_20241103_185036.xlsx b/data_20241103_185036.xlsx new file mode 100644 index 0000000..faeb813 Binary files /dev/null and b/data_20241103_185036.xlsx differ diff --git a/data_20241103_191048.png b/data_20241103_191048.png new file mode 100644 index 0000000..c783219 Binary files /dev/null and b/data_20241103_191048.png differ diff --git a/data_20241103_191048.xlsx b/data_20241103_191048.xlsx new file mode 100644 index 0000000..0a9b56e Binary files /dev/null and b/data_20241103_191048.xlsx differ diff --git a/data_20241103_211702.png b/data_20241103_211702.png new file mode 100644 index 0000000..aa2d132 Binary files /dev/null and b/data_20241103_211702.png differ diff --git a/data_20241103_211702.xlsx b/data_20241103_211702.xlsx new file mode 100644 index 0000000..aa9ae4f Binary files /dev/null and b/data_20241103_211702.xlsx differ diff --git a/data_20241103_212843.png b/data_20241103_212843.png new file mode 100644 index 0000000..6395201 Binary files /dev/null and b/data_20241103_212843.png differ diff --git a/data_20241103_212843.xlsx b/data_20241103_212843.xlsx new file mode 100644 index 0000000..7978f73 Binary files /dev/null and b/data_20241103_212843.xlsx differ diff --git a/src/monitor.py b/src/monitor.py index efc2667..b6e9297 100644 --- a/src/monitor.py +++ b/src/monitor.py @@ -3,8 +3,9 @@ import serial import serial.tools.list_ports import threading import time +import random from collections import deque -from PyQt5.QtWidgets import QApplication, QMainWindow, QHBoxLayout, QVBoxLayout, QWidget, QPushButton, QComboBox, QLabel, QFileDialog, QProgressBar, QGridLayout, QGroupBox, QCheckBox +from PyQt5.QtWidgets import QApplication, QMainWindow, QHBoxLayout, QVBoxLayout, QWidget, QPushButton, QComboBox, QLabel, QFileDialog, QProgressBar, QGridLayout, QGroupBox, QCheckBox, QLineEdit from PyQt5.QtCore import QTimer, Qt import pyqtgraph as pg import pandas as pd @@ -12,8 +13,8 @@ from datetime import datetime import pyqtgraph.exporters class VoltageReaderApp(QMainWindow): - NUM_CHANNELS = 20 # 通道数量 - PACKET_SIZE = 43 # 数据包大小 + NUM_CHANNELS = 2 # 通道数量 + PACKET_SIZE = 7 # 数据包大小 PACKET_HEADER = [0xFE, 0xEE] # 数据包头 PACKET_FOOTER = 0xAA # 数据包尾 BAUD_RATES = ['9600', '19200', '38400', '57600', '115200'] # 波特率选项 @@ -33,6 +34,7 @@ class VoltageReaderApp(QMainWindow): self.timer.timeout.connect(self.update_plot) self.auto_follow = False self.show_raw = False + self.test_mode = False # 添加测试模式标志 def initUI(self): self.setWindowTitle("Voltage Monitor") @@ -117,6 +119,17 @@ class VoltageReaderApp(QMainWindow): self.load_button.clicked.connect(self.load_data) action_layout.addWidget(self.load_button) + self.test_button = QPushButton('测试模式') + self.test_button.setCheckable(True) + self.test_button.clicked.connect(self.toggle_test_mode) + action_layout.addWidget(self.test_button) + + self.remark_label = QLabel('备注(学生姓名):') + action_layout.addWidget(self.remark_label) + + self.remark_input = QLineEdit() + action_layout.addWidget(self.remark_input) + self.message_label = QLabel() self.message_label.setWordWrap(True) layout.addWidget(self.message_label) @@ -128,7 +141,7 @@ class VoltageReaderApp(QMainWindow): def setup_channel_group(self, layout): channel_group = QGroupBox("通道选择") - channel_layout = QGridLayout() + channel_layout = QVBoxLayout() channel_group.setLayout(channel_layout) layout.addWidget(channel_group) @@ -137,11 +150,9 @@ class VoltageReaderApp(QMainWindow): color = (i*12, 255-i*12, 150) checkbox = QCheckBox(f'adc{i+1}') checkbox.setChecked(True) - checkbox.setStyleSheet(f'color: rgb({color[0]}, {color[1]}, {color[2]})') + checkbox.setStyleSheet(f'color: black; background-color: rgb({color[0]}, {color[1]}, {color[2]})') checkbox.stateChanged.connect(lambda state, idx=i: self.toggle_curve_visibility(state, idx)) - row = i // 2 - col = i % 2 - channel_layout.addWidget(checkbox, row, col) + channel_layout.addWidget(checkbox) self.checkboxes.append(checkbox) def setup_plot_widget(self, layout): @@ -170,7 +181,7 @@ class VoltageReaderApp(QMainWindow): self.message_label.setText(f"连接失败: {e}") def start_reading(self): - if self.serial_port: + if self.serial_port or self.test_mode: self.reading_event.set() self.start_button.setEnabled(False) self.stop_button.setEnabled(True) @@ -190,31 +201,45 @@ class VoltageReaderApp(QMainWindow): self.message_label.setText("停止读取数据") def read_data(self): - while self.reading_event.is_set() and self.serial_port: - try: - while self.serial_port.in_waiting > 0: - self.buffer.extend(self.serial_port.read(self.serial_port.in_waiting)) - while len(self.buffer) >= self.PACKET_SIZE: - if self.buffer[:2] == bytearray(self.PACKET_HEADER) and self.buffer[self.PACKET_SIZE-1] == self.PACKET_FOOTER: - packet = self.buffer[:self.PACKET_SIZE] - self.buffer = self.buffer[self.PACKET_SIZE:] - raw_values = [packet[2 + i*2] | (packet[3 + i*2] << 8) for i in range(self.NUM_CHANNELS)] - voltages = [(raw / 4096.0) * 3.3 for raw in raw_values] - for i in range(self.NUM_CHANNELS): - self.raw_data[i].append(raw_values[i]) - self.data[i].append(voltages[i]) - if self.start_time is not None: - elapsed_time = time.time() - self.start_time - self.timestamps.append(elapsed_time) - else: - self.buffer.pop(0) - time.sleep(0.01) - except serial.SerialException as e: - self.message_label.setText(f"读取数据失败: {e}") - self.stop_reading() - except Exception as e: - self.message_label.setText(f"未知错误: {e}") - self.stop_reading() + if self.test_mode: + self.generate_random_waveform() + else: + while self.reading_event.is_set() and self.serial_port: + try: + while self.serial_port.in_waiting > 0: + self.buffer.extend(self.serial_port.read(self.serial_port.in_waiting)) + while len(self.buffer) >= self.PACKET_SIZE: + if self.buffer[:2] == bytearray(self.PACKET_HEADER) and self.buffer[self.PACKET_SIZE-1] == self.PACKET_FOOTER: + packet = self.buffer[:self.PACKET_SIZE] + self.buffer = self.buffer[self.PACKET_SIZE:] + raw_values = [packet[2 + i*2] | (packet[3 + i*2] << 8) for i in range(self.NUM_CHANNELS)] + voltages = [(raw / 4096.0) * 3.3 for raw in raw_values] + for i in range(self.NUM_CHANNELS): + self.raw_data[i].append(raw_values[i]) + self.data[i].append(voltages[i]) + if self.start_time is not None: + elapsed_time = time.time() - self.start_time + self.timestamps.append(elapsed_time) + else: + self.buffer.pop(0) + time.sleep(0.01) + except serial.SerialException as e: + self.message_label.setText(f"读取数据失败: {e}") + self.stop_reading() + except Exception as e: + self.message_label.setText(f"未知错误: {e}") + self.stop_reading() + + def generate_random_waveform(self): + while self.reading_event.is_set(): + elapsed_time = time.time() - self.start_time + self.timestamps.append(elapsed_time) + for i in range(self.NUM_CHANNELS): + raw_value = random.randint(0, 4095) + voltage = (raw_value / 4096.0) * 3.3 + self.raw_data[i].append(raw_value) + self.data[i].append(voltage) + time.sleep(0.1) def update_plot(self): if self.timestamps: @@ -247,6 +272,14 @@ class VoltageReaderApp(QMainWindow): self.show_raw_button.setText('显示计算值' if self.show_raw else '显示原始值') self.update_plot() + def toggle_test_mode(self): + self.test_mode = not self.test_mode + self.test_button.setText('退出测试模式' if self.test_mode else '测试模式') + if self.test_mode: + self.start_reading() + else: + self.stop_reading() + def save_data(self): current_time = datetime.now().strftime("%Y%m%d_%H%M%S") default_file_name = f"data_{current_time}.xlsx" @@ -259,6 +292,8 @@ class VoltageReaderApp(QMainWindow): if not file_name.endswith('.xlsx'): file_name += '.xlsx' + student_name = self.remark_input.text() + data_dict = {"Timestamp": list(self.timestamps)} for i in range(self.NUM_CHANNELS): data_dict[f"Voltage_{i+1}"] = list(self.data[i]) @@ -270,7 +305,12 @@ class VoltageReaderApp(QMainWindow): QApplication.processEvents() try: - df.to_excel(file_name, index=False, engine='openpyxl') + with pd.ExcelWriter(file_name, engine='openpyxl') as writer: + df.to_excel(writer, index=False, startrow=1) + worksheet = writer.sheets['Sheet1'] + worksheet.merge_cells('A1:E1') + worksheet['A1'] = f"备注: {student_name}" + self.progress_bar.setValue(50) QApplication.processEvents() diff --git a/src/test.py b/src/test.py new file mode 100644 index 0000000..b6e9297 --- /dev/null +++ b/src/test.py @@ -0,0 +1,347 @@ +import sys +import serial +import serial.tools.list_ports +import threading +import time +import random +from collections import deque +from PyQt5.QtWidgets import QApplication, QMainWindow, QHBoxLayout, QVBoxLayout, QWidget, QPushButton, QComboBox, QLabel, QFileDialog, QProgressBar, QGridLayout, QGroupBox, QCheckBox, QLineEdit +from PyQt5.QtCore import QTimer, Qt +import pyqtgraph as pg +import pandas as pd +from datetime import datetime +import pyqtgraph.exporters + +class VoltageReaderApp(QMainWindow): + NUM_CHANNELS = 2 # 通道数量 + PACKET_SIZE = 7 # 数据包大小 + PACKET_HEADER = [0xFE, 0xEE] # 数据包头 + PACKET_FOOTER = 0xAA # 数据包尾 + BAUD_RATES = ['9600', '19200', '38400', '57600', '115200'] # 波特率选项 + PLOT_UPDATE_INTERVAL = 100 # 图表更新间隔(毫秒) + + def __init__(self): + super().__init__() + self.initUI() + self.serial_port = None + self.data = [deque() for _ in range(self.NUM_CHANNELS)] + self.raw_data = [deque() for _ in range(self.NUM_CHANNELS)] + self.timestamps = deque() + self.reading_event = threading.Event() + self.start_time = None + self.buffer = bytearray() + self.timer = QTimer() + self.timer.timeout.connect(self.update_plot) + self.auto_follow = False + self.show_raw = False + self.test_mode = False # 添加测试模式标志 + + def initUI(self): + self.setWindowTitle("Voltage Monitor") + self.setGeometry(100, 100, 1600, 700) + + central_widget = QWidget() + self.setCentralWidget(central_widget) + main_layout = QHBoxLayout(central_widget) + + control_layout = QVBoxLayout() + control_widget = QWidget() + control_widget.setLayout(control_layout) + control_widget.setFixedWidth(250) + main_layout.addWidget(control_widget) + + self.setup_port_group(control_layout) + self.setup_action_group(control_layout) + self.setup_channel_group(control_layout) + self.setup_plot_widget(main_layout) + + self.update_ports() + + def setup_port_group(self, layout): + port_group = QGroupBox("串口设置") + port_layout = QVBoxLayout() + port_group.setLayout(port_layout) + layout.addWidget(port_group) + + self.port_label = QLabel('选择串口:') + port_layout.addWidget(self.port_label) + + self.port_combo = QComboBox() + port_layout.addWidget(self.port_combo) + + self.refresh_button = QPushButton('刷新串口') + self.refresh_button.clicked.connect(self.update_ports) + port_layout.addWidget(self.refresh_button) + + self.baud_label = QLabel('选择波特率:') + port_layout.addWidget(self.baud_label) + + self.baud_combo = QComboBox() + self.baud_combo.addItems(self.BAUD_RATES) + self.baud_combo.setCurrentText('115200') + port_layout.addWidget(self.baud_combo) + + self.connect_button = QPushButton('连接') + self.connect_button.clicked.connect(self.connect_serial) + port_layout.addWidget(self.connect_button) + + def setup_action_group(self, layout): + action_group = QGroupBox("操作") + action_layout = QVBoxLayout() + action_group.setLayout(action_layout) + layout.addWidget(action_group) + + self.start_button = QPushButton('开始读取') + self.start_button.clicked.connect(self.start_reading) + self.start_button.setEnabled(False) + action_layout.addWidget(self.start_button) + + self.stop_button = QPushButton('停止读取') + self.stop_button.clicked.connect(self.stop_reading) + self.stop_button.setEnabled(False) + action_layout.addWidget(self.stop_button) + + self.auto_button = QPushButton('自动跟随') + self.auto_button.setCheckable(True) + self.auto_button.clicked.connect(self.toggle_auto_follow) + action_layout.addWidget(self.auto_button) + + self.show_raw_button = QPushButton('显示原始值') + self.show_raw_button.setCheckable(True) + self.show_raw_button.clicked.connect(self.toggle_show_raw) + action_layout.addWidget(self.show_raw_button) + + self.save_button = QPushButton('保存数据') + self.save_button.clicked.connect(self.save_data) + action_layout.addWidget(self.save_button) + + self.load_button = QPushButton('加载数据') + self.load_button.clicked.connect(self.load_data) + action_layout.addWidget(self.load_button) + + self.test_button = QPushButton('测试模式') + self.test_button.setCheckable(True) + self.test_button.clicked.connect(self.toggle_test_mode) + action_layout.addWidget(self.test_button) + + self.remark_label = QLabel('备注(学生姓名):') + action_layout.addWidget(self.remark_label) + + self.remark_input = QLineEdit() + action_layout.addWidget(self.remark_input) + + self.message_label = QLabel() + self.message_label.setWordWrap(True) + layout.addWidget(self.message_label) + + self.progress_bar = QProgressBar() + self.progress_bar.setAlignment(Qt.AlignCenter) + self.progress_bar.setVisible(False) + layout.addWidget(self.progress_bar) + + def setup_channel_group(self, layout): + channel_group = QGroupBox("通道选择") + channel_layout = QVBoxLayout() + channel_group.setLayout(channel_layout) + layout.addWidget(channel_group) + + self.checkboxes = [] + for i in range(self.NUM_CHANNELS): + color = (i*12, 255-i*12, 150) + checkbox = QCheckBox(f'adc{i+1}') + checkbox.setChecked(True) + checkbox.setStyleSheet(f'color: black; background-color: rgb({color[0]}, {color[1]}, {color[2]})') + checkbox.stateChanged.connect(lambda state, idx=i: self.toggle_curve_visibility(state, idx)) + channel_layout.addWidget(checkbox) + self.checkboxes.append(checkbox) + + def setup_plot_widget(self, layout): + self.plot_widget = pg.PlotWidget() + self.plot_widget.showGrid(x=True, y=True, alpha=0.3) + layout.addWidget(self.plot_widget) + + self.plot_data = [self.plot_widget.plot([], [], pen=pg.mkPen(color=(i*12, 255-i*12, 150))) for i in range(self.NUM_CHANNELS)] + + def update_ports(self): + self.port_combo.clear() + ports = [port.device for port in serial.tools.list_ports.comports()] + self.port_combo.addItems(ports) + self.message_label.setText("串口列表已更新") + + def connect_serial(self): + port = self.port_combo.currentText() + baudrate = self.baud_combo.currentText() + if port and baudrate: + try: + self.serial_port = serial.Serial(port, int(baudrate), timeout=1) + self.start_button.setEnabled(True) + self.stop_button.setEnabled(False) + self.message_label.setText(f"已连接到 {port},波特率 {baudrate}") + except serial.SerialException as e: + self.message_label.setText(f"连接失败: {e}") + + def start_reading(self): + if self.serial_port or self.test_mode: + self.reading_event.set() + self.start_button.setEnabled(False) + self.stop_button.setEnabled(True) + if not self.start_time: + self.start_time = time.time() + threading.Thread(target=self.read_data, daemon=True).start() + self.timer.start(self.PLOT_UPDATE_INTERVAL) + self.message_label.setText("开始读取数据") + else: + self.message_label.setText("错误: 串口未打开,无法读取数据") + + def stop_reading(self): + self.reading_event.clear() + self.start_button.setEnabled(True) + self.stop_button.setEnabled(False) + self.timer.stop() + self.message_label.setText("停止读取数据") + + def read_data(self): + if self.test_mode: + self.generate_random_waveform() + else: + while self.reading_event.is_set() and self.serial_port: + try: + while self.serial_port.in_waiting > 0: + self.buffer.extend(self.serial_port.read(self.serial_port.in_waiting)) + while len(self.buffer) >= self.PACKET_SIZE: + if self.buffer[:2] == bytearray(self.PACKET_HEADER) and self.buffer[self.PACKET_SIZE-1] == self.PACKET_FOOTER: + packet = self.buffer[:self.PACKET_SIZE] + self.buffer = self.buffer[self.PACKET_SIZE:] + raw_values = [packet[2 + i*2] | (packet[3 + i*2] << 8) for i in range(self.NUM_CHANNELS)] + voltages = [(raw / 4096.0) * 3.3 for raw in raw_values] + for i in range(self.NUM_CHANNELS): + self.raw_data[i].append(raw_values[i]) + self.data[i].append(voltages[i]) + if self.start_time is not None: + elapsed_time = time.time() - self.start_time + self.timestamps.append(elapsed_time) + else: + self.buffer.pop(0) + time.sleep(0.01) + except serial.SerialException as e: + self.message_label.setText(f"读取数据失败: {e}") + self.stop_reading() + except Exception as e: + self.message_label.setText(f"未知错误: {e}") + self.stop_reading() + + def generate_random_waveform(self): + while self.reading_event.is_set(): + elapsed_time = time.time() - self.start_time + self.timestamps.append(elapsed_time) + for i in range(self.NUM_CHANNELS): + raw_value = random.randint(0, 4095) + voltage = (raw_value / 4096.0) * 3.3 + self.raw_data[i].append(raw_value) + self.data[i].append(voltage) + time.sleep(0.1) + + def update_plot(self): + if self.timestamps: + for i in range(self.NUM_CHANNELS): + if self.checkboxes[i].isChecked(): + if self.show_raw: + self.plot_data[i].setData(list(self.timestamps), list(self.raw_data[i]), clear=True) + else: + self.plot_data[i].setData(list(self.timestamps), list(self.data[i]), clear=True) + else: + self.plot_data[i].clear() + if self.auto_follow: + self.plot_widget.setXRange(self.timestamps[-1] - 5, self.timestamps[-1], padding=0) + + def toggle_curve_visibility(self, state, index): + if state == 0: + self.plot_data[index].clear() + else: + if self.show_raw: + self.plot_data[index].setData(list(self.timestamps), list(self.raw_data[index])) + else: + self.plot_data[index].setData(list(self.timestamps), list(self.data[index])) + + def toggle_auto_follow(self): + self.auto_follow = not self.auto_follow + self.auto_button.setText('取消自动跟随' if self.auto_follow else '自动跟随') + + def toggle_show_raw(self): + self.show_raw = not self.show_raw + self.show_raw_button.setText('显示计算值' if self.show_raw else '显示原始值') + self.update_plot() + + def toggle_test_mode(self): + self.test_mode = not self.test_mode + self.test_button.setText('退出测试模式' if self.test_mode else '测试模式') + if self.test_mode: + self.start_reading() + else: + self.stop_reading() + + def save_data(self): + current_time = datetime.now().strftime("%Y%m%d_%H%M%S") + default_file_name = f"data_{current_time}.xlsx" + + options = QFileDialog.Options() + options |= QFileDialog.DontUseNativeDialog + file_name, _ = QFileDialog.getSaveFileName(self, "保存数据文件", default_file_name, "Excel Files (*.xlsx);;All Files (*)", options=options) + + if file_name: + if not file_name.endswith('.xlsx'): + file_name += '.xlsx' + + student_name = self.remark_input.text() + + data_dict = {"Timestamp": list(self.timestamps)} + for i in range(self.NUM_CHANNELS): + data_dict[f"Voltage_{i+1}"] = list(self.data[i]) + data_dict[f"Raw_{i+1}"] = list(self.raw_data[i]) + df = pd.DataFrame(data_dict) + + self.progress_bar.setVisible(True) + self.progress_bar.setValue(0) + QApplication.processEvents() + + try: + with pd.ExcelWriter(file_name, engine='openpyxl') as writer: + df.to_excel(writer, index=False, startrow=1) + worksheet = writer.sheets['Sheet1'] + worksheet.merge_cells('A1:E1') + worksheet['A1'] = f"备注: {student_name}" + + self.progress_bar.setValue(50) + QApplication.processEvents() + + image_filename = file_name.replace('.xlsx', '.png') + exporter = pg.exporters.ImageExporter(self.plot_widget.plotItem) + exporter.parameters()['width'] = 1600 + exporter.export(image_filename) + + self.progress_bar.setValue(100) + QApplication.processEvents() + + self.message_label.setText(f"数据已保存为 {file_name} 和 {image_filename}") + except Exception as e: + self.message_label.setText(f"保存数据失败: {e}") + finally: + self.progress_bar.setVisible(False) + + def load_data(self): + options = QFileDialog.Options() + file_name, _ = QFileDialog.getOpenFileName(self, "加载数据文件", "", "Excel Files (*.xlsx);;All Files (*)", options=options) + if file_name: + df = pd.read_excel(file_name) + self.timestamps = deque(df["Timestamp"].tolist()) + for i in range(self.NUM_CHANNELS): + self.data[i] = deque(df[f"Voltage_{i+1}"].tolist()) + self.raw_data[i] = deque(df[f"Raw_{i+1}"].tolist()) + self.update_plot() + self.message_label.setText(f"数据已加载自 {file_name}") + +if __name__ == '__main__': + app = QApplication(sys.argv) + window = VoltageReaderApp() + window.show() + sys.exit(app.exec_()) \ No newline at end of file