mirror of
https://github.com/goldenfishs/MRobot.git
synced 2026-03-31 21:07:14 +08:00
bsp写的差不多了
This commit is contained in:
@@ -1,6 +1,12 @@
|
||||
from PyQt5.QtWidgets import QWidget, QVBoxLayout
|
||||
from PyQt5.QtWidgets import QWidget, QVBoxLayout, QHBoxLayout, QSizePolicy, QTreeWidget, QTreeWidgetItem, QStackedWidget
|
||||
from PyQt5.QtCore import Qt
|
||||
from qfluentwidgets import TitleLabel, BodyLabel
|
||||
from qfluentwidgets import TitleLabel, BodyLabel, PushButton
|
||||
from app.tools.analyzing_ioc import analyzing_ioc
|
||||
from app.code_page.bsp_interface import bsp
|
||||
|
||||
import os
|
||||
import csv
|
||||
import importlib
|
||||
|
||||
class CodeGenerateInterface(QWidget):
|
||||
def __init__(self, project_path, parent=None):
|
||||
@@ -8,14 +14,137 @@ class CodeGenerateInterface(QWidget):
|
||||
self.setObjectName("CodeGenerateInterface")
|
||||
self.project_path = project_path
|
||||
|
||||
layout = QVBoxLayout(self)
|
||||
layout.setAlignment(Qt.AlignTop)
|
||||
layout.setContentsMargins(10, 10, 10, 10)
|
||||
self._init_ui()
|
||||
|
||||
title = TitleLabel("代码生成页面")
|
||||
title.setAlignment(Qt.AlignCenter)
|
||||
layout.addWidget(title)
|
||||
def _init_ui(self):
|
||||
"""初始化界面布局"""
|
||||
main_layout = QVBoxLayout(self)
|
||||
main_layout.setAlignment(Qt.AlignTop)
|
||||
main_layout.setContentsMargins(10, 10, 10, 10)
|
||||
|
||||
desc = BodyLabel(f"当前工程路径: {self.project_path}")
|
||||
desc.setAlignment(Qt.AlignCenter)
|
||||
layout.addWidget(desc)
|
||||
top_layout = self._create_top_layout()
|
||||
main_layout.addLayout(top_layout)
|
||||
|
||||
# 下方主区域,左右分栏
|
||||
content_layout = QHBoxLayout()
|
||||
content_layout.setContentsMargins(0, 10, 0, 0)
|
||||
main_layout.addLayout(content_layout)
|
||||
|
||||
# 左侧树形列表
|
||||
self.tree = QTreeWidget()
|
||||
self.tree.setHeaderHidden(True)
|
||||
self.tree.setMaximumWidth(250)
|
||||
content_layout.addWidget(self.tree)
|
||||
|
||||
# 右侧内容区
|
||||
self.stack = QStackedWidget()
|
||||
content_layout.addWidget(self.stack)
|
||||
|
||||
self._load_csv_and_build_tree()
|
||||
self.tree.itemClicked.connect(self.on_tree_item_clicked)
|
||||
|
||||
def _create_top_layout(self):
|
||||
"""创建顶部横向布局"""
|
||||
top_layout = QHBoxLayout()
|
||||
top_layout.setAlignment(Qt.AlignTop)
|
||||
|
||||
# 项目名称标签
|
||||
project_name = os.path.basename(self.project_path)
|
||||
name_label = BodyLabel(f"项目名称: {project_name}")
|
||||
name_label.setSizePolicy(QSizePolicy.Expanding, QSizePolicy.Preferred)
|
||||
top_layout.addWidget(name_label)
|
||||
|
||||
# FreeRTOS状态标签
|
||||
freertos_label = BodyLabel(f"FreeRTOS: {self._get_freertos_status()}")
|
||||
freertos_label.setSizePolicy(QSizePolicy.Expanding, QSizePolicy.Preferred)
|
||||
top_layout.addWidget(freertos_label)
|
||||
|
||||
# 生成代码按钮
|
||||
generate_btn = PushButton("Generate Code")
|
||||
generate_btn.setSizePolicy(QSizePolicy.Fixed, QSizePolicy.Fixed)
|
||||
generate_btn.clicked.connect(self.generate_code)
|
||||
top_layout.addWidget(generate_btn, alignment=Qt.AlignRight)
|
||||
|
||||
return top_layout
|
||||
|
||||
def generate_code(self):
|
||||
"""生成代码逻辑"""
|
||||
# 收集所有已加载的页面对象
|
||||
pages = []
|
||||
for i in range(self.stack.count()):
|
||||
widget = self.stack.widget(i)
|
||||
pages.append(widget)
|
||||
bsp.generate_bsp(self.project_path, pages)
|
||||
# component.generate_component(self.project_path)
|
||||
# device.generate_device(self.project_path)
|
||||
|
||||
|
||||
def _get_freertos_status(self):
|
||||
"""获取FreeRTOS状态"""
|
||||
ioc_files = [f for f in os.listdir(self.project_path) if f.endswith('.ioc')]
|
||||
if ioc_files:
|
||||
ioc_path = os.path.join(self.project_path, ioc_files[0])
|
||||
return "开启" if analyzing_ioc.is_freertos_enabled_from_ioc(ioc_path) else "未开启"
|
||||
return "未找到.ioc文件"
|
||||
|
||||
def _load_csv_and_build_tree(self):
|
||||
# 获取脚本目录
|
||||
script_dir = os.path.dirname(os.path.abspath(__file__))
|
||||
csv_path = os.path.join(script_dir, "../assets/User_code/config.csv")
|
||||
csv_path = os.path.abspath(csv_path)
|
||||
print(f"加载CSV路径: {csv_path}")
|
||||
if not os.path.exists(csv_path):
|
||||
print(f"配置文件未找到: {csv_path}")
|
||||
return
|
||||
self.tree.clear()
|
||||
with open(csv_path, newline='', encoding='utf-8') as f:
|
||||
reader = csv.reader(f)
|
||||
for row in reader:
|
||||
row = [cell.strip() for cell in row if cell.strip()]
|
||||
if not row:
|
||||
continue
|
||||
main_title = row[0]
|
||||
main_item = QTreeWidgetItem([main_title])
|
||||
for sub in row[1:]:
|
||||
sub_item = QTreeWidgetItem([sub])
|
||||
main_item.addChild(sub_item)
|
||||
self.tree.addTopLevelItem(main_item)
|
||||
self.tree.repaint()
|
||||
|
||||
def on_tree_item_clicked(self, item, column):
|
||||
if item.parent():
|
||||
main_title = item.parent().text(0)
|
||||
sub_title = item.text(0)
|
||||
class_name = f"{main_title}_{sub_title}".replace("-", "_")
|
||||
widget = self._get_or_create_page(class_name)
|
||||
if widget:
|
||||
self.stack.setCurrentWidget(widget)
|
||||
|
||||
def _get_or_create_page(self, class_name):
|
||||
for i in range(self.stack.count()):
|
||||
w = self.stack.widget(i)
|
||||
if w.objectName() == class_name:
|
||||
return w
|
||||
try:
|
||||
module_name = f"app.code_page.{class_name.split('_')[0]}_interface"
|
||||
module = importlib.import_module(module_name)
|
||||
cls = getattr(module, class_name)
|
||||
widget = cls(self.project_path)
|
||||
widget.setObjectName(class_name)
|
||||
self.stack.addWidget(widget)
|
||||
print(f"加载页面类: {class_name} 来自模块: {module_name}")
|
||||
return widget
|
||||
except Exception as e:
|
||||
# 自动识别通用外设页面
|
||||
from app.code_page.bsp_interface import BspSimplePeripheral
|
||||
peripheral_name = class_name.split('_')[1] if '_' in class_name else class_name
|
||||
# 模板文件名自动推断
|
||||
template_names = {
|
||||
'header': f"{peripheral_name.lower()}.h",
|
||||
'source': f"{peripheral_name.lower()}.c"
|
||||
}
|
||||
widget = BspSimplePeripheral(self.project_path, peripheral_name, template_names)
|
||||
widget.setObjectName(class_name)
|
||||
self.stack.addWidget(widget)
|
||||
print(f"自动加载通用外设页面: {class_name}")
|
||||
return widget
|
||||
453
app/code_page/bsp_interface.py
Normal file
453
app/code_page/bsp_interface.py
Normal file
@@ -0,0 +1,453 @@
|
||||
from PyQt5.QtWidgets import QWidget, QVBoxLayout, QLineEdit, QCheckBox, QComboBox, QTableWidget, QHeaderView, QMessageBox
|
||||
from qfluentwidgets import TitleLabel, BodyLabel, PushButton
|
||||
from app.tools.analyzing_ioc import analyzing_ioc
|
||||
from app.tools.code_generator import CodeGenerator
|
||||
import os
|
||||
import csv
|
||||
|
||||
class BspSimplePeripheral(QWidget):
|
||||
def __init__(self, project_path, peripheral_name, template_names):
|
||||
super().__init__()
|
||||
self.project_path = project_path
|
||||
self.peripheral_name = peripheral_name
|
||||
self.template_names = template_names
|
||||
# 加载描述
|
||||
describe_path = os.path.join(os.path.dirname(__file__), "../../assets/User_code/bsp/describe.csv")
|
||||
self.descriptions = load_descriptions(describe_path)
|
||||
self._init_ui()
|
||||
self._load_config()
|
||||
|
||||
def _init_ui(self):
|
||||
layout = QVBoxLayout(self)
|
||||
layout.addWidget(TitleLabel(f"{self.peripheral_name} 配置"))
|
||||
desc = self.descriptions.get(self.peripheral_name.lower(), "")
|
||||
if desc:
|
||||
layout.addWidget(BodyLabel(f"功能说明:{desc}"))
|
||||
self.generate_checkbox = QCheckBox(f"启用 {self.peripheral_name}")
|
||||
layout.addWidget(self.generate_checkbox)
|
||||
|
||||
def is_need_generate(self):
|
||||
return self.generate_checkbox.isChecked()
|
||||
|
||||
def _generate_bsp_code_internal(self):
|
||||
if not self.is_need_generate():
|
||||
return False
|
||||
template_dir = CodeGenerator.get_template_dir()
|
||||
# 直接拷贝模板,无需特殊处理
|
||||
for key, filename in self.template_names.items():
|
||||
template_path = os.path.join(template_dir, filename)
|
||||
template_content = CodeGenerator.load_template(template_path)
|
||||
if not template_content:
|
||||
return False
|
||||
output_path = os.path.join(self.project_path, f"User/bsp/{filename}")
|
||||
if not CodeGenerator.save_file(template_content, output_path):
|
||||
return False
|
||||
self._save_config()
|
||||
return True
|
||||
|
||||
def _save_config(self):
|
||||
config_path = os.path.join(self.project_path, "User/bsp/bsp_config.yaml")
|
||||
config_data = CodeGenerator.load_config(config_path)
|
||||
config_data[self.peripheral_name.lower()] = {'enabled': True}
|
||||
CodeGenerator.save_config(config_data, config_path)
|
||||
|
||||
def _load_config(self):
|
||||
config_path = os.path.join(self.project_path, "User/bsp/bsp_config.yaml")
|
||||
config_data = CodeGenerator.load_config(config_path)
|
||||
conf = config_data.get(self.peripheral_name.lower(), {})
|
||||
if conf.get('enabled', False):
|
||||
self.generate_checkbox.setChecked(True)
|
||||
|
||||
|
||||
class BspPeripheralBase(QWidget):
|
||||
def __init__(self, project_path, peripheral_name, template_names, enum_prefix, handle_prefix, yaml_key, get_available_func):
|
||||
super().__init__()
|
||||
self.project_path = project_path
|
||||
self.peripheral_name = peripheral_name
|
||||
self.template_names = template_names
|
||||
self.enum_prefix = enum_prefix
|
||||
self.handle_prefix = handle_prefix
|
||||
self.yaml_key = yaml_key
|
||||
self.get_available_func = get_available_func
|
||||
self.available_list = []
|
||||
# 新增:加载描述
|
||||
describe_path = os.path.join(os.path.dirname(__file__), "../../assets/User_code/bsp/describe.csv")
|
||||
self.descriptions = load_descriptions(describe_path)
|
||||
self._init_ui()
|
||||
self._load_config()
|
||||
|
||||
def _init_ui(self):
|
||||
layout = QVBoxLayout(self)
|
||||
layout.addWidget(TitleLabel(f"{self.peripheral_name} 配置"))
|
||||
desc = self.descriptions.get(self.peripheral_name.lower(), "")
|
||||
if desc:
|
||||
layout.addWidget(BodyLabel(f"功能说明:{desc}"))
|
||||
self.generate_checkbox = QCheckBox(f"生成 {self.peripheral_name} 代码")
|
||||
self.generate_checkbox = QCheckBox(f"生成 {self.peripheral_name} 代码")
|
||||
self.generate_checkbox.stateChanged.connect(self._on_generate_changed)
|
||||
layout.addWidget(self.generate_checkbox)
|
||||
self.content_widget = QWidget()
|
||||
content_layout = QVBoxLayout(self.content_widget)
|
||||
self._get_available_list()
|
||||
if not self.available_list:
|
||||
content_layout.addWidget(BodyLabel(f"在 .ioc 文件中未找到已启用的 {self.peripheral_name}"))
|
||||
else:
|
||||
content_layout.addWidget(BodyLabel(f"可用的 {self.peripheral_name}: {', '.join(self.available_list)}"))
|
||||
self.table = QTableWidget(0, 3)
|
||||
self.table.setHorizontalHeaderLabels(["设备名称", f"{self.peripheral_name}选择", "操作"])
|
||||
self.table.horizontalHeader().setSectionResizeMode(QHeaderView.Stretch)
|
||||
content_layout.addWidget(self.table)
|
||||
add_btn = PushButton(f"添加 {self.peripheral_name} 设备")
|
||||
add_btn.clicked.connect(self._add_device)
|
||||
content_layout.addWidget(add_btn)
|
||||
layout.addWidget(self.content_widget)
|
||||
self.content_widget.setEnabled(False)
|
||||
|
||||
def _get_available_list(self):
|
||||
self.available_list = self.get_available_func(self.project_path)
|
||||
|
||||
def _on_generate_changed(self, state):
|
||||
self.content_widget.setEnabled(state == 2)
|
||||
|
||||
def _add_device(self):
|
||||
row = self.table.rowCount()
|
||||
self.table.insertRow(row)
|
||||
name_edit = QLineEdit()
|
||||
name_edit.setPlaceholderText(f"输入设备名称")
|
||||
self.table.setCellWidget(row, 0, name_edit)
|
||||
combo = QComboBox()
|
||||
combo.addItems(self.available_list)
|
||||
self.table.setCellWidget(row, 1, combo)
|
||||
del_btn = PushButton("删除")
|
||||
del_btn.clicked.connect(lambda: self._delete_device(row))
|
||||
self.table.setCellWidget(row, 2, del_btn)
|
||||
|
||||
def _delete_device(self, row):
|
||||
self.table.removeRow(row)
|
||||
|
||||
def _collect_configs(self):
|
||||
configs = []
|
||||
for row in range(self.table.rowCount()):
|
||||
name_widget = self.table.cellWidget(row, 0)
|
||||
sel_widget = self.table.cellWidget(row, 1)
|
||||
if name_widget and sel_widget:
|
||||
name = name_widget.text().strip()
|
||||
sel = sel_widget.currentText()
|
||||
if name and sel:
|
||||
configs.append((name.upper(), sel))
|
||||
return configs
|
||||
|
||||
def is_need_generate(self):
|
||||
return self.generate_checkbox.isChecked() and bool(self._collect_configs())
|
||||
|
||||
def generate_bsp_code(self):
|
||||
if not self.is_need_generate():
|
||||
return False
|
||||
configs = self._collect_configs()
|
||||
if not configs:
|
||||
return False
|
||||
template_dir = CodeGenerator.get_template_dir()
|
||||
if not self._generate_header_file(configs, template_dir):
|
||||
return False
|
||||
if not self._generate_source_file(configs, template_dir):
|
||||
return False
|
||||
self._save_config(configs)
|
||||
QMessageBox.information(self, "成功", f"{self.peripheral_name} 代码生成成功!")
|
||||
return True
|
||||
|
||||
def _generate_header_file(self, configs, template_dir):
|
||||
template_path = os.path.join(template_dir, self.template_names['header'])
|
||||
template_content = CodeGenerator.load_template(template_path)
|
||||
if not template_content:
|
||||
return False
|
||||
enum_lines = [f" {self.enum_prefix}_{name}," for name, _ in configs]
|
||||
content = CodeGenerator.replace_auto_generated(
|
||||
template_content, f"AUTO GENERATED {self.enum_prefix}_NAME", "\n".join(enum_lines)
|
||||
)
|
||||
output_path = os.path.join(self.project_path, f"User/bsp/{self.template_names['header']}")
|
||||
return CodeGenerator.save_file(content, output_path)
|
||||
|
||||
def _generate_source_file(self, configs, template_dir):
|
||||
template_path = os.path.join(template_dir, self.template_names['source'])
|
||||
template_content = CodeGenerator.load_template(template_path)
|
||||
if not template_content:
|
||||
return False
|
||||
# Get函数
|
||||
get_lines = []
|
||||
for idx, (name, instance) in enumerate(configs):
|
||||
if idx == 0:
|
||||
get_lines.append(f" if ({self.handle_prefix}->Instance == {instance})")
|
||||
else:
|
||||
get_lines.append(f" else if ({self.handle_prefix}->Instance == {instance})")
|
||||
get_lines.append(f" return {self.enum_prefix}_{name};")
|
||||
content = CodeGenerator.replace_auto_generated(
|
||||
template_content, f"AUTO GENERATED {self.enum_prefix.split('_')[1]}_GET", "\n".join(get_lines)
|
||||
)
|
||||
# Handle函数
|
||||
handle_lines = []
|
||||
for name, instance in configs:
|
||||
handle_lines.append(f" case {self.enum_prefix}_{name}:")
|
||||
handle_lines.append(f" return &h{instance.lower()};")
|
||||
content = CodeGenerator.replace_auto_generated(
|
||||
content, f"AUTO GENERATED {self.enum_prefix}_GET_HANDLE", "\n".join(handle_lines)
|
||||
)
|
||||
output_path = os.path.join(self.project_path, f"User/bsp/{self.template_names['source']}")
|
||||
return CodeGenerator.save_file(content, output_path)
|
||||
|
||||
def _save_config(self, configs):
|
||||
config_path = os.path.join(self.project_path, "User/bsp/bsp_config.yaml")
|
||||
config_data = CodeGenerator.load_config(config_path)
|
||||
config_data[self.yaml_key] = {
|
||||
'enabled': True,
|
||||
'devices': [{'name': name, 'instance': instance} for name, instance in configs]
|
||||
}
|
||||
CodeGenerator.save_config(config_data, config_path)
|
||||
|
||||
def _load_config(self):
|
||||
config_path = os.path.join(self.project_path, "User/bsp/bsp_config.yaml")
|
||||
config_data = CodeGenerator.load_config(config_path)
|
||||
conf = config_data.get(self.yaml_key, {})
|
||||
if conf.get('enabled', False):
|
||||
self.generate_checkbox.setChecked(True)
|
||||
devices = conf.get('devices', [])
|
||||
for device in devices:
|
||||
if self.available_list:
|
||||
self._add_device()
|
||||
row = self.table.rowCount() - 1
|
||||
name_widget = self.table.cellWidget(row, 0)
|
||||
sel_widget = self.table.cellWidget(row, 1)
|
||||
if name_widget:
|
||||
name_widget.setText(device.get('name', ''))
|
||||
if sel_widget:
|
||||
instance = device.get('instance', '')
|
||||
if instance in self.available_list:
|
||||
sel_widget.setCurrentText(instance)
|
||||
|
||||
def _generate_bsp_code_internal(self):
|
||||
if not self.is_need_generate():
|
||||
return False
|
||||
configs = self._collect_configs()
|
||||
if not configs:
|
||||
return False
|
||||
template_dir = CodeGenerator.get_template_dir()
|
||||
if not self._generate_header_file(configs, template_dir):
|
||||
return False
|
||||
if not self._generate_source_file(configs, template_dir):
|
||||
return False
|
||||
self._save_config(configs)
|
||||
return True
|
||||
|
||||
def load_descriptions(csv_path):
|
||||
descriptions = {}
|
||||
if os.path.exists(csv_path):
|
||||
with open(csv_path, encoding='utf-8') as f:
|
||||
reader = csv.reader(f)
|
||||
for row in reader:
|
||||
if len(row) >= 2:
|
||||
key, desc = row[0].strip(), row[1].strip()
|
||||
descriptions[key.lower()] = desc
|
||||
return descriptions
|
||||
|
||||
# 各外设的可用列表获取函数
|
||||
def get_available_i2c(project_path):
|
||||
ioc_files = [f for f in os.listdir(project_path) if f.endswith('.ioc')]
|
||||
if ioc_files:
|
||||
ioc_path = os.path.join(project_path, ioc_files[0])
|
||||
return analyzing_ioc.get_enabled_i2c_from_ioc(ioc_path)
|
||||
return []
|
||||
|
||||
def get_available_can(project_path):
|
||||
ioc_files = [f for f in os.listdir(project_path) if f.endswith('.ioc')]
|
||||
if ioc_files:
|
||||
ioc_path = os.path.join(project_path, ioc_files[0])
|
||||
return analyzing_ioc.get_enabled_can_from_ioc(ioc_path)
|
||||
return []
|
||||
|
||||
def get_available_spi(project_path):
|
||||
ioc_files = [f for f in os.listdir(project_path) if f.endswith('.ioc')]
|
||||
if ioc_files:
|
||||
ioc_path = os.path.join(project_path, ioc_files[0])
|
||||
return analyzing_ioc.get_enabled_spi_from_ioc(ioc_path)
|
||||
return []
|
||||
|
||||
def get_available_uart(project_path):
|
||||
ioc_files = [f for f in os.listdir(project_path) if f.endswith('.ioc')]
|
||||
if ioc_files:
|
||||
ioc_path = os.path.join(project_path, ioc_files[0])
|
||||
return analyzing_ioc.get_enabled_uart_from_ioc(ioc_path)
|
||||
return []
|
||||
|
||||
def get_available_gpio(project_path):
|
||||
ioc_files = [f for f in os.listdir(project_path) if f.endswith('.ioc')]
|
||||
if ioc_files:
|
||||
ioc_path = os.path.join(project_path, ioc_files[0])
|
||||
return analyzing_ioc.get_enabled_gpio_from_ioc(ioc_path)
|
||||
return []
|
||||
|
||||
# 具体外设类
|
||||
class bsp_i2c(BspPeripheralBase):
|
||||
def __init__(self, project_path):
|
||||
super().__init__(
|
||||
project_path,
|
||||
"I2C",
|
||||
{'header': 'i2c.h', 'source': 'i2c.c'},
|
||||
"BSP_I2C",
|
||||
"hi2c",
|
||||
"i2c",
|
||||
get_available_i2c
|
||||
)
|
||||
|
||||
class bsp_can(BspPeripheralBase):
|
||||
def __init__(self, project_path):
|
||||
super().__init__(
|
||||
project_path,
|
||||
"CAN",
|
||||
{'header': 'can.h', 'source': 'can.c'},
|
||||
"BSP_CAN",
|
||||
"hcan",
|
||||
"can",
|
||||
get_available_can
|
||||
)
|
||||
|
||||
class bsp_spi(BspPeripheralBase):
|
||||
def __init__(self, project_path):
|
||||
super().__init__(
|
||||
project_path,
|
||||
"SPI",
|
||||
{'header': 'spi.h', 'source': 'spi.c'},
|
||||
"BSP_SPI",
|
||||
"hspi",
|
||||
"spi",
|
||||
get_available_spi
|
||||
)
|
||||
|
||||
class bsp_uart(BspPeripheralBase):
|
||||
def __init__(self, project_path):
|
||||
super().__init__(
|
||||
project_path,
|
||||
"UART",
|
||||
{'header': 'uart.h', 'source': 'uart.c'},
|
||||
"BSP_UART",
|
||||
"huart",
|
||||
"uart",
|
||||
get_available_uart
|
||||
)
|
||||
|
||||
class bsp_gpio(QWidget):
|
||||
def __init__(self, project_path):
|
||||
super().__init__()
|
||||
self.project_path = project_path
|
||||
self.available_list = get_available_gpio(project_path)
|
||||
# 新增:加载描述
|
||||
describe_path = os.path.join(os.path.dirname(__file__), "../../assets/User_code/bsp/describe.csv")
|
||||
self.descriptions = load_descriptions(describe_path)
|
||||
self._init_ui()
|
||||
|
||||
def _init_ui(self):
|
||||
layout = QVBoxLayout(self)
|
||||
layout.addWidget(TitleLabel("GPIO 配置"))
|
||||
# 新增:显示描述
|
||||
desc = self.descriptions.get("gpio", "")
|
||||
if desc:
|
||||
layout.addWidget(BodyLabel(f"功能说明:{desc}"))
|
||||
self.generate_checkbox = QCheckBox("生成 GPIO 代码")
|
||||
layout.addWidget(self.generate_checkbox)
|
||||
if not self.available_list:
|
||||
layout.addWidget(BodyLabel("在 .ioc 文件中未找到可用的 GPIO"))
|
||||
else:
|
||||
self.table = QTableWidget(len(self.available_list), 1)
|
||||
self.table.setHorizontalHeaderLabels(["Label"])
|
||||
self.table.horizontalHeader().setSectionResizeMode(QHeaderView.Stretch)
|
||||
for row, item in enumerate(self.available_list):
|
||||
from PyQt5.QtWidgets import QTableWidgetItem
|
||||
self.table.setItem(row, 0, QTableWidgetItem(item['label']))
|
||||
self.table.setEditTriggers(QTableWidget.NoEditTriggers)
|
||||
layout.addWidget(self.table)
|
||||
|
||||
def is_need_generate(self):
|
||||
return self.generate_checkbox.isChecked() and bool(self.available_list)
|
||||
|
||||
def _generate_bsp_code_internal(self):
|
||||
if not self.is_need_generate():
|
||||
return False
|
||||
template_dir = CodeGenerator.get_template_dir()
|
||||
if not self._generate_header_file(template_dir):
|
||||
return False
|
||||
if not self._generate_source_file(template_dir):
|
||||
return False
|
||||
self._save_config()
|
||||
return True
|
||||
|
||||
def _generate_header_file(self, template_dir):
|
||||
template_path = os.path.join(template_dir, "gpio.h")
|
||||
template_content = CodeGenerator.load_template(template_path)
|
||||
if not template_content:
|
||||
return False
|
||||
# 如有需要可在此处插入自动生成内容
|
||||
output_path = os.path.join(self.project_path, "User/bsp/gpio.h")
|
||||
return CodeGenerator.save_file(template_content, output_path)
|
||||
|
||||
def _generate_source_file(self, template_dir):
|
||||
template_path = os.path.join(template_dir, "gpio.c")
|
||||
template_content = CodeGenerator.load_template(template_path)
|
||||
if not template_content:
|
||||
return False
|
||||
# 生成 IRQ 使能/禁用代码
|
||||
enable_lines = []
|
||||
disable_lines = []
|
||||
for item in self.available_list:
|
||||
label = item['label']
|
||||
enable_lines.append(f" case {label}_Pin:")
|
||||
enable_lines.append(f" HAL_NVIC_EnableIRQ({label}_EXTI_IRQn);")
|
||||
enable_lines.append(f" break;")
|
||||
disable_lines.append(f" case {label}_Pin:")
|
||||
disable_lines.append(f" HAL_NVIC_DisableIRQ({label}_EXTI_IRQn);")
|
||||
disable_lines.append(f" break;")
|
||||
content = CodeGenerator.replace_auto_generated(
|
||||
template_content, "AUTO GENERATED BSP_GPIO_ENABLE_IRQ", "\n".join(enable_lines)
|
||||
)
|
||||
content = CodeGenerator.replace_auto_generated(
|
||||
content, "AUTO GENERATED BSP_GPIO_DISABLE_IRQ", "\n".join(disable_lines)
|
||||
)
|
||||
output_path = os.path.join(self.project_path, "User/bsp/gpio.c")
|
||||
return CodeGenerator.save_file(content, output_path)
|
||||
|
||||
def _save_config(self):
|
||||
config_path = os.path.join(self.project_path, "User/bsp/bsp_config.yaml")
|
||||
config_data = CodeGenerator.load_config(config_path)
|
||||
config_data['gpio'] = {
|
||||
'enabled': True,
|
||||
'labels': [item['label'] for item in self.available_list]
|
||||
}
|
||||
CodeGenerator.save_config(config_data, config_path)
|
||||
|
||||
|
||||
class bsp(QWidget):
|
||||
def __init__(self, project_path):
|
||||
super().__init__()
|
||||
self.project_path = project_path
|
||||
|
||||
@staticmethod
|
||||
def generate_bsp(project_path, pages):
|
||||
total = 0
|
||||
success_count = 0
|
||||
fail_count = 0
|
||||
fail_list = []
|
||||
for page in pages:
|
||||
name = page.objectName() if hasattr(page, "objectName") else str(page)
|
||||
if hasattr(page, "is_need_generate") and page.is_need_generate():
|
||||
total += 1
|
||||
try:
|
||||
result = page._generate_bsp_code_internal()
|
||||
if result:
|
||||
success_count += 1
|
||||
else:
|
||||
fail_count += 1
|
||||
fail_list.append(name)
|
||||
except Exception as e:
|
||||
fail_count += 1
|
||||
fail_list.append(f"{name} (异常: {e})")
|
||||
msg = f"总共尝试生成 {total} 项,成功 {success_count} 项,失败 {fail_count} 项。"
|
||||
if fail_list:
|
||||
msg += "\n失败项:\n" + "\n".join(fail_list)
|
||||
QMessageBox.information(None, "代码生成结果", msg)
|
||||
0
app/code_page/device_interface.py
Normal file
0
app/code_page/device_interface.py
Normal file
0
app/code_page/module_interface.py
Normal file
0
app/code_page/module_interface.py
Normal file
@@ -1,7 +1,146 @@
|
||||
|
||||
|
||||
class analyzing_ioc:
|
||||
def __init__(self, ioc_data): # 初始化方法,接收IOC数据
|
||||
self.ioc_data = ioc_data # 存储IOC数据
|
||||
@staticmethod
|
||||
def is_freertos_enabled_from_ioc(ioc_path):
|
||||
"""
|
||||
检查指定 .ioc 文件是否开启了 FreeRTOS
|
||||
"""
|
||||
config = {}
|
||||
with open(ioc_path, encoding='utf-8') as f:
|
||||
for line in f:
|
||||
line = line.strip()
|
||||
if not line or line.startswith('#'):
|
||||
continue
|
||||
if '=' in line:
|
||||
key, value = line.split('=', 1)
|
||||
config[key.strip()] = value.strip()
|
||||
ip_keys = [k for k in config if k.startswith('Mcu.IP')]
|
||||
for k in ip_keys:
|
||||
if config[k] == 'FREERTOS':
|
||||
return True
|
||||
for k in config:
|
||||
if k.startswith('FREERTOS.'):
|
||||
return True
|
||||
return False
|
||||
|
||||
|
||||
@staticmethod
|
||||
def get_enabled_i2c_from_ioc(ioc_path):
|
||||
"""
|
||||
从.ioc文件中获取已启用的I2C列表
|
||||
返回格式: ['I2C1', 'I2C3'] 等
|
||||
"""
|
||||
enabled_i2c = []
|
||||
with open(ioc_path, encoding='utf-8', errors='ignore') as f:
|
||||
for line in f:
|
||||
line = line.strip()
|
||||
if not line or line.startswith('#'):
|
||||
continue
|
||||
if '=' in line:
|
||||
key, value = line.split('=', 1)
|
||||
key = key.strip()
|
||||
value = value.strip()
|
||||
# 检查是否启用了I2C
|
||||
if key.startswith('Mcu.IP') and value.startswith('I2C'):
|
||||
# 提取I2C编号,如I2C1, I2C2等
|
||||
i2c_name = value.split('.')[0] if '.' in value else value
|
||||
if i2c_name not in enabled_i2c:
|
||||
enabled_i2c.append(i2c_name)
|
||||
return sorted(enabled_i2c)
|
||||
|
||||
@staticmethod
|
||||
def get_enabled_spi_from_ioc(ioc_path):
|
||||
"""
|
||||
获取已启用的SPI列表
|
||||
返回格式: ['SPI1', 'SPI2'] 等
|
||||
"""
|
||||
enabled_spi = []
|
||||
with open(ioc_path, encoding='utf-8', errors='ignore') as f:
|
||||
for line in f:
|
||||
line = line.strip()
|
||||
if not line or line.startswith('#'):
|
||||
continue
|
||||
if '=' in line:
|
||||
key, value = line.split('=', 1)
|
||||
key = key.strip()
|
||||
value = value.strip()
|
||||
if key.startswith('Mcu.IP') and value.startswith('SPI'):
|
||||
spi_name = value.split('.')[0] if '.' in value else value
|
||||
if spi_name not in enabled_spi:
|
||||
enabled_spi.append(spi_name)
|
||||
return sorted(enabled_spi)
|
||||
|
||||
@staticmethod
|
||||
def get_enabled_can_from_ioc(ioc_path):
|
||||
"""
|
||||
获取已启用的CAN列表
|
||||
返回格式: ['CAN1', 'CAN2'] 等
|
||||
"""
|
||||
enabled_can = []
|
||||
with open(ioc_path, encoding='utf-8', errors='ignore') as f:
|
||||
for line in f:
|
||||
line = line.strip()
|
||||
if not line or line.startswith('#'):
|
||||
continue
|
||||
if '=' in line:
|
||||
key, value = line.split('=', 1)
|
||||
key = key.strip()
|
||||
value = value.strip()
|
||||
if key.startswith('Mcu.IP') and value.startswith('CAN'):
|
||||
can_name = value.split('.')[0] if '.' in value else value
|
||||
if can_name not in enabled_can:
|
||||
enabled_can.append(can_name)
|
||||
return sorted(enabled_can)
|
||||
|
||||
@staticmethod
|
||||
def get_enabled_uart_from_ioc(ioc_path):
|
||||
"""
|
||||
获取已启用的UART/USART列表
|
||||
返回格式: ['USART1', 'USART2', 'UART4'] 等
|
||||
"""
|
||||
enabled_uart = []
|
||||
with open(ioc_path, encoding='utf-8', errors='ignore') as f:
|
||||
for line in f:
|
||||
line = line.strip()
|
||||
if not line or line.startswith('#'):
|
||||
continue
|
||||
if '=' in line:
|
||||
key, value = line.split('=', 1)
|
||||
key = key.strip()
|
||||
value = value.strip()
|
||||
# 检查是否启用了UART或USART
|
||||
if key.startswith('Mcu.IP') and (value.startswith('USART') or value.startswith('UART')):
|
||||
uart_name = value.split('.')[0] if '.' in value else value
|
||||
if uart_name not in enabled_uart:
|
||||
enabled_uart.append(uart_name)
|
||||
return sorted(enabled_uart)
|
||||
|
||||
@staticmethod
|
||||
def get_enabled_gpio_from_ioc(ioc_path):
|
||||
"""
|
||||
获取所有带 EXTI 且有 Label 的 GPIO,返回 [{'pin': 'PC4', 'label': 'ACCL_INT'}, ...]
|
||||
"""
|
||||
gpio_list = []
|
||||
gpio_params = {}
|
||||
with open(ioc_path, encoding='utf-8', errors='ignore') as f:
|
||||
for line in f:
|
||||
line = line.strip()
|
||||
if not line or line.startswith('#'):
|
||||
continue
|
||||
if '=' in line:
|
||||
key, value = line.split('=', 1)
|
||||
key = key.strip()
|
||||
value = value.strip()
|
||||
if '.GPIOParameters' in key:
|
||||
gpio_params[key.split('.')[0]] = value
|
||||
elif '.GPIO_Label' in key:
|
||||
pin = key.split('.')[0]
|
||||
gpio_params[f"{pin}_label"] = value
|
||||
elif '.GPIO_ModeDefaultEXTI' in key:
|
||||
pin = key.split('.')[0]
|
||||
gpio_params[f"{pin}_exti"] = value
|
||||
for pin in gpio_params:
|
||||
if not pin.endswith('_label') and not pin.endswith('_exti'):
|
||||
label = gpio_params.get(f"{pin}_label", None)
|
||||
exti = gpio_params.get(f"{pin}_exti", None)
|
||||
if label and exti:
|
||||
gpio_list.append({'pin': pin, 'label': label})
|
||||
return gpio_list
|
||||
75
app/tools/code_generator.py
Normal file
75
app/tools/code_generator.py
Normal file
@@ -0,0 +1,75 @@
|
||||
import os
|
||||
import yaml
|
||||
import shutil
|
||||
from typing import Dict, List, Tuple
|
||||
|
||||
class CodeGenerator:
|
||||
"""通用代码生成器"""
|
||||
|
||||
@staticmethod
|
||||
def load_template(template_path: str) -> str:
|
||||
"""加载代码模板"""
|
||||
try:
|
||||
with open(template_path, 'r', encoding='utf-8') as f:
|
||||
return f.read()
|
||||
except Exception as e:
|
||||
print(f"加载模板失败: {template_path}, 错误: {e}")
|
||||
return ""
|
||||
|
||||
@staticmethod
|
||||
def replace_auto_generated(content: str, marker: str, replacement: str) -> str:
|
||||
"""替换自动生成的代码标记"""
|
||||
marker_line = f"/* {marker} */"
|
||||
if marker_line in content:
|
||||
return content.replace(marker_line, replacement)
|
||||
return content
|
||||
|
||||
@staticmethod
|
||||
def save_file(content: str, file_path: str) -> bool:
|
||||
"""保存文件"""
|
||||
try:
|
||||
os.makedirs(os.path.dirname(file_path), exist_ok=True)
|
||||
with open(file_path, 'w', encoding='utf-8') as f:
|
||||
f.write(content)
|
||||
return True
|
||||
except Exception as e:
|
||||
print(f"保存文件失败: {file_path}, 错误: {e}")
|
||||
return False
|
||||
|
||||
@staticmethod
|
||||
def load_config(config_path: str) -> Dict:
|
||||
"""加载配置文件"""
|
||||
if os.path.exists(config_path):
|
||||
try:
|
||||
with open(config_path, 'r', encoding='utf-8') as f:
|
||||
return yaml.safe_load(f) or {}
|
||||
except Exception as e:
|
||||
print(f"加载配置失败: {config_path}, 错误: {e}")
|
||||
return {}
|
||||
|
||||
@staticmethod
|
||||
def save_config(config: Dict, config_path: str) -> bool:
|
||||
"""保存配置文件"""
|
||||
try:
|
||||
os.makedirs(os.path.dirname(config_path), exist_ok=True)
|
||||
with open(config_path, 'w', encoding='utf-8') as f:
|
||||
yaml.safe_dump(config, f, allow_unicode=True, default_flow_style=False)
|
||||
return True
|
||||
except Exception as e:
|
||||
print(f"保存配置失败: {config_path}, 错误: {e}")
|
||||
return False
|
||||
|
||||
@staticmethod
|
||||
def get_template_dir():
|
||||
"""获取模板文件目录"""
|
||||
# 从当前文件向上找到 MRobot 目录,然后定位到模板目录
|
||||
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/bsp")
|
||||
else:
|
||||
# 如果找不到,使用相对路径作为备选
|
||||
return os.path.join(os.path.dirname(os.path.dirname(os.path.dirname(__file__))), "assets/User_code/bsp")
|
||||
Reference in New Issue
Block a user