mirror of
https://github.com/goldenfishs/MRobot.git
synced 2025-06-14 22:16:38 +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