展示按钮 推送udp

main
risingLee 2026-02-26 16:41:03 +08:00
parent da0e41f475
commit ee351733fe
2 changed files with 309 additions and 2 deletions

181
dashboard_viewer_button.py Normal file
View File

@ -0,0 +1,181 @@
"""
PCM_Report 看板查看器按钮模块
使用方法 ui_main.py 中导入并调用 add_dashboard_button()
"""
import subprocess
import sys
import os
from pathlib import Path
from PySide6.QtWidgets import (
QPushButton, QFileDialog, QMessageBox, QHBoxLayout, QWidget
)
def get_viewer_executable():
"""
获取看板查看器的可执行文件路径
优先查找打包后的 exe否则使用 Python 源文件
"""
# 获取当前程序所在目录
if getattr(sys, 'frozen', False):
# 打包后的环境
current_dir = Path(sys.executable).parent
else:
# 开发环境
current_dir = Path(__file__).parent
# 1. 首先查找打包后的 exe与当前程序同级目录
exe_path = current_dir / "PCM_Viewer.exe"
if exe_path.exists():
return str(exe_path), True # True 表示是 exe
# 2. 查找开发环境的 Python 源文件
dev_path = Path(r"F:\PyPro\PCM_Viewer\main.py")
if dev_path.exists():
return str(dev_path), False # False 表示是 py 文件
# 3. 尝试相对路径(假设两个项目在同一目录下)
relative_path = current_dir.parent / "PCM_Viewer" / "main.py"
if relative_path.exists():
return str(relative_path), False
return None, False
def add_dashboard_button(window, toolbar_layout: QHBoxLayout):
"""
在工具栏添加"打开看板"按钮
Args:
window: MainWindow 实例 (用于日志记录和消息框)
toolbar_layout: 工具栏布局
"""
# 创建按钮
open_viewer_btn = QPushButton("打开看板")
open_viewer_btn.setToolTip("打开看板查看器 (全屏模式)")
# 绑定点击事件
def on_open_viewer():
"""打开 PCM_Viewer 全屏展示"""
# 选择布局文件
file_path, _ = QFileDialog.getOpenFileName(
window,
"选择看板布局文件",
"",
"JSON文件 (*.json)"
)
if not file_path:
return
# 检查文件是否存在
if not Path(file_path).exists():
QMessageBox.warning(window, "警告", "选择的文件不存在!")
return
# 获取查看器可执行文件
viewer_path, is_exe = get_viewer_executable()
if not viewer_path:
QMessageBox.critical(
window,
"错误",
"未找到看板查看器程序!\n"
"请确保 PCM_Viewer.exe 或 main.py 存在。"
)
return
# 启动 PCM_Viewer 子进程
try:
if is_exe:
# 打包后的 exe直接运行
cmd = [viewer_path, file_path, "--fullscreen"]
else:
# Python 源文件,使用 python 解释器运行
cmd = ["python", viewer_path, file_path, "--fullscreen"]
# 使用 subprocess.Popen 启动独立进程
subprocess.Popen(
cmd,
shell=False, # 不使用 shell更安全
stdout=subprocess.DEVNULL,
stderr=subprocess.DEVNULL,
# 设置工作目录为查看器所在目录
cwd=os.path.dirname(viewer_path) if is_exe else None
)
# 记录日志
if hasattr(window, 'logger'):
window.logger.info(f"已启动看板查看器: {viewer_path} {file_path}")
except Exception as e:
QMessageBox.critical(window, "错误", f"启动看板失败: {str(e)}")
if hasattr(window, 'logger'):
window.logger.error(f"启动看板失败: {e}")
open_viewer_btn.clicked.connect(on_open_viewer)
# 添加到工具栏
toolbar_layout.addWidget(open_viewer_btn)
return open_viewer_btn
# 兼容直接复制到 ui_main.py 的函数形式
def open_dashboard_viewer(window):
"""
打开 PCM_Viewer 全屏展示可直接复制到 ui_main.py 使用
"""
# 选择布局文件
file_path, _ = QFileDialog.getOpenFileName(
window,
"选择看板布局文件",
"",
"JSON文件 (*.json)"
)
if not file_path:
return
# 检查文件是否存在
if not Path(file_path).exists():
QMessageBox.warning(window, "警告", "选择的文件不存在!")
return
# 获取查看器可执行文件
viewer_path, is_exe = get_viewer_executable()
if not viewer_path:
QMessageBox.critical(
window,
"错误",
"未找到看板查看器程序!\n"
"请确保 PCM_Viewer.exe 或 main.py 存在。"
)
return
# 启动 PCM_Viewer 子进程
try:
if is_exe:
# 打包后的 exe直接运行
cmd = [viewer_path, file_path, "--fullscreen"]
else:
# Python 源文件,使用 python 解释器运行
cmd = ["python", viewer_path, file_path, "--fullscreen"]
subprocess.Popen(
cmd,
shell=False,
stdout=subprocess.DEVNULL,
stderr=subprocess.DEVNULL,
cwd=os.path.dirname(viewer_path) if is_exe else None
)
if hasattr(window, 'logger'):
window.logger.info(f"已启动看板查看器: {viewer_path} {file_path}")
except Exception as e:
QMessageBox.critical(window, "错误", f"启动看板失败: {str(e)}")
if hasattr(window, 'logger'):
window.logger.error(f"启动看板失败: {e}")

View File

@ -1,6 +1,6 @@
from pathlib import Path
from typing import Dict, Callable, Optional, List, Tuple
import subprocess
from PySide6.QtCore import QTimer, QThread, QObject, Signal, QUrl, QUrlQuery, QDateTime, Qt
from PySide6.QtGui import QDesktopServices, QFont
from PySide6.QtWidgets import (
@ -1219,7 +1219,17 @@ class MainWindow(QMainWindow):
self.power_off_btn.setFixedHeight(36)
self.power_off_btn.clicked.connect(self._power_off_experiment_table)
toolbar_layout.addWidget(self.power_off_btn)
# 数据展示按钮(放在上电和断电之间)
self.data_display_btn = QPushButton("数据展示")
self.data_display_btn.setStyleSheet(
"QPushButton { font-weight:700; font-size:13px; color:white;"
"padding:8px 20px; border-radius:6px; background-color:#2196f3; }"
"QPushButton:hover { background-color:#1976d2; }"
)
self.data_display_btn.setCursor(Qt.PointingHandCursor)
self.data_display_btn.setFixedHeight(36)
self.data_display_btn.clicked.connect(self._open_dashboard_viewer)
toolbar_layout.addWidget(self.data_display_btn)
# 开始记录按钮仅Debug模式显示
self.start_exp_btn = QPushButton("开始记录")
self.start_exp_btn.setStyleSheet(
@ -5483,6 +5493,122 @@ class MainWindow(QMainWindow):
self.statusBar().showMessage(f"❌ 实验台断电异常: {e}", 5000)
QMessageBox.critical(self, "错误", f"实验台断电时发生异常: {str(e)}")
def _open_dashboard_viewer(self):
"""打开 PCM_Viewer 全屏展示"""
# 选择布局文件
file_path, _ = QFileDialog.getOpenFileName(
self,
"选择看板布局文件",
"",
"JSON文件 (*.json)"
)
if not file_path:
return
# 检查文件是否存在
from pathlib import Path
if not Path(file_path).exists():
QMessageBox.warning(self, "警告", "选择的文件不存在!")
return
# 启动 PCM_Viewer 子进程隐藏模式通过UDP通信
try:
self._start_pcm_viewer(file_path)
except Exception as e:
QMessageBox.critical(self, "错误", f"启动看板失败: {str(e)}")
self.logger.error(f"启动看板失败: {e}")
def _start_pcm_viewer(self, layout_path: str, udp_port: int = 9876):
"""启动 PCM_Viewer 并通过 UDP 发送显示命令
Args:
layout_path: 布局文件路径
udp_port: UDP 通信端口
"""
import socket
import json
import subprocess
import time
import os
viewer_path = r"F:\PyPro\PCM_Viewer\dist\PCM_Viewer.exe"
# 检查 PCM_Viewer 是否已在运行通过UDP探测
viewer_running = self._check_viewer_running(udp_port)
if not viewer_running:
# 启动 PCM_Viewer隐藏模式
self.logger.info("启动 PCM_Viewer...")
subprocess.Popen(
[viewer_path, "--hidden"],
shell=False,
stdout=subprocess.DEVNULL,
stderr=subprocess.DEVNULL,
creationflags=subprocess.CREATE_NO_WINDOW # 不显示控制台窗口
)
# 等待 PCM_Viewer 启动
time.sleep(1.5)
# 通过 UDP 发送显示命令
self._send_udp_command({
'action': 'show_and_fullscreen',
'path': layout_path
}, udp_port)
self.logger.info(f"已发送显示命令: {layout_path}")
def _check_viewer_running(self, port: int = 9876) -> bool:
"""检查 PCM_Viewer 是否已在运行
Args:
port: UDP 端口
Returns:
bool: 是否运行中
"""
import socket
try:
sock = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
sock.settimeout(0.5)
# 发送探测命令
sock.sendto(b'ping', ('127.0.0.1', port))
sock.close()
return True
except:
return False
def _send_udp_command(self, command: dict, port: int = 9876):
"""发送 UDP 命令到 PCM_Viewer
Args:
command: 命令字典
port: UDP 端口
"""
import socket
import json
try:
sock = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
message = json.dumps(command, ensure_ascii=False)
sock.sendto(message.encode('utf-8'), ('127.0.0.1', port))
sock.close()
except Exception as e:
self.logger.error(f"发送UDP命令失败: {e}")
raise
def _show_pcm_viewer(self):
"""显示 PCM_Viewer 窗口(用于菜单或按钮调用)"""
self._send_udp_command({'action': 'show'})
def _hide_pcm_viewer(self):
"""隐藏 PCM_Viewer 窗口"""
self._send_udp_command({'action': 'hide'})
def _exit_pcm_viewer(self):
"""退出 PCM_Viewer"""
self._send_udp_command({'action': 'exit'})
def _write_modbus_control_register(self, value: int) -> bool:
"""通过原始Socket直接发送Modbus TCP报文写入控制寄存器1200
完全抛弃pymodbus使用最底层的socket通信模拟Modbus Poll的行为