0720更新 添加规则功能
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;
|
||||
}
|
||||
@ -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']
|
||||
@ -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'
|
||||
]
|
||||
Loading…
Reference in New Issue