0720更新 添加规则功能

main
zcwBit 8 months ago
parent 38fc80f819
commit fa003b920e

@ -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;
}

@ -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; 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; 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; 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; background-color: #F5F5F6;
@ -1103,6 +1103,8 @@ QLabel#trendInfoBubble {
color: #333333; color: #333333;
} }
QMessageBox { QMessageBox {
background-color: #ffffff; background-color: #ffffff;
border-radius: 16px; border-radius: 16px;
@ -1138,3 +1140,29 @@ QMessageBox QPushButton:hover {
QMessageBox QPushButton:pressed { QMessageBox QPushButton:pressed {
background: #2980b9; 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;
}

@ -0,0 +1,4 @@
# 这是控制系统图标的占位符文件
# 请替换为实际的PNG图标文件
# 建议尺寸: 26x26 像素
# 颜色: 灰色调,与其他图标风格一致

@ -0,0 +1,4 @@
# 这是控制系统高亮图标的占位符文件
# 请替换为实际的PNG图标文件
# 建议尺寸: 26x26 像素
# 颜色: 蓝色调 (#2277EF),与其他高亮图标风格一致

File diff suppressed because it is too large Load Diff

@ -0,0 +1,10 @@
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
"""
控制管理界面模块
"""
from .ControlWidget import getControlWidget
__all__ = ['getControlWidget']

@ -27,6 +27,7 @@ from utils import Globals
from utils.DBModels.InitParameterDB import InitParameterDB from utils.DBModels.InitParameterDB import InitParameterDB
from UI.ProfibusWidgets.ProfibusWindow import ProfibusWidgets from UI.ProfibusWidgets.ProfibusWindow import ProfibusWidgets
from UI.ProcedureManager.ProcedureManager import ProcedureManager from UI.ProcedureManager.ProcedureManager import ProcedureManager
from UI.ControlManage.ControlWidget import getControlWidget
class CommonHelper: class CommonHelper:
def __init__(self): def __init__(self):
@ -34,7 +35,7 @@ class CommonHelper:
@staticmethod @staticmethod
def readQss(style): def readQss(style):
with open(style,"r") as f: with open(style, "r", encoding='utf-8') as f:
return f.read() return f.read()
class MainWindow(QMainWindow): class MainWindow(QMainWindow):
@ -85,6 +86,7 @@ class MainWindow(QMainWindow):
self.projectWidget = ProjectWidgets() self.projectWidget = ProjectWidgets()
self.userWidget = UserWidgets() self.userWidget = UserWidgets()
self.procedureManagerWidget = ProcedureManager() self.procedureManagerWidget = ProcedureManager()
self.controlWidget = getControlWidget()
self.ModbusTcpMasterWidget = VarWidgets('ModbusTcpMaster') self.ModbusTcpMasterWidget = VarWidgets('ModbusTcpMaster')
self.ModbusTcpSlaveWidget = VarWidgets('ModbusTcpSlave') self.ModbusTcpSlaveWidget = VarWidgets('ModbusTcpSlave')
@ -144,6 +146,7 @@ class MainWindow(QMainWindow):
self.rightWidget.addWidget(self.userWidget) self.rightWidget.addWidget(self.userWidget)
self.rightWidget.addWidget(self.SettingWidget) self.rightWidget.addWidget(self.SettingWidget)
self.rightWidget.addWidget(self.procedureManagerWidget) self.rightWidget.addWidget(self.procedureManagerWidget)
self.rightWidget.addWidget(self.controlWidget)
self.rightWidget.widget(1) self.rightWidget.widget(1)
self.leftWidget.createProject.clicked.connect(lambda: self.exButtonClicked(0)) 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.userMag.clicked.connect(lambda: self.exButtonClicked(3))
self.leftWidget.protocolMag.clicked.connect(self.showSetting) self.leftWidget.protocolMag.clicked.connect(self.showSetting)
self.leftWidget.procedureMag.clicked.connect(lambda: self.initProcedureDB()) self.leftWidget.procedureMag.clicked.connect(lambda: self.initProcedureDB())
self.leftWidget.controlMag.clicked.connect(lambda: self.showControlSystem())
self.setCentralWidget(self.centralwidget) self.setCentralWidget(self.centralwidget)
self.setWindowOpacity(0.995) # 设置窗口透明度 self.setWindowOpacity(0.995) # 设置窗口透明度
@ -220,6 +224,14 @@ class MainWindow(QMainWindow):
self.procedureManagerWidget.initUI() self.procedureManagerWidget.initUI()
self.exButtonClicked(5) self.exButtonClicked(5)
def showControlSystem(self):
"""显示控制系统界面"""
if Globals.getValue('currentPro') == -1:
return
# 重新加载控制系统(确保规则正确加载)
self.controlWidget.reloadControlSystem()
self.exButtonClicked(6)
def loadVar(self): def loadVar(self):
file_path, _ = QFileDialog.getOpenFileName(self, '选择Excel文件', '', 'Excel Files (*.xlsx *.xls)') file_path, _ = QFileDialog.getOpenFileName(self, '选择Excel文件', '', 'Excel Files (*.xlsx *.xls)')
if not file_path: if not file_path:
@ -413,9 +425,12 @@ class MainWindow(QMainWindow):
QMessageBox.No) QMessageBox.No)
if reply == QMessageBox.Yes: if reply == QMessageBox.Yes:
try: try:
# 保存控制系统配置
if hasattr(self, 'controlWidget') and self.controlWidget:
self.controlWidget.controlManager.shutdown()
Client.close() Client.close()
except: except Exception as e:
pass print(f"关闭程序时出错: {e}")
event.accept() event.accept()
else: else:
event.ignore() event.ignore()

@ -32,6 +32,9 @@ class MainLeft(QWidget):
self.procedureMag = QtWidgets.QPushButton(self) self.procedureMag = QtWidgets.QPushButton(self)
self.procedureMag.setObjectName("protocolMag") self.procedureMag.setObjectName("protocolMag")
self.controlMag = QtWidgets.QPushButton(self)
self.controlMag.setObjectName("controlMag")
self.verticalLayout = QtWidgets.QVBoxLayout(self) self.verticalLayout = QtWidgets.QVBoxLayout(self)
self.verticalLayout.setContentsMargins(0, 0, 0, 0) self.verticalLayout.setContentsMargins(0, 0, 0, 0)
self.verticalLayout.setObjectName("verticalLayout") self.verticalLayout.setObjectName("verticalLayout")
@ -52,6 +55,8 @@ class MainLeft(QWidget):
self.verticalLayout.addWidget(QtWidgets.QSplitter()) self.verticalLayout.addWidget(QtWidgets.QSplitter())
self.verticalLayout.addWidget(self.procedureMag) self.verticalLayout.addWidget(self.procedureMag)
self.verticalLayout.addWidget(QtWidgets.QSplitter()) self.verticalLayout.addWidget(QtWidgets.QSplitter())
self.verticalLayout.addWidget(self.controlMag)
self.verticalLayout.addWidget(QtWidgets.QSplitter())
self.verticalLayout.setStretch(0, 1) self.verticalLayout.setStretch(0, 1)
self.verticalLayout.setStretch(1, 4) self.verticalLayout.setStretch(1, 4)
@ -65,7 +70,9 @@ class MainLeft(QWidget):
self.verticalLayout.setStretch(9, 4) self.verticalLayout.setStretch(9, 4)
self.verticalLayout.setStretch(10, 2) self.verticalLayout.setStretch(10, 2)
self.verticalLayout.setStretch(11, 4) 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) QtCore.QMetaObject.connectSlotsByName(self)
@ -76,6 +83,7 @@ class MainLeft(QWidget):
self.userMag.setText("用户管理") self.userMag.setText("用户管理")
self.protocolMag.setText("通讯配置") self.protocolMag.setText("通讯配置")
self.procedureMag.setText("规程管理") self.procedureMag.setText("规程管理")
self.controlMag.setText("控制系统")
self.createProject.setIcon(QIcon('./Static/newH.png')) self.createProject.setIcon(QIcon('./Static/newH.png'))
# self.openProject.setIcon(QIcon('./Static/open.png')) # self.openProject.setIcon(QIcon('./Static/open.png'))
@ -84,8 +92,9 @@ class MainLeft(QWidget):
self.userMag.setIcon(QIcon('./Static/userMag.png')) self.userMag.setIcon(QIcon('./Static/userMag.png'))
self.protocolMag.setIcon(QIcon('./Static/setting.png')) self.protocolMag.setIcon(QIcon('./Static/setting.png'))
self.procedureMag.setIcon(QIcon('./Static/procedure.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.setBtn(btn)
# self.openProject.clicked.connect(lambda:self.openProject.setIcon(QIcon('./Static/openH.png'))) # 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.trendMag.clicked.connect(lambda:self.trendMag.setIcon(QIcon('./Static/trendH.png')))
self.userMag.clicked.connect(lambda:self.userMag.setIcon(QIcon('./Static/userMagH.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.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.setChecked(True)
self.createProject.setDown(True) self.createProject.setDown(True)
@ -154,6 +164,13 @@ class MainLeft(QWidget):
if event.type() == QtCore.QEvent.HoverLeave and not self.protocolMag.isChecked(): if event.type() == QtCore.QEvent.HoverLeave and not self.protocolMag.isChecked():
self.protocolMag.setIcon(QIcon('./Static/setting.png')) self.protocolMag.setIcon(QIcon('./Static/setting.png'))
return True 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 return False
def clearButton(self): def clearButton(self):
@ -163,6 +180,7 @@ class MainLeft(QWidget):
self.trendMag.setDown(False) self.trendMag.setDown(False)
self.userMag.setDown(False) self.userMag.setDown(False)
self.protocolMag.setDown(False) self.protocolMag.setDown(False)
self.controlMag.setDown(False)
self.createProject.setIcon(QIcon('./Static/new.png')) self.createProject.setIcon(QIcon('./Static/new.png'))
# self.openProject.setIcon(QIcon('./Static/open.png')) # self.openProject.setIcon(QIcon('./Static/open.png'))
@ -170,3 +188,4 @@ class MainLeft(QWidget):
self.trendMag.setIcon(QIcon('./Static/trend.png')) self.trendMag.setIcon(QIcon('./Static/trend.png'))
self.userMag.setIcon(QIcon('./Static/userMag.png')) self.userMag.setIcon(QIcon('./Static/userMag.png'))
self.protocolMag.setIcon(QIcon('./Static/setting.png')) self.protocolMag.setIcon(QIcon('./Static/setting.png'))
self.controlMag.setIcon(QIcon('./Static/control.png'))

@ -84,6 +84,13 @@ class MainTop(QtWidgets.QWidget):
trendWidget.filterVarList(text) trendWidget.filterVarList(text)
return return
# 检查是否在控制系统界面
if currentIndex == 6: # 控制系统界面
controlWidget = self.MainWindow.rightWidget.widget(6)
if hasattr(controlWidget, 'searchRules'):
controlWidget.searchRules(text)
return
# 获取当前活动的变量表格 # 获取当前活动的变量表格
currentVarWidget = self.getCurrentVarTable() currentVarWidget = self.getCurrentVarTable()
@ -215,6 +222,11 @@ class MainTop(QtWidgets.QWidget):
self.searchEdit.setPlaceholderText("当前界面不支持搜索") self.searchEdit.setPlaceholderText("当前界面不支持搜索")
self.searchEdit.setEnabled(False) self.searchEdit.setEnabled(False)
elif currentIndex == 6:
# 控制系统界面
self.searchEdit.setPlaceholderText("搜索规则...")
self.searchEdit.setEnabled(True)
else: else:
# 其他界面 # 其他界面
self.searchEdit.setPlaceholderText("搜索...") self.searchEdit.setPlaceholderText("搜索...")

@ -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

@ -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

@ -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

@ -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'
]

@ -161,6 +161,17 @@ class ProjectManage(object):
Globals.setValue('protocolManage', protocolManage) Globals.setValue('protocolManage', protocolManage)
Globals.getValue('HistoryWidget').exchangeProject() 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(): # if Globals.getValue('FFThread').isRunning():
# Globals.getValue('FFThread').reStart() # Globals.getValue('FFThread').reStart()
# return # return

Loading…
Cancel
Save