MRobot/app/code_page/device_interface.py

388 lines
15 KiB
Python
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

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, gpio_type=None):
"""获取可用的BSP设备GPIO可选类型过滤"""
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 == "gpio" and bsp_config.get("gpio", {}).get("enabled", False):
configs = bsp_config["gpio"].get("configs", [])
# 增加类型过滤
if gpio_type:
configs = [cfg for cfg in configs if cfg.get('type', '').lower() == gpio_type.lower()]
return [f"BSP_GPIO_{cfg['custom_name']}" for cfg in configs]
elif 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 = CodeGenerator.get_assets_dir("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_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', '')
gpio_type = req.get('gpio_type', None) # 新增
req_layout = QHBoxLayout()
label = BodyLabel(f"{bsp_type.upper()}:")
label.setMinimumWidth(80)
req_layout.addWidget(label)
combo = ComboBox()
# 传递gpio_type参数
self._update_bsp_combo(combo, bsp_type, gpio_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, gpio_type=None):
combo.clear()
available_devices = get_available_bsp_devices(self.project_path, bsp_type, gpio_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):
# 头文件和源文件都做变量替换
with open(src_path, 'r', encoding='utf-8') as f:
content = f.read()
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)
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):
"""获取设备模板目录"""
return CodeGenerator.get_assets_dir("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 = CodeGenerator.get_assets_dir("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