Files
zjsh_ui_sysytem/main2.py

569 lines
21 KiB
Python
Raw Permalink Blame History

This file contains invisible Unicode characters

This file contains invisible Unicode characters that are indistinguishable to humans but may be processed differently by a computer. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

#!/usr/bin/env python
# -*- coding: utf-8 -*-
'''
# @Time : 2025/10/31 14:39
# @Author : reenrr
# @Description : 通过tcp连接获取信息并显示在界面上版本2
# @File : main2.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
# 数据保存目录
SAVE_DIR = "operation_records"
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_server_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) # 操作按钮包含新增的3个状态按钮
self.mainLayout.addWidget(self.bigContainer, 1)
def _init_status_groups(self, parent_layout):
"""初始化左右信息各包含3个状态项"""
statusWidget = QWidget()
statusLayout = QHBoxLayout(statusWidget)
statusLayout.setContentsMargins(0, 0, 0, 0)
statusLayout.setSpacing(30) # 减小中间空白间距
leftGroup = QWidget()
leftLayout = QVBoxLayout(leftGroup)
leftLayout.setSpacing(15)
leftLayout.setContentsMargins(30, 0, 0, 0) # 左边组左内边距
rightGroup = QWidget()
rightLayout = QVBoxLayout(rightGroup)
rightLayout.setSpacing(15)
rightLayout.setContentsMargins(0, 0, 30, 0) # 右边组右内边距
# 左边状态项
leftStatusInfo = [
{"name": "任务单号", "value": "", "api_field": "task_id"},
{"name": "工程名称", "value": "", "api_field": "project_name"},
{"name": "配比号", "value": "", "api_field": "produce_mix_id"}
]
# 右边状态项
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)
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)
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)
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(20) # 按钮间距适配5个按钮避免拥挤
# 按钮图标(复用现有图标路径,可根据需求替换)
status_icon_path = "img.png" # 状态类按钮(未下料/下料中/下料完成)共用图标
error_icon_path = "img.png"
cancel_icon_path = "img.png"
self.notFeedingBtn = QPushButton("未下料") # 未下料按钮
self.feedingBtn = QPushButton("下料中") # 下料中按钮
self.feedFinishBtn = QPushButton("下料完成")# 下料完成按钮
self.errorButton = QPushButton("生产异常")
self.cancelButton = QPushButton("生产取消")
# 设置按钮设置(图标、大小、样式)
status_buttons = [self.notFeedingBtn, self.feedingBtn, self.feedFinishBtn]
for btn in status_buttons:
btn.setIcon(QIcon(status_icon_path))
btn.setIconSize(QSize(20, 20))
btn.setFont(QFont("Microsoft YaHei", 12, QFont.Bold))
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))
self.errorButton.setFont(QFont("Microsoft YaHei", 12, QFont.Bold))
self.cancelButton.setFont(QFont("Microsoft YaHei", 12, QFont.Bold))
# 按钮基础样式(统一风格)
button_base_style = """
QPushButton {
font-size: 16px;
font-weight: bold;
padding: 10px 15px; /* 缩小内边距适配5个按钮布局 */
border-radius: 15px;
border: 1px solid;
min-width: 90px; /* 缩小最小宽度 */
}
QPushButton:hover {
opacity: 0.9;
}
QPushButton:pressed {
opacity: 0.8;
}
"""
# 单个按钮样式(不同颜色区分状态)
self.notFeedingBtn.setStyleSheet(button_base_style + """
QPushButton {
background-color: #616161; /* 灰色:未开始状态 */
color: white;
border-color: #424242;
}
""")
self.feedingBtn.setStyleSheet(button_base_style + """
QPushButton {
background-color: #2196F3; /* 蓝色:进行中状态 */
color: white;
border-color: #1976D2;
}
""")
self.feedFinishBtn.setStyleSheet(button_base_style + """
QPushButton {
background-color: #4CAF50; /* 绿色:完成状态 */
color: white;
border-color: #388E3C;
}
""")
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;
}
""")
# ---------------- 绑定按钮点击事件 ----------------
self.notFeedingBtn.clicked.connect(self.on_not_feeding_clicked)
self.feedingBtn.clicked.connect(self.on_feeding_clicked)
self.feedFinishBtn.clicked.connect(self.on_feed_finish_clicked)
self.errorButton.clicked.connect(self.on_error_clicked)
self.cancelButton.clicked.connect(self.on_cancel_clicked)
# ---------------- 初始禁用所有按钮TCP连接后启用 ----------------
all_buttons = status_buttons + [self.errorButton, self.cancelButton]
for btn in all_buttons:
btn.setDisabled(True)
# ---------------- 按钮布局5个按钮横向排列 ----------------
buttonLayout.addStretch(1)
buttonLayout.addWidget(self.notFeedingBtn)
buttonLayout.addWidget(self.feedingBtn)
buttonLayout.addWidget(self.feedFinishBtn)
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")
# 启用所有操作按钮新增的3个状态按钮+原有2个功能按钮
all_buttons = [self.notFeedingBtn, self.feedingBtn, self.feedFinishBtn, self.errorButton, self.cancelButton]
for btn in all_buttons:
btn.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}")
# 禁用所有操作按钮
all_buttons = [self.notFeedingBtn, self.feedingBtn, self.feedFinishBtn, self.errorButton, self.cancelButton]
for btn in all_buttons:
btn.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
# 禁用所有操作按钮
all_buttons = [self.notFeedingBtn, self.feedingBtn, self.feedFinishBtn, self.errorButton, self.cancelButton]
for btn in all_buttons:
btn.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_not_feeding_clicked(self):
"""点击“未下料”:向服务端发送“未下料”状态指令"""
print("🔘 点击「未下料」按钮")
self._clear_ui_info()
self._send_tcp_request("not_feeding") # 指令名可根据服务端需求修改
def on_feeding_clicked(self):
"""点击“下料中”:向服务端发送“下料中”状态指令"""
print("🔘 点击「下料中」按钮")
self._clear_ui_info()
self._send_tcp_request("feeding") # 指令名可根据服务端需求修改
def on_feed_finish_clicked(self):
"""点击“下料完成”:向服务端发送“下料完成”状态指令"""
print("🔘 点击「下料完成」按钮")
self._clear_ui_info()
self._send_tcp_request("feed_finish") # 指令名可根据服务端需求修改
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())