diff --git a/.idea/.gitignore b/.idea/.gitignore new file mode 100644 index 0000000..359bb53 --- /dev/null +++ b/.idea/.gitignore @@ -0,0 +1,3 @@ +# 默认忽略的文件 +/shelf/ +/workspace.xml diff --git a/.idea/inspectionProfiles/Project_Default.xml b/.idea/inspectionProfiles/Project_Default.xml new file mode 100644 index 0000000..78dfc6d --- /dev/null +++ b/.idea/inspectionProfiles/Project_Default.xml @@ -0,0 +1,18 @@ + + + + \ No newline at end of file diff --git a/.idea/inspectionProfiles/profiles_settings.xml b/.idea/inspectionProfiles/profiles_settings.xml new file mode 100644 index 0000000..105ce2d --- /dev/null +++ b/.idea/inspectionProfiles/profiles_settings.xml @@ -0,0 +1,6 @@ + + + + \ No newline at end of file diff --git a/.idea/misc.xml b/.idea/misc.xml new file mode 100644 index 0000000..62ef35e --- /dev/null +++ b/.idea/misc.xml @@ -0,0 +1,7 @@ + + + + + + \ No newline at end of file diff --git a/.idea/modules.xml b/.idea/modules.xml new file mode 100644 index 0000000..acf6533 --- /dev/null +++ b/.idea/modules.xml @@ -0,0 +1,8 @@ + + + + + + + + \ No newline at end of file diff --git a/.idea/vcs.xml b/.idea/vcs.xml new file mode 100644 index 0000000..35eb1dd --- /dev/null +++ b/.idea/vcs.xml @@ -0,0 +1,6 @@ + + + + + + \ No newline at end of file diff --git a/.idea/zjsh_ui_sysytem.iml b/.idea/zjsh_ui_sysytem.iml new file mode 100644 index 0000000..53e58ed --- /dev/null +++ b/.idea/zjsh_ui_sysytem.iml @@ -0,0 +1,8 @@ + + + + + + + + \ No newline at end of file diff --git a/config/config.json b/config/config.json new file mode 100644 index 0000000..65a36b5 --- /dev/null +++ b/config/config.json @@ -0,0 +1,12 @@ +{ + "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": "移动后" +} diff --git a/config/tcp_server.py b/config/tcp_server.py new file mode 100644 index 0000000..b46d34a --- /dev/null +++ b/config/tcp_server.py @@ -0,0 +1,191 @@ +#!/usr/bin/env python +# -*- coding: utf-8 -*- +''' +# @Time : 2025/9/19 09:48 +# @Author : reenrr +# @File : mock_server.py +''' +import socket +import json +import threading +import time +from datetime import datetime +import os + +class TCPServerSimulator: + def __init__(self, host='127.0.0.1', port=8888, config_file='config.json'): + self.host = host + self.port = port + self.server_socket = None + self.is_running = False + self.client_sockets = [] + self.config_file = config_file + + # 初始状态为None + self.data_template = None + + # 从配置文件中加载固定数据 + self.load_config_data() + + # 模拟数据模板 + if self.data_template is None: + self.data_template = { + "task_id": "None", + "project_name": "None", + "section": "None", + "slump": "None", + "mix_ratio_id": "None", + "request_status": "None", + "material_grade": "None", + "volume": "None", + "request_time": "None", + "car_status": "None" + } + + def load_config_data(self): + """从配置文件中加载固定数据""" + try: + if os.path.exists(self.config_file): + with open(self.config_file, 'r', encoding='utf-8') as f: + self.data_template = json.load(f) + print(f"成功从 {self.config_file} 加载配置数据") + else: + print(f"配置文件 {self.config_file} 不存在,将使用默认数据") + # 创建默认配置文件 + self.create_default_config() + except Exception as e: + print(f"加载配置文件时发生错误:{e},将使用默认数据") + self.data_template = None + + def create_default_config(self): + """创建默认配置文件""" + default_data = { + "task_id": "None", + "project_name": "None", + "section": "None", + "slump": "None", + "mix_ratio_id": "None", + "request_status": "None", + "material_grade": "None", + "volume": "None", + "request_time": "None", + "car_status": "None" + } + + try: + with open(self.config_file, 'w', encoding='utf-8') as f: + json.dump(default_data, f, ensure_ascii=False, indent=4) + print(f"已创建默认配置文件 {self.config_file}") + self.data_template = default_data + except Exception as e: + print(f"创建默认配置文件时发生错误: {e}") + self.data_template = None + + def start(self): + """启动服务器""" + self.is_running = True + self.server_socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM) + self.server_socket.bind((self.host, self.port)) + self.server_socket.listen(5) + print(f"服务器已启动,监听 {self.host}:{self.port}...") + + # 启动接受连接的线程 + accept_thread = threading.Thread(target=self.accept_connections, daemon=True) + accept_thread.start() + + try: + while self.is_running: + time.sleep(1) + except KeyboardInterrupt: + print("\n服务器正在关闭...") + self.stop() + + def accept_connections(self): + """接受客户端连接""" + while self.is_running: + try: + client_socket, client_address = self.server_socket.accept() + self.client_sockets.append(client_socket) + print(f"客户端 {client_address} 已连接") + + # 发送数据 + data = self.generate_simulated_data() + self.send_data(client_socket, data) + print(f"已向客户端 {client_address} 发送数据:{data}") + + # 启动一个线程监听客户端发送的指令 + threading.Thread( + target=self.listen_client_commands, + args=(client_socket,client_address), + daemon=True + ).start() + + except Exception as e: + if self.is_running: + print(f"接受连接时发生错误: {e}") + break + + def listen_client_commands(self, client_socket, client_address): + """监听客户端发送的指令""" + while self.is_running and client_socket in self.client_sockets: + try: + # 接收客户端发送的指令 + data = client_socket.recv(1024).decode('utf-8').strip() + if data: + print(f"客户端 {client_address} 发送指令: {data}") + else: + print(f"客户端 {client_address} 已断开连接") + self.client_sockets.remove(client_socket) + client_socket.close() + break + except Exception as e: + print(f"监听客户端 {client_address} 指令时发生错误: {e}") + self.client_sockets.remove(client_socket) + client_socket.close() + break + + def generate_simulated_data(self): + """生成模拟的状态数据""" + if self.data_template is None: + return None + + data = self.data_template.copy() + data["timestamp"] = datetime.now().strftime('%Y-%m-%d %H:%M:%S') + + return data + + def send_data(self, client_socket, data): + """向客户端发送数据""" + try: + # 转换为JSON字符串并添加换行符作为结束标记 + if data is None: + data_str = json.dumps(None) + "\n" + else: + data_str = json.dumps(data) + "\n" + client_socket.sendall(data_str.encode('utf-8')) + except Exception as e: + print(f"向客户端 {client_socket.getpeername()} 发送数据时发生错误: {e}") + + def stop(self): + """停止服务器""" + self.is_running = False + + # 关闭所有客户端连接 + for sock in self.client_sockets: + try: + sock.close() + except Exception as e: + print(f"关闭客户端连接时发生错误: {e}") + + # 关闭服务器套接字 + if self.server_socket: + try: + self.server_socket.close() + except Exception as e: + print(f"关闭服务器套接字时发生错误: {e}") + + print("服务器已关闭") + +if __name__ == '__main__': + server = TCPServerSimulator(host='127.0.0.1', port=8888) + server.start() diff --git a/main.py b/main.py index 649f5d2..f844033 100644 --- a/main.py +++ b/main.py @@ -10,14 +10,27 @@ from PySide6.QtWidgets import ( QApplication, QWidget, QVBoxLayout, QHBoxLayout, QLabel, QFrame, QSizePolicy, QPushButton ) -from PySide6.QtCore import Qt, QTimer, QPoint, QSize +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): @@ -25,6 +38,19 @@ class StatusMonitor(QWidget): 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("中交三航精准布料浇筑要料系统") @@ -40,6 +66,32 @@ class StatusMonitor(QWidget): self._init_title_label() self._init_status_container() self._init_timers() + self._init_save_dir() + + # ---------------- + # 客户端自动后自动连接服务端 + # ---------------- + 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的核心信号(连接、断开、接收数据、错误)""" + # 连接成功信号 + 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): """初始化系统标题标签""" @@ -97,152 +149,30 @@ class StatusMonitor(QWidget): # 左边5个状态项及对应初始值 leftStatusInfo = [ - {"name": "任务单号", "value": "20250706-01"}, - {"name": "工程名称", "value": "18号线二期工程"}, - {"name": "区间段", "value": "停车场工作并上行"}, - {"name": "坍落度", "value": "50~70 mm"}, - {"name": "配合比编号", "value": "P2022=001"} + {"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"} ] # 右边5个状态项及对应初始值 rightStatusInfo = [ - {"name": "要料状态", "value": "请求中"}, - {"name": "要料标号", "value": "C50P12"}, - {"name": "要料方量", "value": "2m³"}, - {"name": "要料时间", "value": "2分钟后"}, - {"name": "小车状态", "value": "移动后"} + {"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"} ] self.statusWidgets = [] # 处理左边状态项 for info in leftStatusInfo: - 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"] - }) - + statusItem = self._create_status_item(info) leftLayout.addWidget(statusItem) # 处理右边状态项 for info in rightStatusInfo: - 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"] - }) - + statusItem = self._create_status_item(info) rightLayout.addWidget(statusItem) statusLayout.addWidget(leftGroup) @@ -251,6 +181,73 @@ class StatusMonitor(QWidget): 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() @@ -259,23 +256,27 @@ 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.startButton = QPushButton("下料完成") - self.restartButton = QPushButton("生产异常") - self.stopButton = QPushButton("生产取消") + self.startFeedButton = QPushButton("开始下料") + self.finishButton = QPushButton("下料完成") + self.errorButton = QPushButton("生产异常") + self.cancelButton = QPushButton("生产取消") # 设置按钮图标 - self.startButton.setIcon(QIcon(down_icon_path)) - self.restartButton.setIcon(QIcon(error_icon_path)) - self.stopButton.setIcon(QIcon(cancel_icon_path)) + 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.startButton.setIconSize(QSize(20, 20)) - self.restartButton.setIconSize(QSize(20, 20)) - self.stopButton.setIconSize(QSize(20, 20)) + self.startFeedButton.setIconSize(QSize(20, 20)) + self.finishButton.setIconSize(QSize(20, 20)) + self.errorButton.setIconSize(QSize(20, 20)) + self.cancelButton.setIconSize(QSize(20, 20)) button_base_style = """ QPushButton { @@ -294,7 +295,15 @@ class StatusMonitor(QWidget): } """ - self.startButton.setStyleSheet(button_base_style + """ + 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; @@ -302,7 +311,7 @@ class StatusMonitor(QWidget): } """) - self.restartButton.setStyleSheet(button_base_style + """ + self.errorButton.setStyleSheet(button_base_style + """ QPushButton { background-color: #E65100; color: white; @@ -310,7 +319,7 @@ class StatusMonitor(QWidget): } """) - self.stopButton.setStyleSheet(button_base_style + """ + self.cancelButton.setStyleSheet(button_base_style + """ QPushButton { background-color: #C62828; color: white; @@ -319,18 +328,27 @@ class StatusMonitor(QWidget): """) button_font = QFont("Microsoft YaHei", 12, QFont.Bold) - self.startButton.setFont(button_font) - self.restartButton.setFont(button_font) - self.stopButton.setFont(button_font) + self.startFeedButton.setFont(button_font) + self.finishButton.setFont(button_font) + self.errorButton.setFont(button_font) + self.cancelButton.setFont(button_font) - self.startButton.clicked.connect(self.on_start_clicked) - self.restartButton.clicked.connect(self.on_restart_clicked) - self.stopButton.clicked.connect(self.on_stop_clicked) + 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.startButton) - buttonLayout.addWidget(self.restartButton) - buttonLayout.addWidget(self.stopButton) + buttonLayout.addWidget(self.startFeedButton) + buttonLayout.addWidget(self.finishButton) + buttonLayout.addWidget(self.errorButton) + buttonLayout.addWidget(self.cancelButton) buttonLayout.addStretch(1) parent_layout.addWidget(buttonContainer) @@ -342,15 +360,14 @@ class StatusMonitor(QWidget): self.time_timer.timeout.connect(self.update_time) self.time_timer.start(1000) - # 状态模拟定时器(每3秒更新一次) - self.status_timer = QTimer() - self.status_timer.timeout.connect(self.simulate_mcu_query) - self.status_timer.start(3000) - 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() @@ -380,92 +397,69 @@ class StatusMonitor(QWidget): # 绘制文本 painter.drawText(x, y + text_rect.height(), text) - def simulate_mcu_query(self): - """模拟单片机状态查询""" - import random # 仅用于模拟,实际项目替换为真实通信逻辑 + def _save_data_to_file(self, button_name): + """ + 将服务端数据+按钮操作信息保存到JSON文件 - if self.is_running: - for widget in self.statusWidgets: - status = random.choice([True, False]) - widget['status'] = status + 参数:button_name:点击的按钮名称 + """ + # 1、检查是否有服务端数据 + if not self.latest_server_data: + print(f"⚠️ 未收到服务端数据,无法保存「{button_name}」操作记录") + return - if status: - # 状态为"是"时的样式 - 去除边框 - widget['valueLabel'].setText("是") - widget['valueLabel'].setStyleSheet(""" - QLabel { - font-size: 18px; - font-weight: bold; - color: #00E676; - background-color: #2D2D2D; /* 与父容器相同背景色 */ - padding: 5px 10px; - border: none; /* 明确去除边框 */ - min-width: 60px; - } - """) - widget['indicator'].setStyleSheet(""" - QLabel { - background-color: #00E676; - border-radius: 10px; - border: 2px solid #00796B; - } - """) - else: - # 状态为"否"时的样式 - 去除边框 - widget['valueLabel'].setText("否") - widget['valueLabel'].setStyleSheet(""" - QLabel { - font-size: 18px; - font-weight: bold; - color: #FF5252; - background-color: #2D2D2D; /* 与父容器相同背景色 */ - padding: 5px 10px; - border: none; /* 明确去除边框 */ - min-width: 60px; - } - """) - widget['indicator'].setStyleSheet(""" - QLabel { - background-color: #FF5252; - border-radius: 10px; - border: 2px solid #C62828; - } - """) + # 2、构建完整数据(服务端数据+按钮操作信息+存档时间) + save_data = { + "opration_button": button_name, + "save_time": self.get_current_time(), + "server_data":self.latest_server_data + } - def on_start_clicked(self): - """开始按钮点击事件""" - if not self.is_running: - self.is_running = True - print("系统已启动,开始监控状态...") - self.simulate_mcu_query() # 启动时立即更新一次状态 + # 3、生成唯一文件名(按时间戳命名) + file_name = f"operation_record_{self.get_timestamp()}.json" + file_path = os.path.join(SAVE_DIR, file_name) - def on_stop_clicked(self): - """停止按钮点击事件""" - if self.is_running: - self.is_running = False - print("系统已停止,状态监控暂停...") + # 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 on_restart_clicked(self): - """重启按钮点击事件""" - 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.finishButton.setDisabled(False) + self.errorButton.setDisabled(False) + self.cancelButton.setDisabled(False) + + @Slot() + def _on_tcp_disconnected(self): + """TCP连接断开回调""" + self.is_tcp_connected = False self.is_running = False - # 重置所有状态为初始值和样式 - for i, widget in enumerate(self.statusWidgets): - widget['status'] = False - # 恢复初始值 - widget['valueLabel'].setText(widget['initial_value']) - # 恢复初始样式(无边框) - widget['valueLabel'].setStyleSheet(""" - QLabel { - font-size: 16px; - font-weight: bold; - color: #FFFFFF; - background-color: #2D2D2D; - border: none; - padding: 0px; - min-width: 80px; - } - """) + 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) + + # 重置状态指示灯为“未连接”状态 + for widget in self.statusWidgets: widget['indicator'].setStyleSheet(""" QLabel { background-color: #9E9E9E; @@ -473,9 +467,118 @@ class StatusMonitor(QWidget): border: 2px solid #555555; } """) - # 重启后自动开始 - QTimer.singleShot(1000, self.on_start_clicked) + @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.startFeedButton.setDisabled(False) + self.finishButton.setDisabled(True) + 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_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._send_tcp_request("production_error") + self._save_data_to_file("production_error") + + def on_cancel_clicked(self): + """点击“生产取消”:向服务端发送取消指令""" + print("🔘 点击「生产取消」按钮") + self._send_tcp_request("cancel_feed") + self._save_data_to_file("cancel_feed") if __name__ == "__main__": app = QApplication(sys.argv) diff --git a/operation_records/operation_record_20250919_204518_696.json b/operation_records/operation_record_20250919_204518_696.json new file mode 100644 index 0000000..0bf38d6 --- /dev/null +++ b/operation_records/operation_record_20250919_204518_696.json @@ -0,0 +1,17 @@ +{ + "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 new file mode 100644 index 0000000..618aba1 --- /dev/null +++ b/operation_records/operation_record_20250919_204539_327.json @@ -0,0 +1,17 @@ +{ + "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 new file mode 100644 index 0000000..dfdf6a3 --- /dev/null +++ b/operation_records/operation_record_20250919_204605_704.json @@ -0,0 +1,17 @@ +{ + "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 new file mode 100644 index 0000000..c5fd98e --- /dev/null +++ b/operation_records/operation_record_20250919_204807_648.json @@ -0,0 +1,17 @@ +{ + "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 new file mode 100644 index 0000000..2a81840 --- /dev/null +++ b/operation_records/operation_record_20250919_204858_912.json @@ -0,0 +1,17 @@ +{ + "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 649f5d2..f844033 100644 --- a/test.py +++ b/test.py @@ -10,14 +10,27 @@ from PySide6.QtWidgets import ( QApplication, QWidget, QVBoxLayout, QHBoxLayout, QLabel, QFrame, QSizePolicy, QPushButton ) -from PySide6.QtCore import Qt, QTimer, QPoint, QSize +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): @@ -25,6 +38,19 @@ class StatusMonitor(QWidget): 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("中交三航精准布料浇筑要料系统") @@ -40,6 +66,32 @@ class StatusMonitor(QWidget): self._init_title_label() self._init_status_container() self._init_timers() + self._init_save_dir() + + # ---------------- + # 客户端自动后自动连接服务端 + # ---------------- + 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的核心信号(连接、断开、接收数据、错误)""" + # 连接成功信号 + 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): """初始化系统标题标签""" @@ -97,152 +149,30 @@ class StatusMonitor(QWidget): # 左边5个状态项及对应初始值 leftStatusInfo = [ - {"name": "任务单号", "value": "20250706-01"}, - {"name": "工程名称", "value": "18号线二期工程"}, - {"name": "区间段", "value": "停车场工作并上行"}, - {"name": "坍落度", "value": "50~70 mm"}, - {"name": "配合比编号", "value": "P2022=001"} + {"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"} ] # 右边5个状态项及对应初始值 rightStatusInfo = [ - {"name": "要料状态", "value": "请求中"}, - {"name": "要料标号", "value": "C50P12"}, - {"name": "要料方量", "value": "2m³"}, - {"name": "要料时间", "value": "2分钟后"}, - {"name": "小车状态", "value": "移动后"} + {"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"} ] self.statusWidgets = [] # 处理左边状态项 for info in leftStatusInfo: - 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"] - }) - + statusItem = self._create_status_item(info) leftLayout.addWidget(statusItem) # 处理右边状态项 for info in rightStatusInfo: - 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"] - }) - + statusItem = self._create_status_item(info) rightLayout.addWidget(statusItem) statusLayout.addWidget(leftGroup) @@ -251,6 +181,73 @@ class StatusMonitor(QWidget): 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() @@ -259,23 +256,27 @@ 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.startButton = QPushButton("下料完成") - self.restartButton = QPushButton("生产异常") - self.stopButton = QPushButton("生产取消") + self.startFeedButton = QPushButton("开始下料") + self.finishButton = QPushButton("下料完成") + self.errorButton = QPushButton("生产异常") + self.cancelButton = QPushButton("生产取消") # 设置按钮图标 - self.startButton.setIcon(QIcon(down_icon_path)) - self.restartButton.setIcon(QIcon(error_icon_path)) - self.stopButton.setIcon(QIcon(cancel_icon_path)) + 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.startButton.setIconSize(QSize(20, 20)) - self.restartButton.setIconSize(QSize(20, 20)) - self.stopButton.setIconSize(QSize(20, 20)) + self.startFeedButton.setIconSize(QSize(20, 20)) + self.finishButton.setIconSize(QSize(20, 20)) + self.errorButton.setIconSize(QSize(20, 20)) + self.cancelButton.setIconSize(QSize(20, 20)) button_base_style = """ QPushButton { @@ -294,7 +295,15 @@ class StatusMonitor(QWidget): } """ - self.startButton.setStyleSheet(button_base_style + """ + 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; @@ -302,7 +311,7 @@ class StatusMonitor(QWidget): } """) - self.restartButton.setStyleSheet(button_base_style + """ + self.errorButton.setStyleSheet(button_base_style + """ QPushButton { background-color: #E65100; color: white; @@ -310,7 +319,7 @@ class StatusMonitor(QWidget): } """) - self.stopButton.setStyleSheet(button_base_style + """ + self.cancelButton.setStyleSheet(button_base_style + """ QPushButton { background-color: #C62828; color: white; @@ -319,18 +328,27 @@ class StatusMonitor(QWidget): """) button_font = QFont("Microsoft YaHei", 12, QFont.Bold) - self.startButton.setFont(button_font) - self.restartButton.setFont(button_font) - self.stopButton.setFont(button_font) + self.startFeedButton.setFont(button_font) + self.finishButton.setFont(button_font) + self.errorButton.setFont(button_font) + self.cancelButton.setFont(button_font) - self.startButton.clicked.connect(self.on_start_clicked) - self.restartButton.clicked.connect(self.on_restart_clicked) - self.stopButton.clicked.connect(self.on_stop_clicked) + 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.startButton) - buttonLayout.addWidget(self.restartButton) - buttonLayout.addWidget(self.stopButton) + buttonLayout.addWidget(self.startFeedButton) + buttonLayout.addWidget(self.finishButton) + buttonLayout.addWidget(self.errorButton) + buttonLayout.addWidget(self.cancelButton) buttonLayout.addStretch(1) parent_layout.addWidget(buttonContainer) @@ -342,15 +360,14 @@ class StatusMonitor(QWidget): self.time_timer.timeout.connect(self.update_time) self.time_timer.start(1000) - # 状态模拟定时器(每3秒更新一次) - self.status_timer = QTimer() - self.status_timer.timeout.connect(self.simulate_mcu_query) - self.status_timer.start(3000) - 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() @@ -380,92 +397,69 @@ class StatusMonitor(QWidget): # 绘制文本 painter.drawText(x, y + text_rect.height(), text) - def simulate_mcu_query(self): - """模拟单片机状态查询""" - import random # 仅用于模拟,实际项目替换为真实通信逻辑 + def _save_data_to_file(self, button_name): + """ + 将服务端数据+按钮操作信息保存到JSON文件 - if self.is_running: - for widget in self.statusWidgets: - status = random.choice([True, False]) - widget['status'] = status + 参数:button_name:点击的按钮名称 + """ + # 1、检查是否有服务端数据 + if not self.latest_server_data: + print(f"⚠️ 未收到服务端数据,无法保存「{button_name}」操作记录") + return - if status: - # 状态为"是"时的样式 - 去除边框 - widget['valueLabel'].setText("是") - widget['valueLabel'].setStyleSheet(""" - QLabel { - font-size: 18px; - font-weight: bold; - color: #00E676; - background-color: #2D2D2D; /* 与父容器相同背景色 */ - padding: 5px 10px; - border: none; /* 明确去除边框 */ - min-width: 60px; - } - """) - widget['indicator'].setStyleSheet(""" - QLabel { - background-color: #00E676; - border-radius: 10px; - border: 2px solid #00796B; - } - """) - else: - # 状态为"否"时的样式 - 去除边框 - widget['valueLabel'].setText("否") - widget['valueLabel'].setStyleSheet(""" - QLabel { - font-size: 18px; - font-weight: bold; - color: #FF5252; - background-color: #2D2D2D; /* 与父容器相同背景色 */ - padding: 5px 10px; - border: none; /* 明确去除边框 */ - min-width: 60px; - } - """) - widget['indicator'].setStyleSheet(""" - QLabel { - background-color: #FF5252; - border-radius: 10px; - border: 2px solid #C62828; - } - """) + # 2、构建完整数据(服务端数据+按钮操作信息+存档时间) + save_data = { + "opration_button": button_name, + "save_time": self.get_current_time(), + "server_data":self.latest_server_data + } - def on_start_clicked(self): - """开始按钮点击事件""" - if not self.is_running: - self.is_running = True - print("系统已启动,开始监控状态...") - self.simulate_mcu_query() # 启动时立即更新一次状态 + # 3、生成唯一文件名(按时间戳命名) + file_name = f"operation_record_{self.get_timestamp()}.json" + file_path = os.path.join(SAVE_DIR, file_name) - def on_stop_clicked(self): - """停止按钮点击事件""" - if self.is_running: - self.is_running = False - print("系统已停止,状态监控暂停...") + # 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 on_restart_clicked(self): - """重启按钮点击事件""" - 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.finishButton.setDisabled(False) + self.errorButton.setDisabled(False) + self.cancelButton.setDisabled(False) + + @Slot() + def _on_tcp_disconnected(self): + """TCP连接断开回调""" + self.is_tcp_connected = False self.is_running = False - # 重置所有状态为初始值和样式 - for i, widget in enumerate(self.statusWidgets): - widget['status'] = False - # 恢复初始值 - widget['valueLabel'].setText(widget['initial_value']) - # 恢复初始样式(无边框) - widget['valueLabel'].setStyleSheet(""" - QLabel { - font-size: 16px; - font-weight: bold; - color: #FFFFFF; - background-color: #2D2D2D; - border: none; - padding: 0px; - min-width: 80px; - } - """) + 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) + + # 重置状态指示灯为“未连接”状态 + for widget in self.statusWidgets: widget['indicator'].setStyleSheet(""" QLabel { background-color: #9E9E9E; @@ -473,9 +467,118 @@ class StatusMonitor(QWidget): border: 2px solid #555555; } """) - # 重启后自动开始 - QTimer.singleShot(1000, self.on_start_clicked) + @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.startFeedButton.setDisabled(False) + self.finishButton.setDisabled(True) + 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_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._send_tcp_request("production_error") + self._save_data_to_file("production_error") + + def on_cancel_clicked(self): + """点击“生产取消”:向服务端发送取消指令""" + print("🔘 点击「生产取消」按钮") + self._send_tcp_request("cancel_feed") + self._save_data_to_file("cancel_feed") if __name__ == "__main__": app = QApplication(sys.argv)