MRobot/app/code_page/device_interface.py

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