base_agent/uas/event/main.py

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>")