mirror of
				https://github.com/goldenfishs/MRobot.git
				synced 2025-10-30 06:35:43 +08:00 
			
		
		
		
	更新MRtool
This commit is contained in:
		
							parent
							
								
									511f9f4da8
								
							
						
					
					
						commit
						544b3745d5
					
				
							
								
								
									
										616
									
								
								MR_Tool.py
									
									
									
									
									
								
							
							
						
						
									
										616
									
								
								MR_Tool.py
									
									
									
									
									
								
							| @ -16,7 +16,12 @@ import numpy as np | ||||
| import pandas as pd | ||||
| import requests | ||||
| import webbrowser | ||||
| 
 | ||||
| import serial | ||||
| import serial.tools.list_ports | ||||
| from PyQt5.QtWidgets import QGroupBox, QGridLayout, QLineEdit, QTextBrowser | ||||
| from PyQt5.QtCore import QTimer, pyqtSlot | ||||
| from PyQt5.QtWidgets import QCheckBox | ||||
| from PyQt5.QtCore import QTimer | ||||
| 
 | ||||
| def resource_path(relative_path): | ||||
|     """兼容PyInstaller打包后资源路径""" | ||||
| @ -735,7 +740,607 @@ class DownloadPage(QWidget): | ||||
|         main_layout.addWidget(footer) | ||||
| 
 | ||||
| # --------- 功能三:串口助手 --------- | ||||
| # class SerialAssistant(QWidget): | ||||
| class SerialAssistant(QWidget): | ||||
|     def __init__(self): | ||||
|         super().__init__() | ||||
|         self.setFont(QFont("微软雅黑", 15)) | ||||
|         self.ser = None | ||||
|         self.timer = None | ||||
|         self.recv_buffer = b"" | ||||
|         self.plot_data = {} | ||||
|         self.curve_colors = ["#e74c3c", "#2980b9", "#27ae60", "#f1c40f", "#8e44ad", "#16a085"] | ||||
|         self.data_types = ["float", "int16", "uint16", "int8", "uint8"] | ||||
|         self.data_type = "float" | ||||
|         self.data_count = 2 | ||||
|         self.sample_idx = 0 | ||||
| 
 | ||||
|         # 新增:HEX模式复选框 | ||||
|         self.hex_send_chk = QCheckBox("HEX发送") | ||||
|         self.hex_recv_chk = QCheckBox("HEX接收") | ||||
|         self.hex_send_chk.setFont(QFont("微软雅黑", 10)) | ||||
|         self.hex_recv_chk.setFont(QFont("微软雅黑", 10)) | ||||
|         self.hex_send_chk.setChecked(False) | ||||
|         self.hex_recv_chk.setChecked(False) | ||||
| 
 | ||||
|         # 主体布局 | ||||
|         main_layout = QHBoxLayout(self) | ||||
|         main_layout.setContentsMargins(32, 32, 32, 32) | ||||
|         main_layout.setSpacing(28) | ||||
| 
 | ||||
|         # 左侧面板 | ||||
|         left_panel = QVBoxLayout() | ||||
|         left_panel.setSpacing(20) | ||||
|         left_panel.setContentsMargins(0, 0, 0, 0) | ||||
| 
 | ||||
|         # 串口配置区 | ||||
|         config_group = QGroupBox("串口配置") | ||||
|         config_group.setFont(QFont("微软雅黑", 14, QFont.Bold)) | ||||
|         config_group.setStyleSheet(""" | ||||
|             QGroupBox { | ||||
|                 border: 2px solid #b5d0ea; | ||||
|                 border-radius: 12px; | ||||
|                 margin-top: 12px; | ||||
|                 background: #f8fbfd; | ||||
|                 color: #2471a3; | ||||
|                 padding: 8px 0 0 0; | ||||
|             } | ||||
|             QGroupBox:title { | ||||
|                 subcontrol-origin: margin; | ||||
|                 left: 16px; | ||||
|                 top: -8px; | ||||
|                 background: transparent; | ||||
|                 padding: 0 8px; | ||||
|             } | ||||
|         """) | ||||
|         config_layout = QGridLayout() | ||||
|         config_layout.setSpacing(12) | ||||
|         config_layout.setContentsMargins(16, 16, 16, 16) | ||||
|         config_layout.addWidget(QLabel("串口号:"), 0, 0) | ||||
|         self.port_box = QComboBox() | ||||
|         self.port_box.setMinimumWidth(120) | ||||
|         self.refresh_ports() | ||||
|         config_layout.addWidget(self.port_box, 0, 1) | ||||
|         config_layout.addWidget(QLabel("波特率:"), 1, 0) | ||||
|         self.baud_box = QComboBox() | ||||
|         self.baud_box.addItems(["9600", "115200", "57600", "38400", "19200", "4800"]) | ||||
|         self.baud_box.setCurrentText("115200") | ||||
|         config_layout.addWidget(self.baud_box, 1, 1) | ||||
|         self.refresh_btn = QPushButton("刷新串口") | ||||
|         self.refresh_btn.clicked.connect(self.refresh_ports) | ||||
|         self.refresh_btn.setStyleSheet(self._btn_style()) | ||||
|         config_layout.addWidget(self.refresh_btn, 2, 0) | ||||
|         self.open_btn = QPushButton("打开串口") | ||||
|         self.open_btn.setCheckable(True) | ||||
|         self.open_btn.clicked.connect(self.toggle_serial) | ||||
|         self.open_btn.setStyleSheet(self._btn_style("#27ae60")) | ||||
|         config_layout.addWidget(self.open_btn, 2, 1) | ||||
|         config_group.setLayout(config_layout) | ||||
|         left_panel.addWidget(config_group) | ||||
| 
 | ||||
|         # 数据协议配置区 | ||||
|         proto_group = QGroupBox("数据协议配置") | ||||
|         proto_group.setFont(QFont("微软雅黑", 14, QFont.Bold)) | ||||
|         proto_group.setStyleSheet(config_group.styleSheet()) | ||||
|         proto_layout = QGridLayout() | ||||
|         proto_layout.setSpacing(12) | ||||
|         proto_layout.setContentsMargins(16, 16, 16, 16) | ||||
|         proto_layout.addWidget(QLabel("数据数量:"), 0, 0) | ||||
|         self.data_count_spin = QSpinBox() | ||||
|         self.data_count_spin.setRange(1, 16) | ||||
|         self.data_count_spin.setValue(self.data_count) | ||||
|         self.data_count_spin.valueChanged.connect(self.apply_proto_config) | ||||
|         proto_layout.addWidget(self.data_count_spin, 0, 1) | ||||
|         proto_layout.addWidget(QLabel("数据类型:"), 1, 0) | ||||
|         self.data_type_box = QComboBox() | ||||
|         self.data_type_box.addItems(self.data_types) | ||||
|         self.data_type_box.setCurrentText(self.data_type) | ||||
|         self.data_type_box.currentTextChanged.connect(self.apply_proto_config) | ||||
|         proto_layout.addWidget(self.data_type_box, 1, 1) | ||||
|         proto_group.setLayout(proto_layout) | ||||
|         left_panel.addWidget(proto_group) | ||||
| 
 | ||||
|         # 发送数据区 | ||||
|         send_group = QGroupBox("发送数据") | ||||
|         send_group.setFont(QFont("微软雅黑", 14, QFont.Bold)) | ||||
|         send_group.setStyleSheet(""" | ||||
|             QGroupBox { | ||||
|                 border: 2px solid #b5d0ea; | ||||
|                 border-radius: 12px; | ||||
|                 margin-top: 12px; | ||||
|                 background: #f8fbfd; | ||||
|                 color: #2471a3; | ||||
|                 padding: 8px 0 0 0; | ||||
|             } | ||||
|             QGroupBox:title { | ||||
|                 subcontrol-origin: margin; | ||||
|                 left: 16px; | ||||
|                 top: -8px; | ||||
|                 background: transparent; | ||||
|                 padding: 0 8px; | ||||
|             } | ||||
|         """) | ||||
|         send_layout = QVBoxLayout() | ||||
|         send_layout.setSpacing(14) | ||||
|         send_layout.setContentsMargins(12, 12, 12, 12) | ||||
| 
 | ||||
|         # 输入框(更大更高字体) | ||||
|         self.send_edit = QLineEdit() | ||||
|         self.send_edit.setFont(QFont("Consolas", 22)) | ||||
|         self.send_edit.setPlaceholderText("输入要发送的数据...") | ||||
|         self.send_edit.setMinimumHeight(54) | ||||
|         self.send_edit.setMinimumWidth(360) | ||||
|         self.send_edit.setStyleSheet(""" | ||||
|             QLineEdit { | ||||
|                 background: #f8fbfd; | ||||
|                 border-radius: 10px; | ||||
|                 border: 1.5px solid #d6eaf8; | ||||
|                 font-size: 22px; | ||||
|                 padding: 12px 18px; | ||||
|             } | ||||
|         """) | ||||
|         send_layout.addWidget(self.send_edit) | ||||
| 
 | ||||
|         # HEX复选框行(字体更小) | ||||
|         hex_chk_row = QHBoxLayout() | ||||
|         self.hex_send_chk.setFont(QFont("微软雅黑", 10)) | ||||
|         self.hex_recv_chk.setFont(QFont("微软雅黑", 10)) | ||||
|         for chk in [self.hex_send_chk, self.hex_recv_chk]: | ||||
|             chk.setStyleSheet(""" | ||||
|                 QCheckBox { | ||||
|                     color: #2471a3; | ||||
|                     spacing: 12px; | ||||
|                 } | ||||
|                 QCheckBox::indicator { | ||||
|                     width: 18px; | ||||
|                     height: 18px; | ||||
|                 } | ||||
|                 QCheckBox::indicator:checked { | ||||
|                     background-color: #2980b9; | ||||
|                     border: 1.5px solid #2980b9; | ||||
|                 } | ||||
|                 QCheckBox::indicator:unchecked { | ||||
|                     background-color: #fff; | ||||
|                     border: 1.5px solid #b5d0ea; | ||||
|                 } | ||||
|             """) | ||||
|         self.hex_send_chk.setChecked(False) | ||||
|         self.hex_recv_chk.setChecked(False) | ||||
|         hex_chk_row.addWidget(self.hex_send_chk) | ||||
|         hex_chk_row.addWidget(self.hex_recv_chk) | ||||
|         hex_chk_row.addStretch(1) | ||||
|         send_layout.addLayout(hex_chk_row) | ||||
| 
 | ||||
|         # 发送按钮 | ||||
|         send_btn_row = QHBoxLayout() | ||||
|         self.send_btn = QPushButton("发送") | ||||
|         self.send_btn.clicked.connect(self.send_data) | ||||
|         self.send_btn.setFont(QFont("微软雅黑", 16, QFont.Bold)) | ||||
|         self.send_btn.setStyleSheet(self._btn_style("#2980b9")) | ||||
|         send_btn_row.addWidget(self.send_btn) | ||||
|         send_layout.addLayout(send_btn_row) | ||||
| 
 | ||||
|         send_group.setLayout(send_layout) | ||||
|         left_panel.addWidget(send_group, stretch=1)  # 让发送区弹性填充 | ||||
| 
 | ||||
|         # 清空按钮 | ||||
|         self.clear_btn = QPushButton("清空接收和曲线") | ||||
|         self.clear_btn.clicked.connect(self.clear_all) | ||||
|         self.clear_btn.setStyleSheet(self._btn_style("#e74c3c")) | ||||
|         left_panel.addWidget(self.clear_btn) | ||||
| 
 | ||||
|         # 弹性空隙(动态扩展填满) | ||||
|         left_panel.addStretch(1) | ||||
| 
 | ||||
|         # 使用说明始终在最下方 | ||||
|         usage_group = QGroupBox("使用说明") | ||||
|         usage_group.setFont(QFont("微软雅黑", 13, QFont.Bold)) | ||||
|         usage_group.setStyleSheet(""" | ||||
|             QGroupBox { | ||||
|                 border: 2px solid #b5d0ea; | ||||
|                 border-radius: 10px; | ||||
|                 margin-top: 10px; | ||||
|                 background: #f8fbfd; | ||||
|                 color: #2471a3; | ||||
|                 padding: 6px 0 0 0; | ||||
|             } | ||||
|             QGroupBox:title { | ||||
|                 subcontrol-origin: margin; | ||||
|                 left: 10px; | ||||
|                 top: -8px; | ||||
|                 background: transparent; | ||||
|                 padding: 0 6px; | ||||
|             } | ||||
|         """) | ||||
|         usage_layout = QVBoxLayout() | ||||
|         usage_label = QLabel( | ||||
|             "1. 选择串口号和波特率,点击“打开串口”。\n" | ||||
|             "2. 在“数据协议配置”中选择数据数量和数据类型。\n" | ||||
|             "3. 下位机发送格式:\n" | ||||
|             "   0x55 + 数据数量(1字节) + 数据 + 校验和(1字节)\n" | ||||
|             "   校验和为包头到最后一个数据字节的累加和的低8位。\n" | ||||
|             "4. 每包数据自动绘制曲线,X轴为采样点(或时间),Y轴为各通道数据。\n" | ||||
|             "5. 支持float/int16/uint16/int8/uint8类型,最多16通道。\n" | ||||
|             "6. 可点击“测试绘图”按钮模拟数据包接收效果。" | ||||
|         ) | ||||
|         usage_label.setWordWrap(True) | ||||
|         usage_label.setFont(QFont("微软雅黑", 9)) | ||||
|         usage_layout.addWidget(usage_label) | ||||
|         usage_group.setLayout(usage_layout) | ||||
|         left_panel.addWidget(usage_group) | ||||
| 
 | ||||
|         main_layout.addLayout(left_panel, 0) | ||||
| 
 | ||||
|         # 右侧面板 | ||||
|         right_panel = QVBoxLayout() | ||||
|         right_panel.setSpacing(20) | ||||
| 
 | ||||
|         # 接收区 | ||||
|         recv_group = QGroupBox("串口接收区") | ||||
|         recv_group.setFont(QFont("微软雅黑", 14, QFont.Bold)) | ||||
|         recv_group.setStyleSheet(config_group.styleSheet()) | ||||
|         recv_layout = QVBoxLayout() | ||||
|         self.recv_box = QTextEdit() | ||||
|         self.recv_box.setFont(QFont("Consolas", 13)) | ||||
|         self.recv_box.setReadOnly(True) | ||||
|         self.recv_box.setMinimumHeight(120) | ||||
|         self.recv_box.setStyleSheet(""" | ||||
|             QTextEdit { | ||||
|                 background: #f8fbfd; | ||||
|                 border-radius: 10px; | ||||
|                 border: 1px solid #d6eaf8; | ||||
|                 font-size: 15px; | ||||
|                 color: #2c3e50; | ||||
|                 padding: 8px; | ||||
|             } | ||||
|         """) | ||||
|         recv_layout.addWidget(self.recv_box) | ||||
|         recv_group.setLayout(recv_layout) | ||||
|         right_panel.addWidget(recv_group) | ||||
| 
 | ||||
|         # 曲线绘图区 | ||||
|         plot_frame = QFrame() | ||||
|         plot_frame.setStyleSheet(""" | ||||
|             QFrame { | ||||
|                 background: #fff; | ||||
|                 border-radius: 16px; | ||||
|                 border: 1px solid #d6eaf8; | ||||
|             } | ||||
|         """) | ||||
|         plot_shadow = QGraphicsDropShadowEffect(self) | ||||
|         plot_shadow.setBlurRadius(18) | ||||
|         plot_shadow.setOffset(0, 4) | ||||
|         plot_shadow.setColor(Qt.gray) | ||||
|         plot_frame.setGraphicsEffect(plot_shadow) | ||||
|         plot_layout2 = QVBoxLayout(plot_frame) | ||||
|         plot_layout2.setContentsMargins(10, 10, 10, 10) | ||||
|         self.figure = Figure(figsize=(7, 4)) | ||||
|         self.canvas = FigureCanvas(self.figure) | ||||
|         plot_layout2.addWidget(self.canvas) | ||||
|         right_panel.addWidget(plot_frame, 2) | ||||
| 
 | ||||
|         main_layout.addLayout(right_panel, 1) | ||||
| 
 | ||||
|         # 定时器接收 | ||||
|         self.timer = QTimer(self) | ||||
|         self.timer.timeout.connect(self.read_serial) | ||||
| 
 | ||||
|         # 新增:正弦波测试定时器 | ||||
|         self.sine_timer = QTimer(self) | ||||
|         self.sine_timer.timeout.connect(self.send_sine_data) | ||||
|         self.sine_phase = 0 | ||||
|         # 默认配置 | ||||
|         self.apply_proto_config() | ||||
| 
 | ||||
| 
 | ||||
|     def simulate_data(self): | ||||
|         """模拟一包数据并自动解析绘图""" | ||||
|         import struct | ||||
|         import random | ||||
|         # 构造协议包 | ||||
|         head = 0x55 | ||||
|         count = self.data_count | ||||
|         dtype = self.data_type | ||||
|         # 随机生成数据 | ||||
|         if dtype == "float": | ||||
|             vals = [random.uniform(-10, 10) for _ in range(count)] | ||||
|             data_bytes = struct.pack(f"<{count}f", *vals) | ||||
|         elif dtype == "int16": | ||||
|             vals = [random.randint(-30000, 30000) for _ in range(count)] | ||||
|             data_bytes = struct.pack(f"<{count}h", *vals) | ||||
|         elif dtype == "uint16": | ||||
|             vals = [random.randint(0, 65535) for _ in range(count)] | ||||
|             data_bytes = struct.pack(f"<{count}H", *vals) | ||||
|         elif dtype == "int8": | ||||
|             vals = [random.randint(-128, 127) for _ in range(count)] | ||||
|             data_bytes = struct.pack(f"<{count}b", *vals) | ||||
|         elif dtype == "uint8": | ||||
|             vals = [random.randint(0, 255) for _ in range(count)] | ||||
|             data_bytes = struct.pack(f"<{count}B", *vals) | ||||
|         else: | ||||
|             vals = [0] * count | ||||
|             data_bytes = b"\x00" * (count * self._type_size()) | ||||
|         # 拼包 | ||||
|         pkt = bytes([head, count]) + data_bytes | ||||
|         checksum = sum(pkt) & 0xFF | ||||
|         pkt += bytes([checksum]) | ||||
|         # 加入接收缓冲区并解析 | ||||
|         self.recv_buffer += pkt | ||||
|         self.parse_and_plot_bin() | ||||
| 
 | ||||
|     def _btn_style(self, color="#2980b9"): | ||||
|         return f""" | ||||
|             QPushButton {{ | ||||
|                 background: qlineargradient(x1:0, y1:0, x2:1, y2:1, | ||||
|                     stop:0 #eaf6fb, stop:1 #d6eaf8); | ||||
|                 color: {color}; | ||||
|                 border-radius: 14px; | ||||
|                 font-size: 16px; | ||||
|                 font-weight: 600; | ||||
|                 padding: 8px 0; | ||||
|                 border: 1.5px solid #d6eaf8; | ||||
|                 letter-spacing: 1px; | ||||
|             }} | ||||
|             QPushButton:hover {{ | ||||
|                 background: qlineargradient(x1:0, y1:0, x2:1, y2:1, | ||||
|                     stop:0 #f8fffe, stop:1 #cfe7fa); | ||||
|                 color: #1a6fae; | ||||
|                 border: 2px solid #b5d0ea; | ||||
|             }} | ||||
|             QPushButton:pressed {{ | ||||
|                 background: #e3f0fa; | ||||
|                 color: {color}; | ||||
|                 border: 2px solid #a4cbe3; | ||||
|             }} | ||||
|         """ | ||||
| 
 | ||||
|     def apply_proto_config(self): | ||||
|         self.data_count = self.data_count_spin.value() | ||||
|         self.data_type = self.data_type_box.currentText() | ||||
|         self.plot_data = {i: [[], []] for i in range(self.data_count)} | ||||
|         self.sample_idx = 0 | ||||
|         self.update_plot() | ||||
| 
 | ||||
|     def refresh_ports(self): | ||||
|         self.port_box.clear() | ||||
|         ports = serial.tools.list_ports.comports() | ||||
|         for port in ports: | ||||
|             self.port_box.addItem(port.device) | ||||
|         if self.port_box.count() == 0: | ||||
|             self.port_box.addItem("无可用串口") | ||||
| 
 | ||||
|     def toggle_serial(self): | ||||
|         if self.open_btn.isChecked(): | ||||
|             port = self.port_box.currentText() | ||||
|             baud = int(self.baud_box.currentText()) | ||||
|             try: | ||||
|                 self.ser = serial.Serial(port, baud, timeout=0.1) | ||||
|                 self.open_btn.setText("关闭串口") | ||||
|                 self.recv_box.append(f"已打开串口 {port} @ {baud}bps") | ||||
|                 self.timer.start(50) | ||||
|             except Exception as e: | ||||
|                 self.recv_box.append(f"打开串口失败: {e}") | ||||
|                 self.open_btn.setChecked(False) | ||||
|         else: | ||||
|             if self.ser and self.ser.is_open: | ||||
|                 self.ser.close() | ||||
|             self.open_btn.setText("打开串口") | ||||
|             self.recv_box.append("串口已关闭") | ||||
|             self.timer.stop() | ||||
| 
 | ||||
|     def send_data(self): | ||||
|         if self.ser and self.ser.is_open: | ||||
|             data = self.send_edit.text() | ||||
|             try: | ||||
|                 if self.hex_send_chk.isChecked(): | ||||
|                     # HEX模式发送 | ||||
|                     data_bytes = bytes.fromhex(data.replace(' ', '')) | ||||
|                     self.ser.write(data_bytes) | ||||
|                     self.recv_box.append(f"发送(HEX): {data_bytes.hex(' ').upper()}") | ||||
|                 else: | ||||
|                     self.ser.write(data.encode('utf-8')) | ||||
|                     self.recv_box.append(f"发送: {data}") | ||||
|             except Exception as e: | ||||
|                 self.recv_box.append(f"发送失败: {e}") | ||||
|         else: | ||||
|             self.recv_box.append("串口未打开,无法发送。") | ||||
| 
 | ||||
|     def send_multi_data(self): | ||||
|         if self.ser and self.ser.is_open: | ||||
|             text = self.send_edit.text() | ||||
|             lines = text.split(";") | ||||
|             for line in lines: | ||||
|                 if line.strip(): | ||||
|                     try: | ||||
|                         if self.hex_send_chk.isChecked(): | ||||
|                             data_bytes = bytes.fromhex(line.strip().replace(' ', '')) | ||||
|                             self.ser.write(data_bytes) | ||||
|                             self.recv_box.append(f"发送(HEX): {data_bytes.hex(' ').upper()}") | ||||
|                         else: | ||||
|                             self.ser.write(line.strip().encode('utf-8')) | ||||
|                             self.recv_box.append(f"发送: {line.strip()}") | ||||
|                     except Exception as e: | ||||
|                         self.recv_box.append(f"发送失败: {e}") | ||||
|         else: | ||||
|             self.recv_box.append("串口未打开,无法发送。") | ||||
| 
 | ||||
|     def read_serial(self): | ||||
|         if self.ser and self.ser.is_open: | ||||
|             try: | ||||
|                 data = self.ser.read_all() | ||||
|                 if data: | ||||
|                     if self.hex_recv_chk.isChecked(): | ||||
|                         self.recv_box.append(f"接收(HEX): {data.hex(' ').upper()}") | ||||
|                     else: | ||||
|                         try: | ||||
|                             self.recv_box.append(f"接收: {data.decode('utf-8', errors='replace')}") | ||||
|                         except Exception: | ||||
|                             self.recv_box.append(f"接收(HEX): {data.hex(' ').upper()}") | ||||
|                     self.recv_buffer += data | ||||
|                     self.parse_and_plot_bin() | ||||
|             except Exception as e: | ||||
|                 self.recv_box.append(f"接收失败: {e}") | ||||
| 
 | ||||
|     def toggle_sine_test(self): | ||||
|         if self.test_btn.isChecked(): | ||||
|             self.test_btn.setText("停止测试") | ||||
|             self.sine_phase = 0 | ||||
|             self.sine_timer.start(80)  # 80ms周期 | ||||
|         else: | ||||
|             self.test_btn.setText("测试正弦波(持续)") | ||||
|             self.sine_timer.stop() | ||||
| 
 | ||||
|     def send_sine_data(self): | ||||
|         import struct, math | ||||
|         head = 0x55 | ||||
|         count = self.data_count | ||||
|         dtype = self.data_type | ||||
|         t = self.sine_phase | ||||
|         vals = [] | ||||
|         for i in range(count): | ||||
|             # 多通道不同相位 | ||||
|             val = math.sin(t / 10.0 + i * math.pi / 4) * 10 | ||||
|             if dtype == "float": | ||||
|                 vals.append(float(val)) | ||||
|             elif dtype == "int16": | ||||
|                 vals.append(int(val * 1000)) | ||||
|             elif dtype == "uint16": | ||||
|                 vals.append(int(val * 1000 + 20000)) | ||||
|             elif dtype == "int8": | ||||
|                 vals.append(int(val * 10)) | ||||
|             elif dtype == "uint8": | ||||
|                 vals.append(int(val * 10 + 100)) | ||||
|         # 打包 | ||||
|         if dtype == "float": | ||||
|             data_bytes = struct.pack(f"<{count}f", *vals) | ||||
|         elif dtype == "int16": | ||||
|             data_bytes = struct.pack(f"<{count}h", *vals) | ||||
|         elif dtype == "uint16": | ||||
|             data_bytes = struct.pack(f"<{count}H", *vals) | ||||
|         elif dtype == "int8": | ||||
|             data_bytes = struct.pack(f"<{count}b", *vals) | ||||
|         elif dtype == "uint8": | ||||
|             data_bytes = struct.pack(f"<{count}B", *vals) | ||||
|         else: | ||||
|             data_bytes = b"\x00" * (count * self._type_size()) | ||||
|         pkt = bytes([head, count]) + data_bytes | ||||
|         checksum = sum(pkt) & 0xFF | ||||
|         pkt += bytes([checksum]) | ||||
|         # 直接走接收流程模拟 | ||||
|         self.recv_buffer += pkt | ||||
|         self.parse_and_plot_bin() | ||||
|         # 在接收区实时显示理论值 | ||||
|         self.recv_box.append(f"理论: {['%.3f'%v for v in vals]}") | ||||
|         self.sine_phase += 1 | ||||
| 
 | ||||
|     def parse_and_plot_bin(self): | ||||
|         # 协议:0x55 + 数据数量(1B) + 数据 + 校验(1B) | ||||
|         min_len = 1 + 1 + self.data_count * self._type_size() + 1 | ||||
|         while len(self.recv_buffer) >= min_len: | ||||
|             idx = self.recv_buffer.find(b'\x55') | ||||
|             if idx == -1: | ||||
|                 self.recv_buffer = b"" | ||||
|                 break | ||||
|             if idx > 0: | ||||
|                 self.recv_buffer = self.recv_buffer[idx:] | ||||
|             if len(self.recv_buffer) < min_len: | ||||
|                 break | ||||
|             # 检查数量 | ||||
|             count = self.recv_buffer[1] | ||||
|             if count != self.data_count: | ||||
|                 self.recv_buffer = self.recv_buffer[2:] | ||||
|                 continue | ||||
|             data_bytes = self.recv_buffer[2:2+count*self._type_size()] | ||||
|             checksum = self.recv_buffer[2+count*self._type_size()] | ||||
|             calc_sum = (sum(self.recv_buffer[:2+count*self._type_size()])) & 0xFF | ||||
|             if checksum != calc_sum: | ||||
|                 self.recv_box.append("校验和错误,丢弃包") | ||||
|                 self.recv_buffer = self.recv_buffer[1:] | ||||
|                 continue | ||||
|             # 解析数据 | ||||
|             values = self._unpack_data(data_bytes, count) | ||||
|             self.recv_box.append(f"接收: {values}") | ||||
|             for i, v in enumerate(values): | ||||
|                 self.plot_data[i][0].append(self.sample_idx) | ||||
|                 self.plot_data[i][1].append(v) | ||||
|                 if len(self.plot_data[i][0]) > 200: | ||||
|                     self.plot_data[i][0].pop(0) | ||||
|                     self.plot_data[i][1].pop(0) | ||||
|             self.sample_idx += 1 | ||||
|             self.recv_buffer = self.recv_buffer[min_len:] | ||||
|             self.update_plot() | ||||
| 
 | ||||
|     def _type_size(self): | ||||
|         if self.data_type == "float": | ||||
|             return 4 | ||||
|         elif self.data_type in ("int16", "uint16"): | ||||
|             return 2 | ||||
|         elif self.data_type in ("int8", "uint8"): | ||||
|             return 1 | ||||
|         return 4 | ||||
| 
 | ||||
|     def _unpack_data(self, data_bytes, count): | ||||
|         import struct | ||||
|         fmt = { | ||||
|             "float": f"<{count}f", | ||||
|             "int16": f"<{count}h", | ||||
|             "uint16": f"<{count}H", | ||||
|             "int8": f"<{count}b", | ||||
|             "uint8": f"<{count}B" | ||||
|         }[self.data_type] | ||||
|         try: | ||||
|             return struct.unpack(fmt, data_bytes) | ||||
|         except Exception: | ||||
|             return [0] * count | ||||
| 
 | ||||
|     def update_plot(self): | ||||
|         self.figure.clear() | ||||
|         ax = self.figure.add_subplot(111) | ||||
|         ax.set_xlabel("采样点", fontsize=14) | ||||
|         ax.set_ylabel("数据值", fontsize=14) | ||||
|         has_curve = False | ||||
|         for idx in range(self.data_count): | ||||
|             color = self.curve_colors[idx % len(self.curve_colors)] | ||||
|             x_list, y_list = self.plot_data.get(idx, ([], [])) | ||||
|             if x_list and y_list: | ||||
|                 ax.plot(x_list, y_list, label=f"CH{idx+1}", color=color, linewidth=2) | ||||
|                 has_curve = True | ||||
|         if has_curve: | ||||
|             ax.legend() | ||||
|         ax.grid(True, linestyle="--", alpha=0.5) | ||||
|         self.canvas.draw() | ||||
| 
 | ||||
|     def clear_all(self): | ||||
|         self.recv_box.clear() | ||||
|         self.plot_data = {i: [[], []] for i in range(self.data_count)} | ||||
|         self.sample_idx = 0 | ||||
|         self.update_plot() | ||||
| 
 | ||||
| # --------- 功能四:MRobot架构生成 --------- | ||||
| class GenerateMRobotCode(QWidget): | ||||
|     def __init__(self): | ||||
|         super().__init__() | ||||
|         self.setFont(QFont("微软雅黑", 15)) | ||||
|         self.setStyleSheet(""" | ||||
|             QWidget { | ||||
|                 background: #f8fbfd; | ||||
|                 border-radius: 16px; | ||||
|                 padding: 20px; | ||||
|             } | ||||
|         """) | ||||
|         self.init_ui() | ||||
| 
 | ||||
|     def init_ui(self): | ||||
|         main_layout = QVBoxLayout(self) | ||||
|         main_layout.setContentsMargins(20, 20, 20, 20) | ||||
|         main_layout.setSpacing(20) | ||||
| 
 | ||||
|         # 功能说明 | ||||
|         desc_label = QLabel("MRobot架构生成工具,帮助您快速生成MRobot项目代码。") | ||||
|         desc_label.setFont(QFont("微软雅黑", 14)) | ||||
|         desc_label.setAlignment(Qt.AlignCenter) | ||||
|         main_layout.addWidget(desc_label) | ||||
| 
 | ||||
|         # 其他UI元素... | ||||
| 
 | ||||
| # --------- 主工具箱UI --------- | ||||
| class ToolboxUI(QWidget): | ||||
| @ -794,7 +1399,7 @@ class ToolboxUI(QWidget): | ||||
|         left_layout.addWidget(logo_label) | ||||
| 
 | ||||
|         # 按钮区 | ||||
|         self.button_names = ["主页", "曲线拟合", "功能三", "软件指南"] | ||||
|         self.button_names = ["主页", "曲线拟合", "Mini串口助手", "MR架构配置","软件指南"] | ||||
|         self.buttons = [] | ||||
|         for idx, name in enumerate(self.button_names): | ||||
|             btn = QPushButton(name) | ||||
| @ -865,8 +1470,9 @@ class ToolboxUI(QWidget): | ||||
|         self.page_widgets = { | ||||
|             0: HomePage(),  # 主页 | ||||
|             1: PolyFitApp(),  # 多项式拟合 | ||||
|             2: self.placeholder_page("功能三开发中..."), | ||||
|             3: DownloadPage(),  # 下载页面 | ||||
|             2: SerialAssistant(),  # 串口助手 | ||||
|             3: GenerateMRobotCode(),  # MRobot架构生成 | ||||
|             4: DownloadPage(),  # 下载页面 | ||||
|         } | ||||
|         for i in range(len(self.button_names)): | ||||
|             self.stack.addWidget(self.page_widgets[i]) | ||||
|  | ||||
		Loading…
	
		Reference in New Issue
	
	Block a user
	 RB
						RB