224 lines
9.4 KiB
Python
224 lines
9.4 KiB
Python
|
|
# -*- coding: utf-8 -*-
|
||
|
|
"""
|
||
|
|
事件模块完整演示
|
||
|
|
─────────────────────────────────────────────────────────────────
|
||
|
|
运行: python main.py
|
||
|
|
依赖: pip install PyQt5
|
||
|
|
─────────────────────────────────────────────────────────────────
|
||
|
|
"""
|
||
|
|
|
||
|
|
import logging
|
||
|
|
|
||
|
|
from PyQt5.QtCore import Qt, pyqtSlot
|
||
|
|
from PyQt5.QtGui import QColor, QFont
|
||
|
|
from PyQt5.QtWidgets import (
|
||
|
|
QMainWindow, QWidget,
|
||
|
|
QVBoxLayout, QHBoxLayout, QTextEdit,
|
||
|
|
QLabel, QPushButton, QTableWidget,
|
||
|
|
QHeaderView, QSplitter,
|
||
|
|
QGroupBox, QStatusBar
|
||
|
|
)
|
||
|
|
from uas.event_bus import event_bus
|
||
|
|
from uas.event_model import Event, EventState, make_event
|
||
|
|
# ── 事件模块 ──────────────────────────────────────────────────────
|
||
|
|
from uas.event_type import EventType, event_registry
|
||
|
|
|
||
|
|
# ── 处理器(触发 @on_event 注册)────────────────────────────────
|
||
|
|
from handlers.device_handlers import (
|
||
|
|
DeviceErrorHandler,
|
||
|
|
DeviceTimeoutHandler,
|
||
|
|
EquipmentFaultHandler,
|
||
|
|
)
|
||
|
|
|
||
|
|
logging.basicConfig(
|
||
|
|
level = logging.DEBUG,
|
||
|
|
format = "%(asctime)s [%(levelname)-8s] %(name)s - %(message)s",
|
||
|
|
)
|
||
|
|
logger = logging.getLogger("main")
|
||
|
|
|
||
|
|
|
||
|
|
# ─────────────────────────────────────────────────────────────────
|
||
|
|
# 主窗口
|
||
|
|
# ─────────────────────────────────────────────────────────────────
|
||
|
|
|
||
|
|
class EventMonitorWindow(QMainWindow):
|
||
|
|
|
||
|
|
# 状态颜色映射
|
||
|
|
STATE_COLORS = {
|
||
|
|
EventState.HANDLED: QColor("#4CAF50"),
|
||
|
|
EventState.FAILED: QColor("#f44336"),
|
||
|
|
EventState.CANCELLED: QColor("#FF9800"),
|
||
|
|
EventState.IGNORED: QColor("#9E9E9E"),
|
||
|
|
EventState.PROCESSING: QColor("#2196F3"),
|
||
|
|
EventState.PENDING: QColor("#607D8B"),
|
||
|
|
}
|
||
|
|
|
||
|
|
def __init__(self):
|
||
|
|
super().__init__()
|
||
|
|
self.setWindowTitle("🛰️ 事件监控系统 — PyQt5 EventBus 演示")
|
||
|
|
self.resize(1200, 750)
|
||
|
|
self._setup_bus()
|
||
|
|
self._setup_ui()
|
||
|
|
self._start_demo()
|
||
|
|
|
||
|
|
# ── 总线初始化 ────────────────────────────────────────────────
|
||
|
|
|
||
|
|
def _setup_bus(self):
|
||
|
|
# 注册类处理器
|
||
|
|
event_bus.register_handler(DeviceErrorHandler())
|
||
|
|
event_bus.register_handler(DeviceTimeoutHandler())
|
||
|
|
event_bus.register_handler(EquipmentFaultHandler())
|
||
|
|
|
||
|
|
# 扫描 @on_event 装饰器处理器
|
||
|
|
count = event_bus.scan_decorators()
|
||
|
|
logger.info(f"扫描到 {count} 个装饰器处理器")
|
||
|
|
|
||
|
|
# 动态注册自定义事件类型
|
||
|
|
event_registry.register(
|
||
|
|
"sensor.overflow",
|
||
|
|
category = "device",
|
||
|
|
description = "传感器数据溢出",
|
||
|
|
cancellable = True,
|
||
|
|
)
|
||
|
|
|
||
|
|
# 连接总线信号到 UI 槽
|
||
|
|
event_bus.event_submitted.connect(self._on_event_submitted)
|
||
|
|
event_bus.event_handled.connect(self._on_event_handled)
|
||
|
|
event_bus.event_failed.connect(self._on_event_failed)
|
||
|
|
event_bus.event_ignored.connect(self._on_event_ignored)
|
||
|
|
event_bus.stats_updated.connect(self._on_stats_updated)
|
||
|
|
|
||
|
|
# ── UI 构建 ───────────────────────────────────────────────────
|
||
|
|
|
||
|
|
def _setup_ui(self):
|
||
|
|
central = QWidget()
|
||
|
|
self.setCentralWidget(central)
|
||
|
|
root = QVBoxLayout(central)
|
||
|
|
root.setContentsMargins(6, 6, 6, 6)
|
||
|
|
|
||
|
|
splitter = QSplitter(Qt.Horizontal)
|
||
|
|
|
||
|
|
# ── 左侧:控制面板 ────────────────────────────────────────
|
||
|
|
left = QWidget()
|
||
|
|
left_layout = QVBoxLayout(left)
|
||
|
|
|
||
|
|
# 统计卡片
|
||
|
|
stats_group = QGroupBox("📊 实时统计")
|
||
|
|
stats_layout = QHBoxLayout(stats_group)
|
||
|
|
self._lbl_total = self._stat_label("总计", "0", "#2196F3")
|
||
|
|
self._lbl_handled = self._stat_label("已处理", "0", "#4CAF50")
|
||
|
|
self._lbl_failed = self._stat_label("失败", "0", "#f44336")
|
||
|
|
stats_layout.addWidget(self._lbl_total)
|
||
|
|
stats_layout.addWidget(self._lbl_handled)
|
||
|
|
stats_layout.addWidget(self._lbl_failed)
|
||
|
|
left_layout.addWidget(stats_group)
|
||
|
|
|
||
|
|
# 事件触发按钮
|
||
|
|
btn_group = QGroupBox("🎯 触发事件")
|
||
|
|
btn_layout = QVBoxLayout(btn_group)
|
||
|
|
buttons = [
|
||
|
|
("🔴 设备错误 (E001)", self._fire_device_error),
|
||
|
|
("⚠️ 设备警告 (高温)", self._fire_device_warning),
|
||
|
|
("⏱️ 设备超时", self._fire_device_timeout),
|
||
|
|
("⚙️ 装备故障 (critical)",self._fire_equipment_fault),
|
||
|
|
("🟢 设备连接", self._fire_device_connected),
|
||
|
|
("✅ 装备恢复", self._fire_equipment_recovered),
|
||
|
|
("💓 系统心跳", self._fire_heartbeat),
|
||
|
|
("🔧 自定义事件", self._fire_custom_event),
|
||
|
|
("💥 紧急停机 (E999)", self._fire_emergency),
|
||
|
|
]
|
||
|
|
for text, slot in buttons:
|
||
|
|
btn = QPushButton(text)
|
||
|
|
btn.setMinimumHeight(32)
|
||
|
|
btn.clicked.connect(slot)
|
||
|
|
btn_layout.addWidget(btn)
|
||
|
|
left_layout.addWidget(btn_group)
|
||
|
|
|
||
|
|
# 批量测试
|
||
|
|
batch_btn = QPushButton("🚀 批量压测 (50个事件)")
|
||
|
|
batch_btn.setStyleSheet("background:#673AB7; color:white; font-weight:bold;")
|
||
|
|
batch_btn.clicked.connect(self._fire_batch)
|
||
|
|
left_layout.addWidget(batch_btn)
|
||
|
|
left_layout.addStretch()
|
||
|
|
splitter.addWidget(left)
|
||
|
|
|
||
|
|
# ── 右侧:事件列表 + 日志 ─────────────────────────────────
|
||
|
|
right = QWidget()
|
||
|
|
right_layout = QVBoxLayout(right)
|
||
|
|
|
||
|
|
# 事件表格
|
||
|
|
table_group = QGroupBox("📋 事件记录")
|
||
|
|
table_layout = QVBoxLayout(table_group)
|
||
|
|
self._table = QTableWidget(0, 6)
|
||
|
|
self._table.setHorizontalHeaderLabels(
|
||
|
|
["时间", "事件类型", "来源", "优先级", "状态", "消息"]
|
||
|
|
)
|
||
|
|
self._table.horizontalHeader().setSectionResizeMode(QHeaderView.Stretch)
|
||
|
|
self._table.setAlternatingRowColors(True)
|
||
|
|
self._table.setEditTriggers(QTableWidget.NoEditTriggers)
|
||
|
|
self._table.setSelectionBehavior(QTableWidget.SelectRows)
|
||
|
|
table_layout.addWidget(self._table)
|
||
|
|
right_layout.addWidget(table_group, stretch=2)
|
||
|
|
|
||
|
|
# 日志输出
|
||
|
|
log_group = QGroupBox("📝 处理日志")
|
||
|
|
log_layout = QVBoxLayout(log_group)
|
||
|
|
self._log_edit = QTextEdit()
|
||
|
|
self._log_edit.setReadOnly(True)
|
||
|
|
self._log_edit.setFont(QFont("Consolas", 10))
|
||
|
|
self._log_edit.setStyleSheet(
|
||
|
|
"background:#1e1e1e; color:#d4d4d4; border-radius:4px;"
|
||
|
|
)
|
||
|
|
log_layout.addWidget(self._log_edit)
|
||
|
|
right_layout.addWidget(log_group, stretch=1)
|
||
|
|
|
||
|
|
splitter.addWidget(right)
|
||
|
|
splitter.setSizes([280, 900])
|
||
|
|
root.addWidget(splitter)
|
||
|
|
|
||
|
|
# 状态栏
|
||
|
|
self._statusbar = QStatusBar()
|
||
|
|
self.setStatusBar(self._statusbar)
|
||
|
|
self._statusbar.showMessage("就绪 | EventBus 运行中")
|
||
|
|
|
||
|
|
def _stat_label(self, title: str, value: str, color: str) -> QLabel:
|
||
|
|
lbl = QLabel(f"<center><b style='font-size:22px;color:{color}'>{value}</b>"
|
||
|
|
f"<br><small>{title}</small></center>")
|
||
|
|
lbl.setMinimumWidth(80)
|
||
|
|
lbl.setProperty("_title", title)
|
||
|
|
lbl.setProperty("_color", color)
|
||
|
|
return lbl
|
||
|
|
|
||
|
|
# ── 信号槽 ────────────────────────────────────────────────────
|
||
|
|
|
||
|
|
@pyqtSlot(object)
|
||
|
|
def _on_event_submitted(self, event):
|
||
|
|
self._add_table_row(event)
|
||
|
|
self._log(f"📥 提交 | {event.type_key} | src={event.source}", "#569cd6")
|
||
|
|
self._statusbar.showMessage(
|
||
|
|
f"提交事件: {event.type_key} [{event.event_id[:8]}]"
|
||
|
|
)
|
||
|
|
|
||
|
|
@pyqtSlot(object)
|
||
|
|
def _on_event_handled(self, event):
|
||
|
|
self._update_table_row(event)
|
||
|
|
results_str = " | ".join(str(r) for r in event.results)
|
||
|
|
self._log(
|
||
|
|
f"✅ 完成 | {event.type_key} | {event.elapsed_ms:.1f}ms | {results_str}",
|
||
|
|
"#4ec9b0"
|
||
|
|
)
|
||
|
|
|
||
|
|
@pyqtSlot(object, str)
|
||
|
|
def _on_event_failed(self, event, error):
|
||
|
|
self._update_table_row(event)
|
||
|
|
self._log(f"❌ 失败 | {event.type_key} | {error}", "#f44747")
|
||
|
|
|
||
|
|
@pyqtSlot(object)
|
||
|
|
def _on_event_ignored(self, event):
|
||
|
|
self._update_table_row(event)
|
||
|
|
self._log(f"⚪ 忽略 | {event.type_key} | 无处理器", "#808080")
|
||
|
|
|
||
|
|
@pyqtSlot(int, int, int)
|
||
|
|
def _on_stats_updated(self, total: int, handled: int, failed: int):
|
||
|
|
self._lbl_total.setText(
|
||
|
|
f"<center><b style='font-size:22px;color:#2196F3'>{total}</b>")
|