#!/usr/bin/env python # -*- coding: utf-8 -*- ''' # @Time : 2025/9/10 11:29 # @Author : reenrr # @Description : 通过tcp连接获取信息,并显示在界面上(版本1) # @File : main.py ''' import sys from PySide6.QtWidgets import ( QApplication, QWidget, QVBoxLayout, QHBoxLayout, QLabel, QFrame, QSizePolicy, QPushButton ) from PySide6.QtCore import Qt, QTimer, QPoint, QSize, Slot from PySide6.QtGui import QFont, QPainter, QColor, QPen, QIcon from datetime import datetime from PySide6.QtNetwork import QTcpSocket, QAbstractSocket import json import os # ----------- # 参数配置 # ----------- tcp_server_host = "127.0.0.1" tcp_server_port = 8888 class StatusMonitor(QWidget): """ 中交三航精准布料浇筑要料系统 - 主界面类(深色主题) 使用TCP进行数据传输(客户端模型,与TCP服务器通信) """ def __init__(self, parent=None): """构造函数:初始化主界面的UI布局、控件和定时器""" super().__init__(parent=parent) self.is_running = False # 系统运行状态标记 self.current_datetime = self.get_current_time() # 当前日期时间 # 缓存服务端发送的最新JSON数据 self.latest_json_data = {} # --------------- # TCP客户端核心配置 # --------------- self.tcp_socket = QTcpSocket(self) # TCP socket实例 self.tcp_server_host = tcp_server_host self.tcp_server_port = tcp_server_port self.is_tcp_connected = False # TCP连接状态标记 # 绑定TCP信号与槽(事件驱动) self._bind_tcp_signals() # 窗口基础设置 self.setWindowTitle("中交三航精准布料浇筑要料系统") self.setGeometry(100, 100, 850, 500) # 设置窗口位置和大小 self.setStyleSheet("background-color: #121212;") # 窗口背景设为深黑色 # 初始化主布局(垂直布局) self.mainLayout = QVBoxLayout(self) self.mainLayout.setContentsMargins(10, 40, 10, 10) # 上边距留空用于显示日期 self.mainLayout.setSpacing(10) # 相邻控件的间距 # 初始化界面组件 self._init_title_label() self._init_status_container() self._init_timers() # ---------------- # 客户端自动后自动连接服务端 # ---------------- print(f"客户端启动,自动连接服务端{self.tcp_server_host}:{self.tcp_server_port}...") self.tcp_socket.connectToHost(self.tcp_server_host, self.tcp_server_port) def _bind_tcp_signals(self): """绑定TCP socket的核心信号(连接、断开、接收数据、错误)""" # 连接成功信号 self.tcp_socket.connected.connect(self._on_tcp_connected) # 断开连接信号 self.tcp_socket.disconnected.connect(self._on_tcp_disconnected) # 接收数据信号(有新数据时触发) self.tcp_socket.readyRead.connect(self._on_tcp_data_received) # 错误信号(连接/通信出错时触发) self.tcp_socket.errorOccurred.connect(self._on_tcp_error) # ---------------- # 界面初始化函数 # ---------------- def _init_title_label(self): """初始化系统标题标签""" titleLabel = QLabel("中交三航精准布料浇筑要料系统") titleLabel.setStyleSheet(""" QLabel { color: #00FF9D; font-size: 20px; font-weight: bold; } """) titleLabel.setAlignment(Qt.AlignmentFlag.AlignCenter) titleLabel.setFont(QFont("Microsoft YaHei", 24, QFont.Bold)) self.mainLayout.addWidget(titleLabel) def _init_status_container(self): """初始化核心状态监控容器(包含状态组和操作按钮)""" self.bigContainer = QFrame() self.bigContainer.setStyleSheet(""" QFrame { background-color: #1E1E1E; border: 2px solid #333333; border-radius: 8px; } """) self.bigContainer.setSizePolicy( QSizePolicy.Policy.Expanding, QSizePolicy.Policy.Expanding ) containerLayout = QVBoxLayout(self.bigContainer) containerLayout.setContentsMargins(20, 20, 20, 20) self._init_status_groups(containerLayout) self._init_operation_buttons(containerLayout) self.mainLayout.addWidget(self.bigContainer, 1) def _init_status_groups(self, parent_layout): """初始化左右信息(各包含5个状态项)""" statusWidget = QWidget() statusLayout = QHBoxLayout(statusWidget) statusLayout.setContentsMargins(0, 0, 0, 0) statusLayout.setSpacing(30) # 减小中间空白间距(原30) leftGroup = QWidget() leftLayout = QVBoxLayout(leftGroup) leftLayout.setSpacing(15) leftLayout.setContentsMargins(30, 0, 0, 0) # 左边组左内边距设为20,增加左边留白 rightGroup = QWidget() rightLayout = QVBoxLayout(rightGroup) rightLayout.setSpacing(15) rightLayout.setContentsMargins(0, 0, 30, 0) # 右边组右内边距设为20,增加右边留白(若需左边也留白,可设左内边距) # 左边5个状态项及对应初始值 leftStatusInfo = [ {"name": "任务单号", "value": "", "api_field": "task_id"}, {"name": "工程名称", "value": "", "api_field": "project_name"}, {"name": "配比号", "value": "", "api_field": "produce_mix_id"} ] # 右边5个状态项及对应初始值 rightStatusInfo = [ {"name": "要料状态", "value": "", "api_field": "flag"}, {"name": "砼强度", "value": "", "api_field": "beton_grade"}, {"name": "要料方量", "value": "", "api_field": "adjusted_volume"}, ] self.statusWidgets = [] # 处理左边状态项 for info in leftStatusInfo: statusItem = self._create_status_item(info) leftLayout.addWidget(statusItem) # 处理右边状态项 for info in rightStatusInfo: statusItem = self._create_status_item(info) rightLayout.addWidget(statusItem) statusLayout.addWidget(leftGroup) statusLayout.addStretch(0) # 减小中间空白比例(原1) statusLayout.addWidget(rightGroup) parent_layout.addWidget(statusWidget) def _create_status_item(self, info): """创建单个状态项""" statusItem = QFrame() statusItem.setStyleSheet(""" QFrame { background-color: #2D2D2D; border: 1px solid #444444; border-radius: 6px; padding: 10px; } """) statusItem.setFixedHeight(80) statusItem.setFixedWidth(320) # 统一加长状态项宽度(原无固定宽度) itemLayout = QHBoxLayout(statusItem) itemLayout.setContentsMargins(10, 5, 10, 5) # 状态指示灯 indicator = QLabel() indicator.setFixedSize(20, 20) indicator.setStyleSheet(""" QLabel { background-color: #9E9E9E; border-radius: 10px; border: 2px solid #555555; } """) # 状态名称标签 nameLabel = QLabel(info["name"]) nameLabel.setFixedWidth(100) # 加宽名称标签(原90) nameLabel.setAlignment(Qt.AlignmentFlag.AlignCenter) nameLabel.setStyleSheet("font-size: 14px; color: #FFFFFF;") nameLabel.setFont(QFont("Microsoft YaHei", 12)) # 状态值标签 valueLabel = QLabel(info["value"]) valueLabel.setStyleSheet(""" QLabel { font-size: 16px; font-weight: bold; color: #FFFFFF; background-color: #2D2D2D; border: none; padding: 0px; } """) valueLabel.setAlignment(Qt.AlignmentFlag.AlignCenter) valueLabel.setMinimumWidth(150) # 加宽值标签(原80) itemLayout.addWidget(indicator) itemLayout.addSpacing(10) itemLayout.addWidget(nameLabel) itemLayout.addStretch() itemLayout.addWidget(valueLabel) self.statusWidgets.append({ 'indicator': indicator, 'nameLabel': nameLabel, 'valueLabel': valueLabel, 'status': False, 'initial_value': info["value"], 'api_field': info["api_field"] }) return statusItem def _init_operation_buttons(self, parent_layout): """初始化操作按钮(下料完成/生产异常/生产取消)""" buttonContainer = QWidget() buttonLayout = QHBoxLayout(buttonContainer) buttonLayout.setContentsMargins(0, 20, 0, 0) buttonLayout.setSpacing(30) # 按钮图标(需替换为实际图标路径) error_icon_path = "img.png" cancel_icon_path = "img.png" self.errorButton = QPushButton("生产异常") self.cancelButton = QPushButton("生产取消") # 设置按钮图标 self.errorButton.setIcon(QIcon(error_icon_path)) self.cancelButton.setIcon(QIcon(cancel_icon_path)) # 设置图标大小 self.errorButton.setIconSize(QSize(20, 20)) self.cancelButton.setIconSize(QSize(20, 20)) button_base_style = """ QPushButton { font-size: 16px; font-weight: bold; padding: 10px 20px; border-radius: 15px; border: 1px solid; min-width: 100px; } QPushButton:hover { opacity: 0.9; } QPushButton:pressed { opacity: 0.8; } """ self.errorButton.setStyleSheet(button_base_style + """ QPushButton { background-color: #E65100; color: white; border-color: #BF360C; } """) self.cancelButton.setStyleSheet(button_base_style + """ QPushButton { background-color: #C62828; color: white; border-color: #8E0000; } """) button_font = QFont("Microsoft YaHei", 12, QFont.Bold) self.errorButton.setFont(button_font) self.cancelButton.setFont(button_font) self.errorButton.clicked.connect(self.on_error_clicked) self.cancelButton.clicked.connect(self.on_cancel_clicked) # 初始禁用“停止”和“异常”按钮(未连接时不可用) self.errorButton.setDisabled(True) self.cancelButton.setDisabled(True) buttonLayout.addStretch(1) buttonLayout.addWidget(self.errorButton) buttonLayout.addWidget(self.cancelButton) buttonLayout.addStretch(1) parent_layout.addWidget(buttonContainer) def _init_timers(self): """初始化定时器(时间更新+状态模拟)""" # 时间更新定时器(每秒更新一次) self.time_timer = QTimer() self.time_timer.timeout.connect(self.update_time) self.time_timer.start(1000) def get_current_time(self): """获取格式化的当前时间""" return datetime.now().strftime('%Y-%m-%d %H:%M:%S') def get_timestamp(self): """获取当前时间戳(秒级)""" return datetime.now().strftime('%Y%m%d_%H%M%S_%f')[:-3] def update_time(self): """更新时间显示并触发重绘""" self.current_datetime = self.get_current_time() self.update() # 触发paintEvent重绘 def paintEvent(self, event): """重写绘画事件,在右上角绘制日期时间文本""" super().paintEvent(event) # 调用父类方法保持原有绘制 # 创建QPainter对象 painter = QPainter(self) painter.setRenderHint(QPainter.RenderHint.TextAntialiasing) # 文本抗锯齿 # 设置字体 font = QFont("Arial", 12, QFont.Bold) painter.setFont(font) # 设置文本颜色 painter.setPen(QColor("#00FF9D")) # 计算文本位置(右上角,留出边距) text = f"🕒 {self.current_datetime}" text_rect = painter.boundingRect(self.rect(), Qt.AlignmentFlag.AlignRight | Qt.AlignmentFlag.AlignTop, text) x = self.width() - text_rect.width() - 15 # 右边距15px y = 15 # 上边距15px # 绘制文本 painter.drawText(x, y + text_rect.height(), text) # -------------------- # 清空界面信息的通用方法 # -------------------- def _clear_ui_info(self): """清空所有状态项的显示内容,并并设置指示灯颜色""" for widget in self.statusWidgets: widget['valueLabel'].setText("") # 指示灯设为初始灰色(与_create_status_item中初始样式一致) widget['indicator'].setStyleSheet(""" QLabel { background-color: #9E9E9E; border-radius: 10px; border: 2px solid #555555; } """) print("ℹ️ 界面信息已清空") # ------------------ # TCP客户端核心功能 # ------------------ @Slot() def _on_tcp_connected(self): """TCP连接成功回调""" self.is_tcp_connected = True self.is_running = True print(f"TCP连接成功:{self.tcp_server_host}:{self.tcp_server_port}") # 连接成功后,向服务器发送“请求初始数据”指令 self._send_tcp_request("get_initial_data") # 更新按钮状态:启用“下料完成”“生产异常”“生产取消” self.errorButton.setDisabled(False) self.cancelButton.setDisabled(False) @Slot() def _on_tcp_disconnected(self): """TCP连接断开回调""" self.is_tcp_connected = False self.is_running = False print(f"TCP连接断开:{self.tcp_server_host}:{self.tcp_server_port}") # 启用/禁用按钮 self.errorButton.setDisabled(True) self.cancelButton.setDisabled(True) # 重置状态指示灯为“未连接”状态 for widget in self.statusWidgets: widget['indicator'].setStyleSheet(""" QLabel { background-color: #9E9E9E; border-radius: 10px; border: 2px solid #555555; } """) @Slot() def _on_tcp_data_received(self): """TCP数据接收回调(服务器发送数据时触发)""" tcp_data = self.tcp_socket.readAll().data().decode("utf-8").strip() print(f"TCP数据接收:{tcp_data}") # 解析数据 try: status_data = json.loads(tcp_data) self.latest_server_data = status_data self._update_ui_from_data(status_data) except json.JSONDecodeError as e: print(f"TCP数据解析失败(非JSON格式):{e}, 原始数据:{tcp_data}") except Exception as e: print(f"TCP数据处理异常:{e}") @Slot(QAbstractSocket.SocketError) def _on_tcp_error(self, error): """TCP错误回调""" error_str = self.tcp_socket.errorString() print(f"TCP错误:{error_str}") self.is_tcp_connected = False self.is_running = False # 启用/禁用按钮 self.errorButton.setDisabled(True) self.cancelButton.setDisabled(True) def _send_tcp_request(self, request_cmd="get_status"): """向TCP服务器发送请求指令""" if not self.is_tcp_connected: print("TCP连接未建立,无法发送请求") return # 构造请求数据 request_data = json.dumps({ "cmd": request_cmd, "timestamp": self.get_current_time(), "client_info": "布料系统客户端" }) + "\n" # 增加换行符作为数据结束标识 # 发送请求数据 self.tcp_socket.write(request_data.encode("utf-8")) print(f"TCP请求发送:{request_data.strip()}") def _update_ui_from_data(self, data): """根据TCP获取的数据更新界面状态""" for widget in self.statusWidgets: api_field = widget['api_field'] if api_field in data: new_value = str(data[api_field]) widget['valueLabel'].setText(new_value) self.set_indicator_status(widget, new_value) # ------------------ # 状态指示灯逻辑 # ------------------ def set_indicator_status(self, widget, value): """根据值设置状态指示灯颜色""" if value and value != "未知" and value != "" and value != "None": # 有效数据:绿色指示灯 widget['indicator'].setStyleSheet(""" QLabel { background-color: #00E676; border-radius: 10px; border: 2px solid #00796B; } """) else: # 无效数据:红色指示灯 widget['indicator'].setStyleSheet(""" QLabel { background-color: #FF5252; border-radius: 10px; border: 2px solid #C62828; } """) # ------------------ # 按钮点击事件 # ------------------ def on_error_clicked(self): """点击“生产异常”:向服务端发送异常指令""" print("🔘 点击「生产异常」按钮") self._clear_ui_info() self._send_tcp_request("production_error") def on_cancel_clicked(self): """点击“生产取消”:向服务端发送取消指令""" print("🔘 点击「生产取消」按钮") self._clear_ui_info() self._send_tcp_request("cancel_feed") if __name__ == "__main__": app = QApplication(sys.argv) window = StatusMonitor() window.show() sys.exit(app.exec())