mirror of
https://github.com/goldenfishs/MRobot.git
synced 2025-07-05 23:04:21 +08:00
Compare commits
No commits in common. "f2fedac360aa29940ee2bac3eb1776918bbd37d2" and "606bd7e05415000e390186589613e482e5e39b31" have entirely different histories.
f2fedac360
...
606bd7e054
1
.gitignore
vendored
1
.gitignore
vendored
@ -32,4 +32,3 @@ Examples/
|
|||||||
build/
|
build/
|
||||||
dist/
|
dist/
|
||||||
*.spec
|
*.spec
|
||||||
*.exe
|
|
@ -1,6 +1,6 @@
|
|||||||
[Setup]
|
[Setup]
|
||||||
AppName=MRobot
|
AppName=MRobot
|
||||||
AppVersion=1.0.1
|
AppVersion=1.0
|
||||||
DefaultDirName={userappdata}\MRobot
|
DefaultDirName={userappdata}\MRobot
|
||||||
DefaultGroupName=MRobot
|
DefaultGroupName=MRobot
|
||||||
OutputDir=.
|
OutputDir=.
|
||||||
|
68
MRobot.py
68
MRobot.py
@ -52,7 +52,7 @@ from qfluentwidgets import (
|
|||||||
from urllib.parse import quote
|
from urllib.parse import quote
|
||||||
|
|
||||||
from packaging.version import parse as vparse
|
from packaging.version import parse as vparse
|
||||||
__version__ = "1.0.1"
|
__version__ = "1.0.0"
|
||||||
|
|
||||||
# ===================== 页面基类 =====================
|
# ===================== 页面基类 =====================
|
||||||
class BaseInterface(QWidget):
|
class BaseInterface(QWidget):
|
||||||
@ -665,7 +665,7 @@ class DataInterface(BaseInterface):
|
|||||||
# 2. 在 /* USER CODE BEGIN RTOS_THREADS */ 区域添加 osThreadNew(Task_Init, NULL, &attr_init);
|
# 2. 在 /* USER CODE BEGIN RTOS_THREADS */ 区域添加 osThreadNew(Task_Init, NULL, &attr_init);
|
||||||
rtos_threads_pattern = r'(\/\* *USER CODE BEGIN RTOS_THREADS *\*\/\s*)(.*?)(\/\* *USER CODE END RTOS_THREADS *\*\/)'
|
rtos_threads_pattern = r'(\/\* *USER CODE BEGIN RTOS_THREADS *\*\/\s*)(.*?)(\/\* *USER CODE END RTOS_THREADS *\*\/)'
|
||||||
match = re.search(rtos_threads_pattern, code, re.DOTALL)
|
match = re.search(rtos_threads_pattern, code, re.DOTALL)
|
||||||
task_line = ' osThreadNew(Task_Init, NULL, &attr_init); // 创建初始化任务\n'
|
task_line = ' initTaskHandle = osThreadNew(Task_Init, NULL, &attr_init); // 创建初始化任务\n'
|
||||||
if match:
|
if match:
|
||||||
threads_code = match.group(2)
|
threads_code = match.group(2)
|
||||||
if 'Task_Init' not in threads_code:
|
if 'Task_Init' not in threads_code:
|
||||||
@ -712,7 +712,7 @@ class DataInterface(BaseInterface):
|
|||||||
)
|
)
|
||||||
|
|
||||||
def open_task_config_dialog(self):
|
def open_task_config_dialog(self):
|
||||||
from PyQt5.QtWidgets import QDialog, QVBoxLayout, QHBoxLayout, QLabel, QLineEdit, QSpinBox, QPushButton, QTableWidget, QTableWidgetItem, QHeaderView, QCheckBox
|
from PyQt5.QtWidgets import QDialog, QVBoxLayout, QHBoxLayout, QLabel, QLineEdit, QSpinBox, QPushButton, QTableWidget, QTableWidgetItem, QHeaderView
|
||||||
import yaml
|
import yaml
|
||||||
import os
|
import os
|
||||||
|
|
||||||
@ -720,16 +720,15 @@ class DataInterface(BaseInterface):
|
|||||||
def __init__(self, parent=None, config_path=None):
|
def __init__(self, parent=None, config_path=None):
|
||||||
super().__init__(parent)
|
super().__init__(parent)
|
||||||
self.setWindowTitle("任务配置")
|
self.setWindowTitle("任务配置")
|
||||||
self.resize(900, 420)
|
self.resize(800, 420)
|
||||||
layout = QVBoxLayout(self)
|
layout = QVBoxLayout(self)
|
||||||
self.table = QTableWidget(0, 6)
|
self.table = QTableWidget(0, 5)
|
||||||
self.table.setHorizontalHeaderLabels(["任务名称", "运行频率", "初始化延迟", "堆栈大小", "任务描述", "频率控制"])
|
self.table.setHorizontalHeaderLabels(["任务名称", "运行频率", "初始化延迟", "堆栈大小", "任务描述"])
|
||||||
self.table.horizontalHeader().setSectionResizeMode(0, QHeaderView.Stretch)
|
self.table.horizontalHeader().setSectionResizeMode(0, QHeaderView.Stretch)
|
||||||
self.table.horizontalHeader().setSectionResizeMode(1, QHeaderView.Stretch)
|
self.table.horizontalHeader().setSectionResizeMode(1, QHeaderView.Stretch)
|
||||||
self.table.horizontalHeader().setSectionResizeMode(2, QHeaderView.Stretch)
|
self.table.horizontalHeader().setSectionResizeMode(2, QHeaderView.Stretch)
|
||||||
self.table.horizontalHeader().setSectionResizeMode(3, QHeaderView.Stretch)
|
self.table.horizontalHeader().setSectionResizeMode(3, QHeaderView.Stretch)
|
||||||
self.table.horizontalHeader().setSectionResizeMode(4, QHeaderView.Stretch)
|
self.table.horizontalHeader().setSectionResizeMode(4, QHeaderView.Stretch)
|
||||||
self.table.horizontalHeader().setSectionResizeMode(5, QHeaderView.ResizeToContents)
|
|
||||||
self.table.setColumnWidth(4, 320) # 任务描述更宽
|
self.table.setColumnWidth(4, 320) # 任务描述更宽
|
||||||
layout.addWidget(self.table)
|
layout.addWidget(self.table)
|
||||||
btn_layout = QHBoxLayout()
|
btn_layout = QHBoxLayout()
|
||||||
@ -749,8 +748,6 @@ class DataInterface(BaseInterface):
|
|||||||
cancel_btn.clicked.connect(self.reject)
|
cancel_btn.clicked.connect(self.reject)
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
# 自动读取配置文件
|
# 自动读取配置文件
|
||||||
if config_path and os.path.exists(config_path):
|
if config_path and os.path.exists(config_path):
|
||||||
try:
|
try:
|
||||||
@ -764,10 +761,6 @@ class DataInterface(BaseInterface):
|
|||||||
item = QTableWidgetItem(str(t.get(key, "")))
|
item = QTableWidgetItem(str(t.get(key, "")))
|
||||||
item.setTextAlignment(Qt.AlignCenter)
|
item.setTextAlignment(Qt.AlignCenter)
|
||||||
self.table.setItem(row, col, item)
|
self.table.setItem(row, col, item)
|
||||||
# 新增频率控制复选框
|
|
||||||
freq_ctrl = QCheckBox()
|
|
||||||
freq_ctrl.setChecked(t.get("freq_control", True))
|
|
||||||
self.table.setCellWidget(row, 5, freq_ctrl)
|
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
pass # 配置文件损坏时忽略
|
pass # 配置文件损坏时忽略
|
||||||
|
|
||||||
@ -781,9 +774,6 @@ class DataInterface(BaseInterface):
|
|||||||
item = QTableWidgetItem(val)
|
item = QTableWidgetItem(val)
|
||||||
item.setTextAlignment(Qt.AlignCenter)
|
item.setTextAlignment(Qt.AlignCenter)
|
||||||
self.table.setItem(row, col, item)
|
self.table.setItem(row, col, item)
|
||||||
freq_ctrl = QCheckBox()
|
|
||||||
freq_ctrl.setChecked(True)
|
|
||||||
self.table.setCellWidget(row, 5, freq_ctrl)
|
|
||||||
|
|
||||||
def del_row(self):
|
def del_row(self):
|
||||||
rows = set([i.row() for i in self.table.selectedItems()])
|
rows = set([i.row() for i in self.table.selectedItems()])
|
||||||
@ -794,31 +784,23 @@ class DataInterface(BaseInterface):
|
|||||||
tasks = []
|
tasks = []
|
||||||
for row in range(self.table.rowCount()):
|
for row in range(self.table.rowCount()):
|
||||||
name = self.table.item(row, 0).text().strip()
|
name = self.table.item(row, 0).text().strip()
|
||||||
freq = self.table.item(row, 1).text()
|
freq = int(self.table.item(row, 1).text())
|
||||||
delay = int(self.table.item(row, 2).text())
|
delay = int(self.table.item(row, 2).text())
|
||||||
stack = int(self.table.item(row, 3).text())
|
stack = int(self.table.item(row, 3).text())
|
||||||
desc = self.table.item(row, 4).text().strip()
|
desc = self.table.item(row, 4).text().strip()
|
||||||
freq_ctrl = self.table.cellWidget(row, 5).isChecked()
|
|
||||||
# 校验 stack 必须为 128*2^n
|
# 校验 stack 必须为 128*2^n
|
||||||
if stack < 128 or (stack & (stack - 1)) != 0 or stack % 128 != 0:
|
if stack < 128 or (stack & (stack - 1)) != 0 or stack % 128 != 0:
|
||||||
raise ValueError(f"第{row+1}行任务“{name}”的堆栈大小必须为128、256、512、1024等(128*2^n)")
|
raise ValueError(f"第{row+1}行任务“{name}”的堆栈大小必须为128、256、512、1024等(128*2^n)")
|
||||||
task = {
|
tasks.append({
|
||||||
"name": name,
|
"name": name,
|
||||||
"function": f"Task_{name}",
|
"function": f"Task_{name}",
|
||||||
|
"frequency": freq,
|
||||||
"delay": delay,
|
"delay": delay,
|
||||||
"stack": stack,
|
"stack": stack,
|
||||||
"description": desc,
|
"description": desc
|
||||||
"freq_control": freq_ctrl
|
})
|
||||||
}
|
|
||||||
if freq_ctrl:
|
|
||||||
task["frequency"] = int(freq)
|
|
||||||
tasks.append(task)
|
|
||||||
return tasks
|
return tasks
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
config_path = os.path.join(self.project_path, "User", "task", "config.yaml")
|
config_path = os.path.join(self.project_path, "User", "task", "config.yaml")
|
||||||
dlg = TaskConfigDialog(self, config_path=config_path)
|
dlg = TaskConfigDialog(self, config_path=config_path)
|
||||||
if dlg.exec() == QDialog.Accepted:
|
if dlg.exec() == QDialog.Accepted:
|
||||||
@ -874,21 +856,19 @@ class DataInterface(BaseInterface):
|
|||||||
init_c_tpl = os.path.join(template_dir, "init.c.template")
|
init_c_tpl = os.path.join(template_dir, "init.c.template")
|
||||||
task_c_tpl = os.path.join(template_dir, "task.c.template")
|
task_c_tpl = os.path.join(template_dir, "task.c.template")
|
||||||
|
|
||||||
# 只统计需要频率控制的任务
|
|
||||||
freq_tasks = [t for t in task_list if t.get("freq_control", True)]
|
|
||||||
|
|
||||||
def render_template(path, context):
|
def render_template(path, context):
|
||||||
with open(path, encoding="utf-8") as f:
|
with open(path, encoding="utf-8") as f:
|
||||||
tpl = Template(f.read())
|
tpl = Template(f.read())
|
||||||
return tpl.render(**context)
|
return tpl.render(**context)
|
||||||
|
|
||||||
|
|
||||||
# 构造模板上下文
|
# 构造模板上下文
|
||||||
context_h = {
|
context_h = {
|
||||||
"thread_definitions": "\n".join([f" osThreadId_t {t['name']};" for t in task_list]),
|
"thread_definitions": "\n".join([f" osThreadId_t {t['name']};" for t in task_list]),
|
||||||
"freq_definitions": "\n".join([f" float {t['name']};" for t in freq_tasks]),
|
"freq_definitions": "\n".join([f" float {t['name']};" for t in task_list]),
|
||||||
"stack_definitions": "\n".join([f" UBaseType_t {t['name']};" for t in task_list]),
|
"stack_definitions": "\n".join([f" UBaseType_t {t['name']};" for t in task_list]),
|
||||||
"last_up_time_definitions": "\n".join([f" float {t['name']};" for t in freq_tasks]),
|
"last_up_time_definitions": "\n".join([f" float {t['name']};" for t in task_list]),
|
||||||
"task_frequency_definitions": "\n".join([f"#define {t['name'].upper()}_FREQ ({t['frequency']})" for t in freq_tasks]),
|
"task_frequency_definitions": "\n".join([f"#define {t['name'].upper()}_FREQ ({t['frequency']})" for t in task_list]),
|
||||||
"task_init_delay_definitions": "\n".join([f"#define {t['name'].upper()}_INIT_DELAY ({t['delay']})" for t in task_list]),
|
"task_init_delay_definitions": "\n".join([f"#define {t['name'].upper()}_INIT_DELAY ({t['delay']})" for t in task_list]),
|
||||||
"task_attr_declarations": "\n".join([f"extern const osThreadAttr_t attr_{t['name']};" for t in task_list]),
|
"task_attr_declarations": "\n".join([f"extern const osThreadAttr_t attr_{t['name']};" for t in task_list]),
|
||||||
"task_function_declarations": "\n".join([f"void {t['function']}(void *argument);" for t in task_list]),
|
"task_function_declarations": "\n".join([f"void {t['function']}(void *argument);" for t in task_list]),
|
||||||
@ -989,16 +969,17 @@ class DataInterface(BaseInterface):
|
|||||||
f.write(init_c)
|
f.write(init_c)
|
||||||
|
|
||||||
# ----------- 生成 task.c -----------
|
# ----------- 生成 task.c -----------
|
||||||
|
task_c_tpl = os.path.join(template_dir, "task.c.template")
|
||||||
for t in task_list:
|
for t in task_list:
|
||||||
|
# 自动换行任务描述
|
||||||
desc = t.get("description", "")
|
desc = t.get("description", "")
|
||||||
desc_wrapped = "\n ".join(textwrap.wrap(desc, 20))
|
desc_wrapped = "\n ".join(textwrap.wrap(desc, 20))
|
||||||
context_task = {
|
context_task = {
|
||||||
"task_name": t["name"],
|
"task_name": t["name"],
|
||||||
"task_function": t["function"],
|
"task_function": t["function"],
|
||||||
"task_frequency": f"{t['name'].upper()}_FREQ" if t.get("freq_control", True) else None,
|
"task_frequency": f"{t['name'].upper()}_FREQ", # 使用宏定义
|
||||||
"task_delay": f"{t['name'].upper()}_INIT_DELAY",
|
"task_delay": f"{t['name'].upper()}_INIT_DELAY", # 使用宏定义
|
||||||
"task_description": desc_wrapped,
|
"task_description": desc_wrapped
|
||||||
"freq_control": t.get("freq_control", True)
|
|
||||||
}
|
}
|
||||||
# 渲染模板
|
# 渲染模板
|
||||||
with open(task_c_tpl, encoding="utf-8") as f:
|
with open(task_c_tpl, encoding="utf-8") as f:
|
||||||
@ -1009,6 +990,7 @@ class DataInterface(BaseInterface):
|
|||||||
if os.path.exists(task_c_path):
|
if os.path.exists(task_c_path):
|
||||||
with open(task_c_path, "r", encoding="utf-8") as f:
|
with open(task_c_path, "r", encoding="utf-8") as f:
|
||||||
old_code = f.read()
|
old_code = f.read()
|
||||||
|
# 只保留USER区域
|
||||||
def preserve_user_region(new_code, old_code, region_name):
|
def preserve_user_region(new_code, old_code, region_name):
|
||||||
pattern = re.compile(
|
pattern = re.compile(
|
||||||
rf"/\*\s*{region_name}\s*BEGIN\s*\*/(.*?)/\*\s*{region_name}\s*END\s*\*/",
|
rf"/\*\s*{region_name}\s*BEGIN\s*\*/(.*?)/\*\s*{region_name}\s*END\s*\*/",
|
||||||
@ -1025,11 +1007,11 @@ class DataInterface(BaseInterface):
|
|||||||
code = preserve_user_region(code, old_code, region)
|
code = preserve_user_region(code, old_code, region)
|
||||||
with open(task_c_path, "w", encoding="utf-8") as f:
|
with open(task_c_path, "w", encoding="utf-8") as f:
|
||||||
f.write(code)
|
f.write(code)
|
||||||
|
|
||||||
# ----------- 保存任务配置到 config.yaml -----------
|
# ----------- 保存任务配置到 config.yaml -----------
|
||||||
config_yaml_path = os.path.join(output_dir, "config.yaml")
|
config_path = os.path.join(output_dir, "config.yaml")
|
||||||
with open(config_yaml_path, "w", encoding="utf-8") as f:
|
with open(config_path, "w", encoding="utf-8") as f:
|
||||||
yaml.safe_dump(task_list, f, allow_unicode=True)
|
yaml.dump(task_list, f, allow_unicode=True)
|
||||||
|
|
||||||
# ===================== 串口终端界面 =====================
|
# ===================== 串口终端界面 =====================
|
||||||
class SerialReadThread(QThread):
|
class SerialReadThread(QThread):
|
||||||
data_received = pyqtSignal(str)
|
data_received = pyqtSignal(str)
|
||||||
|
@ -40,3 +40,4 @@ void Task_Init(void *argument) {
|
|||||||
osKernelUnlock(); // 解锁内核
|
osKernelUnlock(); // 解锁内核
|
||||||
osThreadTerminate(osThreadGetId()); // 任务完成后结束自身
|
osThreadTerminate(osThreadGetId()); // 任务完成后结束自身
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -22,16 +22,13 @@
|
|||||||
void {{task_function}}(void *argument) {
|
void {{task_function}}(void *argument) {
|
||||||
(void)argument; /* 未使用argument,消除警告 */
|
(void)argument; /* 未使用argument,消除警告 */
|
||||||
|
|
||||||
{% if freq_control %}
|
|
||||||
/* 计算任务运行到指定频率需要等待的tick数 */
|
/* 计算任务运行到指定频率需要等待的tick数 */
|
||||||
const uint32_t delay_tick = osKernelGetTickFreq() / {{task_frequency}};
|
const uint32_t delay_tick = osKernelGetTickFreq() / {{task_frequency}};
|
||||||
|
|
||||||
osDelay({{task_delay}}); /* 延时一段时间再开启任务 */
|
osDelay({{task_delay}}); /* 延时一段时间再开启任务 */
|
||||||
|
|
||||||
/* USER CODE INIT BEGIN */
|
/* USER CODE INIT BEGIN */
|
||||||
|
|
||||||
/* USER CODE INIT END */
|
/* USER CODE INIT END */
|
||||||
|
|
||||||
uint32_t tick = osKernelGetTickCount(); /* 控制任务运行频率的计时 */
|
uint32_t tick = osKernelGetTickCount(); /* 控制任务运行频率的计时 */
|
||||||
while (1) {
|
while (1) {
|
||||||
tick += delay_tick; /* 计算下一个唤醒时刻 */
|
tick += delay_tick; /* 计算下一个唤醒时刻 */
|
||||||
@ -40,17 +37,4 @@ void {{task_function}}(void *argument) {
|
|||||||
/* USER CODE END */
|
/* USER CODE END */
|
||||||
osDelayUntil(tick); /* 运行结束,等待下一次唤醒 */
|
osDelayUntil(tick); /* 运行结束,等待下一次唤醒 */
|
||||||
}
|
}
|
||||||
{% else %}
|
|
||||||
osDelay({{task_delay}}); /* 延时一段时间再开启任务 */
|
|
||||||
|
|
||||||
/* USER CODE INIT BEGIN*/
|
|
||||||
|
|
||||||
/* USER CODE INIT END*/
|
|
||||||
|
|
||||||
while (1) {
|
|
||||||
/* USER CODE BEGIN */
|
|
||||||
|
|
||||||
/* USER CODE END */
|
|
||||||
}
|
|
||||||
{% endif %}
|
|
||||||
}
|
}
|
||||||
|
@ -10,4 +10,3 @@ const osThreadAttr_t attr_init = {
|
|||||||
|
|
||||||
/* User_task */
|
/* User_task */
|
||||||
{{task_attr_definitions}}
|
{{task_attr_definitions}}
|
||||||
|
|
||||||
|
@ -78,4 +78,3 @@ void Task_Init(void *argument);
|
|||||||
#ifdef __cplusplus
|
#ifdef __cplusplus
|
||||||
}
|
}
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
|
Loading…
Reference in New Issue
Block a user