添加ioc和自动任务

This commit is contained in:
2026-02-03 21:21:20 +08:00
parent 4a3e0d8391
commit 21052cf0a7
9 changed files with 2034 additions and 2 deletions

View File

@@ -2,8 +2,11 @@ from PyQt5.QtWidgets import QWidget, QVBoxLayout, QStackedWidget, QSizePolicy
from PyQt5.QtCore import Qt
from qfluentwidgets import PushSettingCard, FluentIcon, TabBar
from qfluentwidgets import TitleLabel, BodyLabel, PushButton, FluentIcon
from PyQt5.QtWidgets import QFileDialog
from PyQt5.QtWidgets import QFileDialog, QDialog, QHBoxLayout
from qfluentwidgets import ComboBox, PrimaryPushButton, SubtitleLabel
import os
import shutil
import tempfile
from .function_fit_interface import FunctionFitInterface
from .ai_interface import AIInterface
from qfluentwidgets import InfoBar
@@ -56,6 +59,10 @@ class CodeConfigurationInterface(QWidget):
self.update_template_btn.setFixedWidth(200)
mainLayout.addWidget(self.update_template_btn, alignment=Qt.AlignmentFlag.AlignCenter)
self.preset_ioc_btn = PushButton(FluentIcon.LIBRARY, "获取预设IOC")
self.preset_ioc_btn.setFixedWidth(200)
mainLayout.addWidget(self.preset_ioc_btn, alignment=Qt.AlignmentFlag.AlignCenter)
mainLayout.addSpacing(10)
mainLayout.addStretch()
@@ -69,6 +76,7 @@ class CodeConfigurationInterface(QWidget):
self.tabBar.tabCloseRequested.connect(self.onCloseTab)
self.choose_btn.clicked.connect(self.choose_project_folder) # 启用选择项目路径按钮
self.update_template_btn.clicked.connect(self.on_update_template)
self.preset_ioc_btn.clicked.connect(self.use_preset_ioc) # 启用预设工程按钮
def on_update_template(self):
@@ -88,6 +96,170 @@ class CodeConfigurationInterface(QWidget):
)
update_code(parent=self, info_callback=info, error_callback=error)
def get_preset_ioc_files(self):
"""获取预设的ioc文件列表"""
try:
preset_ioc_dir = os.path.join(os.path.dirname(os.path.dirname(__file__)), "assets", "User_code", "ioc")
if not os.path.exists(preset_ioc_dir):
return []
ioc_files = []
for filename in os.listdir(preset_ioc_dir):
if filename.endswith('.ioc'):
ioc_files.append({
'name': os.path.splitext(filename)[0],
'filename': filename,
'path': os.path.join(preset_ioc_dir, filename)
})
return ioc_files
except Exception as e:
print(f"获取预设ioc文件失败: {e}")
return []
def use_preset_ioc(self):
"""使用预设ioc文件"""
preset_files = self.get_preset_ioc_files()
if not preset_files:
InfoBar.warning(
title="无预设工程",
content="未找到可用的预设工程文件",
parent=self,
duration=2000
)
return
# 创建选择对话框
dialog = QDialog(self)
dialog.setWindowTitle("获取预设IOC")
dialog.resize(400, 200)
dialog.setModal(True)
layout = QVBoxLayout(dialog)
layout.setContentsMargins(24, 24, 24, 24)
layout.setSpacing(16)
# 标题
title_label = SubtitleLabel("选择要使用的IOC模版")
layout.addWidget(title_label)
# 选择下拉框
select_layout = QHBoxLayout()
select_label = BodyLabel("预设IOC")
preset_combo = ComboBox()
# 修复ComboBox数据问题
for i, preset in enumerate(preset_files):
preset_combo.addItem(preset['name'])
select_layout.addWidget(select_label)
select_layout.addWidget(preset_combo)
layout.addLayout(select_layout)
layout.addSpacing(16)
# 按钮区域
btn_layout = QHBoxLayout()
btn_layout.addStretch()
cancel_btn = PushButton("取消")
ok_btn = PrimaryPushButton("保存到")
cancel_btn.clicked.connect(dialog.reject)
ok_btn.clicked.connect(dialog.accept)
btn_layout.addWidget(cancel_btn)
btn_layout.addWidget(ok_btn)
layout.addLayout(btn_layout)
# 显示对话框
if dialog.exec() == QDialog.Accepted:
selected_index = preset_combo.currentIndex()
if selected_index >= 0 and selected_index < len(preset_files):
selected_preset = preset_files[selected_index]
self.save_preset_template(selected_preset)
def save_preset_template(self, preset_info):
"""保存预设模板到用户指定位置"""
try:
# 让用户选择保存位置
save_dir = QFileDialog.getExistingDirectory(
self,
f"选择保存 {preset_info['name']} 模板的位置",
os.path.expanduser("~")
)
if not save_dir:
return
# 复制ioc文件到用户选择的目录
target_path = os.path.join(save_dir, preset_info['filename'])
# 检查目标文件是否已存在
if os.path.exists(target_path):
from qfluentwidgets import Dialog
dialog = Dialog("文件已存在", f"目标位置已存在 {preset_info['filename']},是否覆盖?", self)
if dialog.exec() != Dialog.Accepted:
return
# 复制文件
shutil.copy2(preset_info['path'], target_path)
InfoBar.success(
title="模板保存成功",
content=f"预设模板 {preset_info['name']} 已保存到:\n{target_path}",
parent=self,
duration=3000
)
except Exception as e:
InfoBar.error(
title="保存失败",
content=f"保存预设模板失败: {str(e)}",
parent=self,
duration=3000
)
def open_project_from_path(self, folder_path):
"""从指定路径打开工程"""
try:
if not os.path.exists(folder_path):
return
ioc_files = [f for f in os.listdir(folder_path) if f.endswith('.ioc')]
if ioc_files:
# 检查是否已存在 codeGenPage 标签页
for i in range(self.stackedWidget.count()):
widget = self.stackedWidget.widget(i)
if widget is not None and widget.objectName() == "codeGenPage":
# 如果已存在,则切换到该标签页,并更新路径显示
if hasattr(widget, "project_path"):
widget.project_path = folder_path
if hasattr(widget, "refresh"):
widget.refresh()
self.stackedWidget.setCurrentWidget(widget)
self.tabBar.setCurrentTab("codeGenPage")
return
# 不存在则新建
code_gen_page = CodeGenerateInterface(folder_path, self)
self.addSubInterface(code_gen_page, "codeGenPage", "代码生成")
self.stackedWidget.setCurrentWidget(code_gen_page)
self.tabBar.setCurrentTab("codeGenPage")
else:
InfoBar.error(
title="未找到.ioc文件",
content="所选文件夹不是有效的CUBEMX工程目录",
parent=self,
duration=3000
)
except Exception as e:
InfoBar.error(
title="打开工程失败",
content=f"打开工程失败: {str(e)}",
parent=self,
duration=3000
)
def choose_project_folder(self):
folder = QFileDialog.getExistingDirectory(self, "选择CUBEMX工程目录")
if not folder:

View File

@@ -637,6 +637,31 @@ class DataInterface(QWidget):
for t in task_list:
desc = t.get("description", "")
desc_wrapped = "\n ".join(textwrap.wrap(desc, 20))
# 检查是否是预设任务
if t.get("preset_task"):
# 使用预设任务的代码
preset_task_name = t["preset_task"]
from app.tools.code_generator import CodeGenerator
task_template_dir = CodeGenerator.get_assets_dir("User_code/task/template_task")
preset_task_file = os.path.join(task_template_dir, f"{preset_task_name}.c")
if os.path.exists(preset_task_file):
# 直接复制预设任务文件
task_c_path = os.path.join(output_dir, f"{t['name']}.c")
with open(preset_task_file, 'r', encoding='utf-8') as f:
preset_code = f.read()
# 如果任务名称不同,需要替换函数名
if preset_task_name != t["name"]:
# 替换任务函数名
preset_code = preset_code.replace(f"Task_{preset_task_name}", t["function"])
preset_code = preset_code.replace(f" {preset_task_name} Task", f" {t['name']} Task")
save_with_preserve(task_c_path, preset_code)
continue
# 使用默认模板生成任务代码
context_task = {
"task_name": t["name"],
"task_function": t["function"],

View File

@@ -1,7 +1,7 @@
from PyQt5.QtWidgets import QDialog, QVBoxLayout, QHBoxLayout, QWidget, QScrollArea
from qfluentwidgets import (
BodyLabel, TitleLabel, HorizontalSeparator, PushButton, PrimaryPushButton,
LineEdit, SpinBox, DoubleSpinBox, CheckBox, TextEdit
LineEdit, SpinBox, DoubleSpinBox, CheckBox, TextEdit, ComboBox
)
from qfluentwidgets import theme, Theme
import yaml
@@ -76,12 +76,20 @@ class TaskConfigDialog(QDialog):
self.add_btn = PrimaryPushButton("创建新任务")
self.add_btn.setAutoDefault(False) # 禁止回车触发
self.add_btn.setDefault(False)
# 新增:预设任务按钮
self.preset_btn = PushButton("使用预设任务")
self.preset_btn.setAutoDefault(False)
self.preset_btn.setDefault(False)
self.del_btn = PushButton("删除当前任务")
self.del_btn.setAutoDefault(False) # 禁止回车触发
self.del_btn.setDefault(False)
self.add_btn.clicked.connect(self.add_task)
self.preset_btn.clicked.connect(self.add_preset_task)
self.del_btn.clicked.connect(self.delete_current_task)
btn_layout.addWidget(self.add_btn)
btn_layout.addWidget(self.preset_btn)
btn_layout.addWidget(self.del_btn)
btn_layout.addStretch() # 添加/删除靠左stretch在中间
@@ -135,6 +143,274 @@ class TaskConfigDialog(QDialog):
self.task_btn_layout.addWidget(btn)
self.task_btn_layout.addStretch()
def get_preset_tasks(self):
"""获取所有可用的预设任务"""
from app.tools.code_generator import CodeGenerator
task_dir = CodeGenerator.get_assets_dir("User_code/task/template_task")
preset_tasks = []
if not os.path.exists(task_dir):
return preset_tasks
for filename in os.listdir(task_dir):
if filename.endswith('.c') and not filename.endswith('.template'):
task_name = os.path.splitext(filename)[0]
preset_tasks.append(task_name)
return preset_tasks
def load_preset_task_config(self, task_name):
"""从 yaml 配置文件中加载预设任务的配置"""
try:
from app.tools.code_generator import CodeGenerator
template_dir = CodeGenerator.get_assets_dir("User_code/task/template_task")
config_file = os.path.join(template_dir, "task.yaml")
if not os.path.exists(config_file):
return None
with open(config_file, 'r', encoding='utf-8') as f:
config_data = yaml.safe_load(f)
if config_data and task_name in config_data:
return config_data[task_name]
return None
except Exception as e:
print(f"加载预设任务配置失败: {e}")
return None
def load_preset_task_code(self, task_name):
"""加载预设任务的代码内容"""
from app.tools.code_generator import CodeGenerator
task_dir = CodeGenerator.get_assets_dir("User_code/task/template_task")
task_file = os.path.join(task_dir, f"{task_name}.c")
if os.path.exists(task_file):
with open(task_file, 'r', encoding='utf-8') as f:
return f.read()
return None
def add_preset_task(self):
"""添加预设任务"""
preset_tasks = self.get_preset_tasks()
if not preset_tasks:
InfoBar.warning(
title="无预设任务",
content="未找到可用的预设任务模板",
parent=self,
duration=2000
)
return
# 创建自适应主题的对话框
dialog = QDialog(self)
dialog.setWindowTitle("选择预设任务")
dialog.resize(600, 500)
dialog.setModal(True)
layout = QVBoxLayout(dialog)
layout.setContentsMargins(24, 24, 24, 24)
layout.setSpacing(18)
# 固定内容区域
fixed_content = QWidget()
fixed_content.setFixedHeight(180) # 减少固定高度
fixed_layout = QVBoxLayout(fixed_content)
fixed_layout.setContentsMargins(0, 0, 0, 0)
fixed_layout.setSpacing(18)
# 标题
title_label = TitleLabel("选择预设任务模板")
fixed_layout.addWidget(title_label)
# 说明文字
desc_label = BodyLabel("选择一个预设任务模板,系统将自动复制相应的代码和配置")
fixed_layout.addWidget(desc_label)
fixed_layout.addWidget(HorizontalSeparator())
# 任务选择
select_layout = QHBoxLayout()
select_layout.setSpacing(12)
select_label = BodyLabel("预设任务:")
preset_combo = ComboBox()
preset_combo.addItems(preset_tasks)
preset_combo.setCurrentIndex(0)
preset_combo.setMinimumWidth(160)
select_layout.addWidget(select_label)
select_layout.addWidget(preset_combo)
select_layout.addStretch()
fixed_layout.addLayout(select_layout)
# 参数信息 - 单行显示
params_layout = QHBoxLayout()
params_layout.setSpacing(24) # 适中的间距
self.info_freq = BodyLabel("")
self.info_delay = BodyLabel("")
self.info_stack = BodyLabel("")
self.info_ctrl = BodyLabel("")
self.info_freq.setMinimumWidth(120)
self.info_delay.setMinimumWidth(100)
self.info_stack.setMinimumWidth(120)
self.info_ctrl.setMinimumWidth(100)
params_layout.addWidget(self.info_freq)
params_layout.addWidget(self.info_delay)
params_layout.addWidget(self.info_stack)
params_layout.addWidget(self.info_ctrl)
params_layout.addStretch()
fixed_layout.addLayout(params_layout)
fixed_layout.addStretch() # 在固定区域内保持紧凑
layout.addWidget(fixed_content)
# 弹性描述区域
desc_layout = QVBoxLayout()
desc_layout.setSpacing(8)
desc_title = BodyLabel("描述:")
desc_title.setStyleSheet("font-weight: bold;")
desc_layout.addWidget(desc_title)
self.preview_desc = TextEdit()
self.preview_desc.setReadOnly(True)
self.preview_desc.setMinimumHeight(60) # 最小高度
desc_layout.addWidget(self.preview_desc)
layout.addLayout(desc_layout, stretch=1) # 弹性伸缩
# 按钮区域
layout.addWidget(HorizontalSeparator())
btn_layout = QHBoxLayout()
btn_layout.addStretch()
cancel_btn = PushButton("取消")
ok_btn = PrimaryPushButton("确定添加")
cancel_btn.clicked.connect(dialog.reject)
ok_btn.clicked.connect(dialog.accept)
btn_layout.addWidget(cancel_btn)
btn_layout.addWidget(ok_btn)
layout.addLayout(btn_layout)
# 预览更新函数
def update_preview():
selected = preset_combo.currentText()
self.update_preset_preview(selected)
preset_combo.currentTextChanged.connect(update_preview)
# 存储对话框状态
self.current_preview_dialog = dialog
self.preview_combo = preset_combo
# 初始化预览
if preset_tasks:
update_preview()
# 显示对话框
if dialog.exec() == QDialog.Accepted:
selected_task = preset_combo.currentText()
self.save_form()
new_idx = len(self.tasks)
# 从配置文件加载预设任务参数
config = self.load_preset_task_config(selected_task)
if config:
new_task = self._make_task_obj({
"name": config.get("name", selected_task),
"frequency": config.get("frequency", 500),
"delay": config.get("delay", 0),
"stack": config.get("stack", 256),
"description": config.get("description", ""),
"freq_control": config.get("freq_control", True)
})
else:
new_task = self._make_task_obj({"name": selected_task})
new_task["preset_task"] = selected_task
self.tasks.append(new_task)
self.current_index = new_idx
self.refresh_task_btns()
self.show_task_form(self.tasks[self.current_index])
InfoBar.success(
title="添加成功",
content=f"已添加预设任务:{selected_task}",
parent=self,
duration=2000
)
def update_preset_preview(self, task_name):
"""更新预设任务预览信息"""
# 从配置加载信息
config = self.load_preset_task_config(task_name)
if config:
self.info_freq.setText(f"频率: {config.get('frequency', 500)} Hz")
self.info_delay.setText(f"延时: {config.get('delay', 0)} ms")
self.info_stack.setText(f"堆栈: {config.get('stack', 256)} Bytes")
freq_ctrl = "启用" if config.get('freq_control', True) else "禁用"
self.info_ctrl.setText(f"频率控制: {freq_ctrl}")
description = config.get('description', f'预设任务:{task_name}')
self.preview_desc.setText(description)
else:
self.info_freq.setText("频率: 500 Hz")
self.info_delay.setText("延时: 0 ms")
self.info_stack.setText("堆栈: 256 Bytes")
self.info_ctrl.setText("频率控制: 启用")
self.preview_desc.setText(f"预设任务:{task_name}")
def on_preset_task_selected(self, task_name):
"""预设任务选择事件(向后兼容)"""
pass
def get_preset_task_description(self, task_name):
"""获取预设任务的描述信息"""
# 首先尝试从 yaml 配置中获取描述
config = self.load_preset_task_config(task_name)
if config and 'description' in config:
return f"预设任务:{task_name}\n\n{config['description']}\n\n频率控制:{'启用' if config.get('freq_control', True) else '禁用'}\n运行频率:{config.get('frequency', 500)} Hz\n堆栈大小:{config.get('stack', 256)} Bytes\n初始延时:{config.get('delay', 0)} ms"
# 如果没有配置文件,则从代码注释中提取
try:
task_code = self.load_preset_task_code(task_name)
if task_code:
# 尝试从注释中提取描述
lines = task_code.split('\n')
description_lines = []
in_comment = False
for line in lines[:20]: # 只检查前20行
line = line.strip()
if line.startswith('/*'):
in_comment = True
if 'Task' in line and line != '/*':
description_lines.append(line.replace('/*', '').strip())
continue
elif line.endswith('*/'):
in_comment = False
break
elif in_comment and line and not line.startswith('*'):
description_lines.append(line)
if description_lines:
return '\n'.join(description_lines)
else:
return f"预设任务:{task_name}\n这是一个预定义的任务模板,包含完整的实现代码。"
else:
return f"预设任务:{task_name}\n无法读取任务描述。"
except Exception as e:
return f"预设任务:{task_name}\n读取描述时出现错误:{str(e)}"
def add_task(self):
self.save_form()
new_idx = len(self.tasks)
@@ -302,5 +578,10 @@ class TaskConfigDialog(QDialog):
}
if freq_ctrl:
task["frequency"] = freq
# 保留预设任务信息
if "preset_task" in t:
task["preset_task"] = t["preset_task"]
tasks.append(task)
return tasks