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