diff --git a/main.py b/main1.py similarity index 77% rename from main.py rename to main1.py index f844033..97d3b5e 100644 --- a/main.py +++ b/main1.py @@ -3,7 +3,8 @@ ''' # @Time : 2025/9/10 11:29 # @Author : reenrr -# @File : test.py +# @Description : 通过tcp连接获取信息,并显示在界面上(版本1) +# @File : main.py ''' import sys from PySide6.QtWidgets import ( @@ -24,15 +25,11 @@ 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) @@ -66,7 +63,6 @@ class StatusMonitor(QWidget): self._init_title_label() self._init_status_container() self._init_timers() - self._init_save_dir() # ---------------- # 客户端自动后自动连接服务端 @@ -74,14 +70,6 @@ class StatusMonitor(QWidget): print(f"客户端启动,自动连接服务端{self.tcp_server_host}:{self.tcp_server_port}...") self.tcp_socket.connectToHost(self.tcp_server_host, self.tcp_server_port) - def _init_save_dir(self): - """初始化数据保存目录""" - if not os.path.exists(SAVE_DIR): - os.makedirs(SAVE_DIR) - print(f"已创建数据保存目录:{os.path.abspath(SAVE_DIR)}") - else: - print(f"数据保存目录已存在:{os.path.abspath(SAVE_DIR)}") - def _bind_tcp_signals(self): """绑定TCP socket的核心信号(连接、断开、接收数据、错误)""" # 连接成功信号 @@ -93,6 +81,9 @@ class StatusMonitor(QWidget): # 错误信号(连接/通信出错时触发) self.tcp_socket.errorOccurred.connect(self._on_tcp_error) + # ---------------- + # 界面初始化函数 + # ---------------- def _init_title_label(self): """初始化系统标题标签""" titleLabel = QLabel("中交三航精准布料浇筑要料系统") @@ -149,19 +140,15 @@ class StatusMonitor(QWidget): # 左边5个状态项及对应初始值 leftStatusInfo = [ - {"name": "任务单号", "value": "20250706-01", "api_field": "task_id"}, - {"name": "工程名称", "value": "18号线二期工程", "api_field": "project_name"}, - {"name": "区间段", "value": "停车场工作并上行", "api_field": "section"}, - {"name": "坍落度", "value": "50~70 mm", "api_field": "slump"}, - {"name": "配合比编号", "value": "P2022=001", "api_field": "mix_ratio_id"} + {"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": "request_status"}, - {"name": "要料标号", "value": "C50P12", "api_field": "material_grade"}, - {"name": "要料方量", "value": "2m³", "api_field": "volume"}, - {"name": "要料时间", "value": "2分钟后", "api_field": "request_time"}, - {"name": "小车状态", "value": "移动后", "api_field": "car_status"} + {"name": "要料状态", "value": "", "api_field": "flag"}, + {"name": "砼强度", "value": "", "api_field": "beton_grade"}, + {"name": "要料方量", "value": "", "api_field": "adjusted_volume"}, ] self.statusWidgets = [] @@ -256,25 +243,17 @@ class StatusMonitor(QWidget): buttonLayout.setSpacing(30) # 按钮图标(需替换为实际图标路径) - start_icon_path = "img.png" - down_icon_path = "img.png" error_icon_path = "img.png" cancel_icon_path = "img.png" - self.startFeedButton = QPushButton("开始下料") - self.finishButton = QPushButton("下料完成") self.errorButton = QPushButton("生产异常") self.cancelButton = QPushButton("生产取消") # 设置按钮图标 - self.startFeedButton.setIcon(QIcon(start_icon_path)) - self.finishButton.setIcon(QIcon(down_icon_path)) self.errorButton.setIcon(QIcon(error_icon_path)) self.cancelButton.setIcon(QIcon(cancel_icon_path)) # 设置图标大小 - self.startFeedButton.setIconSize(QSize(20, 20)) - self.finishButton.setIconSize(QSize(20, 20)) self.errorButton.setIconSize(QSize(20, 20)) self.cancelButton.setIconSize(QSize(20, 20)) @@ -294,23 +273,6 @@ class StatusMonitor(QWidget): opacity: 0.8; } """ - - self.startFeedButton.setStyleSheet(button_base_style + """ - QPushButton { - background-color: #2196F3; - color: white; - border-color: #1976D2; - } - """) - - self.finishButton.setStyleSheet(button_base_style + """ - QPushButton { - background-color: #00796B; - color: white; - border-color: #004D40; - } - """) - self.errorButton.setStyleSheet(button_base_style + """ QPushButton { background-color: #E65100; @@ -328,25 +290,17 @@ class StatusMonitor(QWidget): """) button_font = QFont("Microsoft YaHei", 12, QFont.Bold) - self.startFeedButton.setFont(button_font) - self.finishButton.setFont(button_font) self.errorButton.setFont(button_font) self.cancelButton.setFont(button_font) - self.startFeedButton.clicked.connect(self.on_start_clicked) - self.finishButton.clicked.connect(self.on_finish_clicked) self.errorButton.clicked.connect(self.on_error_clicked) self.cancelButton.clicked.connect(self.on_cancel_clicked) # 初始禁用“停止”和“异常”按钮(未连接时不可用) - self.startFeedButton.setDisabled(False) - self.finishButton.setDisabled(True) self.errorButton.setDisabled(True) self.cancelButton.setDisabled(True) buttonLayout.addStretch(1) - buttonLayout.addWidget(self.startFeedButton) - buttonLayout.addWidget(self.finishButton) buttonLayout.addWidget(self.errorButton) buttonLayout.addWidget(self.cancelButton) buttonLayout.addStretch(1) @@ -397,35 +351,22 @@ class StatusMonitor(QWidget): # 绘制文本 painter.drawText(x, y + text_rect.height(), text) - def _save_data_to_file(self, button_name): - """ - 将服务端数据+按钮操作信息保存到JSON文件 - - 参数:button_name:点击的按钮名称 - """ - # 1、检查是否有服务端数据 - if not self.latest_server_data: - print(f"⚠️ 未收到服务端数据,无法保存「{button_name}」操作记录") - return - - # 2、构建完整数据(服务端数据+按钮操作信息+存档时间) - save_data = { - "opration_button": button_name, - "save_time": self.get_current_time(), - "server_data":self.latest_server_data - } - - # 3、生成唯一文件名(按时间戳命名) - file_name = f"operation_record_{self.get_timestamp()}.json" - file_path = os.path.join(SAVE_DIR, file_name) - - # 4、写入JSON文件 - try: - with open(file_path, "w", encoding="utf-8") as f: - json.dump(save_data, f, ensure_ascii=False, indent=4) - print(f"💾 保存「{button_name}」操作记录成功:{os.path.abspath(file_path)}") - except Exception as e: - print(f"💾 保存「{button_name}」操作记录失败:{str(e)}") + # -------------------- + # 清空界面信息的通用方法 + # -------------------- + 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客户端核心功能 @@ -441,7 +382,6 @@ class StatusMonitor(QWidget): self._send_tcp_request("get_initial_data") # 更新按钮状态:启用“下料完成”“生产异常”“生产取消” - self.finishButton.setDisabled(False) self.errorButton.setDisabled(False) self.cancelButton.setDisabled(False) @@ -453,8 +393,6 @@ class StatusMonitor(QWidget): print(f"TCP连接断开:{self.tcp_server_host}:{self.tcp_server_port}") # 启用/禁用按钮 - self.startFeedButton.setDisabled(False) - self.finishButton.setDisabled(True) self.errorButton.setDisabled(True) self.cancelButton.setDisabled(True) @@ -493,8 +431,6 @@ class StatusMonitor(QWidget): self.is_running = False # 启用/禁用按钮 - self.startFeedButton.setDisabled(False) - self.finishButton.setDisabled(True) self.errorButton.setDisabled(True) self.cancelButton.setDisabled(True) @@ -551,34 +487,17 @@ class StatusMonitor(QWidget): # ------------------ # 按钮点击事件 # ------------------ - def on_start_clicked(self): - """点击“开始下料”:向服务端发送开始指令""" - print("🔘 点击「开始下料」按钮") - if not self.is_tcp_connected: - print("TCP连接未建立,尝试重新连接") - self.tcp_socket.connectToHost(self.tcp_server_host, self.tcp_server_port) - QTimer.singleShot(1000, lambda: self._send_tcp_request("start_feed")) # 等待连接成功后再发送请求 - else: - print("TCP连接已建立") - self._send_tcp_request("start_feed") - - def on_finish_clicked(self): - """点击“下料完成”:向服务端发送完成指令""" - print("🔘 点击「下料完成」按钮") - self._send_tcp_request("finish_feed") - self._save_data_to_file("finish_feed") - def on_error_clicked(self): """点击“生产异常”:向服务端发送异常指令""" print("🔘 点击「生产异常」按钮") + self._clear_ui_info() self._send_tcp_request("production_error") - self._save_data_to_file("production_error") def on_cancel_clicked(self): """点击“生产取消”:向服务端发送取消指令""" print("🔘 点击「生产取消」按钮") + self._clear_ui_info() self._send_tcp_request("cancel_feed") - self._save_data_to_file("cancel_feed") if __name__ == "__main__": app = QApplication(sys.argv) diff --git a/main2.py b/main2.py new file mode 100644 index 0000000..f6a5916 --- /dev/null +++ b/main2.py @@ -0,0 +1,569 @@ +#!/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()) \ No newline at end of file diff --git a/operation_records/operation_record_20250919_204518_696.json b/operation_records/operation_record_20250919_204518_696.json deleted file mode 100644 index 0bf38d6..0000000 --- a/operation_records/operation_record_20250919_204518_696.json +++ /dev/null @@ -1,17 +0,0 @@ -{ - "opration_button": "finish_feed", - "save_time": "2025-09-19 20:45:18", - "server_data": { - "task_id": "20250706-01", - "project_name": "18号线二期工程", - "section": "停车场工作井上行", - "slump": "50~70 mm", - "mix_ratio_id": "P2022=001", - "request_status": "请求中", - "material_grade": "C50P12", - "volume": "2m³", - "request_time": "10分钟后", - "car_status": "移动后", - "timestamp": "2025-09-19 20:45:16" - } -} \ No newline at end of file diff --git a/operation_records/operation_record_20250919_204539_327.json b/operation_records/operation_record_20250919_204539_327.json deleted file mode 100644 index 618aba1..0000000 --- a/operation_records/operation_record_20250919_204539_327.json +++ /dev/null @@ -1,17 +0,0 @@ -{ - "opration_button": "production_error", - "save_time": "2025-09-19 20:45:39", - "server_data": { - "task_id": "20250706-01", - "project_name": "18号线二期工程", - "section": "停车场工作井上行", - "slump": "50~70 mm", - "mix_ratio_id": "P2022=001", - "request_status": "请求中", - "material_grade": "C50P12", - "volume": "2m³", - "request_time": "10分钟后", - "car_status": "移动后", - "timestamp": "2025-09-19 20:45:16" - } -} \ No newline at end of file diff --git a/operation_records/operation_record_20250919_204605_704.json b/operation_records/operation_record_20250919_204605_704.json deleted file mode 100644 index dfdf6a3..0000000 --- a/operation_records/operation_record_20250919_204605_704.json +++ /dev/null @@ -1,17 +0,0 @@ -{ - "opration_button": "cancel_feed", - "save_time": "2025-09-19 20:46:05", - "server_data": { - "task_id": "20250706-01", - "project_name": "18号线二期工程", - "section": "停车场工作井上行", - "slump": "50~70 mm", - "mix_ratio_id": "P2022=001", - "request_status": "请求中", - "material_grade": "C50P12", - "volume": "2m³", - "request_time": "10分钟后", - "car_status": "移动后", - "timestamp": "2025-09-19 20:45:16" - } -} \ No newline at end of file diff --git a/operation_records/operation_record_20250919_204807_648.json b/operation_records/operation_record_20250919_204807_648.json deleted file mode 100644 index c5fd98e..0000000 --- a/operation_records/operation_record_20250919_204807_648.json +++ /dev/null @@ -1,17 +0,0 @@ -{ - "opration_button": "finish_feed", - "save_time": "2025-09-19 20:48:07", - "server_data": { - "task_id": "20250706-01", - "project_name": "18号线二期工程", - "section": "停车场工作井上行", - "slump": "50~70 mm", - "mix_ratio_id": "P2022=001", - "request_status": "请求中", - "material_grade": "C50P12", - "volume": "2m³", - "request_time": "10分钟后", - "car_status": "移动后", - "timestamp": "2025-09-19 20:47:57" - } -} \ No newline at end of file diff --git a/operation_records/operation_record_20250919_204858_912.json b/operation_records/operation_record_20250919_204858_912.json deleted file mode 100644 index 2a81840..0000000 --- a/operation_records/operation_record_20250919_204858_912.json +++ /dev/null @@ -1,17 +0,0 @@ -{ - "opration_button": "production_error", - "save_time": "2025-09-19 20:48:58", - "server_data": { - "task_id": "20250706-01", - "project_name": "18号线二期工程", - "section": "停车场工作井上行", - "slump": "50~70 mm", - "mix_ratio_id": "P2022=001", - "request_status": "请求中", - "material_grade": "C50P12", - "volume": "2m³", - "request_time": "10分钟后", - "car_status": "移动后", - "timestamp": "2025-09-19 20:48:53" - } -} \ No newline at end of file diff --git a/test.py b/test.py index f844033..8220a58 100644 --- a/test.py +++ b/test.py @@ -3,6 +3,7 @@ ''' # @Time : 2025/9/10 11:29 # @Author : reenrr +# @Description : 在main函数的基础上添加了重连机制(为连接成功) # @File : test.py ''' import sys @@ -26,13 +27,14 @@ tcp_server_port = 8888 # 数据保存目录 SAVE_DIR = "operation_records" +MAX_RECONNECT = 3 # 最大重连次数 +RECONNECT_INTERVAL = 2000 # 重连间隔(毫秒) class StatusMonitor(QWidget): """ 中交三航精准布料浇筑要料系统 - 主界面类(深色主题) 使用TCP进行数据传输(客户端模型,与TCP服务器通信) """ - def __init__(self, parent=None): """构造函数:初始化主界面的UI布局、控件和定时器""" super().__init__(parent=parent) @@ -48,6 +50,12 @@ class StatusMonitor(QWidget): self.tcp_server_host = tcp_server_host self.tcp_server_port = tcp_server_port self.is_tcp_connected = False # TCP连接状态标记 + self.has_connected_once = False # 连接至服务器至少一次标记(区别首次连接和断开后重连) + self.reconnect_count = 0 # 重连次数计数器 + # 重连定时器,每隔RECONNECT_INTERVAL毫秒重连一次 + self.reconnect_timer = QTimer(self) + self.reconnect_timer.setInterval(RECONNECT_INTERVAL) + self.reconnect_timer.timeout.connect(self._reconnect_to_server) # 绑定重连函数 # 绑定TCP信号与槽(事件驱动) self._bind_tcp_signals() @@ -72,7 +80,23 @@ class StatusMonitor(QWidget): # 客户端自动后自动连接服务端 # ---------------- print(f"客户端启动,自动连接服务端{self.tcp_server_host}:{self.tcp_server_port}...") - self.tcp_socket.connectToHost(self.tcp_server_host, self.tcp_server_port) + self._connect_to_server() + + def _connect_to_server(self): + """主动发起连接(仅在未连接状态下有效""" + if not self.is_tcp_connected: + self.tcp_socket.abort() # 终止现有连接 + self.tcp_socket.connectToHost(self.tcp_server_host, self.tcp_server_port) + + def _reconnect_to_server(self): + """重连执行函数:仅在未连接且未达最大次数时触发""" + if not self.is_tcp_connected and self.reconnect_count < MAX_RECONNECT: + self.reconnect_count += 1 + print(f"第{self.reconnect_count}次重连(共{MAX_RECONNECT}次尝试)...") + self._connect_to_server() + elif self.reconnect_count >= MAX_RECONNECT: + self.reconnect_timer.stop() # 停止重连定时器 + print(f"已达最大重连次数({MAX_RECONNECT}次),停止重连,请检查服务端状态") def _init_save_dir(self): """初始化数据保存目录""" @@ -93,6 +117,9 @@ class StatusMonitor(QWidget): # 错误信号(连接/通信出错时触发) self.tcp_socket.errorOccurred.connect(self._on_tcp_error) + # ------------------ + # 界面初始化函数 + # ------------------ def _init_title_label(self): """初始化系统标题标签""" titleLabel = QLabel("中交三航精准布料浇筑要料系统") @@ -131,7 +158,7 @@ class StatusMonitor(QWidget): self.mainLayout.addWidget(self.bigContainer, 1) def _init_status_groups(self, parent_layout): - """初始化左右信息(各包含5个状态项)""" + """初始化左右信息(各包含3个状态项)""" statusWidget = QWidget() statusLayout = QHBoxLayout(statusWidget) statusLayout.setContentsMargins(0, 0, 0, 0) @@ -149,19 +176,15 @@ class StatusMonitor(QWidget): # 左边5个状态项及对应初始值 leftStatusInfo = [ - {"name": "任务单号", "value": "20250706-01", "api_field": "task_id"}, - {"name": "工程名称", "value": "18号线二期工程", "api_field": "project_name"}, - {"name": "区间段", "value": "停车场工作并上行", "api_field": "section"}, - {"name": "坍落度", "value": "50~70 mm", "api_field": "slump"}, - {"name": "配合比编号", "value": "P2022=001", "api_field": "mix_ratio_id"} + {"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": "request_status"}, - {"name": "要料标号", "value": "C50P12", "api_field": "material_grade"}, - {"name": "要料方量", "value": "2m³", "api_field": "volume"}, - {"name": "要料时间", "value": "2分钟后", "api_field": "request_time"}, - {"name": "小车状态", "value": "移动后", "api_field": "car_status"} + {"name": "要料状态", "value": "", "api_field": "flag"}, + {"name": "砼强度", "value": "", "api_field": "beton_grade"}, + {"name": "要料方量", "value": "", "api_field": "adjusted_volume"}, ] self.statusWidgets = [] @@ -256,25 +279,17 @@ class StatusMonitor(QWidget): buttonLayout.setSpacing(30) # 按钮图标(需替换为实际图标路径) - start_icon_path = "img.png" - down_icon_path = "img.png" error_icon_path = "img.png" cancel_icon_path = "img.png" - self.startFeedButton = QPushButton("开始下料") - self.finishButton = QPushButton("下料完成") self.errorButton = QPushButton("生产异常") self.cancelButton = QPushButton("生产取消") # 设置按钮图标 - self.startFeedButton.setIcon(QIcon(start_icon_path)) - self.finishButton.setIcon(QIcon(down_icon_path)) self.errorButton.setIcon(QIcon(error_icon_path)) self.cancelButton.setIcon(QIcon(cancel_icon_path)) # 设置图标大小 - self.startFeedButton.setIconSize(QSize(20, 20)) - self.finishButton.setIconSize(QSize(20, 20)) self.errorButton.setIconSize(QSize(20, 20)) self.cancelButton.setIconSize(QSize(20, 20)) @@ -294,23 +309,6 @@ class StatusMonitor(QWidget): opacity: 0.8; } """ - - self.startFeedButton.setStyleSheet(button_base_style + """ - QPushButton { - background-color: #2196F3; - color: white; - border-color: #1976D2; - } - """) - - self.finishButton.setStyleSheet(button_base_style + """ - QPushButton { - background-color: #00796B; - color: white; - border-color: #004D40; - } - """) - self.errorButton.setStyleSheet(button_base_style + """ QPushButton { background-color: #E65100; @@ -328,25 +326,17 @@ class StatusMonitor(QWidget): """) button_font = QFont("Microsoft YaHei", 12, QFont.Bold) - self.startFeedButton.setFont(button_font) - self.finishButton.setFont(button_font) self.errorButton.setFont(button_font) self.cancelButton.setFont(button_font) - self.startFeedButton.clicked.connect(self.on_start_clicked) - self.finishButton.clicked.connect(self.on_finish_clicked) self.errorButton.clicked.connect(self.on_error_clicked) self.cancelButton.clicked.connect(self.on_cancel_clicked) # 初始禁用“停止”和“异常”按钮(未连接时不可用) - self.startFeedButton.setDisabled(False) - self.finishButton.setDisabled(True) self.errorButton.setDisabled(True) self.cancelButton.setDisabled(True) buttonLayout.addStretch(1) - buttonLayout.addWidget(self.startFeedButton) - buttonLayout.addWidget(self.finishButton) buttonLayout.addWidget(self.errorButton) buttonLayout.addWidget(self.cancelButton) buttonLayout.addStretch(1) @@ -397,6 +387,9 @@ class StatusMonitor(QWidget): # 绘制文本 painter.drawText(x, y + text_rect.height(), text) + # ------------------ + # 数据保存 + # ------------------ def _save_data_to_file(self, button_name): """ 将服务端数据+按钮操作信息保存到JSON文件 @@ -427,6 +420,23 @@ class StatusMonitor(QWidget): except Exception as e: print(f"💾 保存「{button_name}」操作记录失败:{str(e)}") + # -------------------- + # 清空界面信息的通用方法 + # -------------------- + 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客户端核心功能 # ------------------ @@ -434,14 +444,16 @@ class StatusMonitor(QWidget): def _on_tcp_connected(self): """TCP连接成功回调""" self.is_tcp_connected = True + self.has_connected_once = True + self.reconnect_timer.stop() # 停止重连定时器 + self.reconnect_count = 0 # 重连计数器清零 self.is_running = True print(f"TCP连接成功:{self.tcp_server_host}:{self.tcp_server_port}") # 连接成功后,向服务器发送“请求初始数据”指令 self._send_tcp_request("get_initial_data") - # 更新按钮状态:启用“下料完成”“生产异常”“生产取消” - self.finishButton.setDisabled(False) + # 更新按钮状态:启用“生产异常”“生产取消” self.errorButton.setDisabled(False) self.cancelButton.setDisabled(False) @@ -453,8 +465,6 @@ class StatusMonitor(QWidget): print(f"TCP连接断开:{self.tcp_server_host}:{self.tcp_server_port}") # 启用/禁用按钮 - self.startFeedButton.setDisabled(False) - self.finishButton.setDisabled(True) self.errorButton.setDisabled(True) self.cancelButton.setDisabled(True) @@ -487,16 +497,20 @@ class StatusMonitor(QWidget): @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 + if not self.is_tcp_connected: + error_str = self.tcp_socket.errorString() + print(f"TCP错误:{error_str}") + self.is_tcp_connected = False + self.is_running = False - # 启用/禁用按钮 - self.startFeedButton.setDisabled(False) - self.finishButton.setDisabled(True) - self.errorButton.setDisabled(True) - self.cancelButton.setDisabled(True) + # 启用/禁用按钮 + self.errorButton.setDisabled(True) + self.cancelButton.setDisabled(True) + + # 首次连接失败时,启动重连定时器 + if not self.has_connected_once and self.reconnect_count == 0: + print(f"将在{RECONNECT_INTERVAL / 1000}秒后启动重连(最多{MAX_RECONNECT}次)") + self.reconnect_timer.start() def _send_tcp_request(self, request_cmd="get_status"): """向TCP服务器发送请求指令""" @@ -551,37 +565,20 @@ class StatusMonitor(QWidget): # ------------------ # 按钮点击事件 # ------------------ - def on_start_clicked(self): - """点击“开始下料”:向服务端发送开始指令""" - print("🔘 点击「开始下料」按钮") - if not self.is_tcp_connected: - print("TCP连接未建立,尝试重新连接") - self.tcp_socket.connectToHost(self.tcp_server_host, self.tcp_server_port) - QTimer.singleShot(1000, lambda: self._send_tcp_request("start_feed")) # 等待连接成功后再发送请求 - else: - print("TCP连接已建立") - self._send_tcp_request("start_feed") - - def on_finish_clicked(self): - """点击“下料完成”:向服务端发送完成指令""" - print("🔘 点击「下料完成」按钮") - self._send_tcp_request("finish_feed") - self._save_data_to_file("finish_feed") - def on_error_clicked(self): """点击“生产异常”:向服务端发送异常指令""" print("🔘 点击「生产异常」按钮") + self._clear_ui_info() self._send_tcp_request("production_error") - self._save_data_to_file("production_error") def on_cancel_clicked(self): """点击“生产取消”:向服务端发送取消指令""" print("🔘 点击「生产取消」按钮") + self._clear_ui_info() self._send_tcp_request("cancel_feed") - self._save_data_to_file("cancel_feed") if __name__ == "__main__": app = QApplication(sys.argv) window = StatusMonitor() window.show() - sys.exit(app.exec()) + sys.exit(app.exec()) \ No newline at end of file