main
COT001\DEV 2026-04-20 16:06:47 +08:00
parent d385e770d1
commit 97dee8326b
14 changed files with 347 additions and 311 deletions

View File

@ -0,0 +1,10 @@
{
"permissions": {
"allow": [
"Bash(python3 -c ' *)",
"Bash(sqlite3 data.db \"PRAGMA table_info\\(task_instruction\\);\")",
"Bash(venv/Scripts/python.exe -c ' *)",
"Bash(venv/Scripts/python.exe -c \"import py_compile; py_compile.compile\\('taskForm.py', doraise=True\\)\")"
]
}
}

View File

@ -915,7 +915,36 @@ class Common(QObject):
# export_table_structure_and_data('data.db', 'task_group')
# export_table_structure_and_data('data.db', 'task_instruction')
# 导入表结构和数据
def migrate_sql_to_database(self, db_path, table_name):
import stat
try:
os.chmod(db_path, stat.S_IRUSR | stat.S_IWUSR | stat.S_IRGRP | stat.S_IROTH)
except Exception:
pass
sql_file_path = f"{table_name}.sql"
try:
with open(sql_file_path, 'r', encoding='utf-8') as file:
sql_script = file.read()
except FileNotFoundError:
return
conn = sqlite3.connect(db_path)
for stmt in sql_script.split(';'):
stmt = stmt.strip()
if not stmt:
continue
try:
conn.execute(stmt)
except sqlite3.Error:
pass
conn.commit()
conn.close()
def import_sql_to_database(self,db_path, table_bame):
import stat
try:
os.chmod(db_path, stat.S_IRUSR | stat.S_IWUSR | stat.S_IRGRP | stat.S_IROTH)
except Exception:
pass
# 连接到SQLite数据库
conn = sqlite3.connect(db_path)
cursor = conn.cursor()

View File

@ -704,11 +704,15 @@ class DevModelForm(QWidget):
self.ui.textEditDescription.clear()
groups = dmGroupManager.getInfo()
self.dmGroupModelBlockSignals = True
self.dmGroupModel.blockSignals(True)
self.ui.tableViewDmGroup.setUpdatesEnabled(False)
if len(groups) > 0:
for group in groups:
index = self.dmGroupModel.rowCount()
self.dmGroupModel.setItem(index, 0, QStandardItem(str(group["id"])))
self.dmGroupModel.setItem(index, 1, QStandardItem(group["name"]))
self.dmGroupModel.blockSignals(False)
self.ui.tableViewDmGroup.setUpdatesEnabled(True)
self.dmGroupModelBlockSignals = False
self.batchOperation()
@ -718,12 +722,16 @@ class DevModelForm(QWidget):
self.devModelModel.clear()
self.ui.textEditDescription.clear()
if len(devModels) > 0:
self.devModelModel.blockSignals(True)
self.ui.tableViewModel.setUpdatesEnabled(False)
for data in devModels:
index = self.devModelModel.rowCount()
self.devModelModel.setItem(index, 0, QStandardItem(str(data["id"])))
self.devModelModel.setItem(index, 1, QStandardItem(str(index+1)))
self.devModelModel.setItem(index, 2, QStandardItem(data["name"]))
self.devModelModel.setItem(index, 3, QStandardItem(data["category"]))
self.devModelModel.blockSignals(False)
self.ui.tableViewModel.setUpdatesEnabled(True)
self.ui.tableViewModel.sortByColumn(2, Qt.SortOrder.AscendingOrder)
self.batchOperation()
@ -844,12 +852,13 @@ class DevModelForm(QWidget):
self.batchOperation()
# 加载指令
def loadInstruction(self,devModelId):
def loadInstruction(self,devModelId):
try:
result = instructionManager.getDevmodelInstructions(devModelId)
instructions = instructionManager.getDevmodelInstructions(devModelId)
self.instructionModel.clear()
self.ui.textEditDescription.setText("")
instructions = result
self.ui.textEditDescription.setText("")
self.instructionModel.blockSignals(True)
self.ui.tableViewCmd.setUpdatesEnabled(False)
for data in instructions:
index = self.instructionModel.rowCount()
self.instructionModel.setItem(index, 0, QStandardItem(str(data["id"])))
@ -866,6 +875,8 @@ class DevModelForm(QWidget):
if "description" not in attr:
attr["description"] = ""
self.instructionModel.setItem(index, 5, QStandardItem(attr["description"]))
self.instructionModel.blockSignals(False)
self.ui.tableViewCmd.setUpdatesEnabled(True)
self.ui.tableViewCmd.sortByColumn(2, Qt.SortOrder.AscendingOrder)
self.batchOperation()
except Exception as e:

View File

@ -423,11 +423,13 @@ class DeviceForm(QWidget):
self.updateDevBtnState()
# 加载设备
def loadDevice(self):
def loadDevice(self):
devices = deviceManager.getGroupDevices(self.current_groupId)
self.deviceModel.clear()
self.ui.textEditDescription.clear()
if len(devices) > 0:
self.deviceModel.blockSignals(True)
self.ui.tableViewDevice.setUpdatesEnabled(False)
for device in devices:
index = self.deviceModel.rowCount()
self.deviceModel.setItem(index, 0, QxStandardItem(str(device["id"])))
@ -437,7 +439,9 @@ class DeviceForm(QWidget):
devModel = devModelManager.getInfo(device["dev_model_id"])
if devModel != None:
self.deviceModel.setItem(index, 4, QxStandardItem(devModel["name"], max_length=8))
self.ui.tableViewDevice.sortByColumn(3, Qt.SortOrder.AscendingOrder)
self.deviceModel.blockSignals(False)
self.ui.tableViewDevice.setUpdatesEnabled(True)
self.ui.tableViewDevice.sortByColumn(3, Qt.SortOrder.AscendingOrder)
self.batchOperation()
# 任务分组选中
@ -457,11 +461,15 @@ class DeviceForm(QWidget):
self.ui.textEditDescription.clear()
groups = devGroupManager.getInfo()
self.devGroupModelBlockSignals = True
self.devGroupModel.blockSignals(True)
self.ui.tableViewDevGroup.setUpdatesEnabled(False)
if len(groups) > 0:
for group in groups:
index = self.devGroupModel.rowCount()
self.devGroupModel.setItem(index, 0, QStandardItem(str(group["id"])))
self.devGroupModel.setItem(index, 1, QStandardItem(group["name"]))
self.devGroupModel.blockSignals(False)
self.ui.tableViewDevGroup.setUpdatesEnabled(True)
self.devGroupModelBlockSignals = False
self.batchOperation()
@ -741,20 +749,22 @@ class DeviceForm(QWidget):
def loadDevInstruction(self,devModelId):
instructions = instructionManager.getDevmodelInstructions(devModelId)
self.devInstructionModel.clear()
self.devInstructionModel.blockSignals(True)
self.ui.tableViewDevCmd.setUpdatesEnabled(False)
if instructions:
self.ui.tableViewDevCmd.setItemDelegateForColumn(5, ProgressBarDelegate())
for data in instructions:
index = self.devInstructionModel.rowCount()
self.devInstructionModel.setItem(index, 0, QxStandardItem(str(data["id"])))
self.devInstructionModel.setItem(index, 1, QxStandardItem(str(index+1)))
self.devInstructionModel.setItem(index, 2, QxStandardItem(data["name"]))
attr = data["attr"]
if data["type"] is not None and data["type"] in self.supportedTypes:
self.devInstructionModel.setItem(index, 3, QxStandardItem(attr[self.supportedTypes[data["type"]]["digestAttr"]]))
else:
self.devInstructionModel.setItem(index, 3, QxStandardItem(""))
self.devInstructionModel.setItem(index, 4, QxStandardItem(attr["remark"]))
progressItem = QxProgressStandardItem('',self)
# 从全局缓存恢复进度值(如果有)
instruction_id = str(data["id"])
if instruction_id in self.instructionProgressCache:
cache = self.instructionProgressCache[instruction_id]
@ -762,11 +772,11 @@ class DeviceForm(QWidget):
else:
progressItem.setData({'instruction_id': data["id"], 'name': data["name"]}, Qt.ItemDataRole.UserRole)
self.devInstructionModel.setItem(index, 5, progressItem)
delegate = ProgressBarDelegate()
self.ui.tableViewDevCmd.setItemDelegateForColumn(5, delegate)
if "description" not in attr:
attr["description"] = ""
self.devInstructionModel.setItem(index, 6, QStandardItem(attr["description"]))
self.devInstructionModel.blockSignals(False)
self.ui.tableViewDevCmd.setUpdatesEnabled(True)
self.ui.tableViewDevCmd.sortByColumn(2, Qt.SortOrder.AscendingOrder)
self.batchOperation()

View File

@ -1,6 +1,7 @@
#!/opt/homebrew/bin/python3
# -*- coding:utf-8 -*-
import json
import threading
from PyQt6 import *
from PyQt6.QtCore import *
from logs import log
@ -28,14 +29,16 @@ class InfluxdbManager(QObject):
confData['influxdbName'] = influxdbName
for _influxdb in influxdbs:
if _influxdb["name"] == influxdbName:
influxdb.close()
influxdb.setConfig( _influxdb )
influxdb.close()
influxdb.setConfig(_influxdb)
config.update(confData)
projectInfo = projectManager.getCurrentProInfo()
proname = "name" in projectInfo and projectInfo["name"] or ""
def _open():
influxdb.open()
projectInfo = projectManager.getCurrentProInfo()
proname = "name" in projectInfo and projectInfo["name"] or ""
influxdb.create(proname)
config.update(confData)
return True
threading.Thread(target=_open, daemon=True).start()
return True
return False
@pyqtSlot(str, result=QVariant)

View File

@ -144,42 +144,32 @@ class LogForm(QWidget):
self.ui.plainTextEdit.appendPlainText(text)
def setLogPlainText(self):
"""增量更新:只追加新增的日志,不全量重绘"""
try:
current_id = self._getCurrentId()
if current_id not in self.logThread.msgDataMap:
return
new_count = self.logThread.newCountMap.get(current_id, 0)
if new_count <= 0:
return
self.logThread.newCountMap[current_id] = 0
data_list = self.logThread.msgDataMap[current_id]
total = len(data_list)
# 如果数据被截断(超过 maxCount需要全量重绘
if self.lastRenderedCount > total:
# 超过 maxCount 发生了截断,或首次渲染,全量重绘
if self.lastRenderedCount >= total or self.lastRenderedCount + new_count > total:
self.ui.plainTextEdit.clear()
htmlText = "".join(data_list)
self.ui.plainTextEdit.appendHtml(htmlText)
self.ui.plainTextEdit.appendHtml("".join(data_list))
self.lastRenderedCount = total
else:
# 只追加新增部分
new_items = data_list[self.lastRenderedCount:]
if new_items:
newHtml = "".join(new_items)
# 使用 moveCursor 追加到末尾,避免 clear
cursor = self.ui.plainTextEdit.textCursor()
cursor.movePosition(QTextCursor.MoveOperation.End)
cursor.insertHtml(newHtml)
cursor.insertHtml("".join(new_items))
self.lastRenderedCount = total
# 重置新增计数
self.logThread.newCountMap[current_id] = 0
self.scrollToBottom()
except Exception as e:
log.error(f"setLogPlainText exception: {e}")

View File

@ -84,9 +84,21 @@ class MainWindow(QMainWindow):
def closeEvent(self, event):
running = [a for a in taskActuatorManager.taskActuatorDict.values() if a.isRunning()]
if running:
reply = QMessageBox.question(
self, "确认退出",
f"当前有 {len(running)} 个任务正在运行,是否停止所有任务并退出?",
QMessageBox.StandardButton.Yes | QMessageBox.StandardButton.No,
QMessageBox.StandardButton.No
)
if reply != QMessageBox.StandardButton.Yes:
event.ignore()
return
taskActuatorManager.stopAll()
self.windowClose.emit()
self._exit()
event.accept() # 接受关闭事件
event.accept()
def initWindow(self):
self.ui = Ui_MainWindow()

View File

@ -75,7 +75,7 @@ class TaskInstruction(Base):
delay = Column(String, nullable=True, doc='延时')
target_type = Column(String, nullable=False, doc='目标类型')
target_id = Column(String, nullable=False, doc='目标id')
target_param = Column(Integer, nullable=False, doc='目标id')
target_param = Column(String, nullable=True, doc='目标参数')
interface_index = Column(Integer, nullable=True, doc='设备接口序号')
level = Column(Integer, nullable=True, doc='层级')

View File

@ -171,8 +171,10 @@ class ProjectForm(QWidget):
self.taskDetailModel.clear()
tasks = taskManager.getInfos(self.targetIds)
self.taskModelBlockSignals = True
self.taskModel.blockSignals(True)
self.ui.tableViewTask.setUpdatesEnabled(False)
if len(tasks) > 0:
self.ui.tableViewTask.setItemDelegateForColumn(5, ProgressBarDelegate())
for task in tasks:
index = self.taskModel.rowCount()
task_id = str(task["id"])
@ -180,7 +182,7 @@ class ProjectForm(QWidget):
if self.targetParams:
param = task_id in self.targetParams and self.targetParams[task_id] or ""
progressItem = QxStandardItem()
progressItem.setData({'task_instruction_id': task["id"], 'name': task["name"]}, Qt.ItemDataRole.UserRole)
progressItem.setData({'task_instruction_id': task["id"], 'name': task["name"]}, Qt.ItemDataRole.UserRole)
self.taskModel.setItem(index, 0, QStandardItem(str(task["id"])))
self.taskModel.setItem(index, 1, QStandardItem(task["name"]))
self.taskModel.setItem(index, 2, QStandardItem(str(task["loop"])))
@ -192,8 +194,8 @@ class ProjectForm(QWidget):
self.setUnEditableStyle(self.taskModel.item(index, 2))
self.setUnEditableStyle(self.taskModel.item(index, 3))
self.setUnEditableStyle(self.taskModel.item(index, 5))
delegate = ProgressBarDelegate()
self.ui.tableViewTask.setItemDelegateForColumn(5, delegate)
self.taskModel.blockSignals(False)
self.ui.tableViewTask.setUpdatesEnabled(True)
self.taskModelBlockSignals = False
self.batchOperation()
@ -266,14 +268,17 @@ class ProjectForm(QWidget):
def loadTaskDetail(self, taskId):
self.setItemsEnable(False)
self.taskDetailModelBlockSignals = True
self.taskDetailModel.blockSignals(True)
self.ui.tableViewTaskDetail.setUpdatesEnabled(False)
self.taskDetailModel.clear()
self.loadPageTaskDetail(taskId, 0, 50)
self.taskDetailModel.blockSignals(False)
self.ui.tableViewTaskDetail.setUpdatesEnabled(True)
self.taskDetailModelBlockSignals = False
self.setItemsEnable(True)
self.batchOperation()
def loadPageTaskDetail(self, taskId, row, limit):
QCoreApplication.processEvents()
if taskId:
taskInstructions = taskInstructionManager.getPageInfo(taskId,row, limit)
if len(taskInstructions) > 0:
@ -666,6 +671,8 @@ class ProjectForm(QWidget):
if len(infos) == 0:
projectManager.setCurrentProId(common.emputyProId)
return
self.projectModel.blockSignals(True)
self.ui.tableViewPro.setUpdatesEnabled(False)
for info in infos:
index = self.projectModel.rowCount()
self.projectModel.setItem(index, 0, QStandardItem(infos[info]["id"]))
@ -676,15 +683,16 @@ class ProjectForm(QWidget):
" subcontrol-position: center;"
"}")
radio.toggled.connect(self.proCheckedChanged)
self.projectModel.setItem(index, 2 , QStandardItem())
self.projectModel.setItem(index, 2, QStandardItem())
if index == self.currentProjectIndex:
self.ui.tableViewPro.selectRow(index)
radio.setChecked(True)
projectManager.setCurrentProId(infos[info]["id"])
else:
radio.setChecked(False)
self.ui.tableViewPro.setIndexWidget(self.projectModel.index(index, 2), radio)
self.projectModel.blockSignals(False)
self.ui.tableViewPro.setUpdatesEnabled(True)
self.ui.tableViewPro.selectRow(self.currentProjectIndex)
self.batchOperation()
except Exception as e:
log.error(e)

View File

@ -366,10 +366,7 @@ Item {
height: parent.height
model:serialPorts
enabled: editCtrl.checked
anchors.right: parent.right
onActivated: {
updateInterface(rowIndex)
}
anchors.right: parent.right
}
MouseArea{
anchors.fill: parent
@ -400,12 +397,6 @@ Item {
enabled: editCtrl.checked
model:serialBuadRates
anchors.right: parent.right
onActivated: {
updateInterface(rowIndex)
}
onEditTextChanged:{
updateInterface(rowIndex)
}
}
}
Item {
@ -426,9 +417,6 @@ Item {
enabled: editCtrl.checked
model:serialByteSize
anchors.right: parent.right
onActivated: {
updateInterface(rowIndex)
}
}
}
Item {
@ -449,10 +437,6 @@ Item {
enabled: editCtrl.checked
model:serialStopBits
anchors.right: parent.right
onActivated: {
updateInterface(rowIndex)
}
}
}
Item {
@ -473,9 +457,6 @@ Item {
enabled: editCtrl.checked
model:serialParity
anchors.right: parent.right
onActivated: {
updateInterface(rowIndex)
}
}
}
Item {
@ -495,9 +476,6 @@ Item {
model:tcpRole
enabled: editCtrl.checked
anchors.right: parent.right
onActivated: {
updateInterface(rowIndex)
}
}
}
Item {

View File

@ -704,84 +704,87 @@ class TaskForm(QWidget):
def loadTaskDetail(self, taskId):
self.setItemsEnable(False)
self.taskDetailModelBlockSignals = True
self.taskDetailModel.blockSignals(True)
self.taskDetailModel.clear()
self.ui.textEditDescription.clear()
self.clearTreeModel()
self.loadPageTaskDetail(taskId, 0, 50)
self.taskDetailModel.blockSignals(False)
self.taskDetailModelBlockSignals = False
self.loadPageTaskDetail(taskId, 0, 50)
self.setItemsEnable(True)
self.batchOperation()
def loadPageTaskDetail(self, taskId, row, limit):
QCoreApplication.processEvents()
if taskId:
taskInstructions = taskInstructionManager.getPageInfo(taskId,row, limit)
if len(taskInstructions) > 0:
for taskInstruction in taskInstructions:
index = self.taskDetailModel.rowCount()
self.taskDetailModel.setItem(index, 0, QStandardItem(str(taskInstruction["id"])))
self.taskDetailModel.setItem(index, 1, QStandardItem(str(taskInstruction["task_id"])))
name = ""
type = ""
devicename = ""
dev_model_id = ""
devinterface = []
if taskInstruction["target_type"] == "instruction":
instruction = instructionManager.getInfo(str(taskInstruction["target_id"]))
if instruction:
name = instruction["name"]
type = instruction["type"]
if instruction["type"] is not None and instruction["type"] in self.supportedTypes:
type = self.supportedTypes[instruction["type"]]["name"]
device_id = taskInstruction["device_id"]
device = deviceManager.getInfo(device_id)
if device != None:
devicename = device["name"]
dev_model_id = device["dev_model_id"]
devinterface = device["interface"]
elif taskInstruction["target_type"] == "task":
task = taskManager.getInfo(taskInstruction["target_id"])
if task != None:
devicename = "任务"
name = task["name"]
self.taskDetailModel.setItem(index, 2, QStandardItem(str(index+1)))
self.taskDetailModel.setItem(index, 3, QStandardItem(devicename))
self.taskDetailModel.setItem(index, 4, QStandardItem(name))
self.taskDetailModel.setItem(index, 5, QStandardItem(type))
self.taskDetailModel.setItem(index, 6, QStandardItem(str(taskInstruction["delay"])))
self.taskDetailModel.setItem(index, 7, QStandardItem(str(taskInstruction["loop"])))
self.taskDetailModel.setItem(index, 8, QStandardItem(taskInstruction["target_param"]))
self.taskDetailModel.setItem(index, 9, QStandardItem(""))
self.taskDetailModel.setItem(index, 10, QStandardItem(str(taskInstruction["level"])))
self.taskDetailModel.setItem(index, 11, QStandardItem(str(taskInstruction["target_id"])))
self.setUnEditableStyle(self.taskDetailModel.item(index, 2))
self.setDeviceStyle(self.taskDetailModel.item(index, 2), devicename)
self.setUnEditableStyle(self.taskDetailModel.item(index, 3))
self.setUnEditableStyle(self.taskDetailModel.item(index, 4))
self.setUnEditableStyle(self.taskDetailModel.item(index, 5))
devModel = devModelManager.getInfo(dev_model_id)
if devModel != None:
attr_json_dict = devModel["attr"]
devmodelInterfacetypes = {}
if "interfaceType" in attr_json_dict:
devmodelInterfacetypes = attr_json_dict["interfaceType"]
if len(devmodelInterfacetypes) > 0:
cmb = NoWheelComboBox()
cmb.currentIndexChanged.connect(self.changeInterface)
for devmodelInterfacetype in devmodelInterfacetypes:
cmb.addItem(devmodelInterfacetype)
interface_index = "interface_index" in taskInstruction and taskInstruction["interface_index"] or 0
interface_index = interface_index != "" and interface_index or 0
if interface_index < len(devmodelInterfacetypes):
cmb.setCurrentIndex(interface_index)
self.taskDetailModel.setItem(index, 9, QStandardItem(str(interface_index)))
else:
if len(devmodelInterfacetypes) > 0:
cmb.setCurrentIndex(0)
taskInstructionManager.update(str(taskInstruction["id"]), {"interface_index":0}) # 更新
self.taskDetailModel.setItem(index, 9, QStandardItem("0"))
self.ui.tableViewTaskDetail.setIndexWidget(self.taskDetailModel.index(index, 9), cmb)
try:
index = self.taskDetailModel.rowCount()
self.taskDetailModel.setItem(index, 0, QStandardItem(str(taskInstruction["id"])))
self.taskDetailModel.setItem(index, 1, QStandardItem(str(taskInstruction["task_id"])))
name = ""
type = ""
devicename = ""
dev_model_id = ""
devinterface = []
if taskInstruction["target_type"] == "instruction":
instruction = instructionManager.getInfo(str(taskInstruction["target_id"]))
if instruction:
name = instruction["name"]
type = instruction["type"]
if instruction["type"] is not None and instruction["type"] in self.supportedTypes:
type = self.supportedTypes[instruction["type"]]["name"]
device_id = taskInstruction["device_id"]
device = deviceManager.getInfo(device_id)
if device != None:
devicename = device["name"]
dev_model_id = device["dev_model_id"]
devinterface = device["interface"]
elif taskInstruction["target_type"] == "task":
task = taskManager.getInfo(taskInstruction["target_id"])
if task != None:
devicename = "任务"
name = task["name"]
self.taskDetailModel.setItem(index, 2, QStandardItem(str(index+1)))
self.taskDetailModel.setItem(index, 3, QStandardItem(devicename))
self.taskDetailModel.setItem(index, 4, QStandardItem(name))
self.taskDetailModel.setItem(index, 5, QStandardItem(type))
self.taskDetailModel.setItem(index, 6, QStandardItem(str(taskInstruction["delay"])))
self.taskDetailModel.setItem(index, 7, QStandardItem(str(taskInstruction["loop"])))
self.taskDetailModel.setItem(index, 8, QStandardItem(str(taskInstruction["target_param"] or "")))
self.taskDetailModel.setItem(index, 9, QStandardItem(""))
self.taskDetailModel.setItem(index, 10, QStandardItem(str(taskInstruction["level"])))
self.taskDetailModel.setItem(index, 11, QStandardItem(str(taskInstruction["target_id"])))
self.setUnEditableStyle(self.taskDetailModel.item(index, 2))
self.setDeviceStyle(self.taskDetailModel.item(index, 2), devicename)
self.setUnEditableStyle(self.taskDetailModel.item(index, 3))
self.setUnEditableStyle(self.taskDetailModel.item(index, 4))
self.setUnEditableStyle(self.taskDetailModel.item(index, 5))
devModel = devModelManager.getInfo(dev_model_id)
if devModel != None:
attr_json_dict = devModel["attr"]
devmodelInterfacetypes = {}
if "interfaceType" in attr_json_dict:
devmodelInterfacetypes = attr_json_dict["interfaceType"]
if len(devmodelInterfacetypes) > 0:
cmb = NoWheelComboBox()
cmb.currentIndexChanged.connect(self.changeInterface)
for devmodelInterfacetype in devmodelInterfacetypes:
cmb.addItem(devmodelInterfacetype)
interface_index = "interface_index" in taskInstruction and taskInstruction["interface_index"] or 0
interface_index = interface_index != "" and interface_index or 0
if interface_index < len(devmodelInterfacetypes):
cmb.setCurrentIndex(interface_index)
self.taskDetailModel.setItem(index, 9, QStandardItem(str(interface_index)))
else:
if len(devmodelInterfacetypes) > 0:
cmb.setCurrentIndex(0)
taskInstructionManager.update(str(taskInstruction["id"]), {"interface_index":0})
self.taskDetailModel.setItem(index, 9, QStandardItem("0"))
self.ui.tableViewTaskDetail.setIndexWidget(self.taskDetailModel.index(index, 9), cmb)
except Exception as e:
log.error(f"loadPageTaskDetail error: {e} | data: {taskInstruction}")
self.batchOperation()
self.loadPageTaskDetail(taskId, row + limit,limit)
else:
@ -906,34 +909,34 @@ class TaskForm(QWidget):
self.clearTreeModel()
tasks = taskManager.getGroupTask(self.current_groupId)
self.taskModelBlockSignals = True
self.taskModel.blockSignals(True)
self.ui.tableViewTask.setUpdatesEnabled(False)
if len(tasks) > 0:
self.ui.tableViewTask.setItemDelegateForColumn(6, ProgressBarDelegate())
for task in tasks:
index = self.taskModel.rowCount()
progressItem = QxStandardItem('',self)
# 从全局缓存恢复进度值(如果有)
task_id = str(task["id"])
if task_id in self.taskProgressCache:
cache = self.taskProgressCache[task_id]
progressItem.setData({'task_instruction_id': task["id"], 'name': task["name"], 'value': cache['value'], 'maximum': cache['maximum']}, Qt.ItemDataRole.UserRole)
else:
progressItem.setData({'task_instruction_id': task["id"], 'name': task["name"]}, Qt.ItemDataRole.UserRole)
self.taskModel.setItem(index, 0, QStandardItem(str(task["id"])))
self.taskModel.setItem(index, 1, QStandardItem(str(index+1)))
self.taskModel.setItem(index, 2, QStandardItem(task["name"]))
self.taskModel.setItem(index, 3, QStandardItem(str(task["loop"])))
self.taskModel.setItem(index, 4, QStandardItem(str(task["delay"])))
self.taskModel.setItem(index, 5, QStandardItem(task["remark"]))
self.taskModel.setItem(index, 6, progressItem)
self.taskModel.setItem(index, 7, QStandardItem(str(task["group_id"])))
self.taskModel.setItem(index, 8, QStandardItem("normal"))
row = [
QStandardItem(str(task["id"])),
QStandardItem(str(index+1)),
QStandardItem(task["name"]),
QStandardItem(str(task["loop"])),
QStandardItem(str(task["delay"])),
QStandardItem(task["remark"]),
progressItem,
QStandardItem(str(task["group_id"])),
QStandardItem("parent" if task_id in self.parentTasks else "child" if task_id in self.subTasks else "normal"),
]
self.taskModel.appendRow(row)
self.setUnEditableStyle(self.taskModel.item(index, 1))
delegate = ProgressBarDelegate()
self.ui.tableViewTask.setItemDelegateForColumn(6, delegate)
if task_id in self.parentTasks:
self.taskModel.setItem(index, 8, QStandardItem("parent"))
if task_id in self.subTasks:
self.taskModel.setItem(index, 8, QStandardItem("child"))
self.taskModel.blockSignals(False)
self.ui.tableViewTask.setUpdatesEnabled(True)
self.taskModelBlockSignals = False
self.batchOperation()

View File

@ -57,6 +57,8 @@ class TaskActuator(QThread):
self.updateDetails.emit(taskId, current, total)
def run(self):
proId = None
operator = None
try:
tracemalloc.start()
loopStartTime = 0
@ -93,9 +95,6 @@ class TaskActuator(QThread):
self.currentTask.start()
while not self.currentTask.isFinished and self.running:
if self.running == False:
break
self.currentTask.callBack(0)
self.currentIndex = 0
self.updateProgress.emit(self.id, 0, total)
@ -123,62 +122,18 @@ class TaskActuator(QThread):
if target_type == "instruction":
self.updateDetails.emit(str(self.parentId) + task_instruction_id, 0, target_loop)
for j in range(target_loop):
loopStartTime = time.time()
self.currentTask.callBack(2)
if self.currentTask.isFinished:
if self.currentTask.isFinished or not self.running:
self.updateDetails.emit(str(self.parentId) + task_instruction_id, -1, -1)
break
if self.running == False:
self.updateDetails.emit(str(self.parentId) + task_instruction_id, -1, -1)
break
# 执行指令
currentInstruction.setInfo(taskInstructionInfo["instructionInfo"], taskInstructionInfo["target_param"])
currentInstruction.setProInfo(proInfo)
currentInstruction.setUserInfo(userManager.getCurrentUser())
currentInstruction.setDevInfo(taskInstructionInfo["device"])
interfaceInfos = interfaceManager.getInfo(taskInstructionInfo["interface"])
interfaceInfo = {}
if len(interfaceInfos) <= 0:
continue
interfaceInfo = interfaceInfos[0] #interface接口返回个数为1
currentInstruction.setInterfaceInfo(interfaceInfo)
session = interfaceManager.getSession(taskInstructionInfo["interface"])
try:
currentInstruction.logMsg.disconnect(self.onLogMsg)
currentInstruction.ioCtrl.disconnect(session.ioctrl)
currentInstruction.sendData.disconnect(session.send)
session.newDataArrive.disconnect(currentInstruction.dataHandler)
except:
pass
currentInstruction.logMsg.connect(self.onLogMsg, type=Qt.ConnectionType.DirectConnection)
currentInstruction.ioCtrl.connect(session.ioctrl, type=Qt.ConnectionType.DirectConnection)
currentInstruction.sendData.connect(session.send, type=Qt.ConnectionType.DirectConnection)
while not self.currentTask.isFinished and not session.lock() and self.running:
self.updateProgress.emit(self.id, int( self.currentIndex + int(taskIndex*instructionListLength)), total)
QThread.usleep(1)
session.newDataArrive.connect(currentInstruction.dataHandler, type=Qt.ConnectionType.DirectConnection)
currentInstruction.start()
while not self.currentTask.isFinished and not currentInstruction.isFinished and self.running:
self.updateProgress.emit(self.id, int( self.currentIndex + int(taskIndex*instructionListLength)), total)
currentInstruction.loop()
QThread.usleep(1)
self.updateDetails.emit(str(self.parentId) + task_instruction_id, j+1, target_loop)
#解除信号绑定
try:
currentInstruction.logMsg.disconnect(self.onLogMsg)
currentInstruction.ioCtrl.disconnect(session.ioctrl)
currentInstruction.sendData.disconnect(session.send)
session.newDataArrive.disconnect(currentInstruction.dataHandler)
except:
pass
session.unlock()
progress_cb = lambda: self.updateProgress.emit(self.id, int(self.currentIndex + int(taskIndex * instructionListLength)), total)
self._run_single_instruction(currentInstruction, taskInstructionInfo, proInfo, None,
lambda: not self.currentTask.isFinished, progress_cb)
self.updateDetails.emit(str(self.parentId) + task_instruction_id, j + 1, target_loop)
tmpTime = time.time()
if tmpTime < loopStartTime + target_delay:
if tmpTime < loopStartTime + target_delay:
time.sleep(target_delay - (tmpTime - loopStartTime))
# time.sleep(target_delay) 指令延时在指令内部处理
@ -237,48 +192,10 @@ class TaskActuator(QThread):
if target_type == "instruction":
for j in range(target_loop):
if self.running == False:
if not self.running:
break
# 执行指令
currentInstruction.setInfo(taskInstructionInfo["instructionInfo"], taskInstructionInfo["target_param"])
currentInstruction.setProInfo(proInfo)
currentInstruction.setUserInfo(userManager.getCurrentUser())
currentInstruction.setDevInfo(taskInstructionInfo["device"])
interfaceInfos = interfaceManager.getInfo(taskInstructionInfo["interface"])
interfaceInfo = {}
if len(interfaceInfos) <= 0:
continue
interfaceInfo = interfaceInfos[0] #interface接口返回个数为1
currentInstruction.setInterfaceInfo(interfaceInfo)
session = interfaceManager.getSession(taskInstructionInfo["interface"])
try:
currentInstruction.logMsg.disconnect(self.onLogMsg)
currentInstruction.ioCtrl.disconnect(session.ioctrl)
currentInstruction.sendData.disconnect(session.send)
session.newDataArrive.disconnect(currentInstruction.dataHandler)
except:
pass
currentInstruction.logMsg.connect(self.onLogMsg, type=Qt.ConnectionType.UniqueConnection)
currentInstruction.ioCtrl.connect(session.ioctrl, type=Qt.ConnectionType.UniqueConnection)
currentInstruction.sendData.connect(session.send, type=Qt.ConnectionType.UniqueConnection)
while not session.lock() and self.running:
QThread.usleep(1)
session.newDataArrive.connect(currentInstruction.dataHandler, type=Qt.ConnectionType.DirectConnection)
currentInstruction.start()
while not currentInstruction.isFinished and self.running:
currentInstruction.loop()
QThread.usleep(1)
# 解除信号绑定
try:
currentInstruction.logMsg.disconnect(self.onLogMsg)
currentInstruction.ioCtrl.disconnect(session.ioctrl)
currentInstruction.sendData.disconnect(session.send)
session.newDataArrive.disconnect(currentInstruction.dataHandler)
except:
pass
session.unlock()
self._run_single_instruction(currentInstruction, taskInstructionInfo, proInfo, None,
lambda: True)
time.sleep(target_delay)
elif target_type == "task":
# 执行子任务
@ -304,48 +221,11 @@ class TaskActuator(QThread):
if target_type == "instruction":
self.updateInstructProgress.emit(target_id, 0, target_loop)
for j in range(target_loop):
if self.running == False:
if not self.running:
break
# 执行指令
currentInstruction.setInfo(taskInstructionInfo["instructionInfo"], taskInstructionInfo["target_param"])
currentInstruction.setProInfo(proInfo)
currentInstruction.setUserInfo(userManager.getCurrentUser())
currentInstruction.setDevInfo(taskInstructionInfo["device"])
interfaceInfos = interfaceManager.getInfo(taskInstructionInfo["interface"])
interfaceInfo = {}
if len(interfaceInfos) <= 0:
continue
interfaceInfo = interfaceInfos[0]
currentInstruction.setInterfaceInfo(interfaceInfo)
session = interfaceManager.getSession(taskInstructionInfo["interface"])
try:
currentInstruction.logMsg.disconnect(self.onLogMsg)
currentInstruction.ioCtrl.disconnect(session.ioctrl)
currentInstruction.sendData.disconnect(session.send)
session.newDataArrive.disconnect(currentInstruction.dataHandler)
except:
pass
currentInstruction.logMsg.connect(self.onLogMsg, type=Qt.ConnectionType.DirectConnection)
currentInstruction.ioCtrl.connect(session.ioctrl, type=Qt.ConnectionType.UniqueConnection)
currentInstruction.sendData.connect(session.send, type=Qt.ConnectionType.UniqueConnection)
while not session.lock() and self.running:
self.updateInstructProgress.emit(target_id, j, target_loop)
QThread.msleep(10)
session.newDataArrive.connect(currentInstruction.dataHandler, type=Qt.ConnectionType.DirectConnection)
currentInstruction.start()
while not currentInstruction.isFinished and self.running:
self.updateInstructProgress.emit(target_id, j, target_loop)
currentInstruction.loop()
QThread.msleep(10)
# 解除信号绑定
try:
currentInstruction.ioCtrl.disconnect(session.ioctrl)
currentInstruction.sendData.disconnect(session.send)
currentInstruction.logMsg.disconnect(self.onLogMsg)
session.newDataArrive.disconnect(currentInstruction.dataHandler)
except:
pass
session.unlock()
progress_cb = lambda: self.updateInstructProgress.emit(target_id, j, target_loop)
self._run_single_instruction(currentInstruction, taskInstructionInfo, proInfo, None,
lambda: True, progress_cb)
time.sleep(target_delay)
self.updateInstructProgress.emit(self.id,1,-1)
currentInstruction.deleteLater()
@ -389,20 +269,66 @@ class TaskActuator(QThread):
# for stat in top_stats[:10]:
# print(stat)
def _run_single_instruction(self, currentInstruction, taskInstructionInfo, proInfo, session_ref,
stop_check, progress_cb=None):
"""执行单次指令连接信号、锁session、运行、解锁。返回使用的session。"""
interfaceInfos = interfaceManager.getInfo(taskInstructionInfo["interface"])
if not interfaceInfos:
return None
currentInstruction.setInfo(taskInstructionInfo["instructionInfo"], taskInstructionInfo["target_param"])
currentInstruction.setProInfo(proInfo)
currentInstruction.setUserInfo(userManager.getCurrentUser())
currentInstruction.setDevInfo(taskInstructionInfo["device"])
currentInstruction.setInterfaceInfo(interfaceInfos[0])
session = interfaceManager.getSession(taskInstructionInfo["interface"])
try:
currentInstruction.logMsg.disconnect(self.onLogMsg)
currentInstruction.ioCtrl.disconnect(session.ioctrl)
currentInstruction.sendData.disconnect(session.send)
session.newDataArrive.disconnect(currentInstruction.dataHandler)
except Exception:
pass
currentInstruction.logMsg.connect(self.onLogMsg, type=Qt.ConnectionType.DirectConnection)
currentInstruction.ioCtrl.connect(session.ioctrl, type=Qt.ConnectionType.DirectConnection)
currentInstruction.sendData.connect(session.send, type=Qt.ConnectionType.DirectConnection)
while not session.lock() and self.running and stop_check():
if progress_cb:
progress_cb()
QThread.msleep(1)
if not self.running or not stop_check():
return None
session.newDataArrive.connect(currentInstruction.dataHandler, type=Qt.ConnectionType.DirectConnection)
currentInstruction.start()
while not currentInstruction.isFinished and self.running and stop_check():
if progress_cb:
progress_cb()
currentInstruction.loop()
QThread.msleep(1)
try:
currentInstruction.logMsg.disconnect(self.onLogMsg)
currentInstruction.ioCtrl.disconnect(session.ioctrl)
currentInstruction.sendData.disconnect(session.send)
session.newDataArrive.disconnect(currentInstruction.dataHandler)
except Exception:
pass
session.unlock()
return session
def onLogMsg(self, data):
logManager.addLogMsg(data)
self.logMsg.emit(data)
# 执行指令或者队列
def execute(self, target, taskInfo = {}):
if self.isRunning():
return False
self.running = True
if isinstance(target, list): #队列
self.instructionList = target
self.taskInfo = taskInfo
self.type = taskInfo.get("type", TaskActuator.TASK)
self.name = taskInfo.get("name", TaskActuator.TASK)
self.type = int(taskInfo.get("type", TaskActuator.TASK))
self.name = taskInfo.get("name", "")
self.start()
return True
elif isinstance(target, dict): #指令
@ -436,28 +362,34 @@ class TaskActuator(QThread):
self.quit()
def forceStop(self):
"""强制立即停止任务(非阻塞)"""
# 1. 立即设置停止标志
self.running_lock.lock()
self.running = False
self.running_lock.unlock()
# 2. 停止子任务
if self.subTaskActuator and self.subTaskActuator.isRunning():
self.subTaskActuator.forceStop()
# 3. 终止当前指令的执行
if self.currentTask and hasattr(self.currentTask, 'isFinished'):
self.currentTask.isFinished = True
# 4. 强制终止线程(不等待)
# 等待线程自然退出最多500ms避免 terminate 造成锁泄漏
if self.isRunning():
self.wait(500)
# 超时仍在运行才强杀,并释放所有 session 锁
if self.isRunning():
for item in self.instructionList:
try:
iface = item.get("interface")
if iface:
interfaceManager.getSession(iface).unlock()
except Exception:
pass
self.terminate()
# 5. 发送停止信号
self.wait(200)
self.updateDetails.emit(self.id, -1, -1)
self.taskStop.emit(self.id)
log.info(f"任务 {self.id} 已强制停止")

View File

@ -44,6 +44,7 @@ class TaskActuatorManager(QObject):
else:
taskInstructions = taskInstructionManager.getInfo(taskId)
self.taskActuatorDict[taskId] = TaskActuator(taskId)
self.taskActuatorDict[taskId].logMsg.connect(self.onLogMsg)
self.taskActuatorDict[taskId].updateProgress.connect(self.onUpdateProgress)
self.taskActuatorDict[taskId].updateDetails.connect(self.onUpdateDetails)
self.taskActuatorDict[taskId].taskStop.connect(self.onTaskStop)
@ -57,6 +58,9 @@ class TaskActuatorManager(QObject):
def onExecuteFinished(self, taskId):
self.executeFinished.emit(taskId)
def onLogMsg(self, data):
self.logMsg.emit(data)
def onUpdateInstructProgress(self,id, value, maxValue):
if id in self.instructProgressInfo:
self.instructProgressInfo[id]["value"] = value
@ -92,20 +96,39 @@ class TaskActuatorManager(QObject):
@pyqtSlot()
def stopAll(self, excludeTaskId=None):
"""立即停止所有运行中的任务"""
stopped_count = 0
# 复制键列表,避免遍历时修改字典
for taskId in list(self.taskActuatorDict.keys()):
# 如果指定了排除ID跳过该任务
if excludeTaskId and str(taskId) == str(excludeTaskId):
continue
actuator = self.taskActuatorDict[taskId]
if actuator.isRunning():
actuator.forceStop()
stopped_count += 1
log.info(f"已强制停止 {stopped_count} 个任务")
return stopped_count
actuators = [
a for tid, a in list(self.taskActuatorDict.items())
if a.isRunning() and not (excludeTaskId and str(tid) == str(excludeTaskId))
]
# 先全部设置停止标志
for a in actuators:
a.running_lock.lock()
a.running = False
a.running_lock.unlock()
if a.currentTask and hasattr(a.currentTask, 'isFinished'):
a.currentTask.isFinished = True
if a.subTaskActuator and a.subTaskActuator.isRunning():
a.subTaskActuator.running_lock.lock()
a.subTaskActuator.running = False
a.subTaskActuator.running_lock.unlock()
# 再统一等待/强杀
for a in actuators:
a.quit()
if not a.wait(500):
for item in a.instructionList:
try:
iface = item.get("interface")
if iface:
from interfaceSession.interfaceManager import interfaceManager
interfaceManager.getSession(iface).unlock()
except Exception:
pass
a.terminate()
a.wait(200)
a.updateDetails.emit(a.id, -1, -1)
a.taskStop.emit(a.id)
log.info(f"任务 {a.id} 已停止")
return len(actuators)
def requestStopAll(self, requesterTaskId=None):
"""请求停止所有任务(由主程序处理)"""
@ -123,4 +146,27 @@ class TaskActuatorManager(QObject):
"""启动任务execute 的别名)"""
self.execute(taskId, params)
@pyqtSlot(str, QVariant)
def startInstruction(self, instructionId, attr=None):
try:
if not isinstance(attr, dict) and attr is not None:
attr = attr.toVariant()
attr = attr or {"deviceId": "", "param": "", "interfaceIndex": 0}
taskInstruction = common.getInstructionDetail(instructionId, attr)
if taskInstruction is None:
return False
if instructionId in self.taskActuatorDict:
if self.taskActuatorDict[instructionId].isRunning():
return False
else:
self.taskActuatorDict[instructionId] = TaskActuator(instructionId)
self.taskActuatorDict[instructionId].logMsg.connect(self.onLogMsg)
self.taskActuatorDict[instructionId].updateInstructProgress.connect(self.onUpdateInstructProgress)
self.taskActuatorDict[instructionId].taskStop.connect(self.onTaskStop)
self.taskActuatorDict[instructionId].execute(taskInstruction)
return True
except Exception as e:
log.error(f"startInstruction 出错: {e}")
return False
taskActuatorManager = TaskActuatorManager()

View File

@ -82,16 +82,20 @@ class UserForm(QWidget):
self.ui.tableViewUser.selectRow(row)
# 加载用户
def loadUser(self):
def loadUser(self):
users = userManager.getInfo()
if len(users) > 0:
self.userModel.clear()
self.userModel.blockSignals(True)
self.ui.tableViewUser.setUpdatesEnabled(False)
for user in users:
index = self.userModel.rowCount()
self.userModel.setItem(index, 0, QStandardItem(str(user["id"])))
self.userModel.setItem(index, 1, QStandardItem(user["name"]))
self.userModel.setItem(index, 2, QStandardItem(user["password"]))
self.userModel.setItem(index, 3, QStandardItem(user["role_name"]))
self.userModel.blockSignals(False)
self.ui.tableViewUser.setUpdatesEnabled(True)
self.batchOperation()
# 批量操作