diff --git a/LICENSE b/LICENSE
deleted file mode 100644
index cb386d0..0000000
--- a/LICENSE
+++ /dev/null
@@ -1,21 +0,0 @@
-MIT License
-
-Copyright (c) 2025 zucheng Lv
-
-Permission is hereby granted, free of charge, to any person obtaining a copy
-of this software and associated documentation files (the "Software"), to deal
-in the Software without restriction, including without limitation the rights
-to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
-copies of the Software, and to permit persons to whom the Software is
-furnished to do so, subject to the following conditions:
-
-The above copyright notice and this permission notice shall be included in all
-copies or substantial portions of the Software.
-
-THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
-IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
-FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
-AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
-LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
-OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
-SOFTWARE.
diff --git a/MR_Tool.py b/MR_Tool.py
deleted file mode 100644
index 29bf82c..0000000
--- a/MR_Tool.py
+++ /dev/null
@@ -1,2339 +0,0 @@
-import sys
-import os
-import numpy as np
-import pandas as pd
-import requests
-import webbrowser
-import serial
-import serial.tools.list_ports
-from PyQt5.QtWidgets import (
- QApplication, QWidget, QLabel, QPushButton, QTextEdit, QVBoxLayout,
- QHBoxLayout, QStackedWidget, QSizePolicy, QFrame, QGraphicsDropShadowEffect,
- QSpinBox, QTableWidget, QTableWidgetItem, QFileDialog, QComboBox, QMessageBox, QHeaderView,
- QGroupBox, QGridLayout, QLineEdit, QTextBrowser, QCheckBox
-)
-from PyQt5.QtGui import QPixmap, QFont, QIcon, QPainter, QPen, QColor
-from PyQt5.QtCore import Qt, QTimer, QPointF, pyqtSlot
-import matplotlib
-from matplotlib.backends.backend_qt5agg import FigureCanvasQTAgg as FigureCanvas
-from matplotlib.figure import Figure
-from PyQt5.QtCore import pyqtSignal, pyqtSlot
-from PyQt5.QtWebEngineWidgets import QWebEngineView # 新增
-from PyQt5.QtCore import QUrl
-from PyQt5.QtCore import QThread
-from PyQt5.QtWebEngineWidgets import QWebEngineProfile
-from PyQt5.QtWidgets import QFileDialog
-from PyQt5.QtWebEngineWidgets import QWebEngineView
-from PyQt5.QtWebEngineWidgets import QWebEngineView, QWebEngineProfile, QWebEnginePage
-from PyQt5.QtWebEngineWidgets import QWebEngineView, QWebEnginePage
-from PyQt5.QtWidgets import QSplashScreen
-from PyQt5.QtCore import Qt, QTimer
-from PyQt5.QtGui import QPixmap
-
-
-def resource_path(relative_path):
- """兼容PyInstaller打包后资源路径"""
- if hasattr(sys, '_MEIPASS'):
- return os.path.join(sys._MEIPASS, relative_path)
- return os.path.join(os.path.abspath("."), relative_path)
-
-# --------- 主页 ---------
-class HomePage(QWidget):
- def __init__(self):
- super().__init__()
- layout = QVBoxLayout(self)
- layout.setContentsMargins(60, 60, 60, 60)
- layout.setSpacing(32)
- self.setStyleSheet("""
- QWidget {
- background: qlineargradient(x1:0, y1:0, x2:1, y2:1,
- stop:0 #f8fbfd, stop:1 #eaf6fb);
- border-radius: 18px;
- }
- """)
-
- # 欢迎标题
- title = QLabel("欢迎来到 MRobot 工具箱!")
- title.setFont(QFont("微软雅黑", 26, QFont.Bold))
- title.setAlignment(Qt.AlignCenter)
- title.setStyleSheet("color: #2980b9; letter-spacing: 3px;")
- layout.addWidget(title)
- # 设置高度
- title.setFixedHeight(120)
-
- # 分割线
- line = QFrame()
- line.setFrameShape(QFrame.HLine)
- line.setFrameShadow(QFrame.Sunken)
- line.setStyleSheet("color: #d6eaf8; background: #d6eaf8; min-height: 2px;")
- layout.addWidget(line)
-
- # 介绍内容
- desc = QLabel(
- "🤖 本工具箱由青岛理工大学(QUT)机器人战队开发,\n"
- "涵盖沧溟(Robocon)与MOVE(Robomaster)两支队伍。\n\n"
- "集成了常用小工具与助手功能,持续更新中ing!\n"
- "👉 可通过左侧选择不同模块,助力更高效的机器人开发,\n"
- "节约开发时间,减少繁琐操作。\n\n"
- "欢迎反馈建议,共同完善工具箱!"
- )
- desc.setFont(QFont("微软雅黑", 16))
- desc.setAlignment(Qt.AlignCenter)
- desc.setStyleSheet("color: #34495e;")
- desc.setWordWrap(True)
- layout.addWidget(desc)
-
- # 作者&版本信息
- info = QLabel(
- "作者: QUT RMer & RCer | "
- "版本: 0.0.2 | "
- "联系方式: QQ群 : 857466609"
- )
- info.setFont(QFont("微软雅黑", 14))
- info.setAlignment(Qt.AlignCenter)
- info.setStyleSheet("color: #7f8c8d; margin-top: 24px;")
- info.setFixedHeight(100) # 修改为固定高度
- layout.addWidget(info)
-
- # 页脚
- footer = QLabel("© 2025 MRobot. 保留所有权利。")
- footer.setFont(QFont("微软雅黑", 12))
- footer.setAlignment(Qt.AlignCenter)
- footer.setStyleSheet("color: #b2bec3; margin-top: 18px;")
- footer.setFixedHeight(100) # 修改为固定高度
- layout.addWidget(footer)
-
-# --------- 功能一:多项式拟合工具页面 ---------
-class PolyFitApp(QWidget):
- def __init__(self):
- super().__init__()
- self.setFont(QFont("微软雅黑", 15))
- self.data_x = []
- self.data_y = []
- self.last_coeffs = None
- self.last_xmin = None
- self.last_xmax = None
-
- # 统一背景和边框
- self.setStyleSheet("""
- QWidget {
- background: qlineargradient(x1:0, y1:0, x2:1, y2:1,
- stop:0 #eaf6fb, stop:1 #d6eaf8);
- border-radius: 16px;
- border: 1px solid #d6eaf8;
- }
- """)
-
- main_layout = QHBoxLayout(self)
- main_layout.setContentsMargins(24, 24, 24, 24)
- main_layout.setSpacing(24)
- left_layout = QVBoxLayout()
- left_layout.setSpacing(18)
- right_layout = QVBoxLayout()
- right_layout.setSpacing(18)
- main_layout.addLayout(left_layout, 0)
- main_layout.addLayout(right_layout, 1)
-
- # 标题
- title = QLabel("曲线拟合工具")
- # title.setFont(QFont("微软雅黑", 2, QFont.Bold))
- # 设置文字大小
- title.setFont(QFont("微软雅黑", 14, QFont.Bold))
- title.setAlignment(Qt.AlignCenter)
- title.setStyleSheet("color: #2980b9; letter-spacing: 2px;")
- left_layout.addWidget(title)
-
- # 数据表
- self.table = QTableWidget(0, 2)
- self.table.setFont(QFont("Consolas", 16))
- self.table.setHorizontalHeaderLabels(["x", "y"])
- self.table.horizontalHeader().setSectionResizeMode(QHeaderView.Stretch)
- self.table.setSelectionBehavior(QTableWidget.SelectRows)
- self.table.setStyleSheet("""
- QTableWidget {
- background: #f8fbfd;
- border-radius: 10px;
- border: 1px solid #d6eaf8;
- font-size: 16px;
- }
- QHeaderView::section {
- background-color: #eaf6fb;
- color: #2980b9;
- font-size: 16px;
- font-weight: bold;
- border: 1px solid #d6eaf8;
- height: 36px;
- }
- """)
- self.table.setMinimumHeight(200) # 设置最小高度
- left_layout.addWidget(self.table, stretch=1) # 让表格尽量撑大
-
- # 添加/删除行
- btn_row = QHBoxLayout()
- self.add_row_btn = QPushButton("添加数据")
- self.add_row_btn.setFont(QFont("微软雅黑", 20, QFont.Bold))
- self.add_row_btn.setMinimumHeight(44)
- self.add_row_btn.clicked.connect(self.add_point_row)
- self.del_row_btn = QPushButton("删除选中行")
- self.del_row_btn.setFont(QFont("微软雅黑", 20, QFont.Bold))
- self.del_row_btn.setMinimumHeight(44)
- self.del_row_btn.clicked.connect(self.delete_selected_rows)
- for btn in [self.add_row_btn, self.del_row_btn]:
- btn.setStyleSheet("""
- QPushButton {
- background: qlineargradient(x1:0, y1:0, x2:1, y2:1,
- stop:0 #eaf6fb, stop:1 #d6eaf8);
- color: #2980b9;
- border-radius: 20px;
- font-size: 20px;
- font-weight: 600;
- padding: 10px 0;
- border: 1px solid #d6eaf8;
- }
- QPushButton:hover {
- background: qlineargradient(x1:0, y1:0, x2:1, y2:1,
- stop:0 #f8fffe, stop:1 #cfe7fa);
- color: #1a6fae;
- border: 1.5px solid #b5d0ea;
- }
- QPushButton:pressed {
- background: #e3f0fa;
- color: #2471a3;
- border: 1.5px solid #a4cbe3;
- }
- """)
- btn_row.addWidget(self.add_row_btn)
- btn_row.addWidget(self.del_row_btn)
- left_layout.addLayout(btn_row)
-
- # 导入/导出
- file_btn_row = QHBoxLayout()
- self.import_btn = QPushButton("导入Excel文件")
- self.import_btn.setFont(QFont("微软雅黑", 18, QFont.Bold))
- self.import_btn.setMinimumHeight(44)
- self.import_btn.clicked.connect(self.load_excel)
- self.export_btn = QPushButton("导出Excel文件")
- self.export_btn.setFont(QFont("微软雅黑", 18, QFont.Bold))
- self.export_btn.setMinimumHeight(44)
- self.export_btn.clicked.connect(self.export_excel_and_plot)
- for btn in [self.import_btn, self.export_btn]:
- btn.setStyleSheet("""
- QPushButton {
- background: qlineargradient(x1:0, y1:0, x2:1, y2:1,
- stop:0 #eaf6fb, stop:1 #d6eaf8);
- color: #2980b9;
- border-radius: 20px;
- font-size: 20px;
- font-weight: 600;
- padding: 10px 0;
- border: 1px solid #d6eaf8;
- }
- QPushButton:hover {
- background: qlineargradient(x1:0, y1:0, x2:1, y2:1,
- stop:0 #f8fffe, stop:1 #cfe7fa);
- color: #1a6fae;
- border: 1.5px solid #b5d0ea;
- }
- QPushButton:pressed {
- background: #e3f0fa;
- color: #2471a3;
- border: 1.5px solid #a4cbe3;
- }
- """)
- file_btn_row.addWidget(self.import_btn)
- file_btn_row.addWidget(self.export_btn)
- left_layout.addLayout(file_btn_row)
-
- # 阶数选择
- param_layout = QHBoxLayout()
- label_order = QLabel("多项式阶数:")
- # 文字居中
- label_order.setAlignment(Qt.AlignCenter)
- # 文字加粗
- label_order.setStyleSheet("color: #2980b9;")
- param_layout.addWidget(label_order)
- self.order_spin = QSpinBox()
- self.order_spin.setFont(QFont("微软雅黑", 18))
- self.order_spin.setRange(1, 10)
- self.order_spin.setValue(2)
- self.order_spin.setStyleSheet("""
- QSpinBox {
- background: #f8fbfd;
- border-radius: 10px;
- border: 1px solid #d6eaf8;
- font-size: 18px;
- padding: 4px 12px;
- }
- """)
- param_layout.addWidget(self.order_spin)
- left_layout.addLayout(param_layout)
-
- # 拟合按钮
- self.fit_btn = QPushButton("拟合并显示")
- self.fit_btn.setFont(QFont("微软雅黑", 20, QFont.Bold))
- self.fit_btn.setMinimumHeight(48)
- self.fit_btn.clicked.connect(self.fit_and_plot)
- self.fit_btn.setStyleSheet("""
- QPushButton {
- background: qlineargradient(x1:0, y1:0, x2:1, y2:1,
- stop:0 #eaf6fb, stop:1 #d6eaf8);
- color: #2980b9;
- border-radius: 20px;
- font-size: 22px;
- font-weight: 600;
- padding: 12px 0;
- border: 1px solid #d6eaf8;
- }
- QPushButton:hover {
- background: qlineargradient(x1:0, y1:0, x2:1, y2:1,
- stop:0 #f8fffe, stop:1 #cfe7fa);
- color: #1a6fae;
- border: 1.5px solid #b5d0ea;
- }
- QPushButton:pressed {
- background: #e3f0fa;
- color: #2471a3;
- border: 1.5px solid #a4cbe3;
- }
- """)
- left_layout.addWidget(self.fit_btn)
- # 输出区
- self.output = QTextEdit()
- self.output.setReadOnly(False)
- self.output.setFont(QFont("Consolas", 16))
- self.output.setMaximumHeight(160)
- self.output.setStyleSheet("""
- QTextEdit {
- background: #f4f6f7;
- border-radius: 8px;
- border: 1px solid #d6eaf8;
- font-size: 16px;
- color: #2c3e50;
- padding: 10px;
- }
- """)
- # self.table.setFixedHeight(400) # 设置表格高度为260像素
- left_layout.addWidget(self.output)
- # 代码生成
- code_layout = QHBoxLayout()
- label_code = QLabel("输出代码格式:")
- # label_code.setFont(QFont("微软雅黑", 18, QFont.Bold))
- label_code.setStyleSheet("color: #2980b9;")
- code_layout.addWidget(label_code)
- self.code_type = QComboBox()
- self.code_type.setFont(QFont("微软雅黑", 18))
- self.code_type.addItems(["C", "C++", "Python"])
- self.code_type.setStyleSheet("""
- QComboBox {
- background: #f8fbfd;
- border-radius: 10px;
- border: 1px solid #d6eaf8;
- font-size: 18px;
- padding: 4px 12px;
- }
- """)
- code_layout.addWidget(self.code_type)
- self.gen_code_btn = QPushButton("生成代码")
- self.gen_code_btn.setFont(QFont("微软雅黑", 18, QFont.Bold))
- self.gen_code_btn.setMinimumHeight(44)
- self.gen_code_btn.clicked.connect(self.generate_code)
- self.gen_code_btn.setStyleSheet("""
- QPushButton {
- background: qlineargradient(x1:0, y1:0, x2:1, y2:1,
- stop:0 #eaf6fb, stop:1 #d6eaf8);
- color: #2980b9;
- border-radius: 20px;
- font-size: 20px;
- font-weight: 600;
- padding: 10px 0;
- border: 1px solid #d6eaf8;
- }
- QPushButton:hover {
- background: qlineargradient(x1:0, y1:0, x2:1, y2:1,
- stop:0 #f8fffe, stop:1 #cfe7fa);
- color: #1a6fae;
- border: 1.5px solid #b5d0ea;
- }
- QPushButton:pressed {
- background: #e3f0fa;
- color: #2471a3;
- border: 1.5px solid #a4cbe3;
- }
- """)
- code_layout.addWidget(self.gen_code_btn)
- left_layout.addLayout(code_layout)
-
-
- # 曲线区加圆角和阴影
- curve_frame = QFrame()
- curve_frame.setStyleSheet("""
- QFrame {
- background: #fff;
- border-radius: 16px;
- border: 1px solid #d6eaf8;
- }
- """)
- curve_shadow = QGraphicsDropShadowEffect(self)
- curve_shadow.setBlurRadius(24)
- curve_shadow.setOffset(0, 4)
- curve_shadow.setColor(Qt.gray)
- curve_frame.setGraphicsEffect(curve_shadow)
- curve_layout = QVBoxLayout(curve_frame)
- curve_layout.setContentsMargins(10, 10, 10, 10)
- self.figure = Figure(figsize=(6, 5))
- self.canvas = FigureCanvas(self.figure)
- curve_layout.addWidget(self.canvas)
- right_layout.addWidget(curve_frame)
-
- # 默认显示空坐标系
- self.figure.clear()
- ax = self.figure.add_subplot(111)
- # ax.set_title("拟合结果", fontsize=22, fontweight='bold')
- ax.set_xlabel("x", fontsize=18)
- ax.set_ylabel("y", fontsize=18)
- ax.tick_params(labelsize=15)
- # ax.set_title("拟合结果", fontsize=22, fontweight='bold') # 中文标题
- self.canvas.draw()
-
- def add_point_row(self, x_val="", y_val=""):
- row = self.table.rowCount()
- self.table.insertRow(row)
- self.table.setItem(row, 0, QTableWidgetItem(str(x_val)))
- self.table.setItem(row, 1, QTableWidgetItem(str(y_val)))
-
- def delete_selected_rows(self):
- selected = self.table.selectionModel().selectedRows()
- for idx in sorted(selected, reverse=True):
- self.table.removeRow(idx.row())
-
- def load_excel(self):
- file, _ = QFileDialog.getOpenFileName(self, "选择Excel文件", "", "Excel Files (*.xlsx *.xls)")
- if file:
- try:
- data = pd.read_excel(file, usecols=[0, 1])
- new_x = data.iloc[:, 0].values.tolist()
- new_y = data.iloc[:, 1].values.tolist()
- for x, y in zip(new_x, new_y):
- self.add_point_row(x, y)
- QMessageBox.information(self, "成功", "数据导入成功!")
- except Exception as e:
- QMessageBox.critical(self, "错误", f"读取Excel失败: {e}")
-
- def export_excel_and_plot(self):
- file, _ = QFileDialog.getSaveFileName(self, "导出Excel文件", "", "Excel Files (*.xlsx *.xls)")
- if file:
- x_list, y_list = [], []
- for row in range(self.table.rowCount()):
- try:
- x = float(self.table.item(row, 0).text())
- y = float(self.table.item(row, 1).text())
- x_list.append(x)
- y_list.append(y)
- except Exception:
- continue
- if not x_list or not y_list:
- QMessageBox.warning(self, "导出失败", "没有可导出的数据!")
- return
- df = pd.DataFrame({'x': x_list, 'y': y_list})
- try:
- df.to_excel(file, index=False)
- png_file = file
- if png_file.lower().endswith('.xlsx') or png_file.lower().endswith('.xls'):
- png_file = png_file.rsplit('.', 1)[0] + '.png'
- else:
- png_file = png_file + '.png'
- self.figure.savefig(png_file, dpi=150, bbox_inches='tight')
- QMessageBox.information(self, "导出成功", f"数据已成功导出到Excel文件!\n图像已导出为:{png_file}")
- except Exception as e:
- QMessageBox.critical(self, "导出错误", f"导出Excel或图像失败: {e}")
-
- def get_manual_points(self):
- x_list, y_list = [], []
- for row in range(self.table.rowCount()):
- try:
- x = float(self.table.item(row, 0).text())
- y = float(self.table.item(row, 1).text())
- x_list.append(x)
- y_list.append(y)
- except Exception:
- continue
- return x_list, y_list
-
- def fit_and_plot(self):
- self.data_x, self.data_y = self.get_manual_points()
- try:
- order = int(self.order_spin.value())
- except ValueError:
- QMessageBox.warning(self, "输入错误", "阶数必须为整数!")
- return
- n_points = len(self.data_x)
- if n_points < order + 1:
- QMessageBox.warning(self, "数据不足", "数据点数量不足以拟合该阶多项式!")
- return
- x = np.array(self.data_x, dtype=np.float64)
- y = np.array(self.data_y, dtype=np.float64)
- x_min, x_max = x.min(), x.max()
- if x_max - x_min == 0:
- QMessageBox.warning(self, "数据错误", "所有x值都相同,无法拟合!")
- return
- try:
- coeffs = np.polyfit(x, y, order)
- except Exception as e:
- QMessageBox.critical(self, "拟合错误", f"多项式拟合失败:{e}")
- return
- poly = np.poly1d(coeffs)
- expr = "y = " + " + ".join([f"{c:.6g}*x^{order-i}" for i, c in enumerate(coeffs)])
- self.output.setPlainText(f"{expr}\n")
- self.figure.clear()
- ax = self.figure.add_subplot(111)
- # ax.set_title("拟合结果", fontsize=22, fontweight='bold')
- ax.set_xlabel("x", fontsize=18)
- ax.set_ylabel("y", fontsize=18)
- ax.scatter(x, y, color='red', label='Data')
- x_fit = np.linspace(x_min, x_max, 200)
- y_fit = poly(x_fit)
- ax.plot(x_fit, y_fit, label='Fit Curve')
- ax.legend()
- self.canvas.draw()
- self.last_coeffs = coeffs
- self.last_xmin = x_min
- self.last_xmax = x_max
-
- def generate_code(self):
- if self.last_coeffs is None:
- QMessageBox.warning(self, "未拟合", "请先拟合数据!")
- return
- coeffs = self.last_coeffs
- code_type = self.code_type.currentText()
- if code_type == "C":
- code = self.create_c_function(coeffs)
- elif code_type == "C++":
- code = self.create_cpp_function(coeffs)
- else:
- code = self.create_py_function(coeffs)
- self.output.setPlainText(code)
-
- def create_c_function(self, coeffs):
- lines = ["#include ", "double polynomial(double x) {", " return "]
- n = len(coeffs)
- terms = []
- for i, c in enumerate(coeffs):
- exp = n - i - 1
- if exp == 0:
- terms.append(f"{c:.8g}")
- elif exp == 1:
- terms.append(f"{c:.8g}*x")
- else:
- terms.append(f"{c:.8g}*pow(x,{exp})")
- lines[-1] += " + ".join(terms) + ";"
- lines.append("}")
- return "\n".join(lines)
-
- def create_cpp_function(self, coeffs):
- lines = ["#include ", "double polynomial(double x) {", " return "]
- n = len(coeffs)
- terms = []
- for i, c in enumerate(coeffs):
- exp = n - i - 1
- if exp == 0:
- terms.append(f"{c:.8g}")
- elif exp == 1:
- terms.append(f"{c:.8g}*x")
- else:
- terms.append(f"{c:.8g}*pow(x,{exp})")
- lines[-1] += " + ".join(terms) + ";"
- lines.append("}")
- return "\n".join(lines)
-
- def create_py_function(self, coeffs):
- n = len(coeffs)
- lines = ["def polynomial(x):", " return "]
- terms = []
- for i, c in enumerate(coeffs):
- exp = n - i - 1
- if exp == 0:
- terms.append(f"{c:.8g}")
- elif exp == 1:
- terms.append(f"{c:.8g}*x")
- else:
- terms.append(f"{c:.8g}*x**{exp}")
- lines[-1] += " + ".join(terms)
- return "\n".join(lines)
-
-# --------- 功能二:下载 ---------
-class DownloadPage(QWidget):
- def __init__(self):
- super().__init__()
- self.setFont(QFont("微软雅黑", 15))
- self.setStyleSheet("""
- QWidget {
- background: qlineargradient(x1:0, y1:0, x2:1, y2:1,
- stop:0 #eaf6fb, stop:1 #d6eaf8);
- border-radius: 18px;
- border: 1px solid #d6eaf8;
- }
- """)
- main_layout = QVBoxLayout(self)
- main_layout.setContentsMargins(60, 60, 60, 60)
- main_layout.setSpacing(32)
-
- # 标题
- title = QLabel("常用工具下载")
- title.setFont(QFont("微软雅黑", 26, QFont.Bold))
- title.setAlignment(Qt.AlignCenter)
- title.setStyleSheet("color: #2980b9; letter-spacing: 3px; margin-bottom: 10px;")
- main_layout.addWidget(title)
-
- # 分割线
- line = QFrame()
- line.setFrameShape(QFrame.HLine)
- line.setFrameShadow(QFrame.Sunken)
- line.setStyleSheet("color: #d6eaf8; background: #d6eaf8; min-height: 2px;")
- main_layout.addWidget(line)
-
- # # 说明
- # desc = QLabel("点击下方按钮可直接跳转到常用工具或开发软件的官方下载页面:")
- # desc.setFont(QFont("微软雅黑", 17))
- # desc.setAlignment(Qt.AlignCenter)
- # desc.setStyleSheet("color: #34495e; margin-bottom: 18px;")
- # main_layout.addWidget(desc)
-
- spacer = QFrame()
- spacer.setFixedHeight(4) # 可根据需要调整间隔高度
- spacer.setStyleSheet("background: transparent; border: none;")
- main_layout.addWidget(spacer)
-
- # 小工具类
- tools_tools = [
- ("Geek Uninstaller", "https://geekuninstaller.com/download", "🧹"),
- ("Neat Download Manager", "https://www.neatdownloadmanager.com/index.php/en/", "⬇️"),
- ("Everything", "https://www.voidtools.com/zh-cn/downloads/", "🔍"),
- ("Bandizip", "https://www.bandisoft.com/bandizip/", "🗜️"),
- ("PotPlayer", "https://potplayer.daum.net/", "🎬"),
- ("Typora", "https://typora.io/", "📝"),
- ("Git", "https://git-scm.com/download/win", "🟥"),
- ("Python", "https://www.python.org/downloads/", "🐍"),
- ]
- tools_group = QGroupBox("常用小工具")
- tools_group.setFont(QFont("微软雅黑", 14, QFont.Bold))
- tools_group.setStyleSheet("""
- QGroupBox {
- border: 2px solid #b5d0ea;
- border-radius: 12px;
- margin-top: 16px;
- background: #f8fbfd;
- color: #2471a3;
- padding: 10px 0 0 0;
- }
- QGroupBox:title {
- subcontrol-origin: margin;
- left: 18px;
- top: -10px;
- background: transparent;
- padding: 0 8px;
- }
- """)
- tools_layout = QGridLayout()
- tools_layout.setSpacing(18)
- tools_layout.setContentsMargins(24, 24, 24, 24)
- for idx, (name, url, icon) in enumerate(tools_tools):
- btn = QPushButton(f"{icon} {name}")
- btn.setFont(QFont("微软雅黑", 16, QFont.Bold))
- btn.setMinimumHeight(60)
- btn.setCursor(Qt.PointingHandCursor)
- btn.setStyleSheet("""
- QPushButton {
- background: qlineargradient(x1:0, y1:0, x2:1, y2:1,
- stop:0 #eaf6fb, stop:1 #d6eaf8);
- color: #2471a3;
- 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: #2471a3;
- border: 2px solid #a4cbe3;
- }
- """)
- btn.clicked.connect(lambda checked, link=url: webbrowser.open(link))
- row, col = divmod(idx, 4)
- tools_layout.addWidget(btn, row, col)
- tools_group.setLayout(tools_layout)
- main_layout.addWidget(tools_group)
- spacer = QFrame()
- spacer.setFixedHeight(4) # 可根据需要调整间隔高度
- spacer.setStyleSheet("background: transparent; border: none;")
- main_layout.addWidget(spacer)
-
- # 开发/设计软件类
- dev_tools = [
- ("STM32CubeMX", "https://www.st.com/zh/development-tools/stm32cubemx.html", "🟦"),
- ("Keil MDK", "https://www.keil.com/download/product/", "🟩"),
- ("Visual Studio Code", "https://code.visualstudio.com/", "🟦"),
- ("CLion", "https://www.jetbrains.com/clion/download/", "🟧"),
- ("MATLAB", "https://www.mathworks.com/downloads/", "🟨"),
- ("SolidWorks", "https://www.solidworks.com/sw/support/downloads.htm", "🟫"),
- ("Altium Designer", "https://www.altium.com/zh/altium-designer/downloads", "🟪"),
- ("原神", "https://download-porter.hoyoverse.com/download-porter/2025/03/27/GenshinImpact_install_202503072011.exe?trace_key=GenshinImpact_install_ua_679d0b4e9b10", "🟫"),
- ]
- dev_group = QGroupBox("开发/设计软件")
- dev_group.setFont(QFont("微软雅黑", 14, QFont.Bold))
- dev_group.setStyleSheet("""
- QGroupBox {
- border: 2px solid #b5d0ea;
- border-radius: 12px;
- margin-top: 16px;
- background: #f8fbfd;
- color: #2471a3;
- padding: 10px 0 0 0;
- }
- QGroupBox:title {
- subcontrol-origin: margin;
- left: 18px;
- top: -10px;
- background: transparent;
- padding: 0 8px;
- }
- """)
- dev_layout = QGridLayout()
- dev_layout.setSpacing(18)
- dev_layout.setContentsMargins(24, 24, 24, 24)
- for idx, (name, url, icon) in enumerate(dev_tools):
- btn = QPushButton(f"{icon} {name}")
- btn.setFont(QFont("微软雅黑", 16, QFont.Bold))
- btn.setMinimumHeight(60)
- btn.setCursor(Qt.PointingHandCursor)
- btn.setStyleSheet("""
- QPushButton {
- background: qlineargradient(x1:0, y1:0, x2:1, y2:1,
- stop:0 #eaf6fb, stop:1 #d6eaf8);
- color: #2471a3;
- 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: #2471a3;
- border: 2px solid #a4cbe3;
- }
- """)
- btn.clicked.connect(lambda checked, link=url: webbrowser.open(link))
- row, col = divmod(idx, 4)
- dev_layout.addWidget(btn, row, col)
- dev_group.setLayout(dev_layout)
- main_layout.addWidget(dev_group)
-
- main_layout.addStretch(1)
-
- # 页脚
- footer = QLabel("如有问题或建议,欢迎反馈至QQ群:857466609")
- footer.setFont(QFont("微软雅黑", 13))
- footer.setAlignment(Qt.AlignCenter)
- footer.setStyleSheet("color: #b2bec3; margin-top: 18px;")
- main_layout.addWidget(footer)
-
-# --------- 功能三:串口助手 ---------
-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("微软雅黑", 12))
- self.hex_recv_chk.setFont(QFont("微软雅黑", 12))
- 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 = QHBoxLayout()
- proto_layout.setSpacing(18)
- proto_layout.setContentsMargins(16, 16, 16, 16)
- proto_layout.addWidget(QLabel("数据数量:"))
- self.data_count_spin = QSpinBox()
- self.data_count_spin.setRange(1, 16)
- self.data_count_spin.setValue(self.data_count)
- self.data_count_spin.setFixedWidth(80)
- self.data_count_spin.valueChanged.connect(self.apply_proto_config)
- proto_layout.addWidget(self.data_count_spin)
- proto_layout.addSpacing(18)
- proto_layout.addWidget(QLabel("数据类型:"))
- 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.setFixedWidth(100)
- self.data_type_box.currentTextChanged.connect(self.apply_proto_config)
- proto_layout.addWidget(self.data_type_box)
- # proto_layout.addStretch(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(16)
- send_layout.setContentsMargins(18, 18, 18, 18)
-
- # 输入框(多行)
- self.send_edit = QTextEdit()
- self.send_edit.setFont(QFont("Consolas", 18))
- self.send_edit.setPlaceholderText("输入要发送的数据,可多行(支持HEX/文本)...")
- self.send_edit.setMinimumHeight(140)
- self.send_edit.setMaximumHeight(220)
- self.send_edit.setStyleSheet("""
- QTextEdit {
- background: #f8fbfd;
- border-radius: 12px;
- border: 2px solid #d6eaf8;
- font-size: 18px;
- padding: 14px 20px;
- }
- """)
- send_layout.addWidget(self.send_edit)
-
- # HEX复选框和按钮行
- row1 = QHBoxLayout()
- row1.setSpacing(24)
- self.hex_send_chk.setStyleSheet("""
- QCheckBox {
- color: #2471a3;
- font-size: 15px;
- }
- QCheckBox::indicator {
- width: 22px;
- height: 22px;
- }
- QCheckBox::indicator:checked {
- background-color: #2980b9;
- border: 1.5px solid #2980b9;
- }
- QCheckBox::indicator:unchecked {
- background-color: #fff;
- border: 1.5px solid #b5d0ea;
- }
- """)
- self.hex_recv_chk.setStyleSheet(self.hex_send_chk.styleSheet())
- row1.addWidget(self.hex_send_chk)
- row1.addWidget(self.hex_recv_chk)
- row1.addStretch(1)
- send_layout.addLayout(row1)
-
- # 发送和持续发送按钮+频率(优化为“每秒发送次数”)
- row2 = QHBoxLayout()
- row2.setSpacing(18)
- self.send_btn = QPushButton("发送")
- self.send_btn.clicked.connect(self.send_data)
- self.send_btn.setFont(QFont("微软雅黑", 16, QFont.Bold))
- self.send_btn.setFixedHeight(44)
- self.send_btn.setFixedWidth(120)
- self.send_btn.setStyleSheet(self._btn_style("#2980b9"))
- row2.addWidget(self.send_btn)
-
- self.cont_send_btn = QPushButton("持续发送")
- self.cont_send_btn.setCheckable(True)
- self.cont_send_btn.setFont(QFont("微软雅黑", 15))
- self.cont_send_btn.setFixedHeight(44)
- self.cont_send_btn.setFixedWidth(120)
- self.cont_send_btn.setStyleSheet(self._btn_style("#f1c40f"))
- self.cont_send_btn.clicked.connect(self.toggle_cont_send)
- row2.addWidget(self.cont_send_btn)
-
- freq_label = QLabel(" 每秒发送次数:")
- freq_label.setFont(QFont("微软雅黑", 8))
- freq_label.setFixedWidth(180)
- #文本居中
- # freq_label.setAlignment(Qt.AlignRight | Qt.AlignVCenter)
- row2.addWidget(freq_label)
-
- self.freq_input = QSpinBox()
- self.freq_input.setRange(1, 1000)
- self.freq_input.setValue(5)
- self.freq_input.setFont(QFont("Consolas", 14))
- self.freq_input.setFixedWidth(100)
- self.freq_input.setStyleSheet("""
- QSpinBox {
- background: #f8fbfd;
- border-radius: 8px;
- border: 1px solid #d6eaf8;
- font-size: 15px;
- padding: 2px 8px;
- }
- """)
- row2.addWidget(self.freq_input)
-
- # freq_unit = QLabel("次/秒")
- # freq_unit.setFont(QFont("微软雅黑", 13))
- # freq_unit.setFixedWidth(40)
- # row2.addWidget(freq_unit)
- row2.addStretch(1)
- send_layout.addLayout(row2)
-
- self.cont_send_timer = QTimer(self)
- self.cont_send_timer.timeout.connect(self.send_data)
-
- send_group.setLayout(send_layout)
- left_panel.addWidget(send_group, stretch=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"
- " 0x55 + 数据数量(1字节) + 数据 + 校验和(1字节)\n"
- " 校验和为包头到最后一个数据字节的累加和的低8位。\n"
- "3. 每包数据自动绘制曲线,X轴为采样点(或时间),Y轴为各通道数据。\n"
- )
- usage_label.setWordWrap(True)
- usage_label.setFont(QFont("微软雅黑", 9))
- usage_layout.addWidget(usage_label)
- usage_group.setLayout(usage_layout)
- left_panel.addWidget(usage_group)
-
- # 清空按钮紧贴使用说明
- self.clear_btn = QPushButton("清空接收和曲线")
- self.clear_btn.clicked.connect(self.clear_all)
- self.clear_btn.setStyleSheet(self._btn_style("#e74c3c"))
- self.clear_btn.setFixedHeight(38)
- left_panel.addWidget(self.clear_btn)
-
- 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 parse_hex_string(self, s):
- """支持 0x11 0x22 33 44 格式转bytes"""
- s = s.strip().replace(',', ' ').replace(';', ' ')
- parts = s.split()
- result = []
- for part in parts:
- if part.startswith('0x') or part.startswith('0X'):
- try:
- result.append(int(part, 16))
- except Exception:
- pass
- else:
- try:
- result.append(int(part, 16))
- except Exception:
- pass
- return bytes(result)
-
- def send_data(self):
- if self.ser and self.ser.is_open:
- data = self.send_edit.toPlainText()
- try:
- if self.hex_send_chk.isChecked():
- # 支持 0x11 0x22 33 44 格式
- data_bytes = self.parse_hex_string(data)
- if not data_bytes:
- self.recv_box.append("HEX格式错误,未发送。")
- return
- self.ser.write(data_bytes)
- self.recv_box.append(f"发送(HEX): {' '.join(['%02X'%b for b in data_bytes])}")
- 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 toggle_cont_send(self):
- if self.cont_send_btn.isChecked():
- try:
- interval = int(self.freq_box.currentText())
- except Exception:
- interval = 200
- self.cont_send_timer.start(interval)
- self.cont_send_btn.setText("停止发送")
- else:
- self.cont_send_timer.stop()
- self.cont_send_btn.setText("持续发送")
-
- 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_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 = self.parse_hex_string(line.strip())
- self.ser.write(data_bytes)
- self.recv_box.append(f"发送(HEX): {' '.join(['%02X'%b for b in data_bytes])}")
- 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("Sample", fontsize=14)
- ax.set_ylabel("Value", 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):
- repo_ready_signal = pyqtSignal()
-
- def __init__(self):
- super().__init__()
- self.setFont(QFont("微软雅黑", 15))
- self.setStyleSheet("""
- QWidget {
- background: #f8fbfd;
- border-radius: 16px;
- padding: 20px;
- }
- """)
- # 变量初始化
- self.repo_dir = "MRobot_repo"
- self.repo_url = "http://gitea.qutrobot.top/robofish/MRobot.git"
- self.header_file_vars = {}
- self.task_vars = []
- self.ioc_data = None
- self.add_gitignore = False
- self.auto_configure = False
- self.repo_ready = False # 标志:仓库是否已准备好
- self.init_ui()
- self.repo_ready_signal.connect(self.on_repo_ready)
-
- def showEvent(self, event):
- super().showEvent(event)
- if not self.repo_ready:
- self.log("首次进入,正在克隆MRobot仓库...")
- self.clone_repo_and_refresh()
-
- def clone_repo_and_refresh(self):
- import threading
- def do_clone():
- self.clone_repo()
- self.repo_ready = True
- self.ioc_data = self.find_and_read_ioc_file()
- self.repo_ready_signal.emit()
- threading.Thread(target=do_clone).start()
-
- @pyqtSlot()
- def on_repo_ready(self):
- self.update_freertos_status()
- self.update_header_files()
- self.update_task_ui()
- self.log("仓库准备完成!")
-
- def init_ui(self):
- main_layout = QVBoxLayout(self)
- main_layout.setSpacing(18)
- main_layout.setContentsMargins(32, 32, 32, 32)
- self.setStyleSheet("""
- QWidget {
- background: qlineargradient(x1:0, y1:0, x2:1, y2:1,
- stop:0 #eaf6fb, stop:1 #d6eaf8);
- border-radius: 16px;
- }
- """)
-
- # 顶部标题区
- title = QLabel("MRobot 架构生成工具")
- title.setFont(QFont("微软雅黑", 22, QFont.Bold))
- title.setAlignment(Qt.AlignCenter)
- title.setStyleSheet("color: #2980b9; letter-spacing: 2px; margin-bottom: 2px;")
- main_layout.addWidget(title)
-
- desc = QLabel("快速生成 MRobot 项目代码,自动管理模块、任务与环境配置。")
- desc.setFont(QFont("微软雅黑", 13))
- desc.setAlignment(Qt.AlignCenter)
- desc.setStyleSheet("color: #34495e; margin-bottom: 8px;")
- main_layout.addWidget(desc)
-
- # 状态与选项区
- status_opt_row = QHBoxLayout()
- status_opt_row.setSpacing(24)
-
- # 状态区
- status_col = QVBoxLayout()
- self.freertos_status_label = QLabel("FreeRTOS 状态: 检测中...")
- self.freertos_status_label.setFont(QFont("微软雅黑", 12))
- self.freertos_status_label.setStyleSheet("color: #2471a3;")
- status_col.addWidget(self.freertos_status_label)
- status_col.addStretch(1)
- status_opt_row.addLayout(status_col, 1)
-
- # 选项区
- option_col = QVBoxLayout()
- self.gitignore_chk = QCheckBox("生成 .gitignore")
- self.gitignore_chk.setFont(QFont("微软雅黑", 12))
- self.gitignore_chk.stateChanged.connect(lambda x: setattr(self, "add_gitignore", x == Qt.Checked))
- option_col.addWidget(self.gitignore_chk)
- self.auto_env_chk = QCheckBox("自动环境配置")
- self.auto_env_chk.setFont(QFont("微软雅黑", 12))
- self.auto_env_chk.stateChanged.connect(lambda x: setattr(self, "auto_configure", x == Qt.Checked))
- option_col.addWidget(self.auto_env_chk)
- option_col.addStretch(1)
- status_opt_row.addLayout(option_col, 1)
-
- status_opt_row.addStretch(2)
- main_layout.addLayout(status_opt_row)
-
- # 主体分区:左侧模块选择,右侧任务管理
- body_layout = QHBoxLayout()
- body_layout.setSpacing(24)
-
- # 左侧:模块文件选择
- left_col = QVBoxLayout()
- self.header_group = QGroupBox("模块文件选择")
- self.header_group.setFont(QFont("微软雅黑", 15, QFont.Bold))
- self.header_group.setStyleSheet("""
- QGroupBox {
- border: 2px solid #b5d0ea;
- border-radius: 12px;
- margin-top: 8px;
- background: #f8fbfd;
- color: #2471a3;
- padding: 10px 0 0 0;
- }
- QGroupBox:title {
- subcontrol-origin: margin;
- left: 18px;
- top: -10px;
- background: transparent;
- padding: 0 8px;
- }
- """)
- self.header_layout = QVBoxLayout(self.header_group)
- self.header_layout.setSpacing(8)
- left_col.addWidget(self.header_group)
- left_col.addStretch(1)
- body_layout.addLayout(left_col, 2)
-
- # 右侧:任务管理
- right_col = QVBoxLayout()
- self.task_group = QGroupBox("任务管理 (FreeRTOS)")
- self.task_group.setFont(QFont("微软雅黑", 15, QFont.Bold))
- self.task_group.setStyleSheet(self.header_group.styleSheet())
- self.task_layout = QVBoxLayout(self.task_group)
- self.task_layout.setSpacing(8)
- right_col.addWidget(self.task_group)
- right_col.addStretch(1)
- body_layout.addLayout(right_col, 2)
-
- main_layout.addLayout(body_layout)
-
- # 生成按钮区
- btn_row = QHBoxLayout()
- btn_row.addStretch(1)
- self.generate_btn = QPushButton("一键生成 MRobot 代码")
- self.generate_btn.setFont(QFont("微软雅黑", 18, QFont.Bold))
- self.generate_btn.setMinimumHeight(48)
- self.generate_btn.setStyleSheet("""
- QPushButton {
- background: qlineargradient(x1:0, y1:0, x2:1, y2:1,
- stop:0 #eaf6fb, stop:1 #d6eaf8);
- color: #2980b9;
- border-radius: 20px;
- font-size: 20px;
- font-weight: 600;
- padding: 12px 0;
- border: 1px solid #d6eaf8;
- }
- QPushButton:hover {
- background: qlineargradient(x1:0, y1:0, x2:1, y2:1,
- stop:0 #f8fffe, stop:1 #cfe7fa);
- color: #1a6fae;
- border: 1.5px solid #b5d0ea;
- }
- QPushButton:pressed {
- background: #e3f0fa;
- color: #2471a3;
- border: 1.5px solid #a4cbe3;
- }
- """)
- self.generate_btn.clicked.connect(self.generate_action)
- btn_row.addWidget(self.generate_btn)
- btn_row.addStretch(1)
- main_layout.addLayout(btn_row)
-
- # 日志输出区
- self.msg_box = QTextEdit()
- self.msg_box.setReadOnly(True)
- self.msg_box.setFont(QFont("Consolas", 13))
- self.msg_box.setMaximumHeight(100)
- self.msg_box.setStyleSheet("""
- QTextEdit {
- background: #f4f6f7;
- border-radius: 8px;
- border: 1px solid #d6eaf8;
- font-size: 15px;
- color: #2c3e50;
- padding: 8px;
- }
- """)
- main_layout.addWidget(self.msg_box)
-
- # 页脚
- footer = QLabel("如遇问题请反馈至 QUT 机器人战队")
- footer.setFont(QFont("微软雅黑", 11))
- footer.setAlignment(Qt.AlignCenter)
- footer.setStyleSheet("color: #b2bec3; margin-top: 6px;")
- main_layout.addWidget(footer)
-
- # 初始化内容
- self.update_header_files()
- self.update_task_ui()
-
- # ...其余方法保持不变...
-
- def log(self, msg):
- self.msg_box.append(msg)
-
- def clone_repo(self):
- import shutil
- from git import Repo
- if os.path.exists(self.repo_dir):
- shutil.rmtree(self.repo_dir)
- try:
- self.log("正在克隆仓库...")
- Repo.clone_from(self.repo_url, self.repo_dir, multi_options=["--depth=1"])
- self.log("仓库克隆成功!")
- except Exception as e:
- self.log(f"克隆仓库失败: {e}")
-
- def find_and_read_ioc_file(self):
- for file in os.listdir("."):
- if file.endswith(".ioc"):
- with open(file, "r", encoding="utf-8") as f:
- return f.read()
- self.log("未找到 .ioc 文件!")
- return None
-
- def check_freertos_enabled(self):
- import re
- if not self.ioc_data:
- return False
- return bool(re.search(r"Mcu\.IP\d+=FREERTOS", self.ioc_data))
-
- def update_freertos_status(self):
- if self.ioc_data:
- status = "已启用" if self.check_freertos_enabled() else "未启用"
- else:
- status = "未检测到 .ioc 文件"
- self.freertos_status_label.setText(f"FreeRTOS 状态: {status}")
-
- def update_header_files(self):
- for i in reversed(range(self.header_layout.count())):
- widget = self.header_layout.itemAt(i).widget()
- if widget:
- widget.deleteLater()
- if not self.repo_ready or not os.path.exists(self.repo_dir):
- return
- from collections import defaultdict
- import csv
- folders = ["bsp", "component", "device", "module"]
- dependencies = defaultdict(list)
- for folder in folders:
- folder_dir = os.path.join(self.repo_dir, "User", folder)
- dep_file = os.path.join(folder_dir, "dependencies.csv")
- if os.path.exists(dep_file):
- with open(dep_file, "r", encoding="utf-8") as f:
- reader = csv.reader(f)
- for row in reader:
- if len(row) == 2:
- dependencies[row[0]].append(row[1])
- for folder in folders:
- folder_dir = os.path.join(self.repo_dir, "User", folder)
- if os.path.exists(folder_dir):
- group = QGroupBox(folder)
- g_layout = QHBoxLayout(group)
- for file in os.listdir(folder_dir):
- file_base, file_ext = os.path.splitext(file)
- if file_ext == ".h" and file_base != folder:
- var = QCheckBox(file_base)
- var.stateChanged.connect(lambda x, fb=file_base: self.handle_dependencies(fb, dependencies))
- self.header_file_vars[file_base] = var
- g_layout.addWidget(var)
- self.header_layout.addWidget(group)
-
- def handle_dependencies(self, file_base, dependencies):
- if file_base in self.header_file_vars and self.header_file_vars[file_base].isChecked():
- for dep in dependencies.get(file_base, []):
- dep_base = os.path.basename(dep)
- if dep_base in self.header_file_vars:
- self.header_file_vars[dep_base].setChecked(True)
-
- def update_task_ui(self):
- for i in reversed(range(self.task_layout.count())):
- widget = self.task_layout.itemAt(i).widget()
- if widget:
- widget.deleteLater()
- if not self.repo_ready or not self.check_freertos_enabled():
- self.task_group.setVisible(False)
- return
- self.task_group.setVisible(True)
- for i, (task_var, freq_var) in enumerate(self.task_vars):
- row = QHBoxLayout()
- name_edit = QLineEdit(task_var)
- freq_spin = QSpinBox()
- freq_spin.setRange(1, 1000)
- freq_spin.setValue(freq_var)
- del_btn = QPushButton("删除")
- del_btn.clicked.connect(lambda _, idx=i: self.remove_task(idx))
- row.addWidget(name_edit)
- row.addWidget(QLabel("频率:"))
- row.addWidget(freq_spin)
- row.addWidget(del_btn)
- container = QWidget()
- container.setLayout(row)
- self.task_layout.addWidget(container)
- add_btn = QPushButton("添加任务")
- add_btn.clicked.connect(self.add_task)
- self.task_layout.addWidget(add_btn)
-
- def add_task(self):
- self.task_vars.append([f"Task_{len(self.task_vars)+1}", 100])
- self.update_task_ui()
-
- def remove_task(self, idx):
- if 0 <= idx < len(self.task_vars):
- self.task_vars.pop(idx)
- self.update_task_ui()
-
- def copy_file_from_repo(self, src_path, dest_path):
- import shutil
- if src_path.startswith(self.repo_dir):
- full_src_path = src_path
- else:
- full_src_path = os.path.join(self.repo_dir, src_path.lstrip(os.sep))
- if not os.path.exists(full_src_path):
- self.log(f"文件 {full_src_path} 不存在!")
- return
- dest_dir = os.path.dirname(dest_path)
- if dest_dir and not os.path.exists(dest_dir):
- os.makedirs(dest_dir, exist_ok=True)
- shutil.copy(full_src_path, dest_path)
- self.log(f"已复制 {full_src_path} 到 {dest_path}")
-
- def generate_action(self):
- import threading
- def task():
- self.create_directories()
- if self.add_gitignore:
- self.copy_file_from_repo(".gitignore", ".gitignore")
- if self.ioc_data and self.check_freertos_enabled():
- self.copy_file_from_repo("src/freertos.c", os.path.join("Core", "Src", "freertos.c"))
- folders = ["bsp", "component", "device", "module"]
- for folder in folders:
- folder_dir = os.path.join(self.repo_dir, "User", folder)
- if not os.path.exists(folder_dir):
- continue
- for file_name in os.listdir(folder_dir):
- file_base, file_ext = os.path.splitext(file_name)
- if file_ext not in [".h", ".c"]:
- continue
- if file_base == folder:
- src_path = os.path.join(folder_dir, file_name)
- dest_path = os.path.join("User", folder, file_name)
- self.copy_file_from_repo(src_path, dest_path)
- continue
- if file_base in self.header_file_vars and self.header_file_vars[file_base].isChecked():
- src_path = os.path.join(folder_dir, file_name)
- dest_path = os.path.join("User", folder, file_name)
- self.copy_file_from_repo(src_path, dest_path)
- if self.ioc_data and self.check_freertos_enabled():
- self.modify_user_task_file()
- self.generate_user_task_header()
- self.generate_init_file()
- self.generate_task_files()
- self.log("生成完成!")
- threading.Thread(target=task).start()
-
- def create_directories(self):
- dirs = [
- "User/bsp",
- "User/component",
- "User/device",
- "User/module",
- ]
- if self.ioc_data and self.check_freertos_enabled():
- dirs.append("User/task")
- for d in dirs:
- if not os.path.exists(d):
- os.makedirs(d, exist_ok=True)
- self.log(f"已创建目录: {d}")
-
- def generate_task_files(self):
- try:
- import re
- template_file_path = os.path.join(self.repo_dir, "User", "task", "task.c.template")
- task_dir = os.path.join("User", "task")
- if not os.path.exists(template_file_path):
- self.log(f"模板文件 {template_file_path} 不存在,无法生成 task.c 文件!")
- return
- os.makedirs(task_dir, exist_ok=True)
- with open(template_file_path, "r", encoding="utf-8") as f:
- template_content = f.read()
- for task in self.task_vars:
- if isinstance(task, (list, tuple)):
- task_name = str(task[0])
- else:
- task_name = str(task)
- task_file_path = os.path.join(task_dir, f"{task_name.lower()}.c")
- task_content = template_content.replace("{{task_name}}", task_name)
- task_content = task_content.replace("{{task_function}}", task_name)
- task_content = task_content.replace(
- "{{task_frequency}}", f"TASK_FREQ_{task_name.upper()}"
- )
- task_content = task_content.replace("{{task_delay}}", f"TASK_INIT_DELAY_{task_name.upper()}")
- with open(task_file_path, "w", encoding="utf-8") as f2:
- f2.write(task_content)
- self.log(f"已成功生成 {task_file_path} 文件!")
- except Exception as e:
- self.log(f"生成 task.c 文件时出错: {e}")
-
- def modify_user_task_file(self):
- try:
- import re
- template_file_path = os.path.join(self.repo_dir, "User", "task", "user_task.c.template")
- generated_task_file_path = os.path.join("User", "task", "user_task.c")
- if not os.path.exists(template_file_path):
- self.log(f"模板文件 {template_file_path} 不存在,无法生成 user_task.c 文件!")
- return
- os.makedirs(os.path.dirname(generated_task_file_path), exist_ok=True)
- with open(template_file_path, "r", encoding="utf-8") as f:
- template_content = f.read()
- task_attr_definitions = "\n".join([
- f"""const osThreadAttr_t attr_{str(task[0]).lower()} = {{
- .name = "{str(task[0])}",
- .priority = osPriorityNormal,
- .stack_size = 128 * 4,
-}};"""
- for task in self.task_vars
- ])
- task_content = template_content.replace("{{task_attr_definitions}}", task_attr_definitions)
- with open(generated_task_file_path, "w", encoding="utf-8") as f2:
- f2.write(task_content)
- self.log(f"已成功生成 {generated_task_file_path} 文件!")
- except Exception as e:
- self.log(f"修改 user_task.c 文件时出错: {e}")
-
- def generate_user_task_header(self):
- try:
- import re
- template_file_path = os.path.join(self.repo_dir, "User", "task", "user_task.h.template")
- header_file_path = os.path.join("User", "task", "user_task.h")
- if not os.path.exists(template_file_path):
- self.log(f"模板文件 {template_file_path} 不存在,无法生成 user_task.h 文件!")
- return
- os.makedirs(os.path.dirname(header_file_path), exist_ok=True)
- existing_msgq_content = ""
- if os.path.exists(header_file_path):
- with open(header_file_path, "r", encoding="utf-8") as f:
- content = f.read()
- match = re.search(r"/\* USER MESSAGE BEGIN \*/\s*(.*?)\s*/\* USER MESSAGE END \*/", content, re.DOTALL)
- if match:
- existing_msgq_content = match.group(1).strip()
- self.log("已存在的 msgq 区域内容已保留")
- with open(template_file_path, "r", encoding="utf-8") as f:
- template_content = f.read()
- thread_definitions = "\n".join([f" osThreadId_t {str(task[0]).lower()};" for task in self.task_vars])
- msgq_definitions = existing_msgq_content if existing_msgq_content else " osMessageQueueId_t default_msgq;"
- freq_definitions = "\n".join([f" float {str(task[0]).lower()};" for task in self.task_vars])
- last_up_time_definitions = "\n".join([f" uint32_t {str(task[0]).lower()};" for task in self.task_vars])
- task_attr_declarations = "\n".join([f"extern const osThreadAttr_t attr_{str(task[0]).lower()};" for task in self.task_vars])
- task_function_declarations = "\n".join([f"void {str(task[0])}(void *argument);" for task in self.task_vars])
- task_frequency_definitions = "\n".join([
- f"#define TASK_FREQ_{str(task[0]).upper()} ({int(task[1])}u)"
- for task in self.task_vars
- ])
- task_init_delay_definitions = "\n".join([f"#define TASK_INIT_DELAY_{str(task[0]).upper()} (0u)" for task in self.task_vars])
- task_handle_definitions = "\n".join([f" osThreadId_t {str(task[0]).lower()};" for task in self.task_vars])
- header_content = template_content.replace("{{thread_definitions}}", thread_definitions)
- header_content = header_content.replace("{{msgq_definitions}}", msgq_definitions)
- header_content = header_content.replace("{{freq_definitions}}", freq_definitions)
- header_content = header_content.replace("{{last_up_time_definitions}}", last_up_time_definitions)
- header_content = header_content.replace("{{task_attr_declarations}}", task_attr_declarations)
- header_content = header_content.replace("{{task_function_declarations}}", task_function_declarations)
- header_content = header_content.replace("{{task_frequency_definitions}}", task_frequency_definitions)
- header_content = header_content.replace("{{task_init_delay_definitions}}", task_init_delay_definitions)
- header_content = header_content.replace("{{task_handle_definitions}}", task_handle_definitions)
- if existing_msgq_content:
- header_content = re.sub(
- r"/\* USER MESSAGE BEGIN \*/\s*.*?\s*/\* USER MESSAGE END \*/",
- f"/* USER MESSAGE BEGIN */\n\n {existing_msgq_content}\n\n /* USER MESSAGE END */",
- header_content,
- flags=re.DOTALL
- )
- with open(header_file_path, "w", encoding="utf-8") as f2:
- f2.write(header_content)
- self.log(f"已成功生成 {header_file_path} 文件!")
- except Exception as e:
- self.log(f"生成 user_task.h 文件时出错: {e}")
-
- def generate_init_file(self):
- try:
- import re
- template_file_path = os.path.join(self.repo_dir, "User", "task", "init.c.template")
- generated_file_path = os.path.join("User", "task", "init.c")
- if not os.path.exists(template_file_path):
- self.log(f"模板文件 {template_file_path} 不存在,无法生成 init.c 文件!")
- return
- os.makedirs(os.path.dirname(generated_file_path), exist_ok=True)
- existing_msgq_content = ""
- if os.path.exists(generated_file_path):
- with open(generated_file_path, "r", encoding="utf-8") as f:
- content = f.read()
- match = re.search(r"/\* USER MESSAGE BEGIN \*/\s*(.*?)\s*/\* USER MESSAGE END \*/", content, re.DOTALL)
- if match:
- existing_msgq_content = match.group(1).strip()
- self.log("已存在的消息队列区域内容已保留")
- with open(template_file_path, "r", encoding="utf-8") as f:
- template_content = f.read()
- thread_creation_code = "\n".join([
- f" task_runtime.thread.{str(task[0]).lower()} = osThreadNew({str(task[0])}, NULL, &attr_{str(task[0]).lower()});"
- for task in self.task_vars
- ])
- init_content = template_content.replace("{{thread_creation_code}}", thread_creation_code)
- if existing_msgq_content:
- init_content = re.sub(
- r"/\* USER MESSAGE BEGIN \*/\s*.*?\s*/\* USER MESSAGE END \*/",
- f"/* USER MESSAGE BEGIN */\n {existing_msgq_content}\n /* USER MESSAGE END */",
- init_content,
- flags=re.DOTALL
- )
- with open(generated_file_path, "w", encoding="utf-8") as f2:
- f2.write(init_content)
- self.log(f"已成功生成 {generated_file_path} 文件!")
- except Exception as e:
- self.log(f"生成 init.c 文件时出错: {e}")
-
-# --------- 功能五:零件库 ---------
-
-class CustomWebView(QWebEngineView):
- def __init__(self, parent=None, popup_list=None):
- super().__init__(parent)
- self.popup_list = popup_list
- self._progress_dialog = None
- self.page().profile().downloadRequested.connect(self.handle_download)
- self.setStyleSheet("""
- QWebEngineView {
- border-radius: 12px;
- background: #f8fbfd;
- border: 1px solid #d6eaf8;
- }
- """)
-
- def handle_download(self, download_item):
- from PyQt5.QtWidgets import QFileDialog, QProgressDialog
-
- # 防止重复弹窗
- if hasattr(download_item, "_handled") and download_item._handled:
- return
- download_item._handled = True
-
- suggested = download_item.suggestedFileName()
- path, _ = QFileDialog.getSaveFileName(self, "保存文件", suggested)
- if not path:
- download_item.cancel()
- return
-
- download_item.setPath(path)
- download_item.accept()
-
- # 创建进度对话框
- self._progress_dialog = QProgressDialog(f"正在下载: {suggested}", "取消", 0, 100, self)
- self._progress_dialog.setWindowTitle("下载进度")
- self._progress_dialog.setWindowModality(Qt.WindowModal)
- self._progress_dialog.setMinimumDuration(0)
- self._progress_dialog.setValue(0)
- self._progress_dialog.canceled.connect(download_item.cancel)
- self._progress_dialog.show()
-
- def on_progress(received, total):
- if total > 0:
- percent = int(received * 100 / total)
- self._progress_dialog.setValue(percent)
- else:
- self._progress_dialog.setValue(0)
-
- download_item.downloadProgress.connect(on_progress)
-
- def on_finished():
- self._progress_dialog.setValue(100)
- self._progress_dialog.close()
- if self.parent() and isinstance(self.parent(), CustomWebView):
- self.parent().close()
- elif self.popup_list and self in self.popup_list:
- self.close()
- self.popup_list.remove(self)
-
- download_item.finished.connect(on_finished)
-
- def createWindow(self, _type):
- popup = CustomWebView(popup_list=self.popup_list)
- popup.setAttribute(Qt.WA_DeleteOnClose)
- popup.setWindowTitle("下载")
- popup.resize(900, 600)
- popup.show()
- if self.popup_list is not None:
- self.popup_list.append(popup)
- return popup
-
-class MachineryLibrary(QWidget):
- def __init__(self):
- super().__init__()
- self.popup_windows = []
- 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.setSpacing(18)
- main_layout.setContentsMargins(32, 32, 32, 32)
-
- # 标题区
- title = QLabel("MRobot 零件库")
- title.setFont(QFont("微软雅黑", 22, QFont.Bold))
- title.setAlignment(Qt.AlignCenter)
- title.setStyleSheet("color: #2980b9; letter-spacing: 2px; margin-bottom: 2px;")
- main_layout.addWidget(title)
-
- desc = QLabel("零件库账号:Engineer(无密码)")
- desc.setFont(QFont("微软雅黑", 13))
- desc.setAlignment(Qt.AlignCenter)
- desc.setStyleSheet("color: #34495e; margin-bottom: 8px;")
- main_layout.addWidget(desc)
-
- # 加载提示
- self.loading_label = QLabel("正在加载零件库网页,请稍候...")
- self.loading_label.setAlignment(Qt.AlignCenter)
- self.loading_label.setFont(QFont("微软雅黑", 14))
- self.loading_label.setStyleSheet("color: #888; margin-bottom: 8px;")
- main_layout.addWidget(self.loading_label)
-
- # 网页视图
- self.webview = CustomWebView(parent=self, popup_list=self.popup_windows)
- self.webview.setAttribute(Qt.WA_TranslucentBackground, True)
- self.webview.setMinimumHeight(480)
- self.webview.setSizePolicy(QSizePolicy.Expanding, QSizePolicy.Expanding)
- self.webview.loadFinished.connect(self.on_webview_loaded)
- main_layout.addWidget(self.webview, stretch=10)
-
- # 刷新按钮
- btn_row = QHBoxLayout()
- btn_row.addStretch(1)
- self.refresh_btn = QPushButton("刷新零件库")
- self.refresh_btn.setFont(QFont("微软雅黑", 13, QFont.Bold))
- self.refresh_btn.setFixedWidth(140)
- self.refresh_btn.setStyleSheet("""
- QPushButton {
- background: qlineargradient(x1:0, y1:0, x2:1, y2:1,
- stop:0 #eaf6fb, stop:1 #d6eaf8);
- color: #2980b9;
- border-radius: 14px;
- font-size: 15px;
- font-weight: 600;
- padding: 8px 0;
- border: 1.5px solid #d6eaf8;
- }
- 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: #2471a3;
- border: 2px solid #a4cbe3;
- }
- """)
- self.refresh_btn.clicked.connect(self.reload_webview)
- btn_row.addWidget(self.refresh_btn)
- btn_row.addStretch(1)
- main_layout.addLayout(btn_row)
-
- # 自动加载网页
- QTimer.singleShot(200, lambda: self.webview.setUrl(QUrl("http://alist.qutrobot.top")))
- self.webview.show()
-
- # 定时刷新(可选,防止页面假死)
- self.refresh_timer = QTimer(self)
- self.refresh_timer.setInterval(100)
- self.refresh_timer.timeout.connect(self.webview.update)
- self.refresh_timer.start()
-
- def reload_webview(self):
- self.loading_label.show()
- self.webview.setUrl(QUrl("http://alist.qutrobot.top"))
-
- def on_webview_loaded(self):
- self.loading_label.hide()
-
- def closeEvent(self, event):
- self.refresh_timer.stop()
- super().closeEvent(event)
-# --------- 主工具箱UI ---------
-class ToolboxUI(QWidget):
- def __init__(self):
- super().__init__()
- self.setWindowTitle("MRobot 工具箱")
- self.resize(1920, 1080)
- self.setMinimumSize(900, 600)
- self.setStyleSheet("""
- QWidget {
- background: qlineargradient(x1:0, y1:0, x2:1, y2:1,
- stop:0 #eaf6fb, stop:1 #d6eaf8);
- border-radius: 16px;
- }
- """)
- self.init_ui()
-
- def init_ui(self):
- main_layout = QHBoxLayout(self)
- main_layout.setContentsMargins(20, 20, 20, 20)
- main_layout.setSpacing(20)
-
- # 左侧导航
- left_frame = QFrame()
- left_frame.setFrameShape(QFrame.StyledPanel)
- left_frame.setStyleSheet("""
- QFrame {
- background: #f8fbfd;
- border-radius: 14px;
- border: 1px solid #d6eaf8;
- }
- """)
- left_shadow = QGraphicsDropShadowEffect(self)
- left_shadow.setBlurRadius(16)
- left_shadow.setOffset(0, 4)
- left_shadow.setColor(Qt.gray)
- left_frame.setGraphicsEffect(left_shadow)
-
- left_layout = QVBoxLayout(left_frame)
- left_layout.setSpacing(24)
- left_layout.setContentsMargins(18, 18, 18, 18)
- left_frame.setFixedWidth(260)
- main_layout.addWidget(left_frame)
-
- # Logo
- logo_label = QLabel()
- logo_pixmap = QPixmap(resource_path("mr_tool_img/MRobot.png"))
- if not logo_pixmap.isNull():
- logo_label.setPixmap(logo_pixmap.scaled(180, 80, Qt.KeepAspectRatio, Qt.SmoothTransformation))
- else:
- logo_label.setText("MRobot")
- logo_label.setAlignment(Qt.AlignCenter)
- logo_label.setFont(QFont("Arial", 36, QFont.Bold))
- logo_label.setStyleSheet("color: #3498db;")
- logo_label.setFixedHeight(120)
- left_layout.addWidget(logo_label)
-
- # 按钮区
- self.button_names = ["主页", "曲线拟合", "Mini串口助手", "MR架构配置", "零件库", "软件指南"]
- self.buttons = []
- for idx, name in enumerate(self.button_names):
- btn = QPushButton(name)
- btn.setSizePolicy(QSizePolicy.Expanding, QSizePolicy.Fixed)
- btn.setMinimumHeight(48)
- btn.setStyleSheet("""
- QPushButton {
- background: qlineargradient(x1:0, y1:0, x2:1, y2:1,
- stop:0 #eaf6fb, stop:1 #d6eaf8);
- color: #2980b9;
- border-radius: 20px;
- font-size: 22px;
- font-weight: 600;
- padding: 14px 0;
- border: 1px solid #d6eaf8;
- letter-spacing: 2px;
- }
- QPushButton:hover {
- background: qlineargradient(x1:0, y1:0, x2:1, y2:1,
- stop:0 #f8fffe, stop:1 #cfe7fa);
- color: #1a6fae;
- border: 1.5px solid #b5d0ea;
- }
- QPushButton:pressed {
- background: #e3f0fa;
- color: #2471a3;
- border: 1.5px solid #a4cbe3;
- }
- """)
- btn.clicked.connect(lambda checked, i=idx: self.switch_function(i))
- self.buttons.append(btn)
- left_layout.addWidget(btn)
-
- left_layout.addStretch(1)
-
- # 文本输出框
- self.output_box = QTextEdit()
- self.output_box.setReadOnly(True)
- self.output_box.setFixedHeight(180)
- self.output_box.setStyleSheet("""
- QTextEdit {
- background: #f4f6f7;
- border-radius: 8px;
- border: 1px solid #d6eaf8;
- font-size: 16px;
- color: #2c3e50;
- padding: 10px;
- }
- """)
- left_layout.addWidget(self.output_box)
-
- # 右侧功能区
- self.stack = QStackedWidget()
- self.stack.setStyleSheet("""
- QStackedWidget {
- background: #fff;
- border-radius: 16px;
- border: 1px solid #d6eaf8;
- }
- """)
- right_shadow = QGraphicsDropShadowEffect(self)
- right_shadow.setBlurRadius(24)
- right_shadow.setOffset(0, 4)
- right_shadow.setColor(Qt.gray)
- self.stack.setGraphicsEffect(right_shadow)
-
- # 功能页面注册(后续扩展只需在这里添加页面类即可)
- self.page_widgets = {
- 0: HomePage(), # 主页
- 1: PolyFitApp(), # 多项式拟合
- 2: SerialAssistant(), # 串口助手
- 3: GenerateMRobotCode(), # MRobot架构生成
- 4: MachineryLibrary(), # 零件库
- 5: DownloadPage(), # 下载页面
- }
- for i in range(len(self.button_names)):
- self.stack.addWidget(self.page_widgets[i])
- main_layout.addWidget(self.stack)
-
- self.output_box.append("欢迎使用 MRobot 工具箱!请选择左侧功能。")
-
-
-
- def placeholder_page(self, text):
- page = QWidget()
- layout = QVBoxLayout(page)
- label = QLabel(text)
- label.setAlignment(Qt.AlignCenter)
- label.setFont(QFont("微软雅黑", 22, QFont.Bold))
- label.setStyleSheet("color: #2980b9;")
- layout.addStretch(1)
- layout.addWidget(label)
- layout.addStretch(1)
- return page
-
- def switch_function(self, idx):
- self.stack.setCurrentIndex(idx)
- self.output_box.append(f"已切换到功能:{self.button_names[idx]}")
-
-if __name__ == "__main__":
-
- from PyQt5.QtWidgets import QSplashScreen, QGraphicsDropShadowEffect
- from PyQt5.QtCore import Qt, QTimer, QRect
- from PyQt5.QtGui import QPixmap, QPainter, QColor, QFont, QLinearGradient, QBrush
-
- class CustomSplash(QSplashScreen):
- def __init__(self, pixmap):
- super().__init__(QPixmap(640, 400))
- self.setWindowFlags(Qt.WindowStaysOnTopHint | Qt.FramelessWindowHint)
- self.setAttribute(Qt.WA_TranslucentBackground)
- self.logo = pixmap.scaled(360, 240, Qt.KeepAspectRatio, Qt.SmoothTransformation)
- self.title = "MRobot 工具箱"
- self.subtitle = "欢迎使用 MRobot,正在启动中..."
- self.title_color = QColor("#2471a3")
- self.subtitle_color = QColor("#2980b9")
- self.bg_gradient = QLinearGradient(0, 0, 0, 400)
- self.bg_gradient.setColorAt(0, QColor("#f8fbfd"))
- self.bg_gradient.setColorAt(1, QColor("#eaf6fb"))
- self.border_color = QColor("#2980b9")
- self.setFixedSize(640, 400)
- # 阴影
- effect = QGraphicsDropShadowEffect(self)
- effect.setBlurRadius(36)
- effect.setOffset(0, 10)
- effect.setColor(QColor(80, 120, 180, 80))
- self.setGraphicsEffect(effect)
-
- def paintEvent(self, event):
- painter = QPainter(self)
- painter.setRenderHint(QPainter.Antialiasing)
- # 渐变背景
- painter.setBrush(QBrush(self.bg_gradient))
- painter.setPen(Qt.NoPen)
- painter.drawRoundedRect(0, 0, self.width(), self.height(), 44, 44)
- # 边框
- pen = painter.pen()
- pen.setColor(self.border_color)
- pen.setWidth(3)
- painter.setPen(pen)
- painter.drawRoundedRect(2, 2, self.width()-4, self.height()-4, 44, 44)
- # LOGO
- logo_x = (self.width() - self.logo.width()) // 2
- logo_y = 80
- painter.drawPixmap(logo_x, logo_y, self.logo)
- # 主标题
- # painter.setFont(QFont("微软雅黑", 24, QFont.Bold))
- # painter.setPen(self.title_color)
- # painter.drawText(QRect(0, 200, self.width(), 48), Qt.AlignCenter, self.title)
- # 副标题
- painter.setFont(QFont("微软雅黑", 12))
- painter.setPen(self.subtitle_color)
- painter.drawText(QRect(0, 250, self.width(), 36), Qt.AlignCenter, self.subtitle)
- # 版权
- painter.setFont(QFont("微软雅黑", 10))
- painter.setPen(QColor("#b2bec3"))
- painter.drawText(QRect(0, 360, self.width(), 30), Qt.AlignCenter, "© 2025 MRobot. All rights reserved.")
-
- app = QApplication(sys.argv)
-
- # 立即显示Splash
- logo_pix = QPixmap(resource_path("mr_tool_img/MRobot.png"))
- splash = CustomSplash(logo_pix)
- splash.show()
- app.processEvents() # 强制立即刷新Splash
-
- # 异步加载主窗口
- def load_main():
- win = ToolboxUI()
- splash.finish(win)
- win.show()
-
- QTimer.singleShot(100, load_main)
-
- sys.exit(app.exec_())
\ No newline at end of file
diff --git a/MRobot.py b/MRobot.py
deleted file mode 100644
index 7b103fd..0000000
--- a/MRobot.py
+++ /dev/null
@@ -1,673 +0,0 @@
-import sys
-import os
-import serial
-import serial.tools.list_ports
-
-from PyQt5.QtCore import Qt, pyqtSignal, QThread
-from PyQt5.QtGui import QTextCursor
-from PyQt5.QtWidgets import (
- QApplication, QWidget, QVBoxLayout, QHBoxLayout, QGridLayout, QGroupBox,
- QComboBox, QPushButton, QTextEdit, QLineEdit, QLabel, QSizePolicy,
- QFileDialog, QMessageBox, QStackedLayout
-)
-
-from qfluentwidgets import (
- Theme, setTheme, FluentIcon, SwitchButton, BodyLabel, SubtitleLabel,
- StrongBodyLabel, HorizontalSeparator, InfoBar, MessageDialog, Dialog,
- AvatarWidget, NavigationItemPosition, FluentWindow, NavigationAvatarWidget,
- PushButton, TextEdit, LineEdit, ComboBox, ImageLabel
-)
-from qfluentwidgets import FluentIcon as FIF
-import requests
-import shutil
-from PyQt5.QtCore import Qt
-from PyQt5.QtWidgets import QTreeWidget, QTreeWidgetItem, QAbstractItemView, QHeaderView
-
-from qfluentwidgets import (
- TreeWidget, InfoBar, InfoBarPosition, MessageDialog, TreeItemDelegate
-)
-from qfluentwidgets import CheckBox
-from qfluentwidgets import TreeWidget
-from PyQt5.QtCore import QThread, pyqtSignal
-from PyQt5.QtWidgets import QFileDialog
-from qfluentwidgets import ProgressBar
-
-# ===================== 页面基类 =====================
-class BaseInterface(QWidget):
- def __init__(self, parent=None):
- super().__init__(parent=parent)
-
-# ===================== 首页界面 =====================
-class HomeInterface(BaseInterface):
- def __init__(self, parent=None):
- super().__init__(parent=parent)
- self.setObjectName("homeInterface")
- layout = QVBoxLayout()
- layout.setContentsMargins(60, 60, 60, 60)
- layout.setSpacing(32)
- self.setLayout(layout)
-
- # 顶部logo和欢迎区
- top_layout = QHBoxLayout()
- logo = ImageLabel('img/MRobot.png')
- logo.setFixedSize(260, 80)
- top_layout.addWidget(logo, alignment=Qt.AlignmentFlag.AlignTop)
- title_layout = QVBoxLayout()
- title_layout.addWidget(StrongBodyLabel("欢迎使用 MRobot Toolbox"))
- title_layout.addWidget(SubtitleLabel("让你的机器人开发更高效、更智能"))
- top_layout.addLayout(title_layout)
- top_layout.addStretch()
- layout.addLayout(top_layout)
-
- layout.addWidget(HorizontalSeparator())
-
- # 项目简介
- layout.addWidget(BodyLabel(
- "MRobot Toolbox 是一款集成化的机器人开发辅助工具,"
- "支持代码生成、串口终端、主题切换等多种实用功能。\n"
- "点击左侧导航栏可快速切换各功能页面。"
- ))
-
- # 开发者与项目目标
- layout.addWidget(HorizontalSeparator())
- layout.addWidget(SubtitleLabel("开发者与项目目标"))
- layout.addWidget(BodyLabel("开发团队:QUT 青岛理工大学 MOVE 战队"))
- layout.addWidget(BodyLabel("项目目标:为所有 rmer 和 rcer 提供现代化、简单、高效的机器人开发方式,"
- "让机器人开发变得更轻松、更智能。"))
- layout.addWidget(BodyLabel("适用于 RM、RC、各类嵌入式机器人项目。"))
-
- # layout.addStretch()
-
-# ===================== 代码生成页面 =====================
-class DataInterface(BaseInterface):
- def __init__(self, parent=None):
- super().__init__(parent=parent)
- self.setObjectName("dataInterface")
- self.stacked_layout = QStackedLayout()
- self.setLayout(self.stacked_layout)
-
- # --- 页面1:工程路径选择 ---
- self.select_widget = QWidget()
- select_layout = QVBoxLayout(self.select_widget)
- select_layout.addSpacing(40)
- select_layout.addWidget(SubtitleLabel("MRobot 代码生成"))
- select_layout.addWidget(HorizontalSeparator())
- select_layout.addSpacing(10)
- select_layout.addWidget(BodyLabel("请选择包含 .ioc 文件的工程文件夹,点击下方按钮进行选择。"))
- select_layout.addSpacing(20)
- self.choose_btn = PushButton("选择工程路径")
- self.choose_btn.clicked.connect(self.choose_project_folder)
- select_layout.addWidget(self.choose_btn)
- select_layout.addStretch()
- self.stacked_layout.addWidget(self.select_widget)
-
- # --- 页面2:代码配置 ---
- self.config_widget = QWidget()
- self.config_layout = QVBoxLayout(self.config_widget)
- # 左上角小返回按钮
- top_bar = QHBoxLayout()
- self.back_btn = PushButton('返回', icon=FluentIcon.SKIP_BACK)
- # self.back_btn.setFixedSize(32, 32)
- self.back_btn.clicked.connect(self.back_to_select)
- self.back_btn.setToolTip("返回")
- top_bar.addWidget(self.back_btn, alignment=Qt.AlignmentFlag.AlignLeft)
- top_bar.addStretch()
- self.config_layout.addLayout(top_bar)
- self.config_layout.addWidget(SubtitleLabel("工程配置信息"))
- self.config_layout.addWidget(HorizontalSeparator())
- self.project_info_labels = []
- self.config_layout.addStretch()
- self.stacked_layout.addWidget(self.config_widget)
-
- # 默认显示选择页面
- self.stacked_layout.setCurrentWidget(self.select_widget)
-
- def choose_project_folder(self):
- folder = QFileDialog.getExistingDirectory(self, "请选择代码项目文件夹")
- if not folder:
- return
- ioc_files = [f for f in os.listdir(folder) if f.endswith('.ioc')]
- if not ioc_files:
- QMessageBox.warning(self, "提示", "未找到.ioc文件,请确认项目文件夹。")
- return
- self.project_path = folder
- self.project_name = os.path.basename(folder)
- self.ioc_file = os.path.join(folder, ioc_files[0])
- self.show_config_page()
-
- def show_config_page(self):
- # 清理旧内容
- for label in self.project_info_labels:
- self.config_layout.removeWidget(label)
- label.deleteLater()
- self.project_info_labels.clear()
- # 显示项目信息
- l1 = BodyLabel(f"项目名称: {self.project_name}")
- l2 = BodyLabel(f"项目路径: {self.project_path}")
- l3 = BodyLabel(f"IOC 文件: {self.ioc_file}")
- self.config_layout.insertWidget(2, l1)
- self.config_layout.insertWidget(3, l2)
- self.config_layout.insertWidget(4, l3)
- self.project_info_labels.extend([l1, l2, l3])
- self.stacked_layout.setCurrentWidget(self.config_widget)
-
- def back_to_select(self):
- self.stacked_layout.setCurrentWidget(self.select_widget)
-
-# ===================== 串口终端界面 =====================
-class SerialReadThread(QThread):
- data_received = pyqtSignal(str)
-
- def __init__(self, ser):
- super().__init__()
- self.ser = ser
- self._running = True
-
- def run(self):
- while self._running:
- if self.ser and self.ser.is_open and self.ser.in_waiting:
- try:
- data = self.ser.readline().decode(errors='ignore')
- self.data_received.emit(data)
- except Exception:
- pass
-
- def stop(self):
- self._running = False
- self.wait()
-
-class SerialTerminalInterface(BaseInterface):
- def __init__(self, parent=None):
- super().__init__(parent=parent)
- self.setObjectName("serialTerminalInterface")
- main_layout = QVBoxLayout(self)
- main_layout.setSpacing(12)
-
- # 顶部:串口设置区
- top_hbox = QHBoxLayout()
- top_hbox.addWidget(BodyLabel("串口:"))
- self.port_combo = ComboBox()
- self.refresh_ports()
- top_hbox.addWidget(self.port_combo)
- top_hbox.addWidget(BodyLabel("波特率:"))
- self.baud_combo = ComboBox()
- self.baud_combo.addItems(['9600', '115200', '57600', '38400', '19200', '4800'])
- top_hbox.addWidget(self.baud_combo)
- self.connect_btn = PushButton("连接")
- self.connect_btn.clicked.connect(self.toggle_connection)
- top_hbox.addWidget(self.connect_btn)
- self.refresh_btn = PushButton(FluentIcon.SYNC, "刷新")
- self.refresh_btn.clicked.connect(self.refresh_ports)
- top_hbox.addWidget(self.refresh_btn)
- top_hbox.addStretch()
- main_layout.addLayout(top_hbox)
-
- main_layout.addWidget(HorizontalSeparator())
-
- # 中部:左侧预设命令,右侧显示区
- center_hbox = QHBoxLayout()
- # 左侧:预设命令竖排
- preset_vbox = QVBoxLayout()
- preset_vbox.addWidget(SubtitleLabel("快捷指令"))
- #快捷指令居中
- preset_vbox.setAlignment(Qt.AlignmentFlag.AlignCenter)
- self.preset_commands = [
- ("线程监视器", "RESET"),
- ("陀螺仪校准", "GET_VERSION"),
- ("性能监视", "START"),
- ("重启", "STOP"),
- ("显示所有设备", "SELF_TEST"),
- ("查询id", "STATUS"),
- ]
- for label, cmd in self.preset_commands:
- btn = PushButton(label)
- btn.setSizePolicy(QSizePolicy.Expanding, QSizePolicy.Preferred)
- btn.clicked.connect(lambda _, c=cmd: self.send_preset_command(c))
- preset_vbox.addWidget(btn)
- preset_vbox.addStretch()
- main_layout.addLayout(center_hbox, stretch=1)
-
-
- # 右侧:串口数据显示区
- self.text_edit = TextEdit()
- self.text_edit.setReadOnly(True)
- self.text_edit.setMinimumWidth(400)
- center_hbox.addWidget(self.text_edit, 3)
- center_hbox.addLayout(preset_vbox, 1)
-
- main_layout.addWidget(HorizontalSeparator())
-
- # 底部:输入区
- bottom_hbox = QHBoxLayout()
- self.input_line = LineEdit()
- self.input_line.setPlaceholderText("输入内容,回车发送")
- self.input_line.returnPressed.connect(self.send_data)
- bottom_hbox.addWidget(self.input_line, 4)
- send_btn = PushButton("发送")
- send_btn.clicked.connect(self.send_data)
- bottom_hbox.addWidget(send_btn, 1)
- self.auto_enter_checkbox = CheckBox("自动回车")
- self.auto_enter_checkbox.setChecked(True)
- bottom_hbox.addWidget(self.auto_enter_checkbox)
- bottom_hbox.addStretch()
- main_layout.addLayout(bottom_hbox)
-
- self.ser = None
- self.read_thread = None
-
- def send_preset_command(self, cmd):
- self.input_line.setText(cmd)
- self.send_data()
-
- def refresh_ports(self):
- self.port_combo.clear()
- ports = serial.tools.list_ports.comports()
- for port in ports:
- self.port_combo.addItem(port.device)
-
- def toggle_connection(self):
- if self.ser and self.ser.is_open:
- self.disconnect_serial()
- else:
- self.connect_serial()
-
- def connect_serial(self):
- port = self.port_combo.currentText()
- baud = int(self.baud_combo.currentText())
- try:
- self.ser = serial.Serial(port, baud, timeout=0.1)
- self.connect_btn.setText("断开")
- self.text_edit.append(f"已连接到 {port} @ {baud}")
- self.read_thread = SerialReadThread(self.ser)
- self.read_thread.data_received.connect(self.display_data)
- self.read_thread.start()
- except Exception as e:
- self.text_edit.append(f"连接失败: {e}")
-
- def disconnect_serial(self):
- if self.read_thread:
- self.read_thread.stop()
- self.read_thread = None
- if self.ser:
- self.ser.close()
- self.ser = None
- self.connect_btn.setText("连接")
- self.text_edit.append("已断开连接")
-
- def display_data(self, data):
- self.text_edit.moveCursor(QTextCursor.End)
- self.text_edit.insertPlainText(data)
- self.text_edit.moveCursor(QTextCursor.End)
-
- def send_data(self):
- if self.ser and self.ser.is_open:
- text = self.input_line.text()
- try:
- if not text:
- self.ser.write('\n'.encode())
- else:
- for char in text:
- self.ser.write(char.encode())
- # 判断是否自动回车
- if self.auto_enter_checkbox.isChecked():
- self.ser.write('\n'.encode())
- except Exception as e:
- self.text_edit.append(f"发送失败: {e}")
- self.input_line.clear()
-
-
-# ===================== 零件库页面 =====================
-
-# ...existing code...
-class DownloadThread(QThread):
- progressChanged = pyqtSignal(int)
- finished = pyqtSignal(list, list) # success, fail
-
- def __init__(self, files, server_url, secret_key, local_dir, parent=None):
- super().__init__(parent)
- self.files = files
- self.server_url = server_url
- self.secret_key = secret_key
- self.local_dir = local_dir
-
- def run(self):
- success, fail = [], []
- total = len(self.files)
- max_retry = 3 # 最大重试次数
- for idx, rel_path in enumerate(self.files):
- retry = 0
- while retry < max_retry:
- try:
- url = f"{self.server_url}/download/{rel_path}"
- params = {"key": self.secret_key}
- resp = requests.get(url, params=params, stream=True, timeout=10)
- if resp.status_code == 200:
- local_path = os.path.join(self.local_dir, rel_path)
- os.makedirs(os.path.dirname(local_path), exist_ok=True)
- with open(local_path, "wb") as f:
- shutil.copyfileobj(resp.raw, f)
- success.append(rel_path)
- break # 下载成功,跳出重试循环
- else:
- print(f"下载失败({resp.status_code}): {rel_path},第{retry+1}次尝试")
- retry += 1
- except Exception as e:
- print(f"下载异常: {rel_path},第{retry+1}次尝试,错误: {e}")
- retry += 1
- else:
- fail.append(rel_path)
- self.progressChanged.emit(int((idx + 1) / total * 100))
- self.finished.emit(success, fail)
-
-
-class PartLibraryInterface(BaseInterface):
- SERVER_URL = "http://154.37.215.220:5000"
- SECRET_KEY = "MRobot_Download"
- LOCAL_LIB_DIR = "mech_lib"
-
- def __init__(self, parent=None):
- super().__init__(parent=parent)
- self.setObjectName("partLibraryInterface")
- layout = QVBoxLayout(self)
- layout.setSpacing(16)
-
- layout.addWidget(SubtitleLabel("零件库(在线bate版)"))
- layout.addWidget(HorizontalSeparator())
- layout.addWidget(BodyLabel("可浏览服务器零件库,选择需要的文件下载到本地。(如无法使用或者下载失败,请尝试重新下载或检查网络连接)"))
-
- btn_layout = QHBoxLayout()
- refresh_btn = PushButton(FluentIcon.SYNC, "刷新列表")
- refresh_btn.clicked.connect(self.refresh_list)
- btn_layout.addWidget(refresh_btn)
-
- # 新增:打开本地零件库按钮
- open_local_btn = PushButton(FluentIcon.FOLDER, "打开本地零件库")
- open_local_btn.clicked.connect(self.open_local_lib)
- btn_layout.addWidget(open_local_btn)
- btn_layout.addStretch()
- layout.addLayout(btn_layout)
-
- self.tree = TreeWidget(self)
-
- self.tree.setHeaderLabels(["名称", "类型"])
- self.tree.setSelectionMode(self.tree.ExtendedSelection)
- self.tree.header().setSectionResizeMode(0, QHeaderView.Stretch)
- self.tree.header().setSectionResizeMode(1, QHeaderView.ResizeToContents)
- self.tree.setCheckedColor("#0078d4", "#2d7d9a")
- self.tree.setBorderRadius(8)
- self.tree.setBorderVisible(True)
- layout.addWidget(self.tree, stretch=1)
-
- download_btn = PushButton(FluentIcon.DOWNLOAD, "下载选中文件")
- download_btn.clicked.connect(self.download_selected_files)
- layout.addWidget(download_btn)
-
- self.refresh_list(first=True)
-
- def refresh_list(self, first=False):
- self.tree.clear()
- try:
- resp = requests.get(
- f"{self.SERVER_URL}/list",
- params={"key": self.SECRET_KEY},
- timeout=5
- )
- resp.raise_for_status()
- tree = resp.json()
- self.populate_tree(self.tree, tree, "")
- if not first:
- InfoBar.success(
- title="刷新成功",
- content="零件库已经是最新的!",
- parent=self,
- position=InfoBarPosition.TOP,
- duration=2000
- )
- except Exception as e:
- InfoBar.error(
- title="刷新失败",
- content=f"获取零件库失败: {e}",
- parent=self,
- position=InfoBarPosition.TOP,
- duration=3000
- )
-
- def populate_tree(self, parent, node, path_prefix):
- from PyQt5.QtWidgets import QTreeWidgetItem
- for dname, dnode in node.get("dirs", {}).items():
- item = QTreeWidgetItem([dname, "文件夹"])
- if isinstance(parent, TreeWidget):
- parent.addTopLevelItem(item)
- else:
- parent.addChild(item)
- self.populate_tree(item, dnode, os.path.join(path_prefix, dname))
- for fname in node.get("files", []):
- item = QTreeWidgetItem([fname, "文件"])
- item.setFlags(item.flags() | Qt.ItemIsUserCheckable)
- item.setCheckState(0, Qt.Unchecked)
- item.setData(0, Qt.UserRole, os.path.join(path_prefix, fname))
- if isinstance(parent, TreeWidget):
- parent.addTopLevelItem(item)
- else:
- parent.addChild(item)
-
- def get_checked_files(self):
- files = []
- def _traverse(item):
- for i in range(item.childCount()):
- child = item.child(i)
- if child.text(1) == "文件" and child.checkState(0) == Qt.Checked:
- files.append(child.data(0, Qt.UserRole))
- _traverse(child)
- root = self.tree.invisibleRootItem()
- for i in range(root.childCount()):
- _traverse(root.child(i))
- return files
-
- def download_selected_files(self):
- files = self.get_checked_files()
- if not files:
- InfoBar.info(
- title="提示",
- content="请先勾选要下载的文件。",
- parent=self,
- position=InfoBarPosition.TOP,
- duration=2000
- )
- return
-
- # 进度条对话框
- self.progress_dialog = Dialog(
- title="正在下载",
- content="正在下载选中文件,请稍候...",
- parent=self
- )
- self.progress_bar = ProgressBar()
- self.progress_bar.setValue(0)
- # 插入进度条到内容布局
- self.progress_dialog.textLayout.addWidget(self.progress_bar)
- self.progress_dialog.show()
-
- # 启动下载线程
- self.download_thread = DownloadThread(
- files, self.SERVER_URL, self.SECRET_KEY, self.LOCAL_LIB_DIR
- )
- self.download_thread.progressChanged.connect(self.progress_bar.setValue)
- self.download_thread.finished.connect(self.on_download_finished)
- self.download_thread.finished.connect(self.download_thread.deleteLater)
- self.download_thread.start()
-
- def on_download_finished(self, success, fail):
- self.progress_dialog.close()
- msg = f"成功下载: {len(success)} 个文件\n失败: {len(fail)} 个文件"
- dialog = Dialog(
- title="下载结果",
- content=msg,
- parent=self
- )
- # 添加“打开文件夹”按钮
- open_btn = PushButton("打开文件夹")
- def open_folder():
- folder = os.path.abspath(self.LOCAL_LIB_DIR)
- # 打开文件夹(macOS用open,Windows用explorer,Linux用xdg-open)
- import platform, subprocess
- if platform.system() == "Darwin":
- subprocess.call(["open", folder])
- elif platform.system() == "Windows":
- subprocess.call(["explorer", folder])
- else:
- subprocess.call(["xdg-open", folder])
- dialog.close()
- open_btn.clicked.connect(open_folder)
- # 添加按钮到Dialog布局
- dialog.textLayout.addWidget(open_btn)
- dialog.exec()
-
- def open_local_lib(self):
- folder = os.path.abspath(self.LOCAL_LIB_DIR)
- import platform, subprocess
- if platform.system() == "Darwin":
- subprocess.call(["open", folder])
- elif platform.system() == "Windows":
- subprocess.call(["explorer", folder])
- else:
- subprocess.call(["xdg-open", folder])
-
-# ===================== 设置界面 =====================
-class SettingInterface(BaseInterface):
- themeSwitchRequested = pyqtSignal()
-
- def __init__(self, parent=None):
- super().__init__(parent=parent)
- self.setObjectName("settingInterface")
- layout = QVBoxLayout()
- self.setLayout(layout)
-
- # 标题
- layout.addSpacing(10)
- layout.addWidget(SubtitleLabel("设置中心"))
- layout.addSpacing(10)
- layout.addWidget(HorizontalSeparator())
-
- # 主题切换区域
- theme_title = StrongBodyLabel("外观设置")
- theme_desc = BodyLabel("切换夜间/白天模式,适应不同环境。")
- theme_desc.setWordWrap(True)
- layout.addSpacing(10)
- layout.addWidget(theme_title)
- layout.addWidget(theme_desc)
-
- theme_box = QHBoxLayout()
- self.theme_label = BodyLabel("夜间模式")
- self.theme_switch = SwitchButton()
- self.theme_switch.setChecked(Theme.DARK == Theme.DARK)
- self.theme_switch.checkedChanged.connect(self.on_theme_switch)
- theme_box.addWidget(self.theme_label)
- theme_box.addWidget(self.theme_switch)
- theme_box.addStretch()
- layout.addLayout(theme_box)
-
- layout.addSpacing(15)
- layout.addWidget(HorizontalSeparator())
-
- # 其它设置区域(示例)
- other_title = StrongBodyLabel("其它设置")
- other_desc = BodyLabel("更多功能正在开发中,敬请期待。")
- other_desc.setWordWrap(True)
- layout.addSpacing(10)
- layout.addWidget(other_title)
- layout.addWidget(other_desc)
-
- # 版权信息
- layout.addStretch()
- copyright_label = BodyLabel("© 2025 MRobot Toolbox")
- copyright_label.setAlignment(Qt.AlignmentFlag.AlignHCenter)
- layout.addWidget(copyright_label)
- layout.addSpacing(10)
-
- def on_theme_switch(self, checked):
- self.themeSwitchRequested.emit()
-
-# ===================== 帮助与关于界面 =====================
-class HelpInterface(BaseInterface):
- def __init__(self, parent=None):
- super().__init__(parent=parent)
- self.setObjectName("helpInterface")
- layout = QVBoxLayout()
- self.setLayout(layout)
-
-class AboutInterface(BaseInterface):
- def __init__(self, parent=None):
- super().__init__(parent=parent)
- self.setObjectName("aboutInterface")
- layout = QVBoxLayout()
- self.setLayout(layout)
-
-# ===================== 主窗口与导航 =====================
-class MainWindow(FluentWindow):
- themeChanged = pyqtSignal(Theme)
-
- def __init__(self):
- super().__init__()
- self.setWindowTitle("MR_ToolBox")
- self.resize(1000, 700)
- self.setMinimumSize(800, 600)
-
- # 记录当前主题
- self.current_theme = Theme.DARK
-
- # 创建页面实例
- self.setting_page = SettingInterface(self)
- self.setting_page.themeSwitchRequested.connect(self.toggle_theme)
-
- self.page_registry = [
- (HomeInterface(self), FIF.HOME, "首页", NavigationItemPosition.TOP),
- (DataInterface(self), FIF.LIBRARY, "MRobot代码生成", NavigationItemPosition.SCROLL),
- (SerialTerminalInterface(self), FIF.COMMAND_PROMPT, "Mini_Shell", NavigationItemPosition.SCROLL),
- (PartLibraryInterface(self), FIF.DOWNLOAD, "零件库", NavigationItemPosition.SCROLL), # ← 加上这一行
- (self.setting_page, FIF.SETTING, "设置", NavigationItemPosition.BOTTOM),
- (HelpInterface(self), FIF.HELP, "帮助", NavigationItemPosition.BOTTOM),
- (AboutInterface(self), FIF.INFO, "关于", NavigationItemPosition.BOTTOM),
- ]
- self.initNavigation()
-
- def initNavigation(self):
- for page, icon, name, position in self.page_registry:
- self.addSubInterface(page, icon, name, position)
- self.navigationInterface.addSeparator()
- avatar = NavigationAvatarWidget('用户', ':/qfluentwidgets/images/avatar.png')
- self.navigationInterface.addWidget(
- routeKey='avatar',
- widget=avatar,
- onClick=self.show_user_info, # 这里改为 self.show_user_info
- position=NavigationItemPosition.BOTTOM
- )
-
- def toggle_theme(self):
- # 切换主题
- if self.current_theme == Theme.DARK:
- self.current_theme = Theme.LIGHT
- else:
- self.current_theme = Theme.DARK
- setTheme(self.current_theme)
- # 同步设置界面按钮状态
- self.setting_page.theme_switch.setChecked(self.current_theme == Theme.DARK)
-
- def show_user_info(self):
- dialog = Dialog(
- title="用户信息",
- content="用户:MRobot至尊VIP用户",
- parent=self
- )
- dialog.exec()
-
-# ===================== 程序入口 =====================
-def main():
- app = QApplication(sys.argv)
- setTheme(Theme.DARK)
- window = MainWindow()
- window.show()
- sys.exit(app.exec_())
-
-if __name__ == '__main__':
- main()
\ No newline at end of file
diff --git a/MRobot_old.py b/MRobot_old.py
deleted file mode 100644
index c85ba8c..0000000
--- a/MRobot_old.py
+++ /dev/null
@@ -1,719 +0,0 @@
-import tkinter as tk
-from tkinter import ttk
-from PIL import Image, ImageTk
-import sys
-import os
-import threading
-import shutil
-import re
-from git import Repo
-from collections import defaultdict
-import csv
-import xml.etree.ElementTree as ET
-
-# 配置常量
-REPO_DIR = "MRobot_repo"
-REPO_URL = "http://gitea.qutrobot.top/robofish/MRobot.git"
-if getattr(sys, 'frozen', False): # 检查是否为打包后的环境
- CURRENT_DIR = os.path.dirname(sys.executable) # 使用可执行文件所在目录
-else:
- CURRENT_DIR = os.path.dirname(os.path.abspath(__file__)) # 使用脚本所在目录
-
-MDK_ARM_DIR = os.path.join(CURRENT_DIR, "MDK-ARM")
-USER_DIR = os.path.join(CURRENT_DIR, "User")
-
-class MRobotApp:
- def __init__(self):
- self.ioc_data = None
- self.add_gitignore_var = None # 延迟初始化
- self.header_file_vars = {}
- self.task_vars = [] # 用于存储任务的变量
-
- # 初始化
- def initialize(self):
- print("初始化中,正在克隆仓库...")
- self.clone_repo()
- self.ioc_data = self.find_and_read_ioc_file()
- print("初始化完成,启动主窗口...")
- self.show_main_window()
-
- # 克隆仓库
- def clone_repo(self):
- try:
- if os.path.exists(REPO_DIR):
- shutil.rmtree(REPO_DIR)
- print(f"正在克隆仓库到 {REPO_DIR}(仅克隆当前文件内容)...")
- Repo.clone_from(REPO_URL, REPO_DIR, multi_options=["--depth=1"])
- print("仓库克隆成功!")
- except Exception as e:
- print(f"克隆仓库时出错: {e}")
-
- # 删除克隆的仓库
- def delete_repo(self):
- try:
- if os.path.exists(REPO_DIR):
- shutil.rmtree(REPO_DIR)
- print(f"已删除克隆的仓库目录: {REPO_DIR}")
- except Exception as e:
- print(f"删除仓库目录时出错: {e}")
-
-
- # 复制文件
- def copy_file_from_repo(self, src_path, dest_path):
- try:
- # 修复路径拼接问题,确保 src_path 不重复包含 REPO_DIR
- if src_path.startswith(REPO_DIR):
- full_src_path = src_path
- else:
- full_src_path = os.path.join(REPO_DIR, src_path.lstrip(os.sep))
-
- # 检查源文件是否存在
- if not os.path.exists(full_src_path):
- print(f"文件 {full_src_path} 不存在!(检查路径或仓库内容)")
- return
-
- # 检查目标路径是否有效
- if not dest_path or not dest_path.strip():
- print("目标路径为空或无效,无法复制文件!")
- return
-
- # 创建目标目录(如果不存在)
- dest_dir = os.path.dirname(dest_path)
- if dest_dir and not os.path.exists(dest_dir):
- os.makedirs(dest_dir, exist_ok=True)
-
- # 执行文件复制
- shutil.copy(full_src_path, dest_path)
- print(f"文件已从 {full_src_path} 复制到 {dest_path}")
- except Exception as e:
- print(f"复制文件时出错: {e}")
-
- # 查找并读取 .ioc 文件
- def find_and_read_ioc_file(self):
- try:
- for file in os.listdir("."):
- if file.endswith(".ioc"):
- print(f"找到 .ioc 文件: {file}")
- with open(file, "r", encoding="utf-8") as f:
- return f.read()
- print("未找到 .ioc 文件!")
- except Exception as e:
- print(f"读取 .ioc 文件时出错: {e}")
- return None
-
- # 检查是否启用了 FreeRTOS
- def check_freertos_enabled(self, ioc_data):
- try:
- return bool(re.search(r"Mcu\.IP\d+=FREERTOS", ioc_data))
- except Exception as e:
- print(f"检查 FreeRTOS 配置时出错: {e}")
- return False
-
- # 生成操作
- def generate_action(self):
- def task():
- # 检查并创建目录
- self.create_directories()
-
- if self.add_gitignore_var.get():
- self.copy_file_from_repo(".gitignore", ".gitignore")
- if self.ioc_data and self.check_freertos_enabled(self.ioc_data):
- self.copy_file_from_repo("src/freertos.c", os.path.join("Core", "Src", "freertos.c"))
-
- # 定义需要处理的文件夹
- folders = ["bsp", "component", "device", "module"]
-
- # 遍历每个文件夹,复制选中的 .h 和 .c 文件
- for folder in folders:
- folder_dir = os.path.join(REPO_DIR, "User", folder)
- if not os.path.exists(folder_dir):
- continue # 如果文件夹不存在,跳过
-
- for file_name in os.listdir(folder_dir):
- file_base, file_ext = os.path.splitext(file_name)
- if file_ext not in [".h", ".c"]:
- continue # 只处理 .h 和 .c 文件
-
- # 强制复制与文件夹同名的文件
- if file_base == folder:
- src_path = os.path.join(folder_dir, file_name)
- dest_path = os.path.join("User", folder, file_name)
- self.copy_file_from_repo(src_path, dest_path)
- continue # 跳过后续检查,直接复制
-
- # 检查是否选中了对应的文件
- if file_base in self.header_file_vars and self.header_file_vars[file_base].get():
- src_path = os.path.join(folder_dir, file_name)
- dest_path = os.path.join("User", folder, file_name)
- self.copy_file_from_repo(src_path, dest_path)
-
- threading.Thread(target=task).start()
-
-
-
- # 创建必要的目录
- def create_directories(self):
- try:
- directories = [
- "User/bsp",
- "User/component",
- "User/device",
- "User/module",
- ]
- # 根据是否启用 FreeRTOS 决定是否创建 User/task
- if self.ioc_data and self.check_freertos_enabled(self.ioc_data):
- directories.append("User/task")
-
- for directory in directories:
- if not os.path.exists(directory):
- os.makedirs(directory, exist_ok=True)
- print(f"已创建目录: {directory}")
- else:
- print(f"目录已存在: {directory}")
- except Exception as e:
- print(f"创建目录时出错: {e}")
-
-
- # 更新 FreeRTOS 状态标签
- def update_freertos_status(self, label):
- if self.ioc_data:
- status = "已启用" if self.check_freertos_enabled(self.ioc_data) else "未启用"
- else:
- status = "未检测到 .ioc 文件"
- label.config(text=f"FreeRTOS 状态: {status}")
-
-
-
- # 显示主窗口
- # ...existing code...
- # ...existing code...
-
- # 显示主窗口
- def show_main_window(self):
- root = tk.Tk()
- root.title("MRobot 自动生成脚本")
- root.geometry("1000x650") # 调整窗口大小以适应布局
-
- # 在窗口关闭时调用 on_closing 方法
- root.protocol("WM_DELETE_WINDOW", lambda: self.on_closing(root))
-
- # 初始化 BooleanVar
- self.add_gitignore_var = tk.BooleanVar(value=False)
- self.auto_configure_var = tk.BooleanVar(value=False) # 新增复选框变量
-
- # 创建主框架
- main_frame = ttk.Frame(root)
- main_frame.pack(fill="both", expand=True)
-
- # 添加标题
- title_label = ttk.Label(main_frame, text="MRobot 自动生成脚本", font=("Arial", 16, "bold"))
- title_label.pack(pady=10)
-
- # 添加 FreeRTOS 状态标签
- freertos_status_label = ttk.Label(main_frame, text="FreeRTOS 状态: 检测中...", font=("Arial", 12))
- freertos_status_label.pack(pady=10)
- self.update_freertos_status(freertos_status_label)
-
- # 模块文件选择和任务管理框架(添加滚动功能)
- module_task_frame = ttk.Frame(main_frame)
- module_task_frame.pack(fill="both", expand=True, padx=10, pady=10)
-
- # 创建 Canvas 和 Scrollbar
- canvas = tk.Canvas(module_task_frame)
- scrollbar = ttk.Scrollbar(module_task_frame, orient="vertical", command=canvas.yview)
- scrollable_frame = ttk.Frame(canvas)
-
- # 配置滚动区域
- scrollable_frame.bind(
- "",
- lambda e: canvas.configure(scrollregion=canvas.bbox("all"))
- )
- canvas.create_window((0, 0), window=scrollable_frame, anchor="nw")
- canvas.configure(yscrollcommand=scrollbar.set)
-
- # 绑定鼠标滚轮事件
- def on_mouse_wheel(event):
- canvas.yview_scroll(-1 * int(event.delta / 120), "units")
-
- canvas.bind_all("", on_mouse_wheel)
-
- # 布局 Canvas 和 Scrollbar
- canvas.pack(side="left", fill="both", expand=True)
- scrollbar.pack(side="right", fill="y")
-
- # 左右布局:模块文件选择框和任务管理框
- left_frame = ttk.Frame(scrollable_frame)
- left_frame.pack(side="left", fill="both", expand=True, padx=5, pady=5)
-
- right_frame = ttk.Frame(scrollable_frame)
- right_frame.pack(side="right", fill="both", expand=True, padx=5, pady=5)
-
- # 模块文件选择框
- header_files_frame = ttk.LabelFrame(left_frame, text="模块文件选择", padding=(10, 10))
- header_files_frame.pack(fill="both", expand=True, padx=5)
- self.header_files_frame = header_files_frame
- self.update_header_files()
-
- # 任务管理框
- if self.ioc_data and self.check_freertos_enabled(self.ioc_data):
- task_frame = ttk.LabelFrame(right_frame, text="任务管理", padding=(10, 10))
- task_frame.pack(fill="both", expand=True, padx=5)
- self.task_frame = task_frame
- self.update_task_ui()
-
- # 添加消息框和生成按钮在同一行
- bottom_frame = ttk.Frame(main_frame)
- bottom_frame.pack(fill="x", pady=10, side="bottom")
-
- # 消息框
- self.message_box = tk.Text(bottom_frame, wrap="word", state="disabled", height=5, width=60)
- self.message_box.pack(side="left", fill="x", expand=True, padx=5, pady=5)
-
- # 生成按钮和复选框选项
- button_frame = ttk.Frame(bottom_frame)
- button_frame.pack(side="right", padx=10)
-
- # 添加复选框容器(横向排列复选框)
- checkbox_frame = ttk.Frame(button_frame)
- checkbox_frame.pack(side="top", pady=5)
-
- # 添加 .gitignore 复选框(左侧)
- ttk.Checkbutton(checkbox_frame, text=".gitignore", variable=self.add_gitignore_var).pack(side="left", padx=5)
-
- # 添加自动配置环境复选框(右侧)
- ttk.Checkbutton(checkbox_frame, text="自动环境", variable=self.auto_configure_var).pack(side="left", padx=5)
-
- # 添加生成按钮(竖向排列在复选框下方)
- generate_button = ttk.Button(button_frame, text="一键生成MRobot代码", command=self.generate_action)
- generate_button.pack(side="top", pady=10)
- generate_button.config(width=25) # 设置按钮宽度
-
- # 重定向输出到消息框
- self.redirect_output()
-
- # 打印欢迎信息
- print("欢迎使用 MRobot 自动生成脚本!")
- print("请根据需要选择模块文件和任务。")
- print("点击“一键生成MRobot代码”按钮开始生成。")
-
- # 启动 Tkinter 主事件循环
- root.mainloop()
-
- # ...existing code...
- # ...existing code...
-
- def redirect_output(self):
- """
- 重定向标准输出到消息框
- """
- class TextRedirector:
- def __init__(self, text_widget):
- self.text_widget = text_widget
-
- def write(self, message):
- self.text_widget.config(state="normal")
- self.text_widget.insert("end", message)
- self.text_widget.see("end")
- self.text_widget.config(state="disabled")
-
- def flush(self):
- pass
-
- sys.stdout = TextRedirector(self.message_box)
- sys.stderr = TextRedirector(self.message_box)
-
- # 修改 update_task_ui 方法
- def update_task_ui(self):
- # 检查是否有已存在的任务文件
- task_dir = os.path.join("User", "task")
- if os.path.exists(task_dir):
- for file_name in os.listdir(task_dir):
- file_base, file_ext = os.path.splitext(file_name)
- if file_ext == ".c" and file_base not in ["init", "user_task"] and file_base not in [task_var.get() for task_var, _ in self.task_vars]:
- frequency = 100 # 默认频率
- user_task_header_path = os.path.join("User", "task", "user_task.h")
- if os.path.exists(user_task_header_path):
- try:
- with open(user_task_header_path, "r", encoding="utf-8") as f:
- content = f.read()
- pattern = rf"#define\s+TASK_FREQ_{file_base.upper()}\s*\((\d+)[uU]?\)"
- match = re.search(pattern, content)
- if match:
- frequency = int(match.group(1))
- print(f"从 user_task.h 文件中读取到任务 {file_base} 的频率: {frequency}")
- except Exception as e:
- print(f"读取 user_task.h 文件时出错: {e}")
-
- new_task_var = tk.StringVar(value=file_base)
- self.task_vars.append((new_task_var, tk.IntVar(value=frequency)))
-
- # 清空任务框架中的所有子组件
- for widget in self.task_frame.winfo_children():
- widget.destroy()
-
-
- # 设置任务管理框的固定宽度
- self.task_frame.config(width=400)
-
- # 显示任务列表
- for i, (task_var, freq_var) in enumerate(self.task_vars):
- task_row = ttk.Frame(self.task_frame, width=400)
- task_row.pack(fill="x", pady=5)
-
- ttk.Entry(task_row, textvariable=task_var, width=20).pack(side="left", padx=5)
- ttk.Label(task_row, text="频率:").pack(side="left", padx=5)
- ttk.Spinbox(task_row, from_=1, to=1000, textvariable=freq_var, width=5).pack(side="left", padx=5)
- ttk.Button(task_row, text="删除", command=lambda idx=i: self.remove_task(idx)).pack(side="left", padx=5)
-
- # 添加新任务按钮
- add_task_button = ttk.Button(self.task_frame, text="添加任务", command=self.add_task)
- add_task_button.pack(pady=10)
-
-
- # 修改 add_task 方法
- def add_task(self):
- new_task_var = tk.StringVar(value=f"Task_{len(self.task_vars) + 1}")
- new_freq_var = tk.IntVar(value=100) # 默认频率为 100
- self.task_vars.append((new_task_var, new_freq_var))
- self.update_task_ui()
-
- # 修改 remove_task 方法
- def remove_task(self, idx):
- del self.task_vars[idx]
- self.update_task_ui()
-
- # 更新文件夹显示
- def update_folder_display(self):
- for widget in self.folder_frame.winfo_children():
- widget.destroy()
-
- folders = ["User/bsp", "User/component", "User/device", "User/module"]
- # if self.ioc_data and self.check_freertos_enabled(self.ioc_data):
- # folders.append("User/task")
-
- for folder in folders:
- # 去掉 "User/" 前缀
- display_name = folder.replace("User/", "")
- tk.Label(self.folder_frame, text=display_name).pack()
-
- # 更新 .h 文件复选框
- def update_header_files(self):
- for widget in self.header_files_frame.winfo_children():
- widget.destroy()
-
- folders = ["bsp", "component", "device", "module"]
- dependencies = defaultdict(list)
-
- for folder in folders:
- folder_dir = os.path.join(REPO_DIR, "User", folder)
- if os.path.exists(folder_dir):
- dependencies_file = os.path.join(folder_dir, "dependencies.csv")
- if os.path.exists(dependencies_file):
- with open(dependencies_file, "r", encoding="utf-8") as f:
- reader = csv.reader(f)
- for row in reader:
- if len(row) == 2:
- dependencies[row[0]].append(row[1])
-
- # 创建复选框
- for folder in folders:
- folder_dir = os.path.join(REPO_DIR, "User", folder)
- if os.path.exists(folder_dir):
- module_frame = ttk.LabelFrame(self.header_files_frame, text=folder.capitalize(), padding=(10, 10))
- module_frame.pack(fill="x", pady=5)
-
- row, col = 0, 0
- for file in os.listdir(folder_dir):
- file_base, file_ext = os.path.splitext(file)
- if file_ext == ".h" and file_base != folder:
- var = tk.BooleanVar(value=False)
- self.header_file_vars[file_base] = var
-
- checkbox = ttk.Checkbutton(
- module_frame,
- text=file_base,
- variable=var,
- command=lambda fb=file_base: self.handle_dependencies(fb, dependencies)
- )
- checkbox.grid(row=row, column=col, padx=5, pady=5, sticky="w")
- col += 1
- if col >= 6:
- col = 0
- row += 1
-
-
-
- def handle_dependencies(self, file_base, dependencies):
- """
- 根据依赖关系自动勾选相关模块
- """
- if file_base in self.header_file_vars and self.header_file_vars[file_base].get():
- # 如果当前模块被选中,自动勾选其依赖项
- for dependency in dependencies.get(file_base, []):
- dep_base = os.path.basename(dependency)
- if dep_base in self.header_file_vars:
- self.header_file_vars[dep_base].set(True)
-
- # 在 MRobotApp 类中添加以下方法
- def generate_task_files(self):
- try:
- template_file_path = os.path.join(REPO_DIR, "User", "task", "task.c.template")
- task_dir = os.path.join("User", "task")
-
- if not os.path.exists(template_file_path):
- print(f"模板文件 {template_file_path} 不存在,无法生成 task.c 文件!")
- return
-
- os.makedirs(task_dir, exist_ok=True)
-
- with open(template_file_path, "r", encoding="utf-8") as f:
- template_content = f.read()
-
- # 为每个任务生成对应的 task.c 文件
- for task_var, _ in self.task_vars: # 解包元组
- task_name = f"Task_{task_var.get()}" # 添加前缀 Task_
- task_file_path = os.path.join(task_dir, f"{task_var.get().lower()}.c") # 文件名保持原始小写
-
- # 替换模板中的占位符
- task_content = template_content.replace("{{task_name}}", task_name)
- task_content = task_content.replace("{{task_function}}", task_name)
- task_content = task_content.replace(
- "{{task_frequency}}", f"TASK_FREQ_{task_var.get().upper()}"
- ) # 替换为 user_task.h 中的宏定义
- task_content = task_content.replace("{{task_delay}}", f"TASK_INIT_DELAY_{task_var.get().upper()}")
-
- with open(task_file_path, "w", encoding="utf-8") as f:
- f.write(task_content)
-
- print(f"已成功生成 {task_file_path} 文件!")
- except Exception as e:
- print(f"生成 task.c 文件时出错: {e}")
- # 修改 user_task.c 文件
- def modify_user_task_file(self):
- try:
- template_file_path = os.path.join(REPO_DIR, "User", "task", "user_task.c.template")
- generated_task_file_path = os.path.join("User", "task", "user_task.c")
-
- if not os.path.exists(template_file_path):
- print(f"模板文件 {template_file_path} 不存在,无法生成 user_task.c 文件!")
- return
-
- os.makedirs(os.path.dirname(generated_task_file_path), exist_ok=True)
-
- with open(template_file_path, "r", encoding="utf-8") as f:
- template_content = f.read()
-
- # 生成任务属性定义
- task_attr_definitions = "\n".join([
- f"""const osThreadAttr_t attr_{task_var.get().lower()} = {{
- .name = "{task_var.get()}",
- .priority = osPriorityNormal,
- .stack_size = 128 * 4,
- }};"""
- for task_var, _ in self.task_vars # 解包元组
- ])
-
- # 替换模板中的占位符
- task_content = template_content.replace("{{task_attr_definitions}}", task_attr_definitions)
-
- with open(generated_task_file_path, "w", encoding="utf-8") as f:
- f.write(task_content)
-
- print(f"已成功生成 {generated_task_file_path} 文件!")
- except Exception as e:
- print(f"修改 user_task.c 文件时出错: {e}")
- # ...existing code...
-
- def generate_user_task_header(self):
- try:
- template_file_path = os.path.join(REPO_DIR, "User", "task", "user_task.h.template")
- header_file_path = os.path.join("User", "task", "user_task.h")
-
- if not os.path.exists(template_file_path):
- print(f"模板文件 {template_file_path} 不存在,无法生成 user_task.h 文件!")
- return
-
- os.makedirs(os.path.dirname(header_file_path), exist_ok=True)
-
- # 如果 user_task.h 已存在,提取 /* USER MESSAGE BEGIN */ 和 /* USER MESSAGE END */ 区域内容
- existing_msgq_content = ""
- if os.path.exists(header_file_path):
- with open(header_file_path, "r", encoding="utf-8") as f:
- content = f.read()
- # 提取 /* USER MESSAGE BEGIN */ 和 /* USER MESSAGE END */ 区域内容
- match = re.search(r"/\* USER MESSAGE BEGIN \*/\s*(.*?)\s*/\* USER MESSAGE END \*/", content, re.DOTALL)
- if match:
- existing_msgq_content = match.group(1).strip()
- print("已存在的 msgq 区域内容:")
- print(existing_msgq_content)
-
- with open(template_file_path, "r", encoding="utf-8") as f:
- template_content = f.read()
-
- # 定义占位符内容
- thread_definitions = "\n".join([f" osThreadId_t {task_var.get().lower()};" for task_var, _ in self.task_vars])
- msgq_definitions = existing_msgq_content if existing_msgq_content else " osMessageQueueId_t default_msgq;"
- freq_definitions = "\n".join([f" float {task_var.get().lower()};" for task_var, _ in self.task_vars])
- last_up_time_definitions = "\n".join([f" uint32_t {task_var.get().lower()};" for task_var, _ in self.task_vars])
- task_attr_declarations = "\n".join([f"extern const osThreadAttr_t attr_{task_var.get().lower()};" for task_var, _ in self.task_vars])
- task_function_declarations = "\n".join([f"void Task_{task_var.get()}(void *argument);" for task_var, _ in self.task_vars])
- task_frequency_definitions = "\n".join([
- f"#define TASK_FREQ_{task_var.get().upper()} ({freq_var.get()}u)"
- for task_var, freq_var in self.task_vars
- ])
- task_init_delay_definitions = "\n".join([f"#define TASK_INIT_DELAY_{task_var.get().upper()} (0u)" for task_var, _ in self.task_vars])
- task_handle_definitions = "\n".join([f" osThreadId_t {task_var.get().lower()};" for task_var, _ in self.task_vars])
-
- # 替换模板中的占位符
- header_content = template_content.replace("{{thread_definitions}}", thread_definitions)
- header_content = header_content.replace("{{msgq_definitions}}", msgq_definitions)
- header_content = header_content.replace("{{freq_definitions}}", freq_definitions)
- header_content = header_content.replace("{{last_up_time_definitions}}", last_up_time_definitions)
- header_content = header_content.replace("{{task_attr_declarations}}", task_attr_declarations)
- header_content = header_content.replace("{{task_function_declarations}}", task_function_declarations)
- header_content = header_content.replace("{{task_frequency_definitions}}", task_frequency_definitions)
- header_content = header_content.replace("{{task_init_delay_definitions}}", task_init_delay_definitions)
- header_content = header_content.replace("{{task_handle_definitions}}", task_handle_definitions)
-
- # 如果存在 /* USER MESSAGE BEGIN */ 区域内容,则保留
- if existing_msgq_content:
- header_content = re.sub(
- r"/\* USER MESSAGE BEGIN \*/\s*.*?\s*/\* USER MESSAGE END \*/",
- f"/* USER MESSAGE BEGIN */\n\n {existing_msgq_content}\n\n /* USER MESSAGE END */",
- header_content,
- flags=re.DOTALL
- )
-
- with open(header_file_path, "w", encoding="utf-8") as f:
- f.write(header_content)
-
- print(f"已成功生成 {header_file_path} 文件!")
- except Exception as e:
- print(f"生成 user_task.h 文件时出错: {e}")
-
- def generate_init_file(self):
- try:
- template_file_path = os.path.join(REPO_DIR, "User", "task", "init.c.template")
- generated_file_path = os.path.join("User", "task", "init.c")
-
- if not os.path.exists(template_file_path):
- print(f"模板文件 {template_file_path} 不存在,无法生成 init.c 文件!")
- return
-
- os.makedirs(os.path.dirname(generated_file_path), exist_ok=True)
-
- # 如果 init.c 已存在,提取 /* USER MESSAGE BEGIN */ 和 /* USER MESSAGE END */ 区域内容
- existing_msgq_content = ""
- if os.path.exists(generated_file_path):
- with open(generated_file_path, "r", encoding="utf-8") as f:
- content = f.read()
- # 提取 /* USER MESSAGE BEGIN */ 和 /* USER MESSAGE END */ 区域内容
- match = re.search(r"/\* USER MESSAGE BEGIN \*/\s*(.*?)\s*/\* USER MESSAGE END \*/", content, re.DOTALL)
- if match:
- existing_msgq_content = match.group(1).strip()
- print("已存在的消息队列区域内容:")
- print(existing_msgq_content)
-
- with open(template_file_path, "r", encoding="utf-8") as f:
- template_content = f.read()
-
- # 生成任务创建代码
- thread_creation_code = "\n".join([
- f" task_runtime.thread.{task_var.get().lower()} = osThreadNew(Task_{task_var.get()}, NULL, &attr_{task_var.get().lower()});"
- for task_var, _ in self.task_vars # 解包元组
- ])
-
- # 替换模板中的占位符
- init_content = template_content.replace("{{thread_creation_code}}", thread_creation_code)
-
- # 如果存在 /* USER MESSAGE BEGIN */ 区域内容,则保留
- if existing_msgq_content:
- init_content = re.sub(
- r"/\* USER MESSAGE BEGIN \*/\s*.*?\s*/\* USER MESSAGE END \*/",
- f"/* USER MESSAGE BEGIN */\n {existing_msgq_content}\n /* USER MESSAGE END */",
- init_content,
- flags=re.DOTALL
- )
-
- with open(generated_file_path, "w", encoding="utf-8") as f:
- f.write(init_content)
-
- print(f"已成功生成 {generated_file_path} 文件!")
- except Exception as e:
- print(f"生成 init.c 文件时出错: {e}")
-
- # 修改 generate_action 方法
-
- def generate_action(self):
- def task():
- # 检查并创建目录(与 FreeRTOS 状态无关的模块始终创建)
- self.create_directories()
-
- # 复制 .gitignore 文件
- if self.add_gitignore_var.get():
- self.copy_file_from_repo(".gitignore", ".gitignore")
-
- # 如果启用了 FreeRTOS,复制相关文件
- if self.ioc_data and self.check_freertos_enabled(self.ioc_data):
- self.copy_file_from_repo("src/freertos.c", os.path.join("Core", "Src", "freertos.c"))
-
- # 定义需要处理的文件夹(与 FreeRTOS 状态无关)
- folders = ["bsp", "component", "device", "module"]
-
- # 遍历每个文件夹,复制选中的 .h 和 .c 文件
- for folder in folders:
- folder_dir = os.path.join(REPO_DIR, "User", folder)
- if not os.path.exists(folder_dir):
- continue # 如果文件夹不存在,跳过
-
- for file_name in os.listdir(folder_dir):
- file_base, file_ext = os.path.splitext(file_name)
- if file_ext not in [".h", ".c"]:
- continue # 只处理 .h 和 .c 文件
-
- # 强制复制与文件夹同名的文件
- if file_base == folder:
- src_path = os.path.join(folder_dir, file_name)
- dest_path = os.path.join("User", folder, file_name)
- self.copy_file_from_repo(src_path, dest_path)
- print(f"强制复制与文件夹同名的文件: {file_name}")
- continue # 跳过后续检查,直接复制
-
- # 检查是否选中了对应的文件
- if file_base in self.header_file_vars and self.header_file_vars[file_base].get():
- src_path = os.path.join(folder_dir, file_name)
- dest_path = os.path.join("User", folder, file_name)
- self.copy_file_from_repo(src_path, dest_path)
-
- # 如果启用了 FreeRTOS,执行任务相关的生成逻辑
- if self.ioc_data and self.check_freertos_enabled(self.ioc_data):
- # 修改 user_task.c 文件
- self.modify_user_task_file()
-
- # 生成 user_task.h 文件
- self.generate_user_task_header()
-
- # 生成 init.c 文件
- self.generate_init_file()
-
- # 生成 task.c 文件
- self.generate_task_files()
-
- # # 自动配置环境
- # if self.auto_configure_var.get():
-
- # self.auto_configure_environment()
-
-
- threading.Thread(target=task).start()
-
- # 程序关闭时清理
- def on_closing(self, root):
- self.delete_repo()
- root.destroy()
-
-
-# 程序入口
-if __name__ == "__main__":
- app = MRobotApp()
- app.initialize()
\ No newline at end of file
diff --git a/README.md b/README.md
deleted file mode 100644
index f1f246a..0000000
--- a/README.md
+++ /dev/null
@@ -1,93 +0,0 @@
-# MRobot
-
-更加高效快捷的 STM32 开发架构,诞生于 Robocon 和 Robomaster,但绝不仅限于此。
-
-
-

-
是时候使用更简洁的方式开发单片机了
-
-
-
-
-
----
-
-## 引言
-
-提起嵌入式开发,绝大多数人对每次繁琐的配置,以及查阅各种文档来写东西感到非常枯燥和浪费使时间,对于小形形目创建优雅的架构又比较费事,那么我们哟u没有办法快速完成基础环境的搭建后直接开始写逻辑代码呢?
-
-这就是**MRobot**。
-
-
-
----
-
-## 获取源代码
-
-(此处可补充获取代码的具体方法)
-
----
-
-## 主要特色
-
-(此处可补充项目的主要特色)
-
----
-
-## 组成
-
-
-

-
-
-- `src/bsp`
-- `src/component`
-- `src/device`
-- `src/module`
-- `src/task`
-
----
-
-## 应用案例
-
-> **Robomaster**
-
-- 全向轮步兵
-- 英雄
-- 哨兵
-
----
-
-## 机器人展示
-
-`以上机器人均使用 MRobot 搭建`
-
----
-
-## 硬件支持
-
-(此处可补充支持的硬件列表)
-
----
-
-## 图片展示
-
-
-## 相关依赖
-
-(此处可补充项目依赖的具体内容)
-
----
-
-## 构建 exe
-
-使用以下命令构建可执行文件:
-
-```bash
-pyinstaller --onefile --windowed
-pyinstaller MR_Toolbox.py --onefile --noconsole --icon=img\M.ico --add-data "mr_tool_img\MRobot.png;mr_tool_img"
-
-pyinstaller MR_Tool.py --onefile --noconsole --icon=img\M.ico --add-data "mr_tool_img\MRobot.png;mr_tool_img" --add-data "src;src" --add-data "User;User"
\ No newline at end of file
diff --git a/fluentui.py b/fluentui.py
deleted file mode 100644
index c434363..0000000
--- a/fluentui.py
+++ /dev/null
@@ -1,168 +0,0 @@
-import sys
-import webbrowser
-import serial
-import serial.tools.list_ports
-
-from PyQt5.QtCore import Qt, QSize, pyqtSignal
-from PyQt5.QtGui import QPixmap, QFont
-from PyQt5.QtWidgets import (
- QWidget, QVBoxLayout, QApplication, QLabel, QGroupBox, QGridLayout, QFrame,
- QHBoxLayout, QComboBox, QTextEdit, QLineEdit
-)
-
-from qfluentwidgets import (
- NavigationInterface, NavigationItemPosition, MessageBox,
- setTheme, Theme, FluentWindow, NavigationAvatarWidget,
- InfoBar, InfoBarPosition, PushButton, FluentIcon
-)
-from qfluentwidgets import FluentIcon as FIF
-
-# ===================== 页面基类 =====================
-class BaseInterface(QWidget):
- """所有页面的基类,页面内容完全自定义"""
- def __init__(self, parent=None):
- super().__init__(parent=parent)
-
-# ===================== 首页界面 =====================
-class HomeInterface(BaseInterface):
- def __init__(self, parent=None):
- super().__init__(parent=parent)
- self.setObjectName("homeInterface")
- layout = QVBoxLayout()
- self.setLayout(layout)
-# ===================== 代码生成页面 =====================
-class DataInterface(BaseInterface):
- def __init__(self, parent=None):
- super().__init__(parent=parent)
- self.setObjectName("dataInterface")
- # 空页面示例
- layout = QVBoxLayout()
- self.setLayout(layout)
-
-# ===================== 串口终端界面 =====================
-class SerialTerminalInterface(BaseInterface):
- def __init__(self, parent=None):
- super().__init__(parent=parent)
- self.setObjectName("serialTerminalInterface")
- layout = QVBoxLayout()
-
-# ===================== 设置界面 =====================
-class SettingInterface(BaseInterface):
- def __init__(self, parent=None):
- super().__init__(parent=parent)
- self.setObjectName("settingInterface")
- layout = QVBoxLayout()
- self.themeBtn = PushButton(
- "切换夜间", self, FluentIcon.BRUSH
- )
- self.themeBtn.setFixedWidth(120)
- self.themeBtn.clicked.connect(self.onThemeBtnClicked)
- layout.addWidget(self.themeBtn)
- layout.addStretch(1)
- self.setLayout(layout)
-
- # 监听主题变化
- mw = self.window()
- if hasattr(mw, "themeChanged"):
- mw.themeChanged.connect(self.updateThemeBtn)
-
- def onThemeBtnClicked(self):
- mw = self.window()
- if hasattr(mw, "toggleTheme"):
- mw.toggleTheme()
-
- def updateThemeBtn(self, theme):
- if theme == Theme.LIGHT:
- self.themeBtn.setText("切换夜间")
- else:
- self.themeBtn.setText("切换白天")
-# ===================== 帮助与关于界面 =====================
-class HelpInterface(BaseInterface):
- def __init__(self, parent=None):
- super().__init__(parent=parent)
- self.setObjectName("helpInterface")
- layout = QVBoxLayout()
- self.setLayout(layout)
-
-class AboutInterface(BaseInterface):
- def __init__(self, parent=None):
- super().__init__(parent=parent)
- self.setObjectName("aboutInterface")
- layout = QVBoxLayout()
- self.setLayout(layout)
-
-# ===================== 主窗口与导航 =====================
-class MainWindow(FluentWindow):
- themeChanged = pyqtSignal(Theme)
-
- def __init__(self):
- super().__init__()
- self.setWindowTitle("MR_ToolBox")
- self.resize(1000, 700)
- self.setMinimumSize(800, 600)
- setTheme(Theme.LIGHT)
- self.theme = Theme.LIGHT
-
- self.page_registry = [
- (HomeInterface(self), FIF.HOME, "首页", NavigationItemPosition.TOP),
- (DataInterface(self), FIF.LIBRARY, "MRobot代码生成", NavigationItemPosition.SCROLL),
- (SerialTerminalInterface(self), FIF.COMMAND_PROMPT, "串口终端", NavigationItemPosition.SCROLL),
- (SettingInterface(self), FIF.SETTING, "设置", NavigationItemPosition.BOTTOM),
- (HelpInterface(self), FIF.HELP, "帮助", NavigationItemPosition.BOTTOM),
- (AboutInterface(self), FIF.INFO, "关于", NavigationItemPosition.BOTTOM),
- ]
- self.initNavigation()
-
- # 把切换主题按钮放到标题栏右侧
- self.themeBtn = PushButton("切换夜间", self, FluentIcon.BRUSH)
- self.themeBtn.setFixedWidth(120)
- self.themeBtn.clicked.connect(self.toggleTheme)
- self.addTitleBarWidget(self.themeBtn, align=Qt.AlignRight)
-
- def initNavigation(self):
- for page, icon, name, position in self.page_registry:
- self.addSubInterface(page, icon, name, position)
- self.navigationInterface.addSeparator()
- avatar = NavigationAvatarWidget('用户', ':/qfluentwidgets/images/avatar.png')
- self.navigationInterface.addWidget(
- routeKey='avatar',
- widget=avatar,
- onClick=self.showUserInfo,
- position=NavigationItemPosition.BOTTOM
- )
-
- def toggleTheme(self):
- if self.theme == Theme.LIGHT:
- setTheme(Theme.DARK)
- self.theme = Theme.DARK
- self.themeBtn.setText("切换白天")
- else:
- setTheme(Theme.LIGHT)
- self.theme = Theme.LIGHT
- self.themeBtn.setText("切换夜间")
- self.themeChanged.emit(self.theme)
- self.refreshStyle()
-
- def refreshStyle(self):
- def refresh(widget):
- widget.setStyleSheet(widget.styleSheet())
- for child in widget.findChildren(QWidget):
- refresh(child)
- refresh(self)
-
- def showUserInfo(self):
- MessageBox("用户信息", "当前登录用户:管理员", self).exec()
-# ===================== 程序入口 =====================
-def main():
- QApplication.setHighDpiScaleFactorRoundingPolicy(
- Qt.HighDpiScaleFactorRoundingPolicy.PassThrough)
- QApplication.setAttribute(Qt.ApplicationAttribute.AA_EnableHighDpiScaling)
- QApplication.setAttribute(Qt.ApplicationAttribute.AA_UseHighDpiPixmaps)
-
- app = QApplication(sys.argv)
- window = MainWindow()
- window.show()
- sys.exit(app.exec_())
-
-if __name__ == '__main__':
- main()
\ No newline at end of file
diff --git a/img/.DS_Store b/img/.DS_Store
deleted file mode 100644
index d67ede8..0000000
Binary files a/img/.DS_Store and /dev/null differ
diff --git a/img/M.ico b/img/M.ico
deleted file mode 100644
index 400b26d..0000000
Binary files a/img/M.ico and /dev/null differ
diff --git a/img/M.png b/img/M.png
deleted file mode 100644
index 300d879..0000000
Binary files a/img/M.png and /dev/null differ
diff --git a/img/MR.ico b/img/MR.ico
deleted file mode 100644
index ebad3d5..0000000
Binary files a/img/MR.ico and /dev/null differ
diff --git a/img/MR.png b/img/MR.png
deleted file mode 100644
index c546f6f..0000000
Binary files a/img/MR.png and /dev/null differ
diff --git a/img/MRobot.ico b/img/MRobot.ico
deleted file mode 100644
index 004d771..0000000
Binary files a/img/MRobot.ico and /dev/null differ
diff --git a/img/MRobot.png b/img/MRobot.png
deleted file mode 100644
index 4524089..0000000
Binary files a/img/MRobot.png and /dev/null differ
diff --git a/mr_tool_img/MRobot.png b/mr_tool_img/MRobot.png
deleted file mode 100644
index 4524089..0000000
Binary files a/mr_tool_img/MRobot.png and /dev/null differ
diff --git a/pngico.py b/pngico.py
deleted file mode 100644
index 8a74659..0000000
--- a/pngico.py
+++ /dev/null
@@ -1,42 +0,0 @@
-from PIL import Image
-import os
-
-def crop_transparent_background(input_path, output_path):
- """
- 裁切 PNG 图片的透明背景并保存。
-
- :param input_path: 输入图片路径
- :param output_path: 输出图片路径
- """
- try:
- # 打开图片
- img = Image.open(input_path)
-
- # 确保图片是 RGBA 模式
- if img.mode != "RGBA":
- img = img.convert("RGBA")
-
- # 获取图片的 alpha 通道
- bbox = img.getbbox()
-
- if bbox:
- # 裁切图片
- cropped_img = img.crop(bbox)
- # 保存裁切后的图片
- cropped_img.save(output_path, format="PNG")
- print(f"图片已保存到: {output_path}")
- else:
- print("图片没有透明背景或为空。")
- except Exception as e:
- print(f"处理图片时出错: {e}")
-
-if __name__ == "__main__":
- # 示例:输入和输出路径
- input_file = "C:\Mac\Home\Desktop\MRobot\img\M.png" # 替换为你的输入图片路径
- output_file = "C:\Mac\Home\Desktop\MRobot\img\M.png" # 替换为你的输出图片路径
-
- # 检查文件是否存在
- if os.path.exists(input_file):
- crop_transparent_background(input_file, output_file)
- else:
- print(f"输入文件不存在: {input_file}")
\ No newline at end of file
diff --git a/polynomial.py b/polynomial.py
deleted file mode 100644
index f7057eb..0000000
--- a/polynomial.py
+++ /dev/null
@@ -1,286 +0,0 @@
-import sys
-import numpy as np
-import pandas as pd
-from PyQt5.QtWidgets import (
- QApplication, QWidget, QVBoxLayout, QHBoxLayout, QPushButton, QSpinBox,
- QLabel, QTableWidget, QTableWidgetItem, QFileDialog, QTextEdit,
- QComboBox, QMessageBox, QHeaderView
-)
-from PyQt5.QtGui import QFont
-from PyQt5.QtCore import Qt
-import matplotlib
-from matplotlib.backends.backend_qt5agg import FigureCanvasQTAgg as FigureCanvas
-from matplotlib.figure import Figure
-
-class PolyFitApp(QWidget):
- def __init__(self):
- super().__init__()
- self.setWindowTitle("MRobot 多项式拟合工具")
- self.resize(1440, 1280)
- self.setFont(QFont("微软雅黑", 11))
- self.center()
-
- self.data_x = []
- self.data_y = []
- self.last_coeffs = None
- self.last_xmin = None
- self.last_xmax = None
-
- # 主布局
- main_layout = QHBoxLayout(self)
- main_layout.setContentsMargins(20, 20, 20, 20)
- main_layout.setSpacing(20)
- left_layout = QVBoxLayout()
- left_layout.setSpacing(12)
- right_layout = QVBoxLayout()
- right_layout.setSpacing(12)
- main_layout.addLayout(left_layout, 0)
- main_layout.addLayout(right_layout, 1)
-
- # 数据输入区
- self.table = QTableWidget(0, 2)
- self.table.setFont(QFont("Consolas", 11))
- self.table.setHorizontalHeaderLabels(["x", "y"])
- self.table.horizontalHeader().setSectionResizeMode(QHeaderView.Stretch)
- self.table.setSelectionBehavior(QTableWidget.SelectRows)
- left_layout.addWidget(self.table)
-
- btn_row = QHBoxLayout()
- self.add_row_btn = QPushButton("添加数据")
- self.add_row_btn.setStyleSheet("color: #333;")
- self.add_row_btn.clicked.connect(self.add_point_row)
- btn_row.addWidget(self.add_row_btn)
-
- self.del_row_btn = QPushButton("删除选中行")
- self.del_row_btn.setStyleSheet("color: #333;")
- self.del_row_btn.clicked.connect(self.delete_selected_rows)
- btn_row.addWidget(self.del_row_btn)
- left_layout.addLayout(btn_row)
-
- # 导入导出按钮区
- file_btn_row = QHBoxLayout()
- self.import_btn = QPushButton("导入Excel文件")
- self.import_btn.setStyleSheet("font-weight: bold; color: #333;")
- self.import_btn.clicked.connect(self.load_excel)
- file_btn_row.addWidget(self.import_btn)
-
- self.export_btn = QPushButton("导出Excel文件")
- self.export_btn.setStyleSheet("font-weight: bold; color: #333;")
- self.export_btn.clicked.connect(self.export_excel_and_plot)
- file_btn_row.addWidget(self.export_btn)
- left_layout.addLayout(file_btn_row)
-
- # 拟合参数区
- param_layout = QHBoxLayout()
- param_layout.addWidget(QLabel("多项式阶数:"))
- self.order_spin = QSpinBox()
- self.order_spin.setRange(1, 10)
- self.order_spin.setValue(2)
- param_layout.addWidget(self.order_spin)
- left_layout.addLayout(param_layout)
-
- self.fit_btn = QPushButton("拟合并显示")
- self.fit_btn.setStyleSheet("font-weight: bold; color: #333;")
- self.fit_btn.clicked.connect(self.fit_and_plot)
- left_layout.addWidget(self.fit_btn)
-
- # 输出区
- self.output = QTextEdit()
- self.output.setReadOnly(False)
- self.output.setFont(QFont("Consolas", 10))
- self.output.setMaximumHeight(150)
- left_layout.addWidget(self.output)
-
- code_layout = QHBoxLayout()
- code_layout.addWidget(QLabel("输出代码格式:"))
- self.code_type = QComboBox()
- self.code_type.addItems(["C", "C++", "Python"])
- code_layout.addWidget(self.code_type)
- self.gen_code_btn = QPushButton("生成函数代码")
- self.gen_code_btn.setStyleSheet("color: #333;")
- self.gen_code_btn.clicked.connect(self.generate_code)
- code_layout.addWidget(self.gen_code_btn)
- left_layout.addLayout(code_layout)
-
- # 拟合曲线区
- self.figure = Figure(figsize=(5, 4))
- self.canvas = FigureCanvas(self.figure)
- right_layout.addWidget(self.canvas)
-
- def center(self):
- qr = self.frameGeometry()
- cp = QApplication.desktop().availableGeometry().center()
- qr.moveCenter(cp)
- self.move(qr.topLeft())
-
- def add_point_row(self, x_val="", y_val=""):
- row = self.table.rowCount()
- self.table.insertRow(row)
- self.table.setItem(row, 0, QTableWidgetItem(str(x_val)))
- self.table.setItem(row, 1, QTableWidgetItem(str(y_val)))
-
- def delete_selected_rows(self):
- selected = self.table.selectionModel().selectedRows()
- for idx in sorted(selected, reverse=True):
- self.table.removeRow(idx.row())
-
- def load_excel(self):
- file, _ = QFileDialog.getOpenFileName(self, "选择Excel文件", "", "Excel Files (*.xlsx *.xls)")
- if file:
- try:
- data = pd.read_excel(file, usecols=[0, 1])
- new_x = data.iloc[:, 0].values.tolist()
- new_y = data.iloc[:, 1].values.tolist()
- for x, y in zip(new_x, new_y):
- self.add_point_row(x, y)
- QMessageBox.information(self, "成功", "数据导入成功!")
- except Exception as e:
- QMessageBox.critical(self, "错误", f"读取Excel失败: {e}")
-
- def export_excel_and_plot(self):
- file, _ = QFileDialog.getSaveFileName(self, "导出Excel文件", "", "Excel Files (*.xlsx *.xls)")
- if file:
- x_list, y_list = [], []
- for row in range(self.table.rowCount()):
- try:
- x = float(self.table.item(row, 0).text())
- y = float(self.table.item(row, 1).text())
- x_list.append(x)
- y_list.append(y)
- except Exception:
- continue
- if not x_list or not y_list:
- QMessageBox.warning(self, "导出失败", "没有可导出的数据!")
- return
- df = pd.DataFrame({'x': x_list, 'y': y_list})
- try:
- df.to_excel(file, index=False)
- # 导出同名png图像
- png_file = file
- if png_file.lower().endswith('.xlsx') or png_file.lower().endswith('.xls'):
- png_file = png_file.rsplit('.', 1)[0] + '.png'
- else:
- png_file = png_file + '.png'
- self.figure.savefig(png_file, dpi=150, bbox_inches='tight')
- QMessageBox.information(self, "导出成功", f"数据已成功导出到Excel文件!\n图像已导出为:{png_file}")
- except Exception as e:
- QMessageBox.critical(self, "导出错误", f"导出Excel或图像失败: {e}")
-
- def get_manual_points(self):
- x_list, y_list = [], []
- for row in range(self.table.rowCount()):
- try:
- x = float(self.table.item(row, 0).text())
- y = float(self.table.item(row, 1).text())
- x_list.append(x)
- y_list.append(y)
- except Exception:
- continue
- return x_list, y_list
-
- def fit_and_plot(self):
- matplotlib.rcParams['font.sans-serif'] = ['SimHei', 'Microsoft YaHei']
- matplotlib.rcParams['axes.unicode_minus'] = False
- matplotlib.rcParams['font.size'] = 14
- self.data_x, self.data_y = self.get_manual_points()
- try:
- order = int(self.order_spin.value())
- except ValueError:
- QMessageBox.warning(self, "输入错误", "阶数必须为整数!")
- return
- n_points = len(self.data_x)
- if n_points < order + 1:
- QMessageBox.warning(self, "数据不足", "数据点数量不足以拟合该阶多项式!")
- return
- x = np.array(self.data_x, dtype=np.float64)
- y = np.array(self.data_y, dtype=np.float64)
- x_min, x_max = x.min(), x.max()
- if x_max - x_min == 0:
- QMessageBox.warning(self, "数据错误", "所有x值都相同,无法拟合!")
- return
- try:
- coeffs = np.polyfit(x, y, order)
- except Exception as e:
- QMessageBox.critical(self, "拟合错误", f"多项式拟合失败:{e}")
- return
- poly = np.poly1d(coeffs)
- expr = "y = " + " + ".join([f"{c:.6g}*x^{order-i}" for i, c in enumerate(coeffs)])
- self.output.setPlainText(f"{expr}\n")
- self.figure.clear()
- ax = self.figure.add_subplot(111)
- ax.scatter(x, y, color='red', label='数据点')
- x_fit = np.linspace(x_min, x_max, 200)
- y_fit = poly(x_fit)
- ax.plot(x_fit, y_fit, label='拟合曲线')
- ax.legend()
- self.canvas.draw()
- self.last_coeffs = coeffs
- self.last_xmin = x_min
- self.last_xmax = x_max
-
- def generate_code(self):
- if self.last_coeffs is None:
- QMessageBox.warning(self, "未拟合", "请先拟合数据!")
- return
- coeffs = self.last_coeffs
- code_type = self.code_type.currentText()
- if code_type == "C":
- code = self.create_c_function(coeffs)
- elif code_type == "C++":
- code = self.create_cpp_function(coeffs)
- else:
- code = self.create_py_function(coeffs)
- self.output.setPlainText(code)
-
- def create_c_function(self, coeffs):
- lines = ["#include ", "double polynomial(double x) {", " return "]
- n = len(coeffs)
- terms = []
- for i, c in enumerate(coeffs):
- exp = n - i - 1
- if exp == 0:
- terms.append(f"{c:.8g}")
- elif exp == 1:
- terms.append(f"{c:.8g}*x")
- else:
- terms.append(f"{c:.8g}*pow(x,{exp})")
- lines[-1] += " + ".join(terms) + ";"
- lines.append("}")
- return "\n".join(lines)
-
- def create_cpp_function(self, coeffs):
- lines = ["#include ", "double polynomial(double x) {", " return "]
- n = len(coeffs)
- terms = []
- for i, c in enumerate(coeffs):
- exp = n - i - 1
- if exp == 0:
- terms.append(f"{c:.8g}")
- elif exp == 1:
- terms.append(f"{c:.8g}*x")
- else:
- terms.append(f"{c:.8g}*pow(x,{exp})")
- lines[-1] += " + ".join(terms) + ";"
- lines.append("}")
- return "\n".join(lines)
-
- def create_py_function(self, coeffs):
- n = len(coeffs)
- lines = ["def polynomial(x):", " return "]
- terms = []
- for i, c in enumerate(coeffs):
- exp = n - i - 1
- if exp == 0:
- terms.append(f"{c:.8g}")
- elif exp == 1:
- terms.append(f"{c:.8g}*x")
- else:
- terms.append(f"{c:.8g}*x**{exp}")
- lines[-1] += " + ".join(terms)
- return "\n".join(lines)
-
-if __name__ == "__main__":
- app = QApplication(sys.argv)
- win = PolyFitApp()
- win.show()
- sys.exit(app.exec_())
\ No newline at end of file
diff --git a/src/freertos.c b/src/freertos.c
deleted file mode 100644
index c7fefe5..0000000
--- a/src/freertos.c
+++ /dev/null
@@ -1,131 +0,0 @@
-/* USER CODE BEGIN Header */
-/**
- ******************************************************************************
- * File Name : freertos.c
- * Description : Code for freertos applications
- ******************************************************************************
- * @attention
- *
- * Copyright (c) 2025 STMicroelectronics.
- * All rights reserved.
- *
- * This software is licensed under terms that can be found in the LICENSE file
- * in the root directory of this software component.
- * If no LICENSE file comes with this software, it is provided AS-IS.
- *
- ******************************************************************************
- */
-/* USER CODE END Header */
-
-/* Includes ------------------------------------------------------------------*/
-#include "FreeRTOS.h"
-#include "task.h"
-#include "main.h"
-#include "cmsis_os.h"
-
-/* Private includes ----------------------------------------------------------*/
-/* USER CODE BEGIN Includes */
-#include "task/user_task.h"
-/* USER CODE END Includes */
-
-/* Private typedef -----------------------------------------------------------*/
-/* USER CODE BEGIN PTD */
-
-/* USER CODE END PTD */
-
-/* Private define ------------------------------------------------------------*/
-/* USER CODE BEGIN PD */
-
-/* USER CODE END PD */
-
-/* Private macro -------------------------------------------------------------*/
-/* USER CODE BEGIN PM */
-
-/* USER CODE END PM */
-
-/* Private variables ---------------------------------------------------------*/
-/* USER CODE BEGIN Variables */
-osThreadId_t initTaskHandle; // 定义 Task_Init 的任务句柄
-/* USER CODE END Variables */
-/* Definitions for defaultTask */
-osThreadId_t defaultTaskHandle;
-const osThreadAttr_t defaultTask_attributes = {
- .name = "defaultTask",
- .stack_size = 128 * 4,
- .priority = (osPriority_t) osPriorityNormal,
-};
-
-/* Private function prototypes -----------------------------------------------*/
-/* USER CODE BEGIN FunctionPrototypes */
-
-/* USER CODE END FunctionPrototypes */
-
-void StartDefaultTask(void *argument);
-
-void MX_FREERTOS_Init(void); /* (MISRA C 2004 rule 8.1) */
-
-/**
- * @brief FreeRTOS initialization
- * @param None
- * @retval None
- */
-void MX_FREERTOS_Init(void) {
- /* USER CODE BEGIN Init */
-
- /* USER CODE END Init */
-
- /* USER CODE BEGIN RTOS_MUTEX */
- /* add mutexes, ... */
- /* USER CODE END RTOS_MUTEX */
-
- /* USER CODE BEGIN RTOS_SEMAPHORES */
- /* add semaphores, ... */
- /* USER CODE END RTOS_SEMAPHORES */
-
- /* USER CODE BEGIN RTOS_TIMERS */
- /* start timers, add new ones, ... */
- /* USER CODE END RTOS_TIMERS */
-
- /* USER CODE BEGIN RTOS_QUEUES */
- /* add queues, ... */
- /* USER CODE END RTOS_QUEUES */
-
- /* Create the thread(s) */
- /* creation of defaultTask */
- defaultTaskHandle = osThreadNew(StartDefaultTask, NULL, &defaultTask_attributes);
-
- /* USER CODE BEGIN RTOS_THREADS */
- initTaskHandle = osThreadNew(Task_Init, NULL, &attr_init); // 创建初始化任务
- /* add threads, ... */
- /* USER CODE END RTOS_THREADS */
-
- /* USER CODE BEGIN RTOS_EVENTS */
- /* add events, ... */
- /* USER CODE END RTOS_EVENTS */
-
-}
-
-/* USER CODE BEGIN Header_StartDefaultTask */
-/**
- * @brief Function implementing the defaultTask thread.
- * @param argument: Not used
- * @retval None
- */
-/* USER CODE END Header_StartDefaultTask */
-void StartDefaultTask(void *argument)
-{
- /* USER CODE BEGIN StartDefaultTask */
- /* Infinite loop */
- // for(;;)
- // {
- // osDelay(1);
- // }
- osThreadTerminate(osThreadGetId()); // 结束自身
- /* USER CODE END StartDefaultTask */
-}
-
-/* Private application code --------------------------------------------------*/
-/* USER CODE BEGIN Application */
-
-/* USER CODE END Application */
-
diff --git a/开发要求.md b/开发要求.md
deleted file mode 100644
index 84970c1..0000000
--- a/开发要求.md
+++ /dev/null
@@ -1,105 +0,0 @@
-# 嵌入式 代码
-
-## 软件功能介绍
-
-中心思想:
-
-- 利用好RTOS和中断,释放CPU性能,保证实时性。
-- 一个项目适配不同型号的机器人和不同的操作手。
-
-减少维护的工作量,减少出错的可能性。
-
-## 依赖&环境
-
-- Windows平台下用CubeMX生成项目,然后用Keil uvesrion进行编辑、烧写和调试。
-
-## 使用说明
-
-- 环境安装
- - [MDK-ARM](https://www.keil.com/) (必备)
- - [STM32CubeMX](https://www.st.com/zh/development-tools/stm32cubemx.html) (可选)
-
-
-- 针对不同板子需要到不同的CubeMX工程文件(DevA.ioc、DevC.ioc)。
-
-- (可选)利用CubeMX生成对应的外设初始化代码和Keil工程文件。忽略CAN总线相关错误。
-
- - 每次生成代码后,请利用Git丢弃Middlewares文件夹中的所有改变。原因如下。
-
- 1. 使用了AC6,与CubeMX默认不匹配,会影响到FreeRTOS的移植。
- 2. 使用了比CubeMX更新的FreeRTOS版本,降版本会导致部分代码无法编译。
-
- - 因为已经生成过Keil工程文件,所以只会覆盖以前生成的代码,而不会影响手写的代码。
-
- - 每次生成代码后,请在HAL_InitTick函数中添加uwTickPrio = TickPriority;
-
-- 打开MDK-ARM中的DevC.uvprojx即可进行编辑、烧写或调试。
-
-- Keil工程中有两个Target,其中Debug用来调试,不包含编译器优化等;DevC/DevA用来编译输出最终固件。
-
-## 文件目录结构&文件用途说明
-
-| 文件夹 | 来源 | 内容 |
-| ---- | ---- | ---- |
-| Core | CubeMX | 包含核心代码,外设初始化,系统初始化等 |
-| Doc | 开发者 | 文档 |
-| Drivers | CubeMX | CMSIS相关库、STM32 HAL |
-| Image | 开发者 | 图片 |
-| MDK-ARM | CubeMX | Keil uversion 项目相关文件 |
-| Middlewares | 开发者 / CubeMX | 中间件 |
-| USB_DEVICE | CubeMX | USB相关文件 |
-| User | 开发者 | 手动编写的代码 |
-| Utils | 开发者 | 使用到的工具,如CubeMonitor, Matlab |
-
-| User内 | 内容 |
-| ---- | ---- |
-| bsp | 文件夹内包含开发板信息,基于STM32 HAL对板载的外设进行控制|
-| component | 包含各种组件,自成一体,相互依赖,但不依赖于其他文件夹|
-| device | 独立于开发板的设备,依赖于HAL和bsp|
-| module | 对机器人各模块的抽象,各模块一起组成机器人|
-| task | 独立的任务,module的运行容器,也包含通信、姿态解算等 |
-
-## 系统介绍
-
-### 硬件系统框图
-
-|  |
-|:--:|
-| *步兵嵌入式硬件框图* |
-
-### 软件流程图
-
-|  |
-|:--:|
-| *步兵嵌入式硬件框图* |
-
-|  |
-|:--:|
-| *嵌入式程序结构图* |
-
-## 原理介绍
-
-### 云台控制原理
-
-|  |
-|:--:|
-| *云台控制原理(与PX类似)* |
-
-### 其他参考文献
-
-- 软件架构参考[PX4 Architectural Overview](https://dev.px4.io/master/en/concept/architecture.html)
-
-- 云台控制参考[PX4 Controller Diagrams](https://dev.px4.io/master/en/flight_stack/controller_diagrams.html)
-
-- 底盘Mixer和CAN的Control Group参考[PX4 Mixing and Actuators](https://dev.px4.io/master/en/concept/mixing.html)
-
-## TODO
-- 给BSP USB print加保护,允许不同进程的使用。
- - 给所有BSP加保护
- - device.c里面加上一个Device_Init(),在里面初始化所有mutex
-- CAN设备代码优化。消息解析发送方向。
- - CAN设备动态初始化,保存好几组配置。
-
-## Roadmap
-
-1. 在步兵上完成所有功能。