diff --git a/.claude/settings.local.json b/.claude/settings.local.json new file mode 100644 index 0000000..fc3cf9e --- /dev/null +++ b/.claude/settings.local.json @@ -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\\)\")" + ] + } +} diff --git a/common.py b/common.py index 22ee25c..80db2c4 100644 --- a/common.py +++ b/common.py @@ -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() diff --git a/devModelForm.py b/devModelForm.py index 10b6795..885b658 100644 --- a/devModelForm.py +++ b/devModelForm.py @@ -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: diff --git a/deviceForm.py b/deviceForm.py index 9e3647a..a2bf082 100644 --- a/deviceForm.py +++ b/deviceForm.py @@ -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() diff --git a/influxdbModel/influxdbManager.py b/influxdbModel/influxdbManager.py index 6ca472c..a187b1d 100644 --- a/influxdbModel/influxdbManager.py +++ b/influxdbModel/influxdbManager.py @@ -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) diff --git a/logForm.py b/logForm.py index 23fe2db..dcd6c26 100644 --- a/logForm.py +++ b/logForm.py @@ -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}") diff --git a/mainWindow.py b/mainWindow.py index 754f88b..f00bb90 100644 --- a/mainWindow.py +++ b/mainWindow.py @@ -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() diff --git a/models.py b/models.py index 4a35e81..3a1f052 100644 --- a/models.py +++ b/models.py @@ -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='层级') diff --git a/projectForm.py b/projectForm.py index 324b19d..400091a 100644 --- a/projectForm.py +++ b/projectForm.py @@ -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) \ No newline at end of file diff --git a/qml/PageInterface.qml b/qml/PageInterface.qml index 48c8f8c..7f7c435 100644 --- a/qml/PageInterface.qml +++ b/qml/PageInterface.qml @@ -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 { diff --git a/taskForm.py b/taskForm.py index 3b338f3..a1a5e59 100644 --- a/taskForm.py +++ b/taskForm.py @@ -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() diff --git a/taskModel/taskActuator.py b/taskModel/taskActuator.py index a9d53a8..94d9844 100644 --- a/taskModel/taskActuator.py +++ b/taskModel/taskActuator.py @@ -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} 已强制停止") diff --git a/taskModel/taskActuatorManager.py b/taskModel/taskActuatorManager.py index 5f56863..a3df92a 100644 --- a/taskModel/taskActuatorManager.py +++ b/taskModel/taskActuatorManager.py @@ -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() diff --git a/userForm.py b/userForm.py index 92189e3..ca92d67 100644 --- a/userForm.py +++ b/userForm.py @@ -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() # 批量操作