mirror of
https://github.com/goldenfishs/MRobot.git
synced 2026-02-04 18:00:19 +08:00
实现module生成
This commit is contained in:
parent
def81cc760
commit
f25f474ae8
@ -1,6 +1,6 @@
|
|||||||
[Setup]
|
[Setup]
|
||||||
AppName=MRobot
|
AppName=MRobot
|
||||||
AppVersion=1.0.8
|
AppVersion=1.1.0
|
||||||
DefaultDirName={userappdata}\MRobot
|
DefaultDirName={userappdata}\MRobot
|
||||||
DefaultGroupName=MRobot
|
DefaultGroupName=MRobot
|
||||||
OutputDir=.
|
OutputDir=.
|
||||||
|
|||||||
@ -15,7 +15,7 @@ from app.tools.check_update import check_update
|
|||||||
from app.tools.auto_updater import AutoUpdater, check_update_availability
|
from app.tools.auto_updater import AutoUpdater, check_update_availability
|
||||||
from app.tools.update_check_thread import UpdateCheckThread
|
from app.tools.update_check_thread import UpdateCheckThread
|
||||||
|
|
||||||
__version__ = "1.0.6"
|
__version__ = "1.1.0"
|
||||||
|
|
||||||
class AboutInterface(QWidget):
|
class AboutInterface(QWidget):
|
||||||
def __init__(self, parent=None):
|
def __init__(self, parent=None):
|
||||||
|
|||||||
@ -360,17 +360,76 @@ class CodeGenerateInterface(QWidget):
|
|||||||
continue
|
continue
|
||||||
main_title = row[0]
|
main_title = row[0]
|
||||||
main_item = QTreeWidgetItem([main_title])
|
main_item = QTreeWidgetItem([main_title])
|
||||||
|
|
||||||
|
# 特殊处理 module
|
||||||
|
if main_title == 'module':
|
||||||
|
# 扫描 module 目录
|
||||||
|
module_dir = CodeGenerator.get_assets_dir("User_code/module")
|
||||||
|
if os.path.exists(module_dir):
|
||||||
|
for item in os.listdir(module_dir):
|
||||||
|
item_path = os.path.join(module_dir, item)
|
||||||
|
if not os.path.isdir(item_path):
|
||||||
|
continue
|
||||||
|
if item.startswith('.') or item == 'config':
|
||||||
|
continue
|
||||||
|
|
||||||
|
# 检查是否直接包含代码
|
||||||
|
has_direct_code = any(
|
||||||
|
f.endswith(('.c', '.h'))
|
||||||
|
for f in os.listdir(item_path)
|
||||||
|
if os.path.isfile(os.path.join(item_path, f))
|
||||||
|
)
|
||||||
|
|
||||||
|
if has_direct_code:
|
||||||
|
# 直接的模块(如 cmd)
|
||||||
|
sub_item = QTreeWidgetItem([item])
|
||||||
|
main_item.addChild(sub_item)
|
||||||
|
else:
|
||||||
|
# 有子类型的模块(如 gimbal)
|
||||||
|
module_type_item = QTreeWidgetItem([item])
|
||||||
|
has_subtypes = False
|
||||||
|
for subitem in os.listdir(item_path):
|
||||||
|
subitem_path = os.path.join(item_path, subitem)
|
||||||
|
if not os.path.isdir(subitem_path):
|
||||||
|
continue
|
||||||
|
if subitem.startswith('.'):
|
||||||
|
continue
|
||||||
|
has_code = any(
|
||||||
|
f.endswith(('.c', '.h'))
|
||||||
|
for f in os.listdir(subitem_path)
|
||||||
|
if os.path.isfile(os.path.join(subitem_path, f))
|
||||||
|
)
|
||||||
|
if has_code:
|
||||||
|
subtype_item = QTreeWidgetItem([subitem])
|
||||||
|
module_type_item.addChild(subtype_item)
|
||||||
|
has_subtypes = True
|
||||||
|
|
||||||
|
if has_subtypes:
|
||||||
|
main_item.addChild(module_type_item)
|
||||||
|
else:
|
||||||
|
# 其他模块保持原逻辑
|
||||||
for sub in row[1:]:
|
for sub in row[1:]:
|
||||||
sub_item = QTreeWidgetItem([sub])
|
sub_item = QTreeWidgetItem([sub])
|
||||||
main_item.addChild(sub_item)
|
main_item.addChild(sub_item)
|
||||||
|
|
||||||
self.tree.addTopLevelItem(main_item)
|
self.tree.addTopLevelItem(main_item)
|
||||||
self.tree.repaint()
|
self.tree.repaint()
|
||||||
|
|
||||||
def on_tree_item_clicked(self, item, column):
|
def on_tree_item_clicked(self, item, column):
|
||||||
if item.parent():
|
if item.parent():
|
||||||
|
# 判断层级
|
||||||
|
if item.parent().parent():
|
||||||
|
# 三级树(module/type/instance)
|
||||||
|
root_title = item.parent().parent().text(0)
|
||||||
|
type_title = item.parent().text(0)
|
||||||
|
instance_title = item.text(0)
|
||||||
|
class_name = f"{root_title}_{type_title}_{instance_title}".replace("-", "_")
|
||||||
|
else:
|
||||||
|
# 二级树(category/item)
|
||||||
main_title = item.parent().text(0)
|
main_title = item.parent().text(0)
|
||||||
sub_title = item.text(0)
|
sub_title = item.text(0)
|
||||||
class_name = f"{main_title}_{sub_title}".replace("-", "_")
|
class_name = f"{main_title}_{sub_title}".replace("-", "_")
|
||||||
|
|
||||||
widget = self._get_or_create_page(class_name)
|
widget = self._get_or_create_page(class_name)
|
||||||
if widget:
|
if widget:
|
||||||
self.stack.setCurrentWidget(widget)
|
self.stack.setCurrentWidget(widget)
|
||||||
@ -402,6 +461,18 @@ class CodeGenerateInterface(QWidget):
|
|||||||
from app.code_page.device_interface import get_device_page
|
from app.code_page.device_interface import get_device_page
|
||||||
device_name = class_name[len('device_'):] # 移除 device_ 前缀
|
device_name = class_name[len('device_'):] # 移除 device_ 前缀
|
||||||
page = get_device_page(device_name, self.project_path)
|
page = get_device_page(device_name, self.project_path)
|
||||||
|
elif class_name.startswith('module_'):
|
||||||
|
# Module页面
|
||||||
|
from app.code_page.module_interface import get_module_page
|
||||||
|
# 解析: module_type 或 module_type_instance
|
||||||
|
parts = class_name[len('module_'):].split('_', 1)
|
||||||
|
if len(parts) == 2:
|
||||||
|
module_type = parts[0]
|
||||||
|
instance = parts[1]
|
||||||
|
page = get_module_page(module_type, instance, self.project_path, self)
|
||||||
|
else:
|
||||||
|
module_type = parts[0]
|
||||||
|
page = get_module_page(module_type, None, self.project_path, self)
|
||||||
else:
|
else:
|
||||||
print(f"未知的页面类型: {class_name}")
|
print(f"未知的页面类型: {class_name}")
|
||||||
return None
|
return None
|
||||||
@ -411,4 +482,6 @@ class CodeGenerateInterface(QWidget):
|
|||||||
return page
|
return page
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
print(f"创建页面 {class_name} 失败: {e}")
|
print(f"创建页面 {class_name} 失败: {e}")
|
||||||
|
import traceback
|
||||||
|
traceback.print_exc()
|
||||||
return None
|
return None
|
||||||
@ -0,0 +1,261 @@
|
|||||||
|
from PyQt5.QtWidgets import QWidget, QVBoxLayout, QHBoxLayout
|
||||||
|
from qfluentwidgets import (
|
||||||
|
BodyLabel, CheckBox, SubtitleLabel, PushButton, FluentIcon,
|
||||||
|
InfoBar, InfoBarPosition, CardWidget, TitleLabel
|
||||||
|
)
|
||||||
|
from PyQt5.QtCore import Qt
|
||||||
|
from app.tools.code_generator import CodeGenerator
|
||||||
|
import os
|
||||||
|
import shutil
|
||||||
|
import csv
|
||||||
|
|
||||||
|
|
||||||
|
def get_module_page(module_type, subtype, project_path, parent=None):
|
||||||
|
"""获取模块配置页面"""
|
||||||
|
return ModulePage(module_type, subtype, project_path, parent)
|
||||||
|
|
||||||
|
|
||||||
|
class ModulePage(QWidget):
|
||||||
|
"""单个模块配置页面"""
|
||||||
|
|
||||||
|
def __init__(self, module_type, subtype, project_path, parent=None):
|
||||||
|
super().__init__(parent)
|
||||||
|
self.module_type = module_type
|
||||||
|
self.subtype = subtype
|
||||||
|
self.project_path = project_path
|
||||||
|
|
||||||
|
# 获取模块路径
|
||||||
|
module_dir = CodeGenerator.get_assets_dir("User_code/module")
|
||||||
|
if subtype:
|
||||||
|
self.module_path = os.path.join(module_dir, module_type, subtype)
|
||||||
|
self.module_key = subtype
|
||||||
|
else:
|
||||||
|
self.module_path = os.path.join(module_dir, module_type)
|
||||||
|
self.module_key = module_type
|
||||||
|
|
||||||
|
# 加载描述
|
||||||
|
self.descriptions = self._load_descriptions()
|
||||||
|
|
||||||
|
self._init_ui()
|
||||||
|
self._check_generated_status()
|
||||||
|
|
||||||
|
def _load_descriptions(self):
|
||||||
|
"""从 describe.csv 加载模块描述"""
|
||||||
|
descriptions = {}
|
||||||
|
describe_path = os.path.join(
|
||||||
|
CodeGenerator.get_assets_dir("User_code/module"),
|
||||||
|
"describe.csv"
|
||||||
|
)
|
||||||
|
|
||||||
|
if os.path.exists(describe_path):
|
||||||
|
try:
|
||||||
|
with open(describe_path, 'r', encoding='utf-8') as f:
|
||||||
|
reader = csv.DictReader(f)
|
||||||
|
for row in reader:
|
||||||
|
module_name = row.get('module_name', '').strip()
|
||||||
|
description = row.get('description', '').strip()
|
||||||
|
if module_name and description:
|
||||||
|
descriptions[module_name] = description
|
||||||
|
except Exception as e:
|
||||||
|
print(f"读取模块描述失败: {e}")
|
||||||
|
|
||||||
|
return descriptions
|
||||||
|
|
||||||
|
def _init_ui(self):
|
||||||
|
"""初始化界面"""
|
||||||
|
layout = QVBoxLayout(self)
|
||||||
|
layout.setSpacing(16)
|
||||||
|
layout.setContentsMargins(48, 48, 48, 48)
|
||||||
|
|
||||||
|
# 标题
|
||||||
|
if self.subtype:
|
||||||
|
title_text = f"{self.module_type} / {self.subtype}"
|
||||||
|
else:
|
||||||
|
title_text = self.module_type
|
||||||
|
|
||||||
|
title = TitleLabel(title_text)
|
||||||
|
title.setAlignment(Qt.AlignmentFlag.AlignCenter)
|
||||||
|
layout.addWidget(title)
|
||||||
|
|
||||||
|
# 描述
|
||||||
|
desc_text = self.descriptions.get(self.module_key, "模块功能说明")
|
||||||
|
desc = BodyLabel(desc_text)
|
||||||
|
desc.setWordWrap(True)
|
||||||
|
desc.setAlignment(Qt.AlignmentFlag.AlignCenter)
|
||||||
|
layout.addWidget(desc)
|
||||||
|
|
||||||
|
layout.addSpacing(24)
|
||||||
|
|
||||||
|
# 文件列表卡片
|
||||||
|
files_card = CardWidget()
|
||||||
|
files_layout = QVBoxLayout(files_card)
|
||||||
|
files_layout.setContentsMargins(16, 16, 16, 16)
|
||||||
|
|
||||||
|
files_title = SubtitleLabel("包含文件")
|
||||||
|
files_layout.addWidget(files_title)
|
||||||
|
|
||||||
|
files = self._get_module_files()
|
||||||
|
if files:
|
||||||
|
for file in files:
|
||||||
|
file_label = BodyLabel(f"• {file}")
|
||||||
|
files_layout.addWidget(file_label)
|
||||||
|
else:
|
||||||
|
files_layout.addWidget(BodyLabel("未找到文件"))
|
||||||
|
|
||||||
|
layout.addWidget(files_card)
|
||||||
|
|
||||||
|
# 状态显示
|
||||||
|
self.status_label = BodyLabel()
|
||||||
|
self.status_label.setAlignment(Qt.AlignmentFlag.AlignCenter)
|
||||||
|
layout.addWidget(self.status_label)
|
||||||
|
|
||||||
|
layout.addSpacing(24)
|
||||||
|
|
||||||
|
# 按钮
|
||||||
|
btn_layout = QHBoxLayout()
|
||||||
|
btn_layout.addStretch()
|
||||||
|
|
||||||
|
self.generate_btn = PushButton(FluentIcon.SAVE, "生成模块代码")
|
||||||
|
self.generate_btn.clicked.connect(self._generate_code)
|
||||||
|
btn_layout.addWidget(self.generate_btn)
|
||||||
|
|
||||||
|
btn_layout.addStretch()
|
||||||
|
layout.addLayout(btn_layout)
|
||||||
|
|
||||||
|
layout.addStretch()
|
||||||
|
|
||||||
|
def _get_module_files(self):
|
||||||
|
"""获取模块文件列表"""
|
||||||
|
files = []
|
||||||
|
try:
|
||||||
|
if os.path.exists(self.module_path):
|
||||||
|
for item in os.listdir(self.module_path):
|
||||||
|
if item.endswith(('.c', '.h')):
|
||||||
|
files.append(item)
|
||||||
|
except Exception as e:
|
||||||
|
print(f"读取模块文件失败: {e}")
|
||||||
|
|
||||||
|
return sorted(files)
|
||||||
|
|
||||||
|
def _check_generated_status(self):
|
||||||
|
"""检查模块是否已生成"""
|
||||||
|
if self.subtype:
|
||||||
|
dst_dir = os.path.join(self.project_path, "User/module", self.module_type, self.subtype)
|
||||||
|
else:
|
||||||
|
dst_dir = os.path.join(self.project_path, "User/module", self.module_type)
|
||||||
|
|
||||||
|
if os.path.exists(dst_dir):
|
||||||
|
has_code = any(
|
||||||
|
f.endswith(('.c', '.h'))
|
||||||
|
for f in os.listdir(dst_dir)
|
||||||
|
if os.path.isfile(os.path.join(dst_dir, f))
|
||||||
|
)
|
||||||
|
if has_code:
|
||||||
|
self.status_label.setText("✓ 模块已生成")
|
||||||
|
self.status_label.setStyleSheet("color: green; font-weight: bold;")
|
||||||
|
self.generate_btn.setEnabled(False)
|
||||||
|
return
|
||||||
|
|
||||||
|
self.status_label.setText("○ 模块未生成")
|
||||||
|
self.status_label.setStyleSheet("color: orange;")
|
||||||
|
|
||||||
|
def _generate_code(self):
|
||||||
|
"""生成模块代码"""
|
||||||
|
try:
|
||||||
|
# 首先生成 config(如果不存在)
|
||||||
|
self._generate_config()
|
||||||
|
|
||||||
|
# 目标目录
|
||||||
|
if self.subtype:
|
||||||
|
dst_dir = os.path.join(self.project_path, "User/module", self.module_type, self.subtype)
|
||||||
|
else:
|
||||||
|
dst_dir = os.path.join(self.project_path, "User/module", self.module_type)
|
||||||
|
|
||||||
|
# 检查是否已存在
|
||||||
|
if os.path.exists(dst_dir):
|
||||||
|
has_code = any(
|
||||||
|
f.endswith(('.c', '.h'))
|
||||||
|
for f in os.listdir(dst_dir)
|
||||||
|
if os.path.isfile(os.path.join(dst_dir, f))
|
||||||
|
)
|
||||||
|
if has_code:
|
||||||
|
InfoBar.warning(
|
||||||
|
title="已存在",
|
||||||
|
content="模块代码已存在,不会覆盖",
|
||||||
|
orient=Qt.Horizontal,
|
||||||
|
isClosable=True,
|
||||||
|
position=InfoBarPosition.TOP,
|
||||||
|
duration=2000,
|
||||||
|
parent=self
|
||||||
|
)
|
||||||
|
return
|
||||||
|
|
||||||
|
# 创建目录
|
||||||
|
os.makedirs(dst_dir, exist_ok=True)
|
||||||
|
|
||||||
|
# 复制所有文件
|
||||||
|
file_count = 0
|
||||||
|
for item in os.listdir(self.module_path):
|
||||||
|
src_file = os.path.join(self.module_path, item)
|
||||||
|
dst_file = os.path.join(dst_dir, item)
|
||||||
|
|
||||||
|
if os.path.isfile(src_file):
|
||||||
|
if os.path.exists(dst_file):
|
||||||
|
continue
|
||||||
|
shutil.copy2(src_file, dst_file)
|
||||||
|
file_count += 1
|
||||||
|
print(f"生成文件: {dst_file}")
|
||||||
|
|
||||||
|
if file_count > 0:
|
||||||
|
InfoBar.success(
|
||||||
|
title="生成成功",
|
||||||
|
content=f"已生成 {file_count} 个文件",
|
||||||
|
orient=Qt.Horizontal,
|
||||||
|
isClosable=True,
|
||||||
|
position=InfoBarPosition.TOP,
|
||||||
|
duration=2000,
|
||||||
|
parent=self
|
||||||
|
)
|
||||||
|
self._check_generated_status()
|
||||||
|
else:
|
||||||
|
InfoBar.warning(
|
||||||
|
title="无需生成",
|
||||||
|
content="所有文件已存在",
|
||||||
|
orient=Qt.Horizontal,
|
||||||
|
isClosable=True,
|
||||||
|
position=InfoBarPosition.TOP,
|
||||||
|
duration=2000,
|
||||||
|
parent=self
|
||||||
|
)
|
||||||
|
|
||||||
|
except Exception as e:
|
||||||
|
print(f"生成模块失败: {e}")
|
||||||
|
InfoBar.error(
|
||||||
|
title="生成失败",
|
||||||
|
content=str(e),
|
||||||
|
orient=Qt.Horizontal,
|
||||||
|
isClosable=True,
|
||||||
|
position=InfoBarPosition.TOP,
|
||||||
|
duration=3000,
|
||||||
|
parent=self
|
||||||
|
)
|
||||||
|
|
||||||
|
def _generate_config(self):
|
||||||
|
"""生成 config 文件(如果不存在)"""
|
||||||
|
config_dir = os.path.join(self.project_path, "User/module")
|
||||||
|
config_c = os.path.join(config_dir, "config.c")
|
||||||
|
config_h = os.path.join(config_dir, "config.h")
|
||||||
|
|
||||||
|
os.makedirs(config_dir, exist_ok=True)
|
||||||
|
|
||||||
|
template_dir = CodeGenerator.get_assets_dir("User_code/module")
|
||||||
|
template_c = os.path.join(template_dir, "config.c")
|
||||||
|
template_h = os.path.join(template_dir, "config.h")
|
||||||
|
|
||||||
|
if not os.path.exists(config_c) and os.path.exists(template_c):
|
||||||
|
shutil.copy2(template_c, config_c)
|
||||||
|
print(f"生成 config.c")
|
||||||
|
|
||||||
|
if not os.path.exists(config_h) and os.path.exists(template_h):
|
||||||
|
shutil.copy2(template_h, config_h)
|
||||||
|
print(f"生成 config.h")
|
||||||
@ -1,4 +1,4 @@
|
|||||||
bsp,can,fdcan,dwt,gpio,i2c,mm,spi,uart,pwm,time,flash
|
bsp,can,fdcan,dwt,gpio,i2c,mm,spi,uart,pwm,time,flash
|
||||||
component,ahrs,capacity,cmd,crc8,crc16,error_detect,filter,FreeRTOS_CLI,limiter,mixer,pid,ui,user_math
|
component,ahrs,capacity,cmd,crc8,crc16,error_detect,filter,FreeRTOS_CLI,limiter,mixer,pid,ui,user_math
|
||||||
device,dr16,bmi088,ist8310,motor,motor_rm,motor_dm,motor_vesc,motor_lk,motor_lz,motor_odrive,dm_imu,rc_can,servo,buzzer,led,ws2812,vofa,ops9,oid,lcd_driver
|
device,dr16,bmi088,ist8310,motor,motor_rm,motor_dm,motor_vesc,motor_lk,motor_lz,motor_odrive,dm_imu,rc_can,servo,buzzer,led,ws2812,vofa,ops9,oid,lcd_driver
|
||||||
module,config,
|
module,
|
||||||
|
@ -13,10 +13,17 @@
|
|||||||
|
|
||||||
/* Exported variables ------------------------------------------------------- */
|
/* Exported variables ------------------------------------------------------- */
|
||||||
|
|
||||||
// 机器人参数配置
|
/**
|
||||||
|
* @brief 机器人参数配置
|
||||||
|
* @note 在此配置机器人参数
|
||||||
|
*/
|
||||||
Config_RobotParam_t robot_config = {
|
Config_RobotParam_t robot_config = {
|
||||||
|
/* USER CODE BEGIN robot_config */
|
||||||
|
.example_param = 0, // 示例参数初始化
|
||||||
|
|
||||||
|
// 在此添加您的配置参数初始化
|
||||||
|
|
||||||
|
/* USER CODE END robot_config */
|
||||||
};
|
};
|
||||||
|
|
||||||
/* Private function prototypes ---------------------------------------------- */
|
/* Private function prototypes ---------------------------------------------- */
|
||||||
|
|||||||
@ -9,9 +9,20 @@ extern "C" {
|
|||||||
#endif
|
#endif
|
||||||
|
|
||||||
#include <stdint.h>
|
#include <stdint.h>
|
||||||
|
#include <stdbool.h>
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief 机器人参数配置结构体
|
||||||
|
* @note 在此添加您的配置参数
|
||||||
|
*/
|
||||||
typedef struct {
|
typedef struct {
|
||||||
|
// 示例配置项(可根据实际需求修改或删除)
|
||||||
|
uint8_t example_param; // 示例参数
|
||||||
|
|
||||||
|
/* USER CODE BEGIN Config_RobotParam */
|
||||||
|
// 在此添加您的配置参数
|
||||||
|
|
||||||
|
/* USER CODE END Config_RobotParam */
|
||||||
} Config_RobotParam_t;
|
} Config_RobotParam_t;
|
||||||
|
|
||||||
/* Exported functions prototypes -------------------------------------------- */
|
/* Exported functions prototypes -------------------------------------------- */
|
||||||
|
|||||||
3
assets/User_code/module/describe.csv
Normal file
3
assets/User_code/module/describe.csv
Normal file
@ -0,0 +1,3 @@
|
|||||||
|
module_name,description
|
||||||
|
cmd,命令系统,用于机器人指令处理和行为控制
|
||||||
|
2_axis_gimbal,双轴云台控制模块,支持pitch和yaw轴控制
|
||||||
|
6
config/config.json
Normal file
6
config/config.json
Normal file
@ -0,0 +1,6 @@
|
|||||||
|
{
|
||||||
|
"QFluentWidgets": {
|
||||||
|
"ThemeColor": "#fff18cb9",
|
||||||
|
"ThemeMode": "Light"
|
||||||
|
}
|
||||||
|
}
|
||||||
Loading…
Reference in New Issue
Block a user