mirror of
https://github.com/goldenfishs/MRobot.git
synced 2025-09-14 12:54:33 +08:00
394 lines
15 KiB
Python
394 lines
15 KiB
Python
from PyQt5.QtWidgets import QWidget, QVBoxLayout, QHBoxLayout
|
|
from qfluentwidgets import BodyLabel, CheckBox, ComboBox, SubtitleLabel
|
|
from PyQt5.QtCore import Qt
|
|
from app.tools.code_generator import CodeGenerator
|
|
import os
|
|
import shutil
|
|
import yaml
|
|
import re
|
|
|
|
def load_device_config(config_path):
|
|
"""加载设备配置"""
|
|
if os.path.exists(config_path):
|
|
with open(config_path, 'r', encoding='utf-8') as f:
|
|
return yaml.safe_load(f)
|
|
return {}
|
|
|
|
def get_available_bsp_devices(project_path, bsp_type):
|
|
"""获取可用的BSP设备"""
|
|
bsp_config_path = os.path.join(project_path, "User/bsp/bsp_config.yaml")
|
|
if not os.path.exists(bsp_config_path):
|
|
return []
|
|
|
|
try:
|
|
with open(bsp_config_path, 'r', encoding='utf-8') as f:
|
|
bsp_config = yaml.safe_load(f)
|
|
|
|
if bsp_type in bsp_config and bsp_config[bsp_type].get('enabled', False):
|
|
devices = bsp_config[bsp_type].get('devices', [])
|
|
return [f"BSP_{bsp_type.upper()}_{device['name']}" for device in devices]
|
|
except Exception as e:
|
|
print(f"读取BSP配置失败: {e}")
|
|
|
|
return []
|
|
|
|
def generate_device_header(project_path, enabled_devices):
|
|
"""生成device.h文件"""
|
|
device_dir = os.path.join(os.path.dirname(__file__), "../../assets/User_code/device")
|
|
template_path = os.path.join(device_dir, "device.h")
|
|
|
|
# 读取模板文件
|
|
with open(template_path, 'r', encoding='utf-8') as f:
|
|
content = f.read()
|
|
|
|
# 收集所有需要的信号定义
|
|
signals = []
|
|
current_bit = 0
|
|
|
|
# 加载设备配置来获取信号信息
|
|
config_path = os.path.join(device_dir, "config.yaml")
|
|
device_configs = load_device_config(config_path)
|
|
|
|
for device_name in enabled_devices:
|
|
device_key = device_name.lower()
|
|
if device_key in device_configs.get('devices', {}):
|
|
device_config = device_configs['devices'][device_key]
|
|
thread_signals = device_config.get('thread_signals', [])
|
|
|
|
for signal in thread_signals:
|
|
signal_name = signal['name']
|
|
signals.append(f"#define {signal_name} (1u << {current_bit})")
|
|
current_bit += 1
|
|
|
|
# 生成信号定义文本
|
|
signals_text = '\n'.join(signals) if signals else '/* No signals defined */'
|
|
|
|
# 替换AUTO GENERATED SIGNALS部分
|
|
pattern = r'/\* AUTO GENERATED SIGNALS BEGIN \*/(.*?)/\* AUTO GENERATED SIGNALS END \*/'
|
|
replacement = f'/* AUTO GENERATED SIGNALS BEGIN */\n{signals_text}\n/* AUTO GENERATED SIGNALS END */'
|
|
content = re.sub(pattern, replacement, content, flags=re.DOTALL)
|
|
|
|
# 保存文件
|
|
dst_path = os.path.join(project_path, "User/device/device.h")
|
|
os.makedirs(os.path.dirname(dst_path), exist_ok=True)
|
|
with open(dst_path, 'w', encoding='utf-8') as f:
|
|
f.write(content)
|
|
|
|
|
|
|
|
|
|
class DeviceSimple(QWidget):
|
|
"""简单设备界面"""
|
|
|
|
def __init__(self, project_path, device_name, device_config):
|
|
super().__init__()
|
|
self.project_path = project_path
|
|
self.device_name = device_name
|
|
self.device_config = device_config
|
|
|
|
# 添加必要的属性,确保兼容性
|
|
self.component_name = device_name # 添加这个属性以兼容现有代码
|
|
|
|
self._init_ui()
|
|
self._load_config()
|
|
|
|
def _init_ui(self):
|
|
layout = QVBoxLayout(self)
|
|
|
|
# 顶部横向布局:左侧复选框,居中标题
|
|
top_layout = QHBoxLayout()
|
|
top_layout.setAlignment(Qt.AlignVCenter)
|
|
|
|
self.generate_checkbox = CheckBox(f"启用 {self.device_name}")
|
|
self.generate_checkbox.stateChanged.connect(self._on_checkbox_changed)
|
|
top_layout.addWidget(self.generate_checkbox, alignment=Qt.AlignLeft)
|
|
|
|
# 弹性空间
|
|
top_layout.addStretch()
|
|
|
|
title = SubtitleLabel(f"{self.device_name} 配置 ")
|
|
title.setAlignment(Qt.AlignHCenter)
|
|
top_layout.addWidget(title, alignment=Qt.AlignHCenter)
|
|
|
|
# 再加一个弹性空间,保证标题居中
|
|
top_layout.addStretch()
|
|
|
|
layout.addLayout(top_layout)
|
|
|
|
# 功能说明
|
|
desc = self.device_config.get('description', '')
|
|
if desc:
|
|
desc_label = BodyLabel(f"功能说明:{desc}")
|
|
desc_label.setWordWrap(True)
|
|
layout.addWidget(desc_label)
|
|
|
|
# 依赖信息
|
|
self._add_dependency_info(layout)
|
|
|
|
# BSP配置区域
|
|
self.content_widget = QWidget()
|
|
content_layout = QVBoxLayout(self.content_widget)
|
|
|
|
self._add_bsp_config(content_layout)
|
|
|
|
layout.addWidget(self.content_widget)
|
|
self.content_widget.setEnabled(False)
|
|
|
|
layout.addStretch()
|
|
|
|
def _add_dependency_info(self, layout):
|
|
"""添加依赖信息显示"""
|
|
bsp_deps = self.device_config.get('dependencies', {}).get('bsp', [])
|
|
comp_deps = self.device_config.get('dependencies', {}).get('component', [])
|
|
|
|
if bsp_deps or comp_deps:
|
|
deps_text = "依赖: "
|
|
if bsp_deps:
|
|
deps_text += f"BSP({', '.join(bsp_deps)})"
|
|
if comp_deps:
|
|
if bsp_deps:
|
|
deps_text += ", "
|
|
deps_text += f"Component({', '.join(comp_deps)})"
|
|
|
|
deps_label = BodyLabel(deps_text)
|
|
deps_label.setWordWrap(True)
|
|
deps_label.setStyleSheet("color: #888888;")
|
|
layout.addWidget(deps_label)
|
|
|
|
def _add_bsp_config(self, layout):
|
|
"""添加BSP配置区域"""
|
|
bsp_requirements = self.device_config.get('bsp_requirements', [])
|
|
self.bsp_combos = {}
|
|
|
|
if bsp_requirements:
|
|
layout.addWidget(BodyLabel("BSP设备配置:"))
|
|
|
|
for req in bsp_requirements:
|
|
bsp_type = req['type']
|
|
var_name = req['var_name']
|
|
description = req.get('description', '')
|
|
|
|
# 创建选择组合框
|
|
req_layout = QHBoxLayout()
|
|
|
|
label = BodyLabel(f"{bsp_type.upper()}:")
|
|
label.setMinimumWidth(80)
|
|
req_layout.addWidget(label)
|
|
|
|
combo = ComboBox()
|
|
self._update_bsp_combo(combo, bsp_type)
|
|
|
|
req_layout.addWidget(combo)
|
|
|
|
if description:
|
|
desc_label = BodyLabel(f"({description})")
|
|
desc_label.setStyleSheet("color: #666666; font-size: 12px;")
|
|
req_layout.addWidget(desc_label)
|
|
|
|
req_layout.addStretch()
|
|
layout.addLayout(req_layout)
|
|
|
|
self.bsp_combos[var_name] = combo
|
|
|
|
def _update_bsp_combo(self, combo, bsp_type):
|
|
"""更新BSP组合框选项"""
|
|
combo.clear()
|
|
available_devices = get_available_bsp_devices(self.project_path, bsp_type)
|
|
if available_devices:
|
|
combo.addItems(available_devices)
|
|
else:
|
|
combo.addItem(f"未找到可用的{bsp_type.upper()}设备")
|
|
combo.setEnabled(False)
|
|
|
|
def refresh_bsp_combos(self):
|
|
"""刷新所有BSP组合框"""
|
|
bsp_requirements = self.device_config.get('bsp_requirements', [])
|
|
for req in bsp_requirements:
|
|
bsp_type = req['type']
|
|
var_name = req['var_name']
|
|
if var_name in self.bsp_combos:
|
|
current_text = self.bsp_combos[var_name].currentText()
|
|
self._update_bsp_combo(self.bsp_combos[var_name], bsp_type)
|
|
# 尝试恢复之前的选择
|
|
index = self.bsp_combos[var_name].findText(current_text)
|
|
if index >= 0:
|
|
self.bsp_combos[var_name].setCurrentIndex(index)
|
|
|
|
def _on_checkbox_changed(self, state):
|
|
"""处理复选框状态变化"""
|
|
self.content_widget.setEnabled(state == 2)
|
|
|
|
def is_need_generate(self):
|
|
"""检查是否需要生成代码"""
|
|
return self.generate_checkbox.isChecked()
|
|
|
|
def get_bsp_config(self):
|
|
"""获取BSP配置"""
|
|
config = {}
|
|
for var_name, combo in self.bsp_combos.items():
|
|
if combo.isEnabled():
|
|
config[var_name] = combo.currentText()
|
|
return config
|
|
|
|
def _generate_device_code_internal(self):
|
|
"""生成设备代码"""
|
|
if not self.is_need_generate():
|
|
return False
|
|
|
|
# 获取BSP配置
|
|
bsp_config = self.get_bsp_config()
|
|
|
|
# 复制并修改文件
|
|
template_dir = self._get_device_template_dir()
|
|
files = self.device_config.get('files', {})
|
|
|
|
for file_type, filename in files.items():
|
|
src_path = os.path.join(template_dir, filename)
|
|
dst_path = os.path.join(self.project_path, f"User/device/{filename}")
|
|
|
|
if os.path.exists(src_path):
|
|
if file_type == 'header':
|
|
# 头文件直接复制,不做修改
|
|
os.makedirs(os.path.dirname(dst_path), exist_ok=True)
|
|
shutil.copy2(src_path, dst_path)
|
|
|
|
elif file_type == 'source':
|
|
# 源文件需要替换BSP设备名称
|
|
with open(src_path, 'r', encoding='utf-8') as f:
|
|
content = f.read()
|
|
|
|
# 替换BSP设备名称
|
|
for var_name, device_name in bsp_config.items():
|
|
content = content.replace(var_name, device_name)
|
|
|
|
# 保存文件
|
|
os.makedirs(os.path.dirname(dst_path), exist_ok=True)
|
|
with open(dst_path, 'w', encoding='utf-8') as f:
|
|
f.write(content)
|
|
|
|
self._save_config()
|
|
return True
|
|
|
|
def _get_device_template_dir(self):
|
|
"""获取设备模板目录"""
|
|
current_dir = os.path.dirname(os.path.abspath(__file__))
|
|
# 向上找到 MRobot 根目录
|
|
while os.path.basename(current_dir) != 'MRobot' and current_dir != '/':
|
|
current_dir = os.path.dirname(current_dir)
|
|
|
|
if os.path.basename(current_dir) == 'MRobot':
|
|
return os.path.join(current_dir, "assets/User_code/device")
|
|
else:
|
|
# 如果找不到,使用相对路径作为备选
|
|
return os.path.join(os.path.dirname(os.path.dirname(os.path.dirname(__file__))), "assets/User_code/device")
|
|
|
|
def _save_config(self):
|
|
"""保存配置"""
|
|
config_path = os.path.join(self.project_path, "User/device/device_config.yaml")
|
|
config_data = CodeGenerator.load_config(config_path)
|
|
config_data[self.device_name.lower()] = {
|
|
'enabled': self.is_need_generate(),
|
|
'bsp_config': self.get_bsp_config()
|
|
}
|
|
CodeGenerator.save_config(config_data, config_path)
|
|
|
|
def _load_config(self):
|
|
"""加载配置"""
|
|
config_path = os.path.join(self.project_path, "User/device/device_config.yaml")
|
|
config_data = CodeGenerator.load_config(config_path)
|
|
conf = config_data.get(self.device_name.lower(), {})
|
|
|
|
if conf.get('enabled', False):
|
|
self.generate_checkbox.setChecked(True)
|
|
|
|
# 恢复BSP配置
|
|
bsp_config = conf.get('bsp_config', {})
|
|
for var_name, device_name in bsp_config.items():
|
|
if var_name in self.bsp_combos:
|
|
combo = self.bsp_combos[var_name]
|
|
index = combo.findText(device_name)
|
|
if index >= 0:
|
|
combo.setCurrentIndex(index)
|
|
|
|
def get_device_page(device_name, project_path):
|
|
"""根据设备名返回对应的页面类"""
|
|
# 加载设备配置
|
|
device_dir = os.path.join(os.path.dirname(__file__), "../../assets/User_code/device")
|
|
config_path = os.path.join(device_dir, "config.yaml")
|
|
device_configs = load_device_config(config_path)
|
|
|
|
devices = device_configs.get('devices', {})
|
|
device_key = device_name.lower()
|
|
|
|
if device_key in devices:
|
|
device_config = devices[device_key]
|
|
page = DeviceSimple(project_path, device_name, device_config)
|
|
else:
|
|
# 如果配置中没有找到,返回一个基本的设备页面
|
|
basic_config = {
|
|
'name': device_name,
|
|
'description': f'{device_name}设备',
|
|
'files': {'header': f'{device_name.lower()}.h', 'source': f'{device_name.lower()}.c'},
|
|
'bsp_requirements': [],
|
|
'dependencies': {'bsp': [], 'component': []}
|
|
}
|
|
page = DeviceSimple(project_path, device_name, basic_config)
|
|
|
|
# 确保页面有必要的属性
|
|
page.device_name = device_name
|
|
|
|
return page
|
|
|
|
class device(QWidget):
|
|
"""设备管理器"""
|
|
|
|
def __init__(self, project_path):
|
|
super().__init__()
|
|
self.project_path = project_path
|
|
|
|
@staticmethod
|
|
def generate_device(project_path, pages):
|
|
"""生成所有设备代码"""
|
|
success_count = 0
|
|
fail_count = 0
|
|
fail_list = []
|
|
enabled_devices = []
|
|
|
|
# 生成设备代码
|
|
for page in pages:
|
|
if hasattr(page, "device_name") and hasattr(page, "is_need_generate"):
|
|
if page.is_need_generate():
|
|
enabled_devices.append(page.device_name)
|
|
try:
|
|
result = page._generate_device_code_internal()
|
|
if result:
|
|
success_count += 1
|
|
else:
|
|
fail_count += 1
|
|
fail_list.append(page.device_name)
|
|
except Exception as e:
|
|
fail_count += 1
|
|
fail_list.append(f"{page.device_name} (异常: {e})")
|
|
|
|
# 生成device.h文件
|
|
try:
|
|
generate_device_header(project_path, enabled_devices)
|
|
success_count += 1
|
|
except Exception as e:
|
|
fail_count += 1
|
|
fail_list.append(f"device.h (异常: {e})")
|
|
|
|
# 刷新所有页面的BSP组合框选项
|
|
for page in pages:
|
|
if hasattr(page, 'refresh_bsp_combos'):
|
|
try:
|
|
page.refresh_bsp_combos()
|
|
except Exception as e:
|
|
print(f"刷新页面 {getattr(page, 'device_name', 'Unknown')} 的BSP选项失败: {e}")
|
|
|
|
total_items = success_count + fail_count
|
|
msg = f"设备代码生成完成:总共尝试生成 {total_items} 项,成功 {success_count} 项,失败 {fail_count} 项。"
|
|
if fail_list:
|
|
msg += "\n失败项:\n" + "\n".join(fail_list)
|
|
|
|
return msg |