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