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)