diff --git a/Static/ControlWidget.qss b/Static/ControlWidget.qss new file mode 100644 index 0000000..1783bfe --- /dev/null +++ b/Static/ControlWidget.qss @@ -0,0 +1,477 @@ +/* 控制管理界面样式 */ + +/* 主容器 */ +QWidget#controlWidget { + background-color: #F5F5F5; + border-radius: 8px; +} + +/* 分组框样式 */ +QGroupBox { + font-size: 13px; + font-weight: bold; + color: #333333; + border: 1px solid #E0E0E0; + border-radius: 6px; + margin-top: 8px; + padding-top: 8px; + background-color: #FFFFFF; +} + +QGroupBox::title { + subcontrol-origin: margin; + left: 8px; + padding: 0 6px 0 6px; + color: #2277EF; + font-weight: bold; + font-size: 12px; +} + +/* 按钮样式 */ +QPushButton { + background-color: #2277EF; + color: white; + border: none; + border-radius: 4px; + padding: 6px 12px; + font-size: 12px; + font-weight: bold; + min-width: 70px; + min-height: 28px; +} + +QPushButton:hover { + background-color: #3787F7; +} + +QPushButton:pressed { + background-color: #1A66CC; +} + +QPushButton:disabled { + background-color: #CCCCCC; + color: #666666; +} + +/* 特殊按钮样式 */ +QPushButton#addRuleBtn { + background-color: #28A745; +} + +QPushButton#addRuleBtn:hover { + background-color: #34CE57; +} + +QPushButton#addMultiRuleBtn { + background-color: #17A2B8; +} + +QPushButton#addMultiRuleBtn:hover { + background-color: #20C4DB; +} + +QPushButton#deleteRuleBtn { + background-color: #DC3545; +} + +QPushButton#deleteRuleBtn:hover { + background-color: #E4606D; +} + +QPushButton#testRuleBtn { + background-color: #FFC107; + color: #333333; +} + +QPushButton#testRuleBtn:hover { + background-color: #FFD43B; +} + +/* 表格样式 */ +QTableWidget { + background-color: #FFFFFF; + border: 1px solid #E0E0E0; + border-radius: 6px; + gridline-color: #F0F0F0; + selection-background-color: #E3F2FD; + font-size: 11px; +} + +QTableWidget::item { + padding: 6px 8px; + border-bottom: 1px solid #F0F0F0; + min-height: 24px; +} + +QTableWidget::item:selected { + background-color: #E3F2FD; + color: #1976D2; +} + +QTableWidget::item:hover { + background-color: #F5F5F5; +} + +QHeaderView::section { + background-color: #F8F9FA; + color: #495057; + padding: 8px; + border: none; + border-bottom: 2px solid #E0E0E0; + font-weight: bold; + font-size: 12px; + min-height: 32px; +} + +QHeaderView::section:hover { + background-color: #E9ECEF; +} + +/* 输入框样式 */ +QLineEdit { + border: 1px solid #E0E0E0; + border-radius: 4px; + padding: 6px 10px; + font-size: 12px; + background-color: #FFFFFF; + selection-background-color: #E3F2FD; + min-height: 20px; +} + +QLineEdit:focus { + border-color: #2277EF; + outline: none; +} + +QLineEdit:hover { + border-color: #B0B0B0; +} + +/* 下拉框样式 */ +QComboBox { + border: 1px solid #E0E0E0; + border-radius: 4px; + padding: 6px 10px; + font-size: 12px; + background-color: #FFFFFF; + min-width: 100px; + min-height: 20px; +} + +QComboBox:focus { + border-color: #2277EF; +} + +QComboBox:hover { + border-color: #B0B0B0; +} + +QComboBox::drop-down { + border: none; + width: 20px; +} + +QComboBox::down-arrow { + width: 12px; + height: 12px; + background-color: #666666; +} + +QComboBox QAbstractItemView { + border: 1px solid #E0E0E0; + border-radius: 6px; + background-color: #FFFFFF; + selection-background-color: #E3F2FD; +} + +/* 数字输入框样式 */ +QSpinBox, QDoubleSpinBox { + border: 1px solid #E0E0E0; + border-radius: 4px; + padding: 6px 10px; + font-size: 12px; + background-color: #FFFFFF; + min-height: 20px; +} + +QSpinBox:focus, QDoubleSpinBox:focus { + border-color: #2277EF; +} + +QSpinBox:hover, QDoubleSpinBox:hover { + border-color: #B0B0B0; +} + +/* 复选框样式 */ +QCheckBox { + font-size: 13px; + color: #333333; + spacing: 8px; +} + +QCheckBox::indicator { + width: 18px; + height: 18px; + border: 2px solid #E0E0E0; + border-radius: 4px; + background-color: #FFFFFF; +} + +QCheckBox::indicator:hover { + border-color: #2277EF; +} + +QCheckBox::indicator:checked { + background-color: #2277EF; + border-color: #2277EF; +} + +/* 文本编辑器样式 */ +QTextEdit { + border: 1px solid #E0E0E0; + border-radius: 4px; + padding: 6px; + font-size: 11px; + font-family: "Consolas", "Monaco", monospace; + background-color: #FFFFFF; + selection-background-color: #E3F2FD; + line-height: 1.4; +} + +QTextEdit:focus { + border-color: #2277EF; +} + +/* 标签样式 */ +QLabel { + color: #333333; + font-size: 12px; +} + +QLabel#statusLabel { + color: #28A745; + font-weight: bold; +} + +QLabel#errorLabel { + color: #DC3545; + font-weight: bold; +} + +/* 搜索框样式 */ +QLineEdit#searchEdit { + border: 2px solid #E0E0E0; + border-radius: 20px; + padding: 8px 16px; + font-size: 13px; + background-color: #F8F9FA; +} + +QLineEdit#searchEdit:focus { + border-color: #2277EF; + background-color: #FFFFFF; +} + +/* 分割器样式 */ +QSplitter::handle { + background-color: #E0E0E0; + border-radius: 2px; +} + +QSplitter::handle:horizontal { + width: 4px; + margin: 2px 0; +} + +QSplitter::handle:vertical { + height: 4px; + margin: 0 2px; +} + +QSplitter::handle:hover { + background-color: #2277EF; +} + +/* 滚动条样式 */ +QScrollBar:vertical { + background-color: #F8F9FA; + width: 12px; + border-radius: 6px; +} + +QScrollBar::handle:vertical { + background-color: #CED4DA; + border-radius: 6px; + min-height: 20px; + margin: 2px; +} + +QScrollBar::handle:vertical:hover { + background-color: #ADB5BD; +} + +QScrollBar::add-line:vertical, +QScrollBar::sub-line:vertical { + height: 0px; +} + +QScrollBar:horizontal { + background-color: #F8F9FA; + height: 12px; + border-radius: 6px; +} + +QScrollBar::handle:horizontal { + background-color: #CED4DA; + border-radius: 6px; + min-width: 20px; + margin: 2px; +} + +QScrollBar::handle:horizontal:hover { + background-color: #ADB5BD; +} + +QScrollBar::add-line:horizontal, +QScrollBar::sub-line:horizontal { + width: 0px; +} + +/* 工具提示样式 */ +QToolTip { + background-color: #343A40; + color: #FFFFFF; + border: none; + border-radius: 4px; + padding: 6px 10px; + font-size: 12px; +} + +/* 表格内按钮样式 */ +QTableWidget QPushButton { + min-width: 50px; + min-height: 24px; + padding: 4px 8px; + font-size: 11px; + border-radius: 3px; + margin: 2px; +} + +QTableWidget QPushButton:hover { + transform: none; +} + +/* 对话框样式 */ +QDialog { + background-color: #FFFFFF; + border-radius: 8px; +} + +/* 多条件规则对话框特殊样式 */ +QDialog#multiConditionDialog { + background-color: #F8F9FA; +} + +QDialog#multiConditionDialog QTableWidget { + background-color: #FFFFFF; + border: 1px solid #DEE2E6; +} + +QDialog#multiConditionDialog QPushButton { + min-width: 60px; + min-height: 28px; + font-size: 12px; + padding: 6px 12px; +} + +/* 状态指示器 */ +QLabel#statusIndicator { + border-radius: 6px; + padding: 4px 8px; + font-size: 11px; + font-weight: bold; +} + +QLabel#statusIndicator[status="enabled"] { + background-color: #D4EDDA; + color: #155724; +} + +QLabel#statusIndicator[status="disabled"] { + background-color: #F8D7DA; + color: #721C24; +} + +QLabel#statusIndicator[status="running"] { + background-color: #D1ECF1; + color: #0C5460; +} + +/* 1080P显示器优化 */ +QWidget { + outline: none; +} + +QWidget:focus { + outline: none; +} + +/* 紧凑模式样式 */ +QGroupBox { + margin-top: 6px; + padding-top: 6px; +} + +QVBoxLayout, QHBoxLayout { + spacing: 4px; +} + +/* 工具栏按钮特殊样式 */ +QWidget QHBoxLayout QPushButton { + margin: 1px; + padding: 4px 8px; +} + +/* 状态标签紧凑样式 */ +QLabel#statusLabel, +QLabel#rulesCountLabel, +QLabel#variablesCountLabel, +QLabel#lastUpdateLabel { + font-size: 11px; + margin: 2px; + padding: 2px; +} + +/* 搜索框紧凑样式 */ +QLineEdit#searchEdit { + max-height: 28px; + font-size: 11px; +} + +/* 表格行高优化 */ +QTableWidget { + gridline-color: #F5F5F5; +} + +QTableWidget::item { + padding: 4px 6px; + min-height: 20px; +} + +/* 日志文本框优化 */ +QTextEdit { + line-height: 1.2; +} + +/* 对话框紧凑化 */ +QDialog QGroupBox { + margin-top: 4px; + padding-top: 4px; + font-size: 12px; +} + +QDialog QPushButton { + min-width: 60px; + min-height: 26px; + font-size: 11px; +} \ No newline at end of file diff --git a/Static/Main.qss b/Static/Main.qss index 83b1999..4560e7d 100644 --- a/Static/Main.qss +++ b/Static/Main.qss @@ -120,7 +120,7 @@ QPushButton#closeBtn, QPushButton#minBtn, QPushButton#maxBtn{ } -QPushButton#createProject, QPushButton#openProject, QPushButton#trendMag, QPushButton#varMag, QPushButton#userMag, QPushButton#protocolMag{ +QPushButton#createProject, QPushButton#openProject, QPushButton#trendMag, QPushButton#varMag, QPushButton#userMag, QPushButton#protocolMag, QPushButton#controlMag{ border: none; @@ -135,7 +135,7 @@ QPushButton#createProject, QPushButton#openProject, QPushButton#trendMag, QPushB } -QPushButton#createProject:hover, QPushButton#openProject:hover, QPushButton#trendMag:hover, QPushButton#varMag:hover, QPushButton#userMag:hover, QPushButton#protocolMag:hover{ +QPushButton#createProject:hover, QPushButton#openProject:hover, QPushButton#trendMag:hover, QPushButton#varMag:hover, QPushButton#userMag:hover, QPushButton#protocolMag:hover, QPushButton#controlMag:hover{ color: #2277EF; @@ -146,7 +146,7 @@ QPushButton#createProject:hover, QPushButton#openProject:hover, QPushButton#tren } -QPushButton#createProject:pressed, QPushButton#openProject:pressed, QPushButton#trendMag:pressed, QPushButton#varMag:pressed, QPushButton#userMag:pressed, QPushButton#protocolMag:pressed{ +QPushButton#createProject:pressed, QPushButton#openProject:pressed, QPushButton#trendMag:pressed, QPushButton#varMag:pressed, QPushButton#userMag:pressed, QPushButton#protocolMag:pressed, QPushButton#controlMag:pressed{ background-color: #F5F5F6; @@ -157,7 +157,7 @@ QPushButton#createProject:pressed, QPushButton#openProject:pressed, QPushButton# } -QPushButton#createProject:checked, QPushButton#openProject:checked, QPushButton#trendMag:checked, QPushButton#varMag:checked, QPushButton#userMag:checked, QPushButton#protocolMag:checked{ +QPushButton#createProject:checked, QPushButton#openProject:checked, QPushButton#trendMag:checked, QPushButton#varMag:checked, QPushButton#userMag:checked, QPushButton#protocolMag:checked, QPushButton#controlMag:checked{ background-color: #F5F5F6; @@ -1103,6 +1103,8 @@ QLabel#trendInfoBubble { color: #333333; } + + QMessageBox { background-color: #ffffff; border-radius: 16px; @@ -1138,3 +1140,29 @@ QMessageBox QPushButton:hover { QMessageBox QPushButton:pressed { background: #2980b9; } + +/* Control System Styles */ +QWidget#trendMainWidget { + background-color: #F5F5F5; + border-radius: 8px; +} + +QGroupBox#trendVariableListGroup, QGroupBox#trendChartGroup { + font: bold 16px "PingFangSC-Medium"; + color: #2277EF; + border: 2px solid #E0E0E0; + border-radius: 8px; + margin-top: 12px; + padding-top: 8px; + background-color: #FFFFFF; +} + +QGroupBox#trendTimeGroupBox, QGroupBox#trendButtonGroupBox, QGroupBox#trendInfoGroup { + font: bold 14px "PingFangSC-Medium"; + color: #555555; + border: 1px solid #D0D0D0; + border-radius: 6px; + margin-top: 10px; + padding-top: 6px; + background-color: #FAFAFA; +} diff --git a/Static/control.png b/Static/control.png new file mode 100644 index 0000000..de98c81 --- /dev/null +++ b/Static/control.png @@ -0,0 +1,4 @@ +# 这是控制系统图标的占位符文件 +# 请替换为实际的PNG图标文件 +# 建议尺寸: 26x26 像素 +# 颜色: 灰色调,与其他图标风格一致 \ No newline at end of file diff --git a/Static/controlH.png b/Static/controlH.png new file mode 100644 index 0000000..a820dd1 --- /dev/null +++ b/Static/controlH.png @@ -0,0 +1,4 @@ +# 这是控制系统高亮图标的占位符文件 +# 请替换为实际的PNG图标文件 +# 建议尺寸: 26x26 像素 +# 颜色: 蓝色调 (#2277EF),与其他高亮图标风格一致 \ No newline at end of file diff --git a/UI/ControlManage/ControlWidget.py b/UI/ControlManage/ControlWidget.py new file mode 100644 index 0000000..a2addfb --- /dev/null +++ b/UI/ControlManage/ControlWidget.py @@ -0,0 +1,1403 @@ +#!/usr/bin/env python3 +# -*- coding: utf-8 -*- + +""" +控制系统管理界面 +提供规则配置和监控功能 +""" + +from PyQt5.QtCore import Qt, QTimer, pyqtSignal +from PyQt5.QtGui import QFont +from PyQt5.QtWidgets import (QWidget, QVBoxLayout, QHBoxLayout, QGroupBox, + QTableWidget, QTableWidgetItem, QPushButton, + QLabel, QLineEdit, QComboBox, QSpinBox, + QCheckBox, QTextEdit, QSplitter, QHeaderView, + QMessageBox, QDialog, QFormLayout, QDialogButtonBox, + QDoubleSpinBox) + +from control.ControlManager import getControlManager +from control.RuleEngine import OperatorType, ActionType +from utils import Globals +import os + + +class RuleConfigDialog(QDialog): + """规则配置对话框""" + + def __init__(self, parent=None, ruleData=None): + super().__init__(parent) + self.ruleData = ruleData + self.isEditMode = ruleData is not None + + if self.isEditMode: + self.setWindowTitle("编辑规则") + else: + self.setWindowTitle("新建规则") + + self.setMinimumSize(600, 500) + self.setupUI() + + # 如果是编辑模式,填充现有数据 + if self.isEditMode: + self.loadRuleData() + + def setupUI(self): + """设置界面""" + layout = QVBoxLayout(self) + + # 基本信息 + basicGroup = QGroupBox("基本信息") + basicLayout = QFormLayout(basicGroup) + + self.nameEdit = QLineEdit() + self.nameEdit.setPlaceholderText("输入规则名称") + self.descEdit = QLineEdit() + self.descEdit.setPlaceholderText("输入规则描述(可选)") + + # 启用状态 + self.enabledCheckBox = QCheckBox("启用规则") + self.enabledCheckBox.setChecked(True) + + basicLayout.addRow("规则名称:", self.nameEdit) + basicLayout.addRow("规则描述:", self.descEdit) + basicLayout.addRow("", self.enabledCheckBox) + + layout.addWidget(basicGroup) + + # 条件设置 + conditionGroup = QGroupBox("触发条件") + conditionLayout = QFormLayout(conditionGroup) + + self.variableEdit = QLineEdit() + self.variableEdit.setPlaceholderText("输入变量名(如:温度传感器_01)") + + self.operatorCombo = QComboBox() + self.operatorCombo.addItems([op.value for op in OperatorType]) + + self.thresholdSpin = QDoubleSpinBox() + self.thresholdSpin.setRange(-999999, 999999) + self.thresholdSpin.setDecimals(3) + self.thresholdSpin.setValue(0.0) + + conditionLayout.addRow("监控变量:", self.variableEdit) + conditionLayout.addRow("比较操作:", self.operatorCombo) + conditionLayout.addRow("阈值:", self.thresholdSpin) + + layout.addWidget(conditionGroup) + + # 动作设置 + actionGroup = QGroupBox("执行动作") + actionLayout = QFormLayout(actionGroup) + + self.actionTypeCombo = QComboBox() + self.actionTypeCombo.addItems([act.value for act in ActionType]) + self.actionTypeCombo.currentTextChanged.connect(self.onActionTypeChanged) + + self.targetEdit = QLineEdit() + self.valueEdit = QLineEdit() + + self.delaySpin = QSpinBox() + self.delaySpin.setRange(0, 60000) + self.delaySpin.setSuffix(" ms") + self.delaySpin.setValue(0) + + actionLayout.addRow("动作类型:", self.actionTypeCombo) + actionLayout.addRow("目标:", self.targetEdit) + actionLayout.addRow("值:", self.valueEdit) + actionLayout.addRow("延迟:", self.delaySpin) + + layout.addWidget(actionGroup) + + # 高级设置 + advancedGroup = QGroupBox("高级设置") + advancedLayout = QFormLayout(advancedGroup) + + self.cooldownSpin = QSpinBox() + self.cooldownSpin.setRange(0, 300000) # 最大5分钟 + self.cooldownSpin.setSuffix(" ms") + self.cooldownSpin.setValue(0) + self.cooldownSpin.setToolTip("规则执行后的冷却时间,防止频繁触发") + + self.logicCombo = QComboBox() + self.logicCombo.addItems(["and", "or"]) + self.logicCombo.setToolTip("多条件时的逻辑关系(当前版本仅支持单条件)") + self.logicCombo.setEnabled(False) # 当前版本禁用 + + advancedLayout.addRow("冷却期:", self.cooldownSpin) + advancedLayout.addRow("逻辑操作:", self.logicCombo) + + layout.addWidget(advancedGroup) + + # 按钮 + buttonBox = QDialogButtonBox(QDialogButtonBox.Ok | QDialogButtonBox.Cancel) + if self.isEditMode: + buttonBox.button(QDialogButtonBox.Ok).setText("保存") + else: + buttonBox.button(QDialogButtonBox.Ok).setText("创建") + + buttonBox.accepted.connect(self.validateAndAccept) + buttonBox.rejected.connect(self.reject) + layout.addWidget(buttonBox) + + # 初始化界面状态 + self.onActionTypeChanged(self.actionTypeCombo.currentText()) + + def loadRuleData(self): + """加载规则数据到界面""" + if not self.ruleData: + return + + try: + # 基本信息 + self.nameEdit.setText(self.ruleData.get('name', '')) + self.descEdit.setText(self.ruleData.get('description', '')) + self.enabledCheckBox.setChecked(self.ruleData.get('enabled', True)) + + # 条件信息(取第一个条件) + conditions = self.ruleData.get('conditions', []) + if conditions: + condition = conditions[0] + self.variableEdit.setText(condition.get('variable', '')) + + # 设置操作符 + operator = condition.get('operator', '==') + index = self.operatorCombo.findText(operator) + if index >= 0: + self.operatorCombo.setCurrentIndex(index) + + self.thresholdSpin.setValue(float(condition.get('value', 0))) + + # 动作信息(取第一个动作) + actions = self.ruleData.get('actions', []) + if actions: + action = actions[0] + actionType = action.get('actionType', 'set_variable') + + # 设置动作类型 + index = self.actionTypeCombo.findText(actionType) + if index >= 0: + self.actionTypeCombo.setCurrentIndex(index) + + self.targetEdit.setText(action.get('target', '')) + self.valueEdit.setText(str(action.get('value', ''))) + self.delaySpin.setValue(int(action.get('delay', 0))) + + # 高级设置 + self.cooldownSpin.setValue(int(self.ruleData.get('cooldownPeriod', 0))) + + logicOp = self.ruleData.get('logicOperator', 'and') + index = self.logicCombo.findText(logicOp) + if index >= 0: + self.logicCombo.setCurrentIndex(index) + + except Exception as e: + print(f"加载规则数据失败: {e}") + + def validateAndAccept(self): + """验证输入并接受""" + # 验证必填字段 + if not self.nameEdit.text().strip(): + QMessageBox.warning(self, "验证错误", "规则名称不能为空") + self.nameEdit.setFocus() + return + + if not self.variableEdit.text().strip(): + QMessageBox.warning(self, "验证错误", "监控变量不能为空") + self.variableEdit.setFocus() + return + + if not self.targetEdit.text().strip(): + QMessageBox.warning(self, "验证错误", "动作目标不能为空") + self.targetEdit.setFocus() + return + + if not self.valueEdit.text().strip(): + QMessageBox.warning(self, "验证错误", "动作值不能为空") + self.valueEdit.setFocus() + return + + # 验证通过,接受对话框 + self.accept() + + def onActionTypeChanged(self, actionType): + """动作类型变化时更新界面""" + if actionType == "set_variable": + self.targetEdit.setPlaceholderText("目标变量名(如:阀门_01)") + self.valueEdit.setPlaceholderText("设置值(如:true, false, 100)") + elif actionType == "send_alarm": + self.targetEdit.setPlaceholderText("报警源(如:温度传感器_01)") + self.valueEdit.setPlaceholderText("报警消息内容") + elif actionType == "log_message": + self.targetEdit.setPlaceholderText("日志类别(如:system)") + self.valueEdit.setPlaceholderText("日志消息内容") + elif actionType == "execute_script": + self.targetEdit.setPlaceholderText("脚本路径或命令") + self.valueEdit.setPlaceholderText("脚本参数(可选)") + else: + self.targetEdit.setPlaceholderText("目标") + self.valueEdit.setPlaceholderText("值") + + def getRuleData(self): + """获取规则数据""" + # 处理值的类型转换 + value = self.valueEdit.text().strip() + actionType = self.actionTypeCombo.currentText() + + # 如果是设置变量动作,尝试转换为数值 + if actionType == "set_variable": + try: + # 尝试转换为数值 + if '.' in value: + value = float(value) + else: + value = int(value) + except ValueError: + # 如果转换失败,检查是否是布尔值 + if value.lower() in ['true', 'false']: + value = value.lower() == 'true' + # 否则保持字符串 + + return { + 'name': self.nameEdit.text().strip(), + 'description': self.descEdit.text().strip(), + 'enabled': self.enabledCheckBox.isChecked(), + 'variable': self.variableEdit.text().strip(), + 'operator': self.operatorCombo.currentText(), + 'threshold': self.thresholdSpin.value(), + 'actionType': actionType, + 'target': self.targetEdit.text().strip(), + 'value': value, + 'delay': self.delaySpin.value(), + 'cooldownPeriod': self.cooldownSpin.value(), + 'logicOperator': self.logicCombo.currentText() + } + + +class ControlWidget(QWidget): + """控制系统管理主界面""" + + def __init__(self): + super().__init__() + self.controlManager = getControlManager() + + # 设置对象名称用于QSS样式 + self.setObjectName("controlWidget") + + # 加载样式 + self.loadStyleSheet() + + self.setupUI() + self.setupTimer() + + # 初始化控制系统 + self.controlManager.initialize() + + # 刷新界面 + self.refreshRuleTable() + self.refreshStatus() + + def loadStyleSheet(self): + """加载QSS样式文件""" + try: + qss_path = os.path.join(os.path.dirname(os.path.dirname(os.path.dirname(__file__))), + "Static", "ControlWidget.qss") + if os.path.exists(qss_path): + with open(qss_path, 'r', encoding='utf-8') as f: + self.setStyleSheet(f.read()) + print("控制界面样式加载成功") + else: + print(f"样式文件不存在: {qss_path}") + except Exception as e: + print(f"加载样式文件失败: {e}") + + def setupUI(self): + """设置界面""" + layout = QVBoxLayout(self) + layout.setSpacing(8) + layout.setContentsMargins(8, 8, 8, 8) + + # 创建分割器 + splitter = QSplitter(Qt.Vertical) + splitter.setHandleWidth(4) + + # 上半部分:规则管理 + ruleWidget = self.createRuleManageWidget() + splitter.addWidget(ruleWidget) + + # 下半部分:状态监控 + statusWidget = self.createStatusWidget() + splitter.addWidget(statusWidget) + + # 设置分割比例 - 针对1080P优化 + splitter.setStretchFactor(0, 4) # 规则管理占更多空间 + splitter.setStretchFactor(1, 1) # 状态监控占较少空间 + splitter.setSizes([600, 200]) # 设置初始大小 + + layout.addWidget(splitter) + + def createRuleManageWidget(self): + """创建规则管理组件""" + widget = QGroupBox("规则管理") + layout = QVBoxLayout(widget) + + # 工具栏 - 分两行布局以适应1080P + toolWidget = QWidget() + toolMainLayout = QVBoxLayout(toolWidget) + toolMainLayout.setSpacing(4) + toolMainLayout.setContentsMargins(0, 0, 0, 0) + + # 第一行:主要操作按钮 + toolLayout1 = QHBoxLayout() + toolLayout1.setSpacing(6) + + self.addRuleBtn = QPushButton("简单规则") + self.addRuleBtn.setObjectName("addRuleBtn") + self.addRuleBtn.setToolTip("创建单条件规则") + self.addRuleBtn.setFixedSize(80, 32) + self.addRuleBtn.clicked.connect(self.addRule) + + self.addMultiRuleBtn = QPushButton("多条件规则") + self.addMultiRuleBtn.setObjectName("addMultiRuleBtn") + self.addMultiRuleBtn.setToolTip("创建多条件AND/OR规则") + self.addMultiRuleBtn.setFixedSize(90, 32) + self.addMultiRuleBtn.clicked.connect(self.addMultiConditionRule) + + self.editRuleBtn = QPushButton("编辑") + self.editRuleBtn.setToolTip("编辑选中的规则") + self.editRuleBtn.setFixedSize(60, 32) + self.editRuleBtn.clicked.connect(self.editRule) + + self.deleteRuleBtn = QPushButton("删除") + self.deleteRuleBtn.setObjectName("deleteRuleBtn") + self.deleteRuleBtn.setToolTip("删除选中的规则") + self.deleteRuleBtn.setFixedSize(60, 32) + self.deleteRuleBtn.clicked.connect(self.deleteRule) + + self.testRuleBtn = QPushButton("测试") + self.testRuleBtn.setObjectName("testRuleBtn") + self.testRuleBtn.setToolTip("测试规则条件和显示变量值") + self.testRuleBtn.setFixedSize(60, 32) + self.testRuleBtn.clicked.connect(self.testRule) + + toolLayout1.addWidget(self.addRuleBtn) + toolLayout1.addWidget(self.addMultiRuleBtn) + toolLayout1.addWidget(self.editRuleBtn) + toolLayout1.addWidget(self.deleteRuleBtn) + toolLayout1.addWidget(self.testRuleBtn) + toolLayout1.addStretch() + + # 第二行:辅助操作按钮 + toolLayout2 = QHBoxLayout() + toolLayout2.setSpacing(6) + + self.setVarBtn = QPushButton("设置变量") + self.setVarBtn.setToolTip("手动设置变量值用于测试") + self.setVarBtn.setFixedSize(80, 28) + self.setVarBtn.clicked.connect(self.setVariableValue) + + self.copyRuleBtn = QPushButton("复制") + self.copyRuleBtn.setToolTip("复制选中的规则") + self.copyRuleBtn.setFixedSize(60, 28) + self.copyRuleBtn.clicked.connect(self.copyRule) + + # 批量操作按钮 + self.enableAllBtn = QPushButton("全部启用") + self.enableAllBtn.setFixedSize(80, 28) + self.enableAllBtn.clicked.connect(self.enableAllRules) + + self.disableAllBtn = QPushButton("全部禁用") + self.disableAllBtn.setFixedSize(80, 28) + self.disableAllBtn.clicked.connect(self.disableAllRules) + + toolLayout2.addWidget(self.setVarBtn) + toolLayout2.addWidget(self.copyRuleBtn) + toolLayout2.addWidget(self.enableAllBtn) + toolLayout2.addWidget(self.disableAllBtn) + toolLayout2.addStretch() + + toolMainLayout.addLayout(toolLayout1) + toolMainLayout.addLayout(toolLayout2) + + layout.addWidget(toolWidget) + + # 搜索框 + searchLayout = QHBoxLayout() + searchLabel = QLabel("搜索规则:") + self.searchEdit = QLineEdit() + self.searchEdit.setObjectName("searchEdit") + self.searchEdit.setPlaceholderText("输入规则名称进行搜索...") + self.searchEdit.textChanged.connect(self.searchRules) + + searchLayout.addWidget(searchLabel) + searchLayout.addWidget(self.searchEdit) + searchLayout.addStretch() + + layout.addLayout(searchLayout) + + # 规则表格 + self.ruleTable = QTableWidget() + self.ruleTable.setColumnCount(5) + self.ruleTable.setHorizontalHeaderLabels(["规则名称", "描述", "状态", "最后执行", "操作"]) + + # 设置表格属性 + header = self.ruleTable.horizontalHeader() + header.setSectionResizeMode(0, QHeaderView.Interactive) # 规则名称 + header.setSectionResizeMode(1, QHeaderView.Stretch) # 描述 + header.setSectionResizeMode(2, QHeaderView.Fixed) # 状态 + header.setSectionResizeMode(3, QHeaderView.Fixed) # 最后执行 + header.setSectionResizeMode(4, QHeaderView.Fixed) # 操作 + + # 设置固定列宽 + header.resizeSection(0, 180) # 规则名称 + header.resizeSection(2, 80) # 状态 + header.resizeSection(3, 140) # 最后执行 + header.resizeSection(4, 80) # 操作 + + self.ruleTable.setSelectionBehavior(QTableWidget.SelectRows) + self.ruleTable.setAlternatingRowColors(True) + self.ruleTable.setSortingEnabled(True) + self.ruleTable.setRowHeight(0, 32) # 设置行高 + + layout.addWidget(self.ruleTable) + + return widget + + def createStatusWidget(self): + """创建状态监控组件""" + widget = QGroupBox("系统状态") + layout = QHBoxLayout(widget) + layout.setSpacing(8) + layout.setContentsMargins(8, 8, 8, 8) + + # 左侧:状态信息 - 使用网格布局更紧凑 + statusWidget = QWidget() + statusLayout = QVBoxLayout(statusWidget) + statusLayout.setSpacing(4) + + # 状态信息行 + statusInfoLayout = QHBoxLayout() + statusInfoLayout.setSpacing(20) + + self.statusLabel = QLabel("系统状态: 未知") + self.statusLabel.setObjectName("statusLabel") + self.statusLabel.setStyleSheet("QLabel { font-weight: bold; color: #28A745; }") + + self.rulesCountLabel = QLabel("规则: 0") + self.variablesCountLabel = QLabel("变量: 0") + self.lastUpdateLabel = QLabel("更新: --") + + statusInfoLayout.addWidget(self.statusLabel) + statusInfoLayout.addWidget(self.rulesCountLabel) + statusInfoLayout.addWidget(self.variablesCountLabel) + statusInfoLayout.addWidget(self.lastUpdateLabel) + statusInfoLayout.addStretch() + + statusLayout.addLayout(statusInfoLayout) + + # 变量值显示 - 更紧凑 + self.variableValuesLabel = QLabel("变量值: --") + self.variableValuesLabel.setWordWrap(True) + self.variableValuesLabel.setMaximumHeight(60) + self.variableValuesLabel.setStyleSheet(""" + QLabel { + background-color: #F8F9FA; + border: 1px solid #DEE2E6; + border-radius: 4px; + padding: 6px; + font-size: 11px; + font-family: 'Consolas', 'Monaco', monospace; + } + """) + statusLayout.addWidget(self.variableValuesLabel) + + layout.addWidget(statusWidget) + + # 右侧:日志显示 - 更紧凑 + logWidget = QWidget() + logLayout = QVBoxLayout(logWidget) + logLayout.setSpacing(4) + logLayout.setContentsMargins(0, 0, 0, 0) + + logLabel = QLabel("系统日志:") + logLabel.setStyleSheet("QLabel { font-weight: bold; }") + logLayout.addWidget(logLabel) + + self.logText = QTextEdit() + self.logText.setMaximumHeight(120) + self.logText.setReadOnly(True) + self.logText.setStyleSheet(""" + QTextEdit { + font-size: 10px; + font-family: 'Consolas', 'Monaco', monospace; + background-color: #FAFAFA; + border: 1px solid #E0E0E0; + border-radius: 4px; + padding: 4px; + } + """) + + logLayout.addWidget(self.logText) + + layout.addWidget(logWidget) + layout.setStretchFactor(statusWidget, 1) + layout.setStretchFactor(logWidget, 2) + + return widget + + def setupTimer(self): + """设置定时器""" + self.refreshTimer = QTimer() + self.refreshTimer.timeout.connect(self.refreshStatus) + self.refreshTimer.start(5000) # 5秒刷新一次 + + def addRule(self): + """添加规则""" + dialog = RuleConfigDialog(self) + if dialog.exec_() == QDialog.Accepted: + data = dialog.getRuleData() + + try: + # 验证输入数据 + if not data['name'] or not data['variable']: + QMessageBox.warning(self, "错误", "规则名称和监控变量不能为空") + return + + if data['actionType'] == "set_variable": + if not data['target']: + QMessageBox.warning(self, "错误", "目标变量不能为空") + return + + ruleId = self.controlManager.createSimpleRule( + data['name'], + data['variable'], + data['operator'], + data['threshold'], + data['target'], + data['value'] + ) + elif data['actionType'] == "send_alarm": + if not data['value']: + QMessageBox.warning(self, "错误", "报警消息不能为空") + return + + ruleId = self.controlManager.createAlarmRule( + data['name'], + data['variable'], + data['operator'], + data['threshold'], + data['value'] + ) + else: + QMessageBox.warning(self, "错误", f"不支持的动作类型: {data['actionType']}") + return + + # 添加变量监控 + self.controlManager.addMonitorVariable(data['variable']) + + # 保存配置 + self.controlManager.saveConfiguration() + + self.refreshRuleTable() + self.logMessage(f"已添加规则: {data['name']} (ID: {ruleId})") + + except Exception as e: + import traceback + traceback.print_exc() + QMessageBox.warning(self, "错误", f"添加规则失败: {str(e)}") + + def addMultiConditionRule(self): + """添加多条件规则""" + dialog = MultiConditionRuleDialog(self) + if dialog.exec_() == QDialog.Accepted: + try: + ruleData = dialog.getRuleData() + + # 使用控制管理器创建多条件规则 + ruleId = self.controlManager.createComplexRule(ruleData) + + # 保存配置 + self.controlManager.saveConfiguration() + + self.logMessage(f"成功创建多条件规则: {ruleData['name']} (ID: {ruleId})") + self.refreshRuleTable() + + except Exception as e: + import traceback + traceback.print_exc() + QMessageBox.warning(self, "错误", f"创建多条件规则失败: {str(e)}") + self.logMessage(f"创建多条件规则失败: {str(e)}") + + def editRule(self): + """编辑规则""" + currentRow = self.ruleTable.currentRow() + if currentRow >= 0: + try: + rules = self.controlManager.getAllRules() + if currentRow < len(rules): + rule = rules[currentRow] + ruleId = rule['ruleId'] + + # 创建编辑对话框 + dialog = RuleConfigDialog(self, rule) + if dialog.exec_() == QDialog.Accepted: + # 获取编辑后的数据 + updatedData = dialog.getRuleData() + + # 验证规则名称是否重复(排除当前规则) + existingNames = [r['name'] for r in rules if r['ruleId'] != ruleId] + if updatedData['name'] in existingNames: + QMessageBox.warning(self, "错误", f"规则名称 '{updatedData['name']}' 已存在") + return + + # 更新规则 + success = self.controlManager.updateRule(ruleId, updatedData) + if success: + # 更新变量监控(如果变量名发生变化) + self.controlManager.addMonitorVariable(updatedData['variable']) + + # 保存配置 + self.controlManager.saveConfiguration() + + self.refreshRuleTable() + self.logMessage(f"已更新规则: {updatedData['name']}") + QMessageBox.information(self, "成功", f"规则 '{updatedData['name']}' 已成功更新") + else: + QMessageBox.warning(self, "错误", "更新规则失败") + else: + QMessageBox.warning(self, "错误", "无法获取规则信息") + except Exception as e: + import traceback + traceback.print_exc() + QMessageBox.warning(self, "错误", f"编辑规则失败: {str(e)}") + + def deleteRule(self): + """删除规则""" + currentRow = self.ruleTable.currentRow() + if currentRow >= 0: + try: + rules = self.controlManager.getAllRules() + if currentRow < len(rules): + rule = rules[currentRow] + ruleName = rule['name'] + ruleId = rule['ruleId'] + + reply = QMessageBox.question(self, "确认", f"确定要删除规则 '{ruleName}' 吗?") + + if reply == QMessageBox.Yes: + self.controlManager.deleteRule(ruleId) + + # 保存配置 + self.controlManager.saveConfiguration() + + self.logMessage(f"已删除规则: {ruleName}") + self.refreshRuleTable() + else: + QMessageBox.warning(self, "错误", "无法获取规则信息") + except Exception as e: + QMessageBox.warning(self, "错误", f"删除规则失败: {str(e)}") + + def testRule(self): + """测试规则""" + currentRow = self.ruleTable.currentRow() + if currentRow >= 0: + try: + rules = self.controlManager.getAllRules() + if currentRow < len(rules): + rule = rules[currentRow] + ruleName = rule['name'] + ruleId = rule['ruleId'] + + # 获取规则详细信息 + ruleObj = self.controlManager.ruleEngine.getRule(ruleId) + if not ruleObj: + QMessageBox.warning(self, "错误", "无法获取规则对象") + return + + # 构建详细信息 + detailInfo = f"规则: {ruleName}\n" + detailInfo += f"状态: {'启用' if ruleObj.enabled else '禁用'}\n\n" + + # 显示条件信息 + if ruleObj.conditions: + detailInfo += "条件检查:\n" + for i, condition in enumerate(ruleObj.conditions): + varName = condition.variable + currentValue = self.controlManager.variableMonitor.getVariableValue(varName) + + detailInfo += f" {i+1}. {varName} {condition.operator.value} {condition.value}\n" + detailInfo += f" 当前值: {currentValue}\n" + + if currentValue is None: + detailInfo += f" 状态: ❌ 变量值未设置\n" + else: + condResult = condition.evaluate(self.controlManager.variableMonitor) + detailInfo += f" 状态: {'✅ 满足' if condResult else '❌ 不满足'}\n" + + # 测试规则条件 + result = self.controlManager.testRule(ruleId) + + detailInfo += f"\n整体结果: {'✅ 规则会被触发' if result else '❌ 规则不会被触发'}" + + if result: + self.logMessage(f"测试规则 '{ruleName}': 条件满足") + QMessageBox.information(self, "测试结果 - 满足条件", detailInfo) + else: + self.logMessage(f"测试规则 '{ruleName}': 条件不满足") + QMessageBox.information(self, "测试结果 - 不满足条件", detailInfo) + else: + QMessageBox.warning(self, "错误", "无法获取规则信息") + except Exception as e: + import traceback + traceback.print_exc() + QMessageBox.warning(self, "错误", f"测试规则失败: {str(e)}") + self.logMessage(f"测试规则失败: {str(e)}") + + def setVariableValue(self): + """设置变量值(用于测试)""" + from PyQt5.QtWidgets import QInputDialog + + try: + # 获取所有监控的变量 + allVars = self.controlManager.variableMonitor.getAllVariables() + varNames = list(allVars.keys()) + + if not varNames: + QMessageBox.information(self, "提示", "当前没有监控的变量。\n请先创建规则,系统会自动添加变量监控。") + return + + # 选择变量 + varName, ok = QInputDialog.getItem(self, "选择变量", "选择要设置值的变量:", varNames, 0, False) + if not ok: + return + + # 获取当前值 + currentValue = allVars.get(varName, 0) + + # 输入新值 + newValue, ok = QInputDialog.getDouble(self, "设置变量值", + f"变量: {varName}\n当前值: {currentValue}\n\n请输入新值:", + currentValue, -999999, 999999, 3) + if not ok: + return + + # 更新变量值 + self.controlManager.variableMonitor.updateVariable(varName, newValue) + + # 验证设置是否成功 + actualValue = self.controlManager.variableMonitor.getVariableValue(varName) + + if actualValue == newValue: + self.logMessage(f"已设置变量 {varName} = {newValue}") + QMessageBox.information(self, "成功", f"变量 '{varName}' 已设置为 {newValue}") + + # 刷新状态显示 + self.refreshStatus() + else: + QMessageBox.warning(self, "错误", f"设置变量值失败\n期望: {newValue}\n实际: {actualValue}") + + except Exception as e: + QMessageBox.warning(self, "错误", f"设置变量值失败: {str(e)}") + self.logMessage(f"设置变量值失败: {str(e)}") + + def copyRule(self): + """复制规则""" + currentRow = self.ruleTable.currentRow() + if currentRow >= 0: + try: + rules = self.controlManager.getAllRules() + if currentRow < len(rules): + rule = rules[currentRow] + + # 创建规则副本数据 + copyData = rule.copy() + copyData['name'] = f"{rule['name']}_副本" + copyData['ruleId'] = None # 清除ID,让系统生成新的 + + # 创建编辑对话框,预填充副本数据 + dialog = RuleConfigDialog(self, copyData) + dialog.setWindowTitle("复制规则") + + if dialog.exec_() == QDialog.Accepted: + # 获取编辑后的数据 + newRuleData = dialog.getRuleData() + + # 验证规则名称是否重复 + existingNames = [r['name'] for r in rules] + if newRuleData['name'] in existingNames: + QMessageBox.warning(self, "错误", f"规则名称 '{newRuleData['name']}' 已存在") + return + + # 创建新规则 + try: + if newRuleData['actionType'] == "set_variable": + ruleId = self.controlManager.createSimpleRule( + newRuleData['name'], + newRuleData['variable'], + newRuleData['operator'], + newRuleData['threshold'], + newRuleData['target'], + newRuleData['value'] + ) + elif newRuleData['actionType'] == "send_alarm": + ruleId = self.controlManager.createAlarmRule( + newRuleData['name'], + newRuleData['variable'], + newRuleData['operator'], + newRuleData['threshold'], + newRuleData['value'] + ) + + # 更新规则的其他属性 + if ruleId: + newRule = self.controlManager.ruleEngine.getRule(ruleId) + if newRule: + newRule.enabled = newRuleData.get('enabled', True) + newRule.cooldownPeriod = newRuleData.get('cooldownPeriod', 0) + newRule.description = newRuleData.get('description', '') + + # 添加变量监控 + self.controlManager.addMonitorVariable(newRuleData['variable']) + + self.refreshRuleTable() + self.logMessage(f"已复制规则: {newRuleData['name']}") + QMessageBox.information(self, "成功", f"规则 '{newRuleData['name']}' 已成功创建") + + except Exception as e: + QMessageBox.warning(self, "错误", f"复制规则失败: {str(e)}") + else: + QMessageBox.warning(self, "错误", "无法获取规则信息") + except Exception as e: + QMessageBox.warning(self, "错误", f"复制规则失败: {str(e)}") + + def enableAllRules(self): + """启用所有规则""" + try: + rules = self.controlManager.getAllRules() + enabledCount = 0 + + for rule in rules: + if not rule['enabled']: + self.controlManager.enableRule(rule['ruleId']) + enabledCount += 1 + + if enabledCount > 0: + self.refreshRuleTable() + self.logMessage(f"已启用 {enabledCount} 个规则") + QMessageBox.information(self, "成功", f"已启用 {enabledCount} 个规则") + else: + QMessageBox.information(self, "提示", "所有规则都已经是启用状态") + + except Exception as e: + QMessageBox.warning(self, "错误", f"批量启用失败: {str(e)}") + + def disableAllRules(self): + """禁用所有规则""" + try: + rules = self.controlManager.getAllRules() + disabledCount = 0 + + reply = QMessageBox.question(self, "确认", + f"确定要禁用所有 {len(rules)} 个规则吗?\n这将停止所有自动化控制功能。") + + if reply == QMessageBox.Yes: + for rule in rules: + if rule['enabled']: + self.controlManager.disableRule(rule['ruleId']) + disabledCount += 1 + + if disabledCount > 0: + self.refreshRuleTable() + self.logMessage(f"已禁用 {disabledCount} 个规则") + QMessageBox.information(self, "成功", f"已禁用 {disabledCount} 个规则") + else: + QMessageBox.information(self, "提示", "所有规则都已经是禁用状态") + + except Exception as e: + QMessageBox.warning(self, "错误", f"批量禁用失败: {str(e)}") + + def reloadControlSystem(self): + """重新加载控制系统(在工程切换后调用)""" + try: + # 重新初始化控制系统 + if not self.controlManager.isInitialized: + self.controlManager.initialize() + else: + # 如果已初始化,重新加载配置 + self.controlManager._loadConfiguration() + + # 刷新界面 + self.refreshRuleTable() + self.refreshStatus() + + print("控制系统重新加载完成") + except Exception as e: + print(f"重新加载控制系统失败: {e}") + + def refreshRuleTable(self): + """刷新规则表格""" + rules = self.controlManager.getAllRules() + self.ruleTable.setRowCount(len(rules)) + + for i, rule in enumerate(rules): + # 规则名称 + nameItem = QTableWidgetItem(rule['name']) + nameItem.setToolTip(f"规则ID: {rule['ruleId']}") + self.ruleTable.setItem(i, 0, nameItem) + + # 描述 + descItem = QTableWidgetItem(rule['description']) + descItem.setToolTip(rule['description']) + self.ruleTable.setItem(i, 1, descItem) + + # 状态指示器 + statusLabel = QLabel() + statusLabel.setObjectName("statusIndicator") + if rule['enabled']: + statusLabel.setText("● 启用") + statusLabel.setProperty("status", "enabled") + statusLabel.setStyleSheet(""" + QLabel { + color: #28A745; + font-weight: bold; + background-color: #D4EDDA; + border-radius: 4px; + padding: 4px 8px; + } + """) + else: + statusLabel.setText("● 禁用") + statusLabel.setProperty("status", "disabled") + statusLabel.setStyleSheet(""" + QLabel { + color: #DC3545; + font-weight: bold; + background-color: #F8D7DA; + border-radius: 4px; + padding: 4px 8px; + } + """) + statusLabel.setAlignment(Qt.AlignCenter) + self.ruleTable.setCellWidget(i, 2, statusLabel) + + # 最后执行时间 + ruleObj = self.controlManager.ruleEngine.getRule(rule['ruleId']) + if ruleObj and ruleObj.lastExecutionTime: + lastExec = ruleObj.lastExecutionTime.strftime("%Y-%m-%d %H:%M:%S") + else: + lastExec = "未执行" + + lastExecItem = QTableWidgetItem(lastExec) + lastExecItem.setToolTip(f"规则最后一次执行时间: {lastExec}") + self.ruleTable.setItem(i, 3, lastExecItem) + + # 操作按钮 - 更紧凑的设计 + toggleBtn = QPushButton("禁用" if rule['enabled'] else "启用") + toggleBtn.setFixedSize(60, 26) # 固定按钮大小 + + if rule['enabled']: + toggleBtn.setStyleSheet(""" + QPushButton { + background-color: #FFC107; + color: #333; + font-size: 10px; + font-weight: bold; + border-radius: 3px; + border: none; + } + QPushButton:hover { background-color: #FFD43B; } + """) + else: + toggleBtn.setStyleSheet(""" + QPushButton { + background-color: #28A745; + color: white; + font-size: 10px; + font-weight: bold; + border-radius: 3px; + border: none; + } + QPushButton:hover { background-color: #34CE57; } + """) + + toggleBtn.clicked.connect(lambda checked, rId=rule['ruleId']: self.toggleRule(rId)) + self.ruleTable.setCellWidget(i, 4, toggleBtn) + + # 设置行高 + self.ruleTable.setRowHeight(i, 36) + + def toggleRule(self, ruleId: str): + """切换规则状态""" + rule = self.controlManager.ruleEngine.getRule(ruleId) + if rule: + if rule.enabled: + self.controlManager.disableRule(ruleId) + self.logMessage(f"已禁用规则: {rule.name}") + else: + self.controlManager.enableRule(ruleId) + self.logMessage(f"已启用规则: {rule.name}") + + # 保存配置 + self.controlManager.saveConfiguration() + + self.refreshRuleTable() + + def refreshStatus(self): + """刷新状态信息""" + status = self.controlManager.getSystemStatus() + + self.statusLabel.setText(f"系统状态: {'运行中' if status['initialized'] else '未初始化'}") + self.rulesCountLabel.setText(f"规则数量: {status['totalRules']} (启用: {status['enabledRules']})") + self.variablesCountLabel.setText(f"监控变量: {status['monitoredVariables']}") + self.lastUpdateLabel.setText(f"最后更新: {status['lastUpdate'][:19]}") + + # 显示变量值 + try: + allVars = self.controlManager.variableMonitor.getAllVariables() + if allVars: + varText = "变量值:\n" + for varName, value in allVars.items(): + varText += f" {varName}: {value}\n" + self.variableValuesLabel.setText(varText.strip()) + else: + self.variableValuesLabel.setText("变量值: 无监控变量") + except Exception as e: + self.variableValuesLabel.setText(f"变量值: 获取失败 ({e})") + + def searchRules(self, searchText: str): + """搜索规则""" + try: + for row in range(self.ruleTable.rowCount()): + item = self.ruleTable.item(row, 0) # 规则名称列 + if item: + # 如果搜索文本为空或者规则名称包含搜索文本,则显示该行 + shouldShow = not searchText or searchText.lower() in item.text().lower() + self.ruleTable.setRowHidden(row, not shouldShow) + except Exception as e: + print(f"搜索规则失败: {e}") + + def logMessage(self, message: str): + """记录日志消息""" + from datetime import datetime + timestamp = datetime.now().strftime("%H:%M:%S") + self.logText.append(f"[{timestamp}] {message}") + + # 限制日志行数 + if self.logText.document().blockCount() > 100: + cursor = self.logText.textCursor() + cursor.movePosition(cursor.Start) + cursor.select(cursor.BlockUnderCursor) + cursor.removeSelectedText() + + def closeEvent(self, event): + """关闭事件""" + self.controlManager.shutdown() + event.accept() + + +# 全局控制界面实例 +_controlWidget = None + + +def getControlWidget(): + """获取控制界面实例""" + global _controlWidget + if _controlWidget is None: + _controlWidget = ControlWidget() + return _controlWidget +class MultiConditionRuleDialog(QDialog): + """多条件规则创建对话框""" + + def __init__(self, parent=None): + super().__init__(parent) + self.setWindowTitle("创建多条件规则") + self.setModal(True) + self.resize(800, 600) + self.setObjectName("multiConditionDialog") + + # 应用父窗口的样式 + if parent and hasattr(parent, 'styleSheet') and parent.styleSheet(): + self.setStyleSheet(parent.styleSheet()) + + self.setupUI() + + def setupUI(self): + """设置界面""" + layout = QVBoxLayout(self) + + # 基本信息 + basicGroup = QGroupBox("基本信息") + basicLayout = QFormLayout(basicGroup) + + self.nameEdit = QLineEdit() + self.nameEdit.setPlaceholderText("输入规则名称") + self.descEdit = QLineEdit() + self.descEdit.setPlaceholderText("输入规则描述") + + self.enabledCheckBox = QCheckBox("启用规则") + self.enabledCheckBox.setChecked(True) + + self.logicCombo = QComboBox() + self.logicCombo.addItems(["and", "or"]) + self.logicCombo.setCurrentText("and") + + self.cooldownSpin = QSpinBox() + self.cooldownSpin.setRange(0, 60000) + self.cooldownSpin.setSuffix(" ms") + self.cooldownSpin.setValue(0) + + basicLayout.addRow("规则名称:", self.nameEdit) + basicLayout.addRow("规则描述:", self.descEdit) + basicLayout.addRow("", self.enabledCheckBox) + basicLayout.addRow("条件逻辑:", self.logicCombo) + basicLayout.addRow("冷却期:", self.cooldownSpin) + + layout.addWidget(basicGroup) + + # 条件设置 + conditionGroup = QGroupBox("触发条件") + conditionLayout = QVBoxLayout(conditionGroup) + + # 条件列表 + self.conditionTable = QTableWidget() + self.conditionTable.setColumnCount(4) + self.conditionTable.setHorizontalHeaderLabels(["变量名", "操作符", "阈值", "操作"]) + self.conditionTable.horizontalHeader().setStretchLastSection(True) + + conditionLayout.addWidget(self.conditionTable) + + # 条件操作按钮 + condBtnLayout = QHBoxLayout() + self.addConditionBtn = QPushButton("添加条件") + self.addConditionBtn.clicked.connect(self.addCondition) + self.removeConditionBtn = QPushButton("删除条件") + self.removeConditionBtn.clicked.connect(self.removeCondition) + + condBtnLayout.addWidget(self.addConditionBtn) + condBtnLayout.addWidget(self.removeConditionBtn) + condBtnLayout.addStretch() + + conditionLayout.addLayout(condBtnLayout) + layout.addWidget(conditionGroup) + + # 动作设置 + actionGroup = QGroupBox("执行动作") + actionLayout = QVBoxLayout(actionGroup) + + # 动作列表 + self.actionTable = QTableWidget() + self.actionTable.setColumnCount(4) + self.actionTable.setHorizontalHeaderLabels(["动作类型", "目标", "值", "操作"]) + self.actionTable.horizontalHeader().setStretchLastSection(True) + + actionLayout.addWidget(self.actionTable) + + # 动作操作按钮 + actionBtnLayout = QHBoxLayout() + self.addActionBtn = QPushButton("添加动作") + self.addActionBtn.clicked.connect(self.addAction) + self.removeActionBtn = QPushButton("删除动作") + self.removeActionBtn.clicked.connect(self.removeAction) + + actionBtnLayout.addWidget(self.addActionBtn) + actionBtnLayout.addWidget(self.removeActionBtn) + actionBtnLayout.addStretch() + + actionLayout.addLayout(actionBtnLayout) + layout.addWidget(actionGroup) + + # 对话框按钮 + buttonLayout = QHBoxLayout() + self.okBtn = QPushButton("确定") + self.okBtn.clicked.connect(self.accept) + self.cancelBtn = QPushButton("取消") + self.cancelBtn.clicked.connect(self.reject) + + buttonLayout.addStretch() + buttonLayout.addWidget(self.okBtn) + buttonLayout.addWidget(self.cancelBtn) + + layout.addLayout(buttonLayout) + + # 初始化一个条件和一个动作 + self.addCondition() + self.addAction() + + def addCondition(self): + """添加条件""" + row = self.conditionTable.rowCount() + self.conditionTable.insertRow(row) + + # 变量名输入框 + varEdit = QLineEdit() + varEdit.setPlaceholderText("变量名") + self.conditionTable.setCellWidget(row, 0, varEdit) + + # 操作符下拉框 + opCombo = QComboBox() + opCombo.addItems([op.value for op in OperatorType]) + self.conditionTable.setCellWidget(row, 1, opCombo) + + # 阈值输入框 + valueEdit = QLineEdit() + valueEdit.setPlaceholderText("阈值") + self.conditionTable.setCellWidget(row, 2, valueEdit) + + # 删除按钮 + delBtn = QPushButton("删除") + delBtn.clicked.connect(lambda: self.removeConditionRow(row)) + self.conditionTable.setCellWidget(row, 3, delBtn) + + def removeCondition(self): + """删除选中的条件""" + currentRow = self.conditionTable.currentRow() + if currentRow >= 0: + self.conditionTable.removeRow(currentRow) + + def removeConditionRow(self, row): + """删除指定行的条件""" + self.conditionTable.removeRow(row) + # 重新设置删除按钮的连接 + for i in range(self.conditionTable.rowCount()): + delBtn = self.conditionTable.cellWidget(i, 3) + if delBtn: + delBtn.clicked.disconnect() + delBtn.clicked.connect(lambda checked, r=i: self.removeConditionRow(r)) + + def addAction(self): + """添加动作""" + row = self.actionTable.rowCount() + self.actionTable.insertRow(row) + + # 动作类型下拉框 + typeCombo = QComboBox() + typeCombo.addItems([act.value for act in ActionType]) + self.actionTable.setCellWidget(row, 0, typeCombo) + + # 目标输入框 + targetEdit = QLineEdit() + targetEdit.setPlaceholderText("目标") + self.actionTable.setCellWidget(row, 1, targetEdit) + + # 值输入框 + valueEdit = QLineEdit() + valueEdit.setPlaceholderText("值") + self.actionTable.setCellWidget(row, 2, valueEdit) + + # 删除按钮 + delBtn = QPushButton("删除") + delBtn.clicked.connect(lambda: self.removeActionRow(row)) + self.actionTable.setCellWidget(row, 3, delBtn) + + def removeAction(self): + """删除选中的动作""" + currentRow = self.actionTable.currentRow() + if currentRow >= 0: + self.actionTable.removeRow(currentRow) + + def removeActionRow(self, row): + """删除指定行的动作""" + self.actionTable.removeRow(row) + # 重新设置删除按钮的连接 + for i in range(self.actionTable.rowCount()): + delBtn = self.actionTable.cellWidget(i, 3) + if delBtn: + delBtn.clicked.disconnect() + delBtn.clicked.connect(lambda checked, r=i: self.removeActionRow(r)) + + def getRuleData(self): + """获取规则数据""" + # 基本信息 + ruleData = { + 'name': self.nameEdit.text().strip(), + 'description': self.descEdit.text().strip(), + 'enabled': self.enabledCheckBox.isChecked(), + 'logicOperator': self.logicCombo.currentText(), + 'cooldownPeriod': self.cooldownSpin.value(), + 'conditions': [], + 'actions': [] + } + + # 收集条件 + for row in range(self.conditionTable.rowCount()): + varEdit = self.conditionTable.cellWidget(row, 0) + opCombo = self.conditionTable.cellWidget(row, 1) + valueEdit = self.conditionTable.cellWidget(row, 2) + + if varEdit and opCombo and valueEdit: + varName = varEdit.text().strip() + operator = opCombo.currentText() + valueText = valueEdit.text().strip() + + if varName and valueText: + try: + # 尝试转换为数值 + if '.' in valueText: + value = float(valueText) + else: + value = int(valueText) + except ValueError: + value = valueText # 保持字符串 + + ruleData['conditions'].append({ + 'variable': varName, + 'operator': operator, + 'value': value + }) + + # 收集动作 + for row in range(self.actionTable.rowCount()): + typeCombo = self.actionTable.cellWidget(row, 0) + targetEdit = self.actionTable.cellWidget(row, 1) + valueEdit = self.actionTable.cellWidget(row, 2) + + if typeCombo and targetEdit and valueEdit: + actionType = typeCombo.currentText() + target = targetEdit.text().strip() + valueText = valueEdit.text().strip() + + if target and valueText: + # 根据动作类型处理值 + if actionType == "set_variable": + try: + if '.' in valueText: + value = float(valueText) + else: + value = int(valueText) + except ValueError: + value = valueText + else: + value = valueText + + ruleData['actions'].append({ + 'actionType': actionType, + 'target': target, + 'value': value + }) + + return ruleData + + def validate(self): + """验证输入""" + if not self.nameEdit.text().strip(): + QMessageBox.warning(self, "验证错误", "规则名称不能为空") + return False + + if not self.getRuleData()['conditions']: + QMessageBox.warning(self, "验证错误", "至少需要一个条件") + return False + + if not self.getRuleData()['actions']: + QMessageBox.warning(self, "验证错误", "至少需要一个动作") + return False + + return True + + def accept(self): + """确定按钮处理""" + if self.validate(): + super().accept() + + +def getControlWidget(): + """获取控制界面实例""" + return ControlWidget() \ No newline at end of file diff --git a/UI/ControlManage/__init__.py b/UI/ControlManage/__init__.py new file mode 100644 index 0000000..d1fa286 --- /dev/null +++ b/UI/ControlManage/__init__.py @@ -0,0 +1,10 @@ +#!/usr/bin/env python3 +# -*- coding: utf-8 -*- + +""" +控制管理界面模块 +""" + +from .ControlWidget import getControlWidget + +__all__ = ['getControlWidget'] \ No newline at end of file diff --git a/UI/Main/Main.py b/UI/Main/Main.py index 1d982f6..fd26770 100644 --- a/UI/Main/Main.py +++ b/UI/Main/Main.py @@ -27,6 +27,7 @@ from utils import Globals from utils.DBModels.InitParameterDB import InitParameterDB from UI.ProfibusWidgets.ProfibusWindow import ProfibusWidgets from UI.ProcedureManager.ProcedureManager import ProcedureManager +from UI.ControlManage.ControlWidget import getControlWidget class CommonHelper: def __init__(self): @@ -34,7 +35,7 @@ class CommonHelper: @staticmethod def readQss(style): - with open(style,"r") as f: + with open(style, "r", encoding='utf-8') as f: return f.read() class MainWindow(QMainWindow): @@ -85,6 +86,7 @@ class MainWindow(QMainWindow): self.projectWidget = ProjectWidgets() self.userWidget = UserWidgets() self.procedureManagerWidget = ProcedureManager() + self.controlWidget = getControlWidget() self.ModbusTcpMasterWidget = VarWidgets('ModbusTcpMaster') self.ModbusTcpSlaveWidget = VarWidgets('ModbusTcpSlave') @@ -144,6 +146,7 @@ class MainWindow(QMainWindow): self.rightWidget.addWidget(self.userWidget) self.rightWidget.addWidget(self.SettingWidget) self.rightWidget.addWidget(self.procedureManagerWidget) + self.rightWidget.addWidget(self.controlWidget) self.rightWidget.widget(1) self.leftWidget.createProject.clicked.connect(lambda: self.exButtonClicked(0)) @@ -152,6 +155,7 @@ class MainWindow(QMainWindow): self.leftWidget.userMag.clicked.connect(lambda: self.exButtonClicked(3)) self.leftWidget.protocolMag.clicked.connect(self.showSetting) self.leftWidget.procedureMag.clicked.connect(lambda: self.initProcedureDB()) + self.leftWidget.controlMag.clicked.connect(lambda: self.showControlSystem()) self.setCentralWidget(self.centralwidget) self.setWindowOpacity(0.995) # 设置窗口透明度 @@ -219,6 +223,14 @@ class MainWindow(QMainWindow): self.procedureManagerWidget.initDB() self.procedureManagerWidget.initUI() self.exButtonClicked(5) + + def showControlSystem(self): + """显示控制系统界面""" + if Globals.getValue('currentPro') == -1: + return + # 重新加载控制系统(确保规则正确加载) + self.controlWidget.reloadControlSystem() + self.exButtonClicked(6) def loadVar(self): file_path, _ = QFileDialog.getOpenFileName(self, '选择Excel文件', '', 'Excel Files (*.xlsx *.xls)') @@ -413,9 +425,12 @@ class MainWindow(QMainWindow): QMessageBox.No) if reply == QMessageBox.Yes: try: + # 保存控制系统配置 + if hasattr(self, 'controlWidget') and self.controlWidget: + self.controlWidget.controlManager.shutdown() Client.close() - except: - pass + except Exception as e: + print(f"关闭程序时出错: {e}") event.accept() else: event.ignore() diff --git a/UI/Main/MainLeft.py b/UI/Main/MainLeft.py index 44d3379..0275ea0 100644 --- a/UI/Main/MainLeft.py +++ b/UI/Main/MainLeft.py @@ -32,6 +32,9 @@ class MainLeft(QWidget): self.procedureMag = QtWidgets.QPushButton(self) self.procedureMag.setObjectName("protocolMag") + self.controlMag = QtWidgets.QPushButton(self) + self.controlMag.setObjectName("controlMag") + self.verticalLayout = QtWidgets.QVBoxLayout(self) self.verticalLayout.setContentsMargins(0, 0, 0, 0) self.verticalLayout.setObjectName("verticalLayout") @@ -52,6 +55,8 @@ class MainLeft(QWidget): self.verticalLayout.addWidget(QtWidgets.QSplitter()) self.verticalLayout.addWidget(self.procedureMag) self.verticalLayout.addWidget(QtWidgets.QSplitter()) + self.verticalLayout.addWidget(self.controlMag) + self.verticalLayout.addWidget(QtWidgets.QSplitter()) self.verticalLayout.setStretch(0, 1) self.verticalLayout.setStretch(1, 4) @@ -65,7 +70,9 @@ class MainLeft(QWidget): self.verticalLayout.setStretch(9, 4) self.verticalLayout.setStretch(10, 2) self.verticalLayout.setStretch(11, 4) - self.verticalLayout.setStretch(12, 40) + self.verticalLayout.setStretch(12, 2) + self.verticalLayout.setStretch(13, 4) + self.verticalLayout.setStretch(14, 35) QtCore.QMetaObject.connectSlotsByName(self) @@ -76,6 +83,7 @@ class MainLeft(QWidget): self.userMag.setText("用户管理") self.protocolMag.setText("通讯配置") self.procedureMag.setText("规程管理") + self.controlMag.setText("控制系统") self.createProject.setIcon(QIcon('./Static/newH.png')) # self.openProject.setIcon(QIcon('./Static/open.png')) @@ -84,8 +92,9 @@ class MainLeft(QWidget): self.userMag.setIcon(QIcon('./Static/userMag.png')) self.protocolMag.setIcon(QIcon('./Static/setting.png')) self.procedureMag.setIcon(QIcon('./Static/procedure.png')) + self.controlMag.setIcon(QIcon('./Static/control.png')) - for btn in [self.createProject, self.varMag, self.trendMag, self.userMag, self.protocolMag, self.procedureMag]: + for btn in [self.createProject, self.varMag, self.trendMag, self.userMag, self.protocolMag, self.procedureMag, self.controlMag]: self.setBtn(btn) # self.openProject.clicked.connect(lambda:self.openProject.setIcon(QIcon('./Static/openH.png'))) @@ -94,6 +103,7 @@ class MainLeft(QWidget): self.trendMag.clicked.connect(lambda:self.trendMag.setIcon(QIcon('./Static/trendH.png'))) self.userMag.clicked.connect(lambda:self.userMag.setIcon(QIcon('./Static/userMagH.png'))) self.protocolMag.clicked.connect(lambda:self.protocolMag.setIcon(QIcon('./Static/settingH.png'))) + self.controlMag.clicked.connect(lambda:self.controlMag.setIcon(QIcon('./Static/controlH.png'))) self.createProject.setChecked(True) self.createProject.setDown(True) @@ -154,6 +164,13 @@ class MainLeft(QWidget): if event.type() == QtCore.QEvent.HoverLeave and not self.protocolMag.isChecked(): self.protocolMag.setIcon(QIcon('./Static/setting.png')) return True + if object == self.controlMag: + if event.type() == QtCore.QEvent.HoverEnter: + self.controlMag.setIcon(QIcon('./Static/controlH.png')) + return True + if event.type() == QtCore.QEvent.HoverLeave and not self.controlMag.isChecked(): + self.controlMag.setIcon(QIcon('./Static/control.png')) + return True return False def clearButton(self): @@ -163,6 +180,7 @@ class MainLeft(QWidget): self.trendMag.setDown(False) self.userMag.setDown(False) self.protocolMag.setDown(False) + self.controlMag.setDown(False) self.createProject.setIcon(QIcon('./Static/new.png')) # self.openProject.setIcon(QIcon('./Static/open.png')) @@ -170,3 +188,4 @@ class MainLeft(QWidget): self.trendMag.setIcon(QIcon('./Static/trend.png')) self.userMag.setIcon(QIcon('./Static/userMag.png')) self.protocolMag.setIcon(QIcon('./Static/setting.png')) + self.controlMag.setIcon(QIcon('./Static/control.png')) diff --git a/UI/Main/MainTop.py b/UI/Main/MainTop.py index 1f6704e..3d7d189 100644 --- a/UI/Main/MainTop.py +++ b/UI/Main/MainTop.py @@ -84,6 +84,13 @@ class MainTop(QtWidgets.QWidget): trendWidget.filterVarList(text) return + # 检查是否在控制系统界面 + if currentIndex == 6: # 控制系统界面 + controlWidget = self.MainWindow.rightWidget.widget(6) + if hasattr(controlWidget, 'searchRules'): + controlWidget.searchRules(text) + return + # 获取当前活动的变量表格 currentVarWidget = self.getCurrentVarTable() @@ -215,6 +222,11 @@ class MainTop(QtWidgets.QWidget): self.searchEdit.setPlaceholderText("当前界面不支持搜索") self.searchEdit.setEnabled(False) + elif currentIndex == 6: + # 控制系统界面 + self.searchEdit.setPlaceholderText("搜索规则...") + self.searchEdit.setEnabled(True) + else: # 其他界面 self.searchEdit.setPlaceholderText("搜索...") diff --git a/control/ControlManager.py b/control/ControlManager.py new file mode 100644 index 0000000..a3a6f5e --- /dev/null +++ b/control/ControlManager.py @@ -0,0 +1,482 @@ +#!/usr/bin/env python3 +# -*- coding: utf-8 -*- + +""" +控制系统管理器 +整合事件系统和规则引擎,提供统一的控制接口 +""" + +import os +import threading +import time +from typing import Dict, List, Any, Optional +from datetime import datetime + +from .EventSystem import getEventBus, getVariableMonitor, VariableEvent, EventType +from .RuleEngine import getRuleEngine, Rule, Condition, Action, OperatorType, ActionType +from utils import Globals + + +class ControlManager: + """控制系统管理器""" + + def __init__(self): + self.eventBus = getEventBus() + self.variableMonitor = getVariableMonitor() + self.ruleEngine = getRuleEngine() + self.isInitialized = False + self._lock = threading.RLock() + + # 动态获取配置路径(基于当前工程) + self.configPath = self._getConfigPath() + + # 确保配置目录存在 + os.makedirs(self.configPath, exist_ok=True) + + def _getConfigPath(self): + """获取当前工程的配置路径""" + currentProject = Globals.getValue('currentPro') + if currentProject: + # 确保工程名是字符串 + projectName = str(currentProject) + # 基于工程的配置路径 + projectPath = os.path.join('project', projectName, 'control') + return projectPath + else: + # 默认配置路径(兼容性) + return "control/config" + + def initialize(self): + """初始化控制系统""" + if self.isInitialized: + return + + try: + # 集成现有的协议管理器 + self._integrateWithProtocolManager() + + # 加载规则配置 + self._loadConfiguration() + + # 启动变量监控 + self.variableMonitor.startMonitoring() + + self.isInitialized = True + print("控制系统初始化成功") + except Exception as e: + print(f"控制系统初始化失败: {e}") + + def _integrateWithProtocolManager(self): + """与现有协议管理器集成""" + protocolManager = Globals.getValue("protocolManage") + if protocolManager: + # 扩展协议管理器,添加变量变化通知 + originalWriteMethod = protocolManager.writeVariableValue + originalReadMethod = getattr(protocolManager, 'readVariableValue', None) + + def enhancedWrite(varName, value, **kwargs): + result = originalWriteMethod(varName, value, **kwargs) + if result: + # 通知变量监控器 + self.variableMonitor.updateVariable(varName, value) + return result + + # 替换写入方法 + protocolManager.writeVariableValue = enhancedWrite + + # 同步现有的变量值到变量监控器 + self._syncExistingVariables(protocolManager) + + print("已集成协议管理器") + + def _syncExistingVariables(self, protocolManager): + """同步现有变量值到变量监控器""" + try: + # 获取所有变量名 + allVarNames = list(protocolManager.getAllVariableNames()) + print(f"发现 {len(allVarNames)} 个变量,开始同步...") + + # 同步变量值 + syncCount = 0 + for varName in allVarNames: + try: + value = protocolManager.readVariableValue(varName) + if value is not None: + # 添加到监控器(不触发事件) + with self.variableMonitor._lock: + self.variableMonitor.variableCache[varName] = value + syncCount += 1 + except Exception as e: + print(f"同步变量 {varName} 失败: {e}") + + print(f"成功同步 {syncCount} 个变量值") + + except Exception as e: + print(f"同步变量失败: {e}") + + def _loadConfiguration(self): + """加载配置""" + # 动态更新配置路径 + self.configPath = self._getConfigPath() + os.makedirs(self.configPath, exist_ok=True) + + rulesFile = os.path.join(self.configPath, "rules.json") + if os.path.exists(rulesFile): + self.ruleEngine.loadRulesFromFile(rulesFile) + print(f"从工程配置加载规则: {rulesFile}") + else: + print(f"工程规则文件不存在,将创建新文件: {rulesFile}") + + def saveConfiguration(self): + """保存配置""" + # 动态更新配置路径 + self.configPath = self._getConfigPath() + os.makedirs(self.configPath, exist_ok=True) + + rulesFile = os.path.join(self.configPath, "rules.json") + self.ruleEngine.saveRulesToFile(rulesFile) + print(f"规则已保存到工程配置: {rulesFile}") + + def switchProject(self, projectName: str = None): + """切换工程时重新加载规则配置""" + try: + # 保存当前工程的规则(如果有的话) + if self.isInitialized and hasattr(self, 'configPath'): + oldConfigPath = self.configPath + # 只有当有规则时才保存到旧工程路径 + if len(self.ruleEngine.rules) > 0: + # 确保旧工程目录存在 + os.makedirs(oldConfigPath, exist_ok=True) + oldRulesFile = os.path.join(oldConfigPath, "rules.json") + self.ruleEngine.saveRulesToFile(oldRulesFile) + print(f"已保存旧工程规则: {oldConfigPath}") + + # 清空当前规则和变量监控 + with self._lock: + self.ruleEngine.rules.clear() + self.variableMonitor.monitorConfig.clear() + self.variableMonitor.variableCache.clear() + + # 更新配置路径(基于新的当前工程) + self.configPath = self._getConfigPath() + + # 加载新工程的规则 + self._loadConfiguration() + + print(f"已切换到工程规则配置: {self.configPath}") + + except Exception as e: + print(f"切换工程规则配置失败: {e}") + + def addMonitorVariable(self, varName: str, threshold: float = 0.1): + """添加监控变量""" + self.variableMonitor.addVariable(varName, threshold) + print(f"已添加监控变量: {varName}") + + def removeMonitorVariable(self, varName: str): + """移除监控变量""" + self.variableMonitor.removeVariable(varName) + print(f"已移除监控变量: {varName}") + + def createSimpleRule(self, name: str, varName: str, operator: str, + threshold: Any, targetVar: str, targetValue: Any) -> str: + """创建简单规则""" + ruleId = f"rule_{int(time.time())}" + + # 创建条件 + condition = Condition( + variable=varName, + operator=OperatorType(operator), + value=threshold + ) + + # 创建动作 + action = Action( + actionType=ActionType.SET_VARIABLE, + target=targetVar, + value=targetValue + ) + + # 创建规则 + rule = Rule( + ruleId=ruleId, + name=name, + description=f"当 {varName} {operator} {threshold} 时,设置 {targetVar} = {targetValue}", + enabled=True, + conditions=[condition], + actions=[action] + ) + + self.ruleEngine.addRule(rule) + + # 自动添加变量到监控器 + self.addMonitorVariable(varName) + + return ruleId + + def createAlarmRule(self, name: str, varName: str, operator: str, + threshold: Any, alarmMessage: str) -> str: + """创建报警规则""" + ruleId = f"alarm_{int(time.time())}" + + condition = Condition( + variable=varName, + operator=OperatorType(operator), + value=threshold + ) + + action = Action( + actionType=ActionType.SEND_ALARM, + target=varName, + value=alarmMessage + ) + + rule = Rule( + ruleId=ruleId, + name=name, + description=f"当 {varName} {operator} {threshold} 时,发送报警", + enabled=True, + conditions=[condition], + actions=[action], + cooldownPeriod=5000 # 5秒冷却期 + ) + + self.ruleEngine.addRule(rule) + + # 自动添加变量到监控器 + self.addMonitorVariable(varName) + + return ruleId + + def createMultiConditionRule(self, name: str, description: str, + conditions: List[Dict], actions: List[Dict], + logicOperator: str = "and", cooldownPeriod: int = 0) -> str: + """创建多条件规则 + + Args: + name: 规则名称 + description: 规则描述 + conditions: 条件列表,格式: [{"variable": "var1", "operator": ">", "value": 10}, ...] + actions: 动作列表,格式: [{"actionType": "set_variable", "target": "var2", "value": 1}, ...] + logicOperator: 逻辑操作符 ("and" 或 "or") + cooldownPeriod: 冷却期(毫秒) + + Returns: + 规则ID + """ + ruleId = f"multi_{int(time.time())}" + + # 创建条件对象列表 + conditionObjs = [] + for condData in conditions: + condition = Condition( + variable=condData['variable'], + operator=OperatorType(condData['operator']), + value=condData['value'] + ) + conditionObjs.append(condition) + # 自动添加变量到监控器 + self.addMonitorVariable(condData['variable']) + + # 创建动作对象列表 + actionObjs = [] + for actionData in actions: + action = Action( + actionType=ActionType(actionData['actionType']), + target=actionData['target'], + value=actionData['value'], + delay=actionData.get('delay', 0) + ) + actionObjs.append(action) + + # 创建规则 + rule = Rule( + ruleId=ruleId, + name=name, + description=description, + enabled=True, + conditions=conditionObjs, + actions=actionObjs, + logicOperator=logicOperator, + cooldownPeriod=cooldownPeriod + ) + + self.ruleEngine.addRule(rule) + + return ruleId + + def createComplexRule(self, ruleData: Dict) -> str: + """创建复杂规则(通用方法) + + Args: + ruleData: 完整的规则数据字典 + + Returns: + 规则ID + """ + ruleId = ruleData.get('ruleId', f"complex_{int(time.time())}") + + # 解析条件 + conditions = [] + for condData in ruleData.get('conditions', []): + condition = Condition( + variable=condData['variable'], + operator=OperatorType(condData['operator']), + value=condData['value'] + ) + conditions.append(condition) + # 自动添加变量到监控器 + self.addMonitorVariable(condData['variable']) + + # 解析动作 + actions = [] + for actionData in ruleData.get('actions', []): + action = Action( + actionType=ActionType(actionData['actionType']), + target=actionData['target'], + value=actionData['value'], + delay=actionData.get('delay', 0) + ) + actions.append(action) + + # 创建规则 + rule = Rule( + ruleId=ruleId, + name=ruleData['name'], + description=ruleData.get('description', ''), + enabled=ruleData.get('enabled', True), + conditions=conditions, + actions=actions, + logicOperator=ruleData.get('logicOperator', 'and'), + cooldownPeriod=ruleData.get('cooldownPeriod', 0) + ) + + self.ruleEngine.addRule(rule) + + return ruleId + + def enableRule(self, ruleId: str): + """启用规则""" + self.ruleEngine.enableRule(ruleId) + + def disableRule(self, ruleId: str): + """禁用规则""" + self.ruleEngine.disableRule(ruleId) + + def deleteRule(self, ruleId: str): + """删除规则""" + self.ruleEngine.removeRule(ruleId) + + def updateRule(self, ruleId: str, ruleData: Dict) -> bool: + """更新规则""" + try: + # 获取现有规则 + existingRule = self.ruleEngine.getRule(ruleId) + if not existingRule: + return False + + # 创建新的条件 + condition = Condition( + variable=ruleData['variable'], + operator=OperatorType(ruleData['operator']), + value=ruleData['threshold'] + ) + + # 创建新的动作 + action = Action( + actionType=ActionType(ruleData['actionType']), + target=ruleData['target'], + value=ruleData['value'], + delay=ruleData.get('delay', 0) + ) + + # 更新规则属性 + existingRule.name = ruleData['name'] + existingRule.description = ruleData['description'] + existingRule.enabled = ruleData.get('enabled', True) + existingRule.conditions = [condition] + existingRule.actions = [action] + existingRule.cooldownPeriod = ruleData.get('cooldownPeriod', 0) + existingRule.logicOperator = ruleData.get('logicOperator', 'and') + + return True + + except Exception as e: + print(f"更新规则失败: {e}") + return False + + def getAllRules(self) -> List[Dict]: + """获取所有规则""" + return [rule.toDict() for rule in self.ruleEngine.getAllRules()] + + def getSystemStatus(self) -> Dict: + """获取系统状态""" + rules = self.ruleEngine.getAllRules() + variables = self.variableMonitor.getAllVariables() + currentProject = Globals.getValue('currentPro') + + return { + 'initialized': self.isInitialized, + 'currentProject': currentProject or '未选择工程', + 'configPath': self.configPath, + 'totalRules': len(rules), + 'enabledRules': len([r for r in rules if r.enabled]), + 'monitoredVariables': len(variables), + 'lastUpdate': datetime.now().isoformat() + } + + def testRule(self, ruleId: str) -> bool: + """测试规则""" + rule = self.ruleEngine.getRule(ruleId) + if rule: + return rule.evaluate(self.variableMonitor) + return False + + def shutdown(self): + """关闭控制系统""" + try: + # 保存当前工程的规则配置 + self.saveConfiguration() + + # 停止变量监控 + self.variableMonitor.stopMonitoring() + + print("控制系统已关闭,规则配置已保存") + except Exception as e: + print(f"关闭控制系统时出错: {e}") + + +# 全局控制管理器实例 +_globalControlManager = None + + +def getControlManager() -> ControlManager: + """获取全局控制管理器""" + global _globalControlManager + if _globalControlManager is None: + _globalControlManager = ControlManager() + return _globalControlManager + + +def resetControlManager(): + """重置全局控制管理器(用于测试)""" + global _globalControlManager + if _globalControlManager: + try: + _globalControlManager.shutdown() + except: + pass + _globalControlManager = None + + # 同时重置事件系统和规则引擎 + from .EventSystem import resetEventSystem + from .RuleEngine import resetRuleEngine + resetEventSystem() + resetRuleEngine() + + +def initializeControlSystem(): + """初始化控制系统""" + controlManager = getControlManager() + controlManager.initialize() + return controlManager \ No newline at end of file diff --git a/control/EventSystem.py b/control/EventSystem.py new file mode 100644 index 0000000..cfe0634 --- /dev/null +++ b/control/EventSystem.py @@ -0,0 +1,289 @@ +#!/usr/bin/env python3 +# -*- coding: utf-8 -*- + +""" +事件驱动系统核心模块 +提供变量监控、事件分发和规则执行功能 +""" + +import time +import threading +import json +from datetime import datetime +from typing import Dict, List, Callable, Any, Optional +from dataclasses import dataclass, asdict +from enum import Enum +import uuid + + +class EventType(Enum): + """事件类型枚举""" + VARIABLE_CHANGE = "variable_change" + DEVICE_STATUS = "device_status" + ALARM = "alarm" + TIMER = "timer" + + +class Priority(Enum): + """优先级枚举""" + LOW = 1 + NORMAL = 2 + HIGH = 3 + CRITICAL = 4 + + +@dataclass +class VariableEvent: + """变量事件数据结构""" + eventId: str + timestamp: datetime + eventType: EventType + variableName: str + oldValue: Any + newValue: Any + quality: str = "good" + priority: Priority = Priority.NORMAL + tags: List[str] = None + + def __post_init__(self): + if self.tags is None: + self.tags = [] + + def toDict(self) -> Dict: + """转换为字典格式""" + data = asdict(self) + data['timestamp'] = self.timestamp.isoformat() + data['eventType'] = self.eventType.value + data['priority'] = self.priority.value + return data + + +class EventBus: + """事件总线 - 负责事件的发布和订阅""" + + def __init__(self): + self._subscribers: Dict[EventType, List[Callable]] = {} + self._lock = threading.RLock() + + def subscribe(self, eventType: EventType, callback: Callable): + """订阅事件""" + with self._lock: + if eventType not in self._subscribers: + self._subscribers[eventType] = [] + self._subscribers[eventType].append(callback) + + def unsubscribe(self, eventType: EventType, callback: Callable): + """取消订阅""" + with self._lock: + if eventType in self._subscribers: + try: + self._subscribers[eventType].remove(callback) + except ValueError: + pass + + def publish(self, event: VariableEvent): + """发布事件""" + with self._lock: + subscribers = self._subscribers.get(event.eventType, []) + for callback in subscribers: + try: + callback(event) + except Exception as e: + print(f"事件处理错误: {e}") + + +class VariableMonitor: + """变量监控器 - 监控变量变化并生成事件""" + + def __init__(self, eventBus: EventBus): + self.eventBus = eventBus + self.variableCache: Dict[str, Any] = {} + self.monitorConfig: Dict[str, Dict] = {} + self.isRunning = False + self.monitorThread: Optional[threading.Thread] = None + self._lock = threading.RLock() + self.scanInterval = 1.0 # 扫描间隔(秒) + + def addVariable(self, varName: str, threshold: float = 0.1, scanInterval: int = 1000): + """添加监控变量""" + with self._lock: + self.monitorConfig[varName] = { + 'threshold': threshold, + 'scanInterval': scanInterval, + 'lastScanTime': 0, + 'enabled': True + } + + def removeVariable(self, varName: str): + """移除监控变量""" + with self._lock: + self.monitorConfig.pop(varName, None) + self.variableCache.pop(varName, None) + + def updateVariable(self, varName: str, newValue: Any, quality: str = "good"): + """更新变量值(由外部协议管理器调用)""" + with self._lock: + if varName not in self.monitorConfig: + return + + oldValue = self.variableCache.get(varName) + + # 检查是否需要触发事件 + if self._shouldTriggerEvent(varName, oldValue, newValue): + event = VariableEvent( + eventId=str(uuid.uuid4()), + timestamp=datetime.now(), + eventType=EventType.VARIABLE_CHANGE, + variableName=varName, + oldValue=oldValue, + newValue=newValue, + quality=quality + ) + + self.variableCache[varName] = newValue + self.eventBus.publish(event) + + def _shouldTriggerEvent(self, varName: str, oldValue: Any, newValue: Any) -> bool: + """判断是否应该触发事件""" + if oldValue is None: + return True + + config = self.monitorConfig.get(varName, {}) + threshold = config.get('threshold', 0.1) + + try: + # 数值类型的变化检测 + if isinstance(oldValue, (int, float)) and isinstance(newValue, (int, float)): + return abs(newValue - oldValue) >= threshold + # 其他类型直接比较 + else: + return oldValue != newValue + except: + return oldValue != newValue + + def getVariableValue(self, varName: str) -> Any: + """获取变量当前值""" + # 首先尝试从协议管理器获取实时值 + from utils import Globals + protocolManager = Globals.getValue("protocolManage") + if protocolManager: + try: + realValue = protocolManager.readVariableValue(varName) + if realValue is not None: + # 更新缓存 + with self._lock: + self.variableCache[varName] = realValue + return realValue + except Exception as e: + print(f"从协议管理器读取变量 {varName} 失败: {e}") + + # 如果协议管理器不可用,返回缓存值 + return self.variableCache.get(varName) + + def getAllVariables(self) -> Dict[str, Any]: + """获取所有变量值""" + return self.variableCache.copy() + + def startMonitoring(self): + """启动变量监控""" + if self.isRunning: + return + + self.isRunning = True + self.monitorThread = threading.Thread(target=self._monitorLoop, daemon=True) + self.monitorThread.start() + print("变量监控器已启动") + + def stopMonitoring(self): + """停止变量监控""" + self.isRunning = False + if self.monitorThread: + self.monitorThread.join(timeout=2) + print("变量监控器已停止") + + def _monitorLoop(self): + """监控循环""" + from utils import Globals + + while self.isRunning: + try: + protocolManager = Globals.getValue("protocolManage") + if protocolManager: + # 检查所有监控的变量 + with self._lock: + for varName in list(self.monitorConfig.keys()): + if not self.monitorConfig[varName].get('enabled', True): + continue + + try: + # 从协议管理器读取最新值 + newValue = protocolManager.readVariableValue(varName) + if newValue is not None: + oldValue = self.variableCache.get(varName) + + # 检查是否需要触发事件 + if self._shouldTriggerEvent(varName, oldValue, newValue): + # 更新缓存 + self.variableCache[varName] = newValue + + # 发布变量变化事件 + event = VariableEvent( + eventId=str(uuid.uuid4()), + timestamp=datetime.now(), + eventType=EventType.VARIABLE_CHANGE, + variableName=varName, + oldValue=oldValue, + newValue=newValue, + quality="good" + ) + + self.eventBus.publish(event) + print(f"变量变化事件: {varName} {oldValue} -> {newValue}") + + except Exception as e: + print(f"监控变量 {varName} 时出错: {e}") + + time.sleep(self.scanInterval) + + except Exception as e: + print(f"变量监控循环出错: {e}") + time.sleep(self.scanInterval) + + +# 全局事件系统实例 +_globalEventBus = None +_globalVariableMonitor = None + + +def getEventBus() -> EventBus: + """获取全局事件总线""" + global _globalEventBus + if _globalEventBus is None: + _globalEventBus = EventBus() + return _globalEventBus + + +def getVariableMonitor() -> VariableMonitor: + """获取全局变量监控器""" + global _globalVariableMonitor, _globalEventBus + if _globalVariableMonitor is None: + if _globalEventBus is None: + _globalEventBus = EventBus() + _globalVariableMonitor = VariableMonitor(_globalEventBus) + return _globalVariableMonitor + + +def resetEventSystem(): + """重置全局事件系统(用于测试和工程切换)""" + global _globalEventBus, _globalVariableMonitor + + # 停止变量监控器 + if _globalVariableMonitor: + try: + _globalVariableMonitor.stopMonitoring() + except: + pass + + # 重置实例 + _globalEventBus = None + _globalVariableMonitor = None \ No newline at end of file diff --git a/control/RuleEngine.py b/control/RuleEngine.py new file mode 100644 index 0000000..ff9fd3e --- /dev/null +++ b/control/RuleEngine.py @@ -0,0 +1,348 @@ +#!/usr/bin/env python3 +# -*- coding: utf-8 -*- + +""" +规则引擎模块 +提供规则定义、条件评估和动作执行功能 +""" + +import json +import threading +import time +from datetime import datetime +from typing import Dict, List, Any, Optional, Callable +from dataclasses import dataclass, asdict +from enum import Enum +import operator +import uuid + +from .EventSystem import VariableEvent, EventType, getEventBus, getVariableMonitor + + +class OperatorType(Enum): + """操作符类型""" + EQUAL = "==" + NOT_EQUAL = "!=" + GREATER = ">" + LESS = "<" + GREATER_EQUAL = ">=" + LESS_EQUAL = "<=" + + +class ActionType(Enum): + """动作类型""" + SET_VARIABLE = "set_variable" + LOG_MESSAGE = "log_message" + SEND_ALARM = "send_alarm" + EXECUTE_SCRIPT = "execute_script" + + +@dataclass +class Condition: + """条件定义""" + variable: str + operator: OperatorType + value: Any + + def evaluate(self, variableMonitor) -> bool: + """评估条件""" + currentValue = variableMonitor.getVariableValue(self.variable) + if currentValue is None: + return False + + try: + opMap = { + OperatorType.EQUAL: operator.eq, + OperatorType.NOT_EQUAL: operator.ne, + OperatorType.GREATER: operator.gt, + OperatorType.LESS: operator.lt, + OperatorType.GREATER_EQUAL: operator.ge, + OperatorType.LESS_EQUAL: operator.le, + } + + opFunc = opMap.get(self.operator) + if opFunc: + return opFunc(currentValue, self.value) + return False + except Exception as e: + print(f"条件评估错误: {e}") + return False + + +@dataclass +class Action: + """动作定义""" + actionType: ActionType + target: str + value: Any + delay: int = 0 # 延迟执行时间(毫秒) + + def execute(self, context: Dict = None): + """执行动作""" + if self.delay > 0: + time.sleep(self.delay / 1000.0) + + try: + if self.actionType == ActionType.SET_VARIABLE: + self._setVariable() + elif self.actionType == ActionType.LOG_MESSAGE: + self._logMessage() + elif self.actionType == ActionType.SEND_ALARM: + self._sendAlarm() + elif self.actionType == ActionType.EXECUTE_SCRIPT: + self._executeScript() + except Exception as e: + print(f"动作执行错误: {e}") + + def _setVariable(self): + """设置变量值""" + # 这里需要与协议管理器集成 + from utils import Globals + protocolManager = Globals.getValue("protocolManage") + if protocolManager: + # 首先检查目标变量是否存在,如果不存在则直接在缓存中创建 + with protocolManager.cacheLock: + if self.target not in protocolManager.variableValueCache: + # 如果变量不存在,直接在缓存中创建 + protocolManager.variableValueCache[self.target] = self.value + print(f"创建并设置变量 {self.target} = {self.value}") + success = True + else: + # 变量存在,尝试通过协议管理器写入 + success = protocolManager.writeVariableValue(self.target, self.value) + if not success: + # 如果写入失败,直接更新缓存 + protocolManager.variableValueCache[self.target] = self.value + print(f"直接更新变量缓存 {self.target} = {self.value}") + success = True + else: + print(f"设置变量 {self.target} = {self.value}, 结果: {success}") + + return success + return False + + def _logMessage(self): + """记录日志""" + timestamp = datetime.now().strftime("%Y-%m-%d %H:%M:%S") + print(f"[{timestamp}] 规则日志: {self.value}") + + def _sendAlarm(self): + """发送报警""" + print(f"报警: {self.target} - {self.value}") + + def _executeScript(self): + """执行脚本""" + print(f"执行脚本: {self.target}") + + +@dataclass +class Rule: + """规则定义""" + ruleId: str + name: str + description: str + enabled: bool + conditions: List[Condition] + actions: List[Action] + logicOperator: str = "and" # and/or + cooldownPeriod: int = 0 # 冷却期(毫秒) + lastExecutionTime: Optional[datetime] = None + + def evaluate(self, variableMonitor) -> bool: + """评估规则条件""" + if not self.enabled or not self.conditions: + return False + + # 检查冷却期 + if self.lastExecutionTime and self.cooldownPeriod > 0: + elapsed = (datetime.now() - self.lastExecutionTime).total_seconds() * 1000 + if elapsed < self.cooldownPeriod: + return False + + # 评估所有条件 + results = [condition.evaluate(variableMonitor) for condition in self.conditions] + + if self.logicOperator.lower() == "and": + return all(results) + elif self.logicOperator.lower() == "or": + return any(results) + else: + return all(results) # 默认使用 and + + def execute(self): + """执行规则动作""" + self.lastExecutionTime = datetime.now() + + for action in self.actions: + try: + action.execute() + except Exception as e: + print(f"规则 {self.name} 动作执行失败: {e}") + + def toDict(self) -> Dict: + """转换为字典格式""" + data = { + 'ruleId': self.ruleId, + 'name': self.name, + 'description': self.description, + 'enabled': self.enabled, + 'logicOperator': self.logicOperator, + 'cooldownPeriod': self.cooldownPeriod, + 'conditions': [ + { + 'variable': c.variable, + 'operator': c.operator.value, + 'value': c.value + } for c in self.conditions + ], + 'actions': [ + { + 'actionType': a.actionType.value, + 'target': a.target, + 'value': a.value, + 'delay': a.delay + } for a in self.actions + ] + } + if self.lastExecutionTime: + data['lastExecutionTime'] = self.lastExecutionTime.isoformat() + return data + + @classmethod + def fromDict(cls, data: Dict) -> 'Rule': + """从字典创建规则""" + # 转换条件 + conditions = [] + for condData in data.get('conditions', []): + condition = Condition( + variable=condData['variable'], + operator=OperatorType(condData['operator']), + value=condData['value'] + ) + conditions.append(condition) + + # 转换动作 + actions = [] + for actionData in data.get('actions', []): + action = Action( + actionType=ActionType(actionData['actionType']), + target=actionData['target'], + value=actionData['value'], + delay=actionData.get('delay', 0) + ) + actions.append(action) + + # 转换最后执行时间 + lastExecutionTime = None + if data.get('lastExecutionTime'): + lastExecutionTime = datetime.fromisoformat(data['lastExecutionTime']) + + return cls( + ruleId=data['ruleId'], + name=data['name'], + description=data['description'], + enabled=data['enabled'], + conditions=conditions, + actions=actions, + logicOperator=data.get('logicOperator', 'and'), + cooldownPeriod=data.get('cooldownPeriod', 0), + lastExecutionTime=lastExecutionTime + ) + + +class RuleEngine: + """规则引擎""" + + def __init__(self): + self.rules: Dict[str, Rule] = {} + self.eventBus = getEventBus() + self.variableMonitor = getVariableMonitor() + self.isRunning = False + self._lock = threading.RLock() + + # 订阅变量变化事件 + self.eventBus.subscribe(EventType.VARIABLE_CHANGE, self._onVariableChange) + + def addRule(self, rule: Rule): + """添加规则""" + with self._lock: + self.rules[rule.ruleId] = rule + + def removeRule(self, ruleId: str): + """移除规则""" + with self._lock: + self.rules.pop(ruleId, None) + + def getRule(self, ruleId: str) -> Optional[Rule]: + """获取规则""" + return self.rules.get(ruleId) + + def getAllRules(self) -> List[Rule]: + """获取所有规则""" + return list(self.rules.values()) + + def enableRule(self, ruleId: str): + """启用规则""" + rule = self.rules.get(ruleId) + if rule: + rule.enabled = True + + def disableRule(self, ruleId: str): + """禁用规则""" + rule = self.rules.get(ruleId) + if rule: + rule.enabled = False + + def _onVariableChange(self, event: VariableEvent): + """处理变量变化事件""" + with self._lock: + for rule in self.rules.values(): + if rule.evaluate(self.variableMonitor): + try: + rule.execute() + print(f"规则 '{rule.name}' 已执行") + except Exception as e: + print(f"规则 '{rule.name}' 执行失败: {e}") + + def saveRulesToFile(self, filePath: str): + """保存规则到文件""" + try: + rulesData = [rule.toDict() for rule in self.rules.values()] + with open(filePath, 'w', encoding='utf-8') as f: + json.dump(rulesData, f, ensure_ascii=False, indent=2) + except Exception as e: + print(f"保存规则失败: {e}") + + def loadRulesFromFile(self, filePath: str): + """从文件加载规则""" + try: + with open(filePath, 'r', encoding='utf-8') as f: + rulesData = json.load(f) + + with self._lock: + self.rules.clear() + for ruleData in rulesData: + rule = Rule.fromDict(ruleData) + self.rules[rule.ruleId] = rule + + print(f"成功加载 {len(self.rules)} 个规则") + except Exception as e: + print(f"加载规则失败: {e}") + + +# 全局规则引擎实例 +_globalRuleEngine = None + + +def getRuleEngine() -> RuleEngine: + """获取全局规则引擎""" + global _globalRuleEngine + if _globalRuleEngine is None: + _globalRuleEngine = RuleEngine() + return _globalRuleEngine + + +def resetRuleEngine(): + """重置全局规则引擎(用于测试和工程切换)""" + global _globalRuleEngine + _globalRuleEngine = None \ No newline at end of file diff --git a/control/__init__.py b/control/__init__.py new file mode 100644 index 0000000..13c9118 --- /dev/null +++ b/control/__init__.py @@ -0,0 +1,19 @@ +#!/usr/bin/env python3 +# -*- coding: utf-8 -*- + +""" +控制系统模块 +提供基于事件驱动的规则引擎功能 +""" + +from .EventSystem import getEventBus, getVariableMonitor +from .RuleEngine import getRuleEngine +from .ControlManager import getControlManager, initializeControlSystem + +__all__ = [ + 'getEventBus', + 'getVariableMonitor', + 'getRuleEngine', + 'getControlManager', + 'initializeControlSystem' +] \ No newline at end of file diff --git a/model/ProjectModel/ProjectManage.py b/model/ProjectModel/ProjectManage.py index ee5cb64..6297023 100644 --- a/model/ProjectModel/ProjectManage.py +++ b/model/ProjectModel/ProjectManage.py @@ -160,6 +160,17 @@ class ProjectManage(object): protocolManage = ProtocolManage() Globals.setValue('protocolManage', protocolManage) Globals.getValue('HistoryWidget').exchangeProject() + + # 通知控制系统工程已切换 + try: + from control.ControlManager import getControlManager + controlManager = getControlManager() + # 确保控制系统已初始化 + if not controlManager.isInitialized: + controlManager.initialize() + controlManager.switchProject() + except Exception as e: + print(f"通知控制系统工程切换失败: {e}") # if Globals.getValue('FFThread').isRunning(): # Globals.getValue('FFThread').reStart()