From 360cb13b73c1d095de17ed0f6aeb8475b939e7f7 Mon Sep 17 00:00:00 2001 From: yanganjie Date: Tue, 13 Jan 2026 18:31:54 +0800 Subject: [PATCH] =?UTF-8?q?add(=E7=B3=BB=E7=BB=9F=E8=AF=8A=E6=96=AD:=20?= =?UTF-8?q?=E8=AE=BE=E5=A4=87=E6=A3=80=E6=B5=8B=E5=92=8C=E7=B3=BB=E7=BB=9F?= =?UTF-8?q?=E6=A3=80=E6=B5=8B=E3=80=81dals=E4=B8=AD=E6=95=B0=E6=8D=AE?= =?UTF-8?q?=E5=BA=93=E8=B7=AF=E5=BE=84)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- busisness/dals.py | 2 +- config/monitor_config.ini | 4 +- controller/bottom_control_controller.py | 119 +++++++++++++----- ...ce_monitor_thread.py => monitor_thread.py} | 87 +++++++++---- view/widgets/system_diagnostics_dialog.py | 48 +++++-- 5 files changed, 193 insertions(+), 67 deletions(-) rename service/{device_monitor_thread.py => monitor_thread.py} (58%) diff --git a/busisness/dals.py b/busisness/dals.py index 8f20bd4..1588a68 100644 --- a/busisness/dals.py +++ b/busisness/dals.py @@ -16,7 +16,7 @@ class BaseDal: def __init__(self) -> None: """初始化数据访问层,创建数据库连接""" # 假设数据库文件在db目录下 - self.db_dao = SQLiteHandler.get_instance("../db/three.db", max_readers=50, busy_timeout=4000) + self.db_dao = SQLiteHandler.get_instance("db/three.db", max_readers=50, busy_timeout=4000) class ArtifactDal(BaseDal): def __init__(self): diff --git a/config/monitor_config.ini b/config/monitor_config.ini index b5ce263..5ce4dcf 100644 --- a/config/monitor_config.ini +++ b/config/monitor_config.ini @@ -18,5 +18,7 @@ ip = 192.168.250.77 [PLC] ip = 192.168.250.233 -[本地] +# 系统诊断检测的服务 +[OPC] ip = 127.0.0.1 +port = 4840 diff --git a/controller/bottom_control_controller.py b/controller/bottom_control_controller.py index c2971c6..457b2dc 100644 --- a/controller/bottom_control_controller.py +++ b/controller/bottom_control_controller.py @@ -8,7 +8,7 @@ from view.widgets.message_popup_widget import MessagePopupWidget from service.msg_recorder import MessageRecorder from service.msg_query_thread import MsgQueryThread -from service.device_monitor_thread import DeviceMonitorThread +from service.monitor_thread import MonitorThread """ 控制主界面底部的所有按钮, 包括系统诊断、系统中心等的行为。 @@ -22,8 +22,10 @@ class BottomControlController: # 系统诊断弹窗 self.system_diagnostics_dialog = SystemDiagnosticsDialog(self.main_window) - self.current_diagnostics_row = 0 # 系统诊断弹窗的行号,从0开始 - self.current_diagnostics_col = 0 # 系统诊断弹窗的列号,从0开始 + self.diagnostics_device_row = 0 # 系统诊断弹窗的设备检测行号,从0开始 + self.diagnostics_device_col = 0 # 系统诊断弹窗的设备检测列号,从0开始 + self.diagnostics_service_row = 0 # 系统诊断弹窗的服务检测行号,从0开始 + self.diagnostics_service_col = 2 # 系统诊断弹窗的服务检测行号,从2开始 # 系统中心弹窗 self.system_center_dialog = SystemCenterDialog(self.main_window) @@ -42,14 +44,14 @@ class BottomControlController: self.msg_query_thread.start() # 启动线程 # ======================================================================= - # ===================== 设备检测(系统诊断相关) ==================================== - self.device_monitor = DeviceMonitorThread() + # ===================== 检测线程(系统诊断相关) ==================================== + self.device_monitor = MonitorThread() self.device_monitor.start() # ======================================================================= # 绑定主界面底部按钮的信号(如:系统诊断按钮等,点击触发弹窗) self._bind_buttons() - # 绑定弹窗中按钮的信号 + # 绑定弹窗的信号 self._bind_dialog_signals() @Slot(int, list) @@ -83,20 +85,23 @@ class BottomControlController: # 底部预警消息列表按钮 → 触发预警消息列表显示/隐藏 self.bottom_control_widget.warning_list_btn.clicked.connect(self.toggle_system_warning_msg_list) - # 设备检测结果显示(底部的系统诊断按钮处显示) + # 底部系统诊断按钮 → 设备检测结果显示(底部的系统诊断按钮处显示) self.device_monitor.state_result.connect( self.bottom_control_widget.set_system_status, Qt.QueuedConnection) def _bind_dialog_signals(self): - """绑定弹窗按钮的信号""" + """绑定弹窗的信号""" # 系统中心弹窗的按钮信号 self.system_center_dialog.sys_setting_clicked.connect(self.handle_sys_setting) self.system_center_dialog.data_center_clicked.connect(self.handle_data_center) self.system_center_dialog.user_center_clicked.connect(self.handle_user_center) # 系统诊断弹窗的信号 - self.device_monitor.check_finished.connect(self.reset_diagnostics_row_col) # 重置系统诊断的行号和列号 - self.device_monitor.connect_success.connect(self._handle_diagnostics_connect_success) # 设备正常 - self.device_monitor.connect_failed.connect(self._handle_diagnostics_connect_failed) # 设备异常 + self.device_monitor.check_finished.connect(self._reset_diagnostics_device) # 重置系统诊断弹窗中的设备检测 + self.device_monitor.connect_success.connect(self._handle_diagnostics_device_connect_success) # 设备正常 + self.device_monitor.connect_failed.connect(self._handle_diagnostics_device_connect_failed) # 设备异常 + self.device_monitor.check_finished.connect(self._reset_diagnostics_service) # 重置系统诊断弹窗中的服务检测 + self.device_monitor.service_normal.connect(self._handle_diagnostics_service_normal) # 服务正常 + self.device_monitor.service_error.connect(self._handle_diagnostics_service_error) # 服务异常 # ------------------- 系统中心弹窗逻辑------------------- def toggle_system_center_dialog(self): @@ -176,39 +181,93 @@ class BottomControlController: # 设置弹窗位置 self.system_diagnostics_dialog.move(dialog_x, dialog_y) - def get_diagnostics_row_col(self): - """获取系统诊断弹窗的 行号 和 列号, 都从0开始""" - diagnostics_row = self.current_diagnostics_row - diagnostics_col = self.current_diagnostics_col - self.current_diagnostics_col += 1 - if self.current_diagnostics_col == self.system_diagnostics_dialog.max_col: - self.current_diagnostics_row += 1 - self.current_diagnostics_col = 0 - if self.current_diagnostics_row == self.system_diagnostics_dialog.max_row: - self.current_diagnostics_row = 0 - return diagnostics_row, diagnostics_col + def _get_diagnostics_device_row_col(self): + """获取系统诊断弹窗中 设备检测的 行号 和 列号, 都从0开始""" + device_row = self.diagnostics_device_row + device_col = self.diagnostics_device_col + self.diagnostics_device_col += 1 + if self.diagnostics_device_col == self.system_diagnostics_dialog.max_col // 2: # 左边两列是 设备检测 + self.diagnostics_device_row += 1 + self.diagnostics_device_col = 0 + if self.diagnostics_device_row == self.system_diagnostics_dialog.max_row: + self.diagnostics_device_row = 0 + return device_row, device_col - def reset_diagnostics_row_col(self): - """重置系统诊断弹窗的 行号 和 列号 为0""" - self.current_diagnostics_row = 0 - self.current_diagnostics_col = 0 + def _reset_diagnostics_device(self): + """重置系统诊断弹窗中 设备检测""" + # 0、隐藏多余的设备检测框 + # 1、设备检测的行号 和 列号(diagnostics_device_row、 self.diagnostics_device_col) 重置为0 + while True: + if self.diagnostics_device_row == 0 and self.diagnostics_device_col == 0: + break + row, col = self._get_diagnostics_device_row_col() + self.system_diagnostics_dialog.set_selected_hide(row, col) + - def _handle_diagnostics_connect_success(self, device_name:str, delay:int): + def _handle_diagnostics_device_connect_success(self, device_name:str, delay:int): """处理系统诊断弹窗: 设备连接成功 (设备检测正常)""" - row, col = self.get_diagnostics_row_col() + row, col = self._get_diagnostics_device_row_col() self.system_diagnostics_dialog.set_selected_device(row, col, device_name) self.system_diagnostics_dialog.set_ms_value(row, col, delay) if delay <= self.device_monitor.warning_delay: self.system_diagnostics_dialog.set_circle_status(row, col, "normal") else: self.system_diagnostics_dialog.set_circle_status(row, col, "warning") + # 显示检测组件 + self.system_diagnostics_dialog.set_selected_show(row, col) + - def _handle_diagnostics_connect_failed(self, device_name:str): + def _handle_diagnostics_device_connect_failed(self, device_name:str): """处理系统诊断弹窗: 设备检测异常""" - row, col = self.get_diagnostics_row_col() + row, col = self._get_diagnostics_device_row_col() self.system_diagnostics_dialog.set_selected_device(row, col, device_name) self.system_diagnostics_dialog.set_ms_value(row, col, -1) self.system_diagnostics_dialog.set_circle_status(row, col, "error") + # 显示检测组件 + self.system_diagnostics_dialog.set_selected_show(row, col) + + def _get_diagnostics_service_row_col(self): + """获取系统诊断弹窗中 服务检测的 行号 和 列号""" + service_row = self.diagnostics_service_row + service_col = self.diagnostics_service_col + self.diagnostics_service_col += 1 + if self.diagnostics_service_col == self.system_diagnostics_dialog.max_col: # 右边两列是 服务检测 + self.diagnostics_service_row += 1 + self.diagnostics_service_col = 2 + if self.diagnostics_service_row == self.system_diagnostics_dialog.max_row: + self.diagnostics_service_row = 0 + return service_row, service_col + + def _handle_diagnostics_service_normal(self, service_name:str, delay:int): + """处理系统诊断弹窗: 服务检测正常""" + row, col = self._get_diagnostics_service_row_col() + self.system_diagnostics_dialog.set_selected_service(row, col, service_name) + self.system_diagnostics_dialog.set_ms_value(row, col, delay) + if delay <= self.device_monitor.warning_delay: + self.system_diagnostics_dialog.set_circle_status(row, col, "normal") + else: + self.system_diagnostics_dialog.set_circle_status(row, col, "warning") + # 显示检测组件 + self.system_diagnostics_dialog.set_selected_show(row, col) + + def _handle_diagnostics_service_error(self, service_name:str): + """处理系统诊断弹窗: 服务检测异常""" + row, col = self._get_diagnostics_service_row_col() + self.system_diagnostics_dialog.set_selected_service(row, col, service_name) + self.system_diagnostics_dialog.set_ms_value(row, col, -1) + self.system_diagnostics_dialog.set_circle_status(row, col, "error") + # 显示检测组件 + self.system_diagnostics_dialog.set_selected_show(row, col) + + def _reset_diagnostics_service(self): + """重置系统诊断弹窗中 服务检测""" + # 0、隐藏多余的服务检测框 + # 1、设备检测的行号 self.diagnostics_service_row 重置为0, self.diagnostics_service_col 重置为2 + while True: + if self.diagnostics_service_row == 0 and self.diagnostics_service_col == 2: + break + row, col = self._get_diagnostics_service_row_col() + self.system_diagnostics_dialog.set_selected_hide(row, col) # ================== 系统状态消息列表: 显示系统消息 =================== def toggle_system_status_msg_list(self): diff --git a/service/device_monitor_thread.py b/service/monitor_thread.py similarity index 58% rename from service/device_monitor_thread.py rename to service/monitor_thread.py index f42ba46..0a7a104 100644 --- a/service/device_monitor_thread.py +++ b/service/monitor_thread.py @@ -2,10 +2,13 @@ import configparser from typing import Dict, Union from icmplib import ping, ICMPLibError from PySide6.QtCore import QThread, Signal +import socket -class DeviceMonitorThread(QThread): +class MonitorThread(QThread): connect_success = Signal(str, int) # 成功信号:设备名称(str) + 延迟毫秒数(int) connect_failed = Signal(str) # 失败信号:设备名称(str) + service_normal = Signal(str, int) # 服务正常信号: 服务名称(str) + 延迟毫秒数(int) + service_error = Signal(str) # 服务异常信号:服务名称(str) state_result = Signal(str, int) # 全局状态(str: normal/warning/error) + 异常设备数量(int) check_finished = Signal() # 本轮检测结束 @@ -13,12 +16,12 @@ class DeviceMonitorThread(QThread): super().__init__(parent) # 初始化你的原有配置参数,完全不变 self.config_path = config_path - self.ping_timeout_ms = 2000 + self.ping_timeout_ms = 1000 self.ping_count = 2 self.check_interval = 10 # 默认检测间隔10秒 self.warning_delay = 30 # 默认的警告的网络延迟(单位: ms) self.is_stop = False # 线程停止标志位 - self.force_check = False # 立即检测标志 + self.force_check = False # 立即检测标志 def _ping_device(self, ip: str, timeout_ms: int = 2000) -> Union[int, None]: """设备连接状态检测""" @@ -42,7 +45,8 @@ class DeviceMonitorThread(QThread): def _read_device_config(self) -> Dict[str, str]: """读取ini配置文件""" - device_dict = {} + device_dict = {} # 存储设备名和设备中的ip + port_dict = {} # 存储服务名和服务的端口号 config = configparser.ConfigParser() try: config.read(self.config_path, encoding="utf-8") @@ -50,15 +54,37 @@ class DeviceMonitorThread(QThread): if "ip" in config[section]: device_ip = config[section]["ip"].strip() device_dict[section] = device_ip + if "port" in config[section]: + service_port = config[section]["port"].strip() + port_dict[section] = int(service_port) + except ValueError: + print(f"配置文件[{self.config_path}]: [{section}] 下的端口值[{service_port}]格式错误") except FileNotFoundError: print(f"配置文件[{self.config_path}]不存在,请检查路径!") except Exception as e: print(f"读取配置文件失败: {str(e)}") - return device_dict + return device_dict, port_dict + + def check_service_status(self, ip: str, port: int, timeout: float = 1.0) -> bool: + """ + 检测指定IP的指定端口的服务是否正常 + :param ip: 目标IP地址 + :param port: 目标端口号 + :param timeout: 超时时间 + :return: True=服务正常, False=服务异常 + """ + try: + sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM) + sock.settimeout(timeout) + result = sock.connect_ex((ip, port)) # 0=连接成功,其他=失败 + sock.close() + return result == 0 + except Exception as e: + return False def run(self) -> None: """网络设备检测""" - print("✅ 设备检测线程已启动") + print("设备/服务检测线程已启动") self.is_stop = False # 重置停止标志位 while True: # 线程退出 @@ -66,26 +92,39 @@ class DeviceMonitorThread(QThread): break # 批量检测所有设备 - device_dict = self._read_device_config() - check_result = {} # 所有设备的检测结果 + device_dict, port_dict = self._read_device_config() + check_result = {} # 所有的检测结果 if not device_dict: - print("设备检测线程: 未读取到任何设备配置!") + print("设备/服务检测线程: 未读取到任何配置或配置异常!") else: for device_name, device_ip in device_dict.items(): if self.is_stop: break delay_ms = self._ping_device(device_ip, self.ping_timeout_ms) - if delay_ms is not None: - self.connect_success.emit(device_name, delay_ms) # 发送成功信号 - else: - self.connect_failed.emit(device_name) # 发送失败信号 + if device_name not in port_dict: # 只是设备而不是服务 + if delay_ms is not None: + self.connect_success.emit(device_name, delay_ms) # 发送成功信号 + else: + self.connect_failed.emit(device_name) # 发送失败信号 + else: # 服务 + service_name = device_name # 此时,设备名就是服务名 + if delay_ms is None: + self.service_error.emit(service_name) + else: + status = self.check_service_status(device_ip, port_dict[device_name]) + if status: # 服务正常 + self.service_normal.emit(service_name, delay_ms) + else: # 服务异常 + self.service_error.emit(service_name) + delay_ms = None # 标志服务异常 + check_result[device_name] = delay_ms # 本轮检测完成 self.check_finished.emit() - # 设备状态统计 - self._calc_device_state(check_result) + # 状态统计 + self._calc_state(check_result) # 等待指定间隔后,继续下一次检测 @@ -101,10 +140,10 @@ class DeviceMonitorThread(QThread): break self.msleep(sleep_slice_ms) - # ============ 设备状态统计 ============ - def _calc_device_state(self, check_result:dict): - offline_count = 0 # 离线设备数 - delay_warn_count = 0# 延迟超30ms的设备数 + # ============ 状态统计 ============ + def _calc_state(self, check_result:dict): + offline_count = 0 # 离线(异常)数 + delay_warn_count = 0# 延迟超30ms的设备/服务数 # 遍历结果统计 for delay in check_result.values(): if delay is None: @@ -113,13 +152,13 @@ class DeviceMonitorThread(QThread): delay_warn_count += 1 # 按优先级判定全局状态 if offline_count > 0: - # 设备异常 → error + 离线(异常)数量 + # 设备/服务异常 → error + 离线(异常)数量 self.state_result.emit("error", offline_count) elif delay_warn_count > 0: - # 全部在线但有延迟超标 → warning + 延迟超标数量 + # 全部正常但有延迟超标 → warning + 延迟超标数量 self.state_result.emit("warning", delay_warn_count) else: - # 全部在线且延迟都<30ms → normal + 0 + # 全部正常且延迟都<30ms → normal + 0 self.state_result.emit("normal", 0) # ============ 修改检测间隔 ============ @@ -140,9 +179,9 @@ class DeviceMonitorThread(QThread): def stop_thread(self): self.is_stop = True self.wait() - print("设备检测线程已退出") + print("设备/服务检测线程已退出") # ============ 立即检测============ def force_immediate_check(self): - """马上执行新一轮设备检测""" + """马上执行新一轮检测""" self.force_check = True diff --git a/view/widgets/system_diagnostics_dialog.py b/view/widgets/system_diagnostics_dialog.py index f7c5b2c..ff6ffd5 100644 --- a/view/widgets/system_diagnostics_dialog.py +++ b/view/widgets/system_diagnostics_dialog.py @@ -173,13 +173,13 @@ class CustomDropdown(QWidget): for i in range(self.list_widget.count()): self.list_widget.item(i).setFont(font) - # 获取当前选中的设备名 - def get_selected_device(self): + # 获取当前选中的检测框的名称 + def get_selected_name(self): return self.result_label.text() - # 设置选中的设备名 - def set_selected_device(self, device_name:str): - self.result_label.setText(device_name) + # 设置当前选中的检测框的名称 + def set_selected_name(self, name:str): + self.result_label.setText(name) class SystemDiagnosticsDialog(QDialog): def __init__(self, parent=None): @@ -204,8 +204,9 @@ class SystemDiagnosticsDialog(QDialog): self.setFixedSize(self.bg_pixmap.size()) # 网格布局(8行4列小框) - grid_layout = QGridLayout(self) - grid_layout.setContentsMargins(24, 28, 20, 24) + self.grid_layout = QGridLayout(self) + self.grid_layout.setContentsMargins(28, 28, 20, 24) + self.grid_layout.setAlignment(Qt.AlignLeft | Qt.AlignTop) # 图片路径(替换为实际路径) box_image_path = ImagePaths.SYSTEM_DIAGNOSTICS_BOX @@ -225,6 +226,7 @@ class SystemDiagnosticsDialog(QDialog): for col in range(self.max_col): box_container = QWidget() box_container.setObjectName(f"box_{row}_{col}") + box_container.setFixedSize(156, 50) box_container.setStyleSheet( f""" background-image: url("{box_image_path}"); @@ -247,7 +249,7 @@ class SystemDiagnosticsDialog(QDialog): # ========== 自定义下拉框(支持获取设备名) ========== led_dropdown = CustomDropdown( - options=["LED1", "LED2", "LED3"], arrow_img_path=dropdown_arrow_path + options=["----", "LED1", "LED2", "LED3"], arrow_img_path=dropdown_arrow_path ) # ========== 秒数输入框(获取毫秒值) ========== @@ -290,7 +292,10 @@ class SystemDiagnosticsDialog(QDialog): # box_layout.addItem(spacer3) box_layout.addWidget(ms_container) - grid_layout.addWidget(box_container, row, col) + self.grid_layout.addWidget(box_container, row, col) + + # 初始状态为隐藏 + box_container.hide() def paintEvent(self, event): """重写绘制事件,手动在透明背景上绘制图片""" @@ -318,7 +323,7 @@ class SystemDiagnosticsDialog(QDialog): """获取指定行列的选中设备名, 行号和列号都从0开始""" box = self.findChild(QWidget, f"box_{row}_{col}") if box and hasattr(box, "dropdown"): - return box.dropdown.get_selected_device() + return box.dropdown.get_selected_name() return None # ========== 对外接口:设置选中的设备名 ========== @@ -326,7 +331,14 @@ class SystemDiagnosticsDialog(QDialog): """设置指定行列的选中设备名, 行号和列号都从0开始""" box = self.findChild(QWidget, f"box_{row}_{col}") if box and hasattr(box, "dropdown"): - return box.dropdown.set_selected_device(device_name) + box.dropdown.set_selected_name(device_name) + + # ========== 对外接口:设置选中的服务名 ========== + def set_selected_service(self, row, col, service_name:str): + """设置指定行列的选中服务名, 行号和列号都从0开始""" + box = self.findChild(QWidget, f"box_{row}_{col}") + if box and hasattr(box, "dropdown"): + box.dropdown.set_selected_name(service_name) # ========== 对外接口:获取毫秒值 ========== def get_ms_value(self, row, col): @@ -351,6 +363,20 @@ class SystemDiagnosticsDialog(QDialog): if box and hasattr(box, "ms_edit"): box.ms_edit.setText(f"{ms}ms") + # ========== 对外接口:设置显示检测组件 ========== + def set_selected_show(self, row, col): + """设置指定行列的检测组件显示""" + box = self.findChild(QWidget, f"box_{row}_{col}") + if box and box.isHidden(): + box.show() + self.grid_layout.update() + + # ========== 对外接口:设置隐藏检测组件 ========== + def set_selected_hide(self, row, col): + """设置指定行列的检测组件隐藏""" + box = self.findChild(QWidget, f"box_{row}_{col}") + if box and box.isVisible(): + box.hide() if __name__ == "__main__": app = QApplication(sys.argv)