添加TCP通讯,添加开始上料按钮,修改按钮逻辑

This commit is contained in:
2025-09-19 20:51:35 +08:00
parent b439b1d91a
commit b7fc062f3f
16 changed files with 1036 additions and 486 deletions

3
.idea/.gitignore generated vendored Normal file
View File

@ -0,0 +1,3 @@
# 默认忽略的文件
/shelf/
/workspace.xml

View File

@ -0,0 +1,18 @@
<component name="InspectionProjectProfileManager">
<profile version="1.0">
<option name="myName" value="Project Default" />
<inspection_tool class="PyPackageRequirementsInspection" enabled="true" level="WARNING" enabled_by_default="true">
<option name="ignoredPackages">
<value>
<list size="5">
<item index="0" class="java.lang.String" itemvalue="scipy" />
<item index="1" class="java.lang.String" itemvalue="numpy" />
<item index="2" class="java.lang.String" itemvalue="snap7" />
<item index="3" class="java.lang.String" itemvalue="jsonchema" />
<item index="4" class="java.lang.String" itemvalue="werkzeung" />
</list>
</value>
</option>
</inspection_tool>
</profile>
</component>

View File

@ -0,0 +1,6 @@
<component name="InspectionProjectProfileManager">
<settings>
<option name="USE_PROJECT_PROFILE" value="false" />
<version value="1.0" />
</settings>
</component>

7
.idea/misc.xml generated Normal file
View File

@ -0,0 +1,7 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="Black">
<option name="sdkName" value="pyside6" />
</component>
<component name="ProjectRootManager" version="2" project-jdk-name="pyside6" project-jdk-type="Python SDK" />
</project>

8
.idea/modules.xml generated Normal file
View File

@ -0,0 +1,8 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="ProjectModuleManager">
<modules>
<module fileurl="file://$PROJECT_DIR$/.idea/zjsh_ui_sysytem.iml" filepath="$PROJECT_DIR$/.idea/zjsh_ui_sysytem.iml" />
</modules>
</component>
</project>

6
.idea/vcs.xml generated Normal file
View File

@ -0,0 +1,6 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="VcsDirectoryMappings">
<mapping directory="" vcs="Git" />
</component>
</project>

8
.idea/zjsh_ui_sysytem.iml generated Normal file
View File

@ -0,0 +1,8 @@
<?xml version="1.0" encoding="UTF-8"?>
<module type="PYTHON_MODULE" version="4">
<component name="NewModuleRootManager">
<content url="file://$MODULE_DIR$" />
<orderEntry type="jdk" jdkName="pyside6" jdkType="Python SDK" />
<orderEntry type="sourceFolder" forTests="false" />
</component>
</module>

12
config/config.json Normal file
View File

@ -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": "移动后"
}

191
config/tcp_server.py Normal file
View File

@ -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()

467
main.py
View File

@ -10,14 +10,27 @@ from PySide6.QtWidgets import (
QApplication, QWidget, QVBoxLayout, QHBoxLayout, QApplication, QWidget, QVBoxLayout, QHBoxLayout,
QLabel, QFrame, QSizePolicy, QPushButton 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 PySide6.QtGui import QFont, QPainter, QColor, QPen, QIcon
from datetime import datetime 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): class StatusMonitor(QWidget):
""" """
中交三航精准布料浇筑要料系统 - 主界面类(深色主题) 中交三航精准布料浇筑要料系统 - 主界面类(深色主题)
使用TCP进行数据传输客户端模型与TCP服务器通信
""" """
def __init__(self, parent=None): def __init__(self, parent=None):
@ -25,6 +38,19 @@ class StatusMonitor(QWidget):
super().__init__(parent=parent) super().__init__(parent=parent)
self.is_running = False # 系统运行状态标记 self.is_running = False # 系统运行状态标记
self.current_datetime = self.get_current_time() # 当前日期时间 self.current_datetime = self.get_current_time() # 当前日期时间
# 缓存服务端发送的最新JSON数据
self.latest_json_data = {}
# ---------------
# TCP客户端核心配置
# ---------------
self.tcp_socket = QTcpSocket(self) # TCP socket实例
self.tcp_server_host = tcp_server_host
self.tcp_server_port = tcp_server_port
self.is_tcp_connected = False # TCP连接状态标记
# 绑定TCP信号与槽事件驱动
self._bind_tcp_signals()
# 窗口基础设置 # 窗口基础设置
self.setWindowTitle("中交三航精准布料浇筑要料系统") self.setWindowTitle("中交三航精准布料浇筑要料系统")
@ -40,6 +66,32 @@ class StatusMonitor(QWidget):
self._init_title_label() self._init_title_label()
self._init_status_container() self._init_status_container()
self._init_timers() 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): def _init_title_label(self):
"""初始化系统标题标签""" """初始化系统标题标签"""
@ -97,24 +149,40 @@ class StatusMonitor(QWidget):
# 左边5个状态项及对应初始值 # 左边5个状态项及对应初始值
leftStatusInfo = [ leftStatusInfo = [
{"name": "任务单号", "value": "20250706-01"}, {"name": "任务单号", "value": "20250706-01", "api_field": "task_id"},
{"name": "工程名称", "value": "18号线二期工程"}, {"name": "工程名称", "value": "18号线二期工程", "api_field": "project_name"},
{"name": "区间段", "value": "停车场工作并上行"}, {"name": "区间段", "value": "停车场工作并上行", "api_field": "section"},
{"name": "坍落度", "value": "50~70 mm"}, {"name": "坍落度", "value": "50~70 mm", "api_field": "slump"},
{"name": "配合比编号", "value": "P2022=001"} {"name": "配合比编号", "value": "P2022=001", "api_field": "mix_ratio_id"}
] ]
# 右边5个状态项及对应初始值 # 右边5个状态项及对应初始值
rightStatusInfo = [ rightStatusInfo = [
{"name": "要料状态", "value": "请求中"}, {"name": "要料状态", "value": "请求中", "api_field": "request_status"},
{"name": "要料标号", "value": "C50P12"}, {"name": "要料标号", "value": "C50P12", "api_field": "material_grade"},
{"name": "要料方量", "value": "2m³"}, {"name": "要料方量", "value": "2m³", "api_field": "volume"},
{"name": "要料时间", "value": "2分钟后"}, {"name": "要料时间", "value": "2分钟后", "api_field": "request_time"},
{"name": "小车状态", "value": "移动后"} {"name": "小车状态", "value": "移动后", "api_field": "car_status"}
] ]
self.statusWidgets = [] self.statusWidgets = []
# 处理左边状态项 # 处理左边状态项
for info in leftStatusInfo: for info in leftStatusInfo:
statusItem = self._create_status_item(info)
leftLayout.addWidget(statusItem)
# 处理右边状态项
for info in rightStatusInfo:
statusItem = self._create_status_item(info)
rightLayout.addWidget(statusItem)
statusLayout.addWidget(leftGroup)
statusLayout.addStretch(0) # 减小中间空白比例原1
statusLayout.addWidget(rightGroup)
parent_layout.addWidget(statusWidget)
def _create_status_item(self, info):
"""创建单个状态项"""
statusItem = QFrame() statusItem = QFrame()
statusItem.setStyleSheet(""" statusItem.setStyleSheet("""
QFrame { QFrame {
@ -174,82 +242,11 @@ class StatusMonitor(QWidget):
'nameLabel': nameLabel, 'nameLabel': nameLabel,
'valueLabel': valueLabel, 'valueLabel': valueLabel,
'status': False, 'status': False,
'initial_value': info["value"] 'initial_value': info["value"],
'api_field': info["api_field"]
}) })
leftLayout.addWidget(statusItem) return 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"]
})
rightLayout.addWidget(statusItem)
statusLayout.addWidget(leftGroup)
statusLayout.addStretch(0) # 减小中间空白比例原1
statusLayout.addWidget(rightGroup)
parent_layout.addWidget(statusWidget)
def _init_operation_buttons(self, parent_layout): def _init_operation_buttons(self, parent_layout):
"""初始化操作按钮(下料完成/生产异常/生产取消)""" """初始化操作按钮(下料完成/生产异常/生产取消)"""
@ -259,23 +256,27 @@ class StatusMonitor(QWidget):
buttonLayout.setSpacing(30) buttonLayout.setSpacing(30)
# 按钮图标(需替换为实际图标路径) # 按钮图标(需替换为实际图标路径)
start_icon_path = "img.png"
down_icon_path = "img.png" down_icon_path = "img.png"
error_icon_path = "img.png" error_icon_path = "img.png"
cancel_icon_path = "img.png" cancel_icon_path = "img.png"
self.startButton = QPushButton("下料完成") self.startFeedButton = QPushButton("开始下料")
self.restartButton = QPushButton("生产异常") self.finishButton = QPushButton("下料完成")
self.stopButton = QPushButton("生产取消") self.errorButton = QPushButton("生产异常")
self.cancelButton = QPushButton("生产取消")
# 设置按钮图标 # 设置按钮图标
self.startButton.setIcon(QIcon(down_icon_path)) self.startFeedButton.setIcon(QIcon(start_icon_path))
self.restartButton.setIcon(QIcon(error_icon_path)) self.finishButton.setIcon(QIcon(down_icon_path))
self.stopButton.setIcon(QIcon(cancel_icon_path)) self.errorButton.setIcon(QIcon(error_icon_path))
self.cancelButton.setIcon(QIcon(cancel_icon_path))
# 设置图标大小 # 设置图标大小
self.startButton.setIconSize(QSize(20, 20)) self.startFeedButton.setIconSize(QSize(20, 20))
self.restartButton.setIconSize(QSize(20, 20)) self.finishButton.setIconSize(QSize(20, 20))
self.stopButton.setIconSize(QSize(20, 20)) self.errorButton.setIconSize(QSize(20, 20))
self.cancelButton.setIconSize(QSize(20, 20))
button_base_style = """ button_base_style = """
QPushButton { 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 { QPushButton {
background-color: #00796B; background-color: #00796B;
color: white; color: white;
@ -302,7 +311,7 @@ class StatusMonitor(QWidget):
} }
""") """)
self.restartButton.setStyleSheet(button_base_style + """ self.errorButton.setStyleSheet(button_base_style + """
QPushButton { QPushButton {
background-color: #E65100; background-color: #E65100;
color: white; color: white;
@ -310,7 +319,7 @@ class StatusMonitor(QWidget):
} }
""") """)
self.stopButton.setStyleSheet(button_base_style + """ self.cancelButton.setStyleSheet(button_base_style + """
QPushButton { QPushButton {
background-color: #C62828; background-color: #C62828;
color: white; color: white;
@ -319,18 +328,27 @@ class StatusMonitor(QWidget):
""") """)
button_font = QFont("Microsoft YaHei", 12, QFont.Bold) button_font = QFont("Microsoft YaHei", 12, QFont.Bold)
self.startButton.setFont(button_font) self.startFeedButton.setFont(button_font)
self.restartButton.setFont(button_font) self.finishButton.setFont(button_font)
self.stopButton.setFont(button_font) self.errorButton.setFont(button_font)
self.cancelButton.setFont(button_font)
self.startButton.clicked.connect(self.on_start_clicked) self.startFeedButton.clicked.connect(self.on_start_clicked)
self.restartButton.clicked.connect(self.on_restart_clicked) self.finishButton.clicked.connect(self.on_finish_clicked)
self.stopButton.clicked.connect(self.on_stop_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.addStretch(1)
buttonLayout.addWidget(self.startButton) buttonLayout.addWidget(self.startFeedButton)
buttonLayout.addWidget(self.restartButton) buttonLayout.addWidget(self.finishButton)
buttonLayout.addWidget(self.stopButton) buttonLayout.addWidget(self.errorButton)
buttonLayout.addWidget(self.cancelButton)
buttonLayout.addStretch(1) buttonLayout.addStretch(1)
parent_layout.addWidget(buttonContainer) parent_layout.addWidget(buttonContainer)
@ -342,15 +360,14 @@ class StatusMonitor(QWidget):
self.time_timer.timeout.connect(self.update_time) self.time_timer.timeout.connect(self.update_time)
self.time_timer.start(1000) 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): def get_current_time(self):
"""获取格式化的当前时间""" """获取格式化的当前时间"""
return datetime.now().strftime('%Y-%m-%d %H:%M:%S') 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): def update_time(self):
"""更新时间显示并触发重绘""" """更新时间显示并触发重绘"""
self.current_datetime = self.get_current_time() self.current_datetime = self.get_current_time()
@ -380,29 +397,140 @@ class StatusMonitor(QWidget):
# 绘制文本 # 绘制文本
painter.drawText(x, y + text_rect.height(), text) painter.drawText(x, y + text_rect.height(), text)
def simulate_mcu_query(self): def _save_data_to_file(self, button_name):
"""模拟单片机状态查询""" """
import random # 仅用于模拟,实际项目替换为真实通信逻辑 将服务端数据+按钮操作信息保存到JSON文件
if self.is_running: 参数button_name:点击的按钮名称
"""
# 1、检查是否有服务端数据
if not self.latest_server_data:
print(f"⚠️ 未收到服务端数据,无法保存「{button_name}」操作记录")
return
# 2、构建完整数据服务端数据+按钮操作信息+存档时间)
save_data = {
"opration_button": button_name,
"save_time": self.get_current_time(),
"server_data":self.latest_server_data
}
# 3、生成唯一文件名按时间戳命名
file_name = f"operation_record_{self.get_timestamp()}.json"
file_path = os.path.join(SAVE_DIR, file_name)
# 4、写入JSON文件
try:
with open(file_path, "w", encoding="utf-8") as f:
json.dump(save_data, f, ensure_ascii=False, indent=4)
print(f"💾 保存「{button_name}」操作记录成功:{os.path.abspath(file_path)}")
except Exception as e:
print(f"💾 保存「{button_name}」操作记录失败:{str(e)}")
# ------------------
# 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
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: for widget in self.statusWidgets:
status = random.choice([True, False]) widget['indicator'].setStyleSheet("""
widget['status'] = status
if status:
# 状态为"是"时的样式 - 去除边框
widget['valueLabel'].setText("")
widget['valueLabel'].setStyleSheet("""
QLabel { QLabel {
font-size: 18px; background-color: #9E9E9E;
font-weight: bold; border-radius: 10px;
color: #00E676; border: 2px solid #555555;
background-color: #2D2D2D; /* 与父容器相同背景色 */
padding: 5px 10px;
border: none; /* 明确去除边框 */
min-width: 60px;
} }
""") """)
@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(""" widget['indicator'].setStyleSheet("""
QLabel { QLabel {
background-color: #00E676; background-color: #00E676;
@ -411,19 +539,7 @@ class StatusMonitor(QWidget):
} }
""") """)
else: 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(""" widget['indicator'].setStyleSheet("""
QLabel { QLabel {
background-color: #FF5252; background-color: #FF5252;
@ -432,50 +548,37 @@ class StatusMonitor(QWidget):
} }
""") """)
# ------------------
# 按钮点击事件
# ------------------
def on_start_clicked(self): def on_start_clicked(self):
"""开始按钮点击事件""" """点击“开始下料”:向服务端发送开始指令"""
if not self.is_running: print("🔘 点击「开始下料」按钮")
self.is_running = True if not self.is_tcp_connected:
print("系统已启动,开始监控状态...") print("TCP连接未建立尝试重新连接")
self.simulate_mcu_query() # 启动时立即更新一次状态 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_stop_clicked(self): def on_finish_clicked(self):
"""停止按钮点击事件""" """点击“下料完成”:向服务端发送完成指令"""
if self.is_running: print("🔘 点击「下料完成」按钮")
self.is_running = False self._send_tcp_request("finish_feed")
print("系统已停止,状态监控暂停...") self._save_data_to_file("finish_feed")
def on_restart_clicked(self): def on_error_clicked(self):
"""重启按钮点击事件""" """点击“生产异常”:向服务端发送异常指令"""
print("系统正在重启...") print("🔘 点击「生产异常」按钮")
self.is_running = False self._send_tcp_request("production_error")
# 重置所有状态为初始值和样式 self._save_data_to_file("production_error")
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;
}
""")
widget['indicator'].setStyleSheet("""
QLabel {
background-color: #9E9E9E;
border-radius: 10px;
border: 2px solid #555555;
}
""")
# 重启后自动开始
QTimer.singleShot(1000, self.on_start_clicked)
def on_cancel_clicked(self):
"""点击“生产取消”:向服务端发送取消指令"""
print("🔘 点击「生产取消」按钮")
self._send_tcp_request("cancel_feed")
self._save_data_to_file("cancel_feed")
if __name__ == "__main__": if __name__ == "__main__":
app = QApplication(sys.argv) app = QApplication(sys.argv)

View File

@ -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"
}
}

View File

@ -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"
}
}

View File

@ -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"
}
}

View File

@ -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"
}
}

View File

@ -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"
}
}

467
test.py
View File

@ -10,14 +10,27 @@ from PySide6.QtWidgets import (
QApplication, QWidget, QVBoxLayout, QHBoxLayout, QApplication, QWidget, QVBoxLayout, QHBoxLayout,
QLabel, QFrame, QSizePolicy, QPushButton 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 PySide6.QtGui import QFont, QPainter, QColor, QPen, QIcon
from datetime import datetime 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): class StatusMonitor(QWidget):
""" """
中交三航精准布料浇筑要料系统 - 主界面类(深色主题) 中交三航精准布料浇筑要料系统 - 主界面类(深色主题)
使用TCP进行数据传输客户端模型与TCP服务器通信
""" """
def __init__(self, parent=None): def __init__(self, parent=None):
@ -25,6 +38,19 @@ class StatusMonitor(QWidget):
super().__init__(parent=parent) super().__init__(parent=parent)
self.is_running = False # 系统运行状态标记 self.is_running = False # 系统运行状态标记
self.current_datetime = self.get_current_time() # 当前日期时间 self.current_datetime = self.get_current_time() # 当前日期时间
# 缓存服务端发送的最新JSON数据
self.latest_json_data = {}
# ---------------
# TCP客户端核心配置
# ---------------
self.tcp_socket = QTcpSocket(self) # TCP socket实例
self.tcp_server_host = tcp_server_host
self.tcp_server_port = tcp_server_port
self.is_tcp_connected = False # TCP连接状态标记
# 绑定TCP信号与槽事件驱动
self._bind_tcp_signals()
# 窗口基础设置 # 窗口基础设置
self.setWindowTitle("中交三航精准布料浇筑要料系统") self.setWindowTitle("中交三航精准布料浇筑要料系统")
@ -40,6 +66,32 @@ class StatusMonitor(QWidget):
self._init_title_label() self._init_title_label()
self._init_status_container() self._init_status_container()
self._init_timers() 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): def _init_title_label(self):
"""初始化系统标题标签""" """初始化系统标题标签"""
@ -97,24 +149,40 @@ class StatusMonitor(QWidget):
# 左边5个状态项及对应初始值 # 左边5个状态项及对应初始值
leftStatusInfo = [ leftStatusInfo = [
{"name": "任务单号", "value": "20250706-01"}, {"name": "任务单号", "value": "20250706-01", "api_field": "task_id"},
{"name": "工程名称", "value": "18号线二期工程"}, {"name": "工程名称", "value": "18号线二期工程", "api_field": "project_name"},
{"name": "区间段", "value": "停车场工作并上行"}, {"name": "区间段", "value": "停车场工作并上行", "api_field": "section"},
{"name": "坍落度", "value": "50~70 mm"}, {"name": "坍落度", "value": "50~70 mm", "api_field": "slump"},
{"name": "配合比编号", "value": "P2022=001"} {"name": "配合比编号", "value": "P2022=001", "api_field": "mix_ratio_id"}
] ]
# 右边5个状态项及对应初始值 # 右边5个状态项及对应初始值
rightStatusInfo = [ rightStatusInfo = [
{"name": "要料状态", "value": "请求中"}, {"name": "要料状态", "value": "请求中", "api_field": "request_status"},
{"name": "要料标号", "value": "C50P12"}, {"name": "要料标号", "value": "C50P12", "api_field": "material_grade"},
{"name": "要料方量", "value": "2m³"}, {"name": "要料方量", "value": "2m³", "api_field": "volume"},
{"name": "要料时间", "value": "2分钟后"}, {"name": "要料时间", "value": "2分钟后", "api_field": "request_time"},
{"name": "小车状态", "value": "移动后"} {"name": "小车状态", "value": "移动后", "api_field": "car_status"}
] ]
self.statusWidgets = [] self.statusWidgets = []
# 处理左边状态项 # 处理左边状态项
for info in leftStatusInfo: for info in leftStatusInfo:
statusItem = self._create_status_item(info)
leftLayout.addWidget(statusItem)
# 处理右边状态项
for info in rightStatusInfo:
statusItem = self._create_status_item(info)
rightLayout.addWidget(statusItem)
statusLayout.addWidget(leftGroup)
statusLayout.addStretch(0) # 减小中间空白比例原1
statusLayout.addWidget(rightGroup)
parent_layout.addWidget(statusWidget)
def _create_status_item(self, info):
"""创建单个状态项"""
statusItem = QFrame() statusItem = QFrame()
statusItem.setStyleSheet(""" statusItem.setStyleSheet("""
QFrame { QFrame {
@ -174,82 +242,11 @@ class StatusMonitor(QWidget):
'nameLabel': nameLabel, 'nameLabel': nameLabel,
'valueLabel': valueLabel, 'valueLabel': valueLabel,
'status': False, 'status': False,
'initial_value': info["value"] 'initial_value': info["value"],
'api_field': info["api_field"]
}) })
leftLayout.addWidget(statusItem) return 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"]
})
rightLayout.addWidget(statusItem)
statusLayout.addWidget(leftGroup)
statusLayout.addStretch(0) # 减小中间空白比例原1
statusLayout.addWidget(rightGroup)
parent_layout.addWidget(statusWidget)
def _init_operation_buttons(self, parent_layout): def _init_operation_buttons(self, parent_layout):
"""初始化操作按钮(下料完成/生产异常/生产取消)""" """初始化操作按钮(下料完成/生产异常/生产取消)"""
@ -259,23 +256,27 @@ class StatusMonitor(QWidget):
buttonLayout.setSpacing(30) buttonLayout.setSpacing(30)
# 按钮图标(需替换为实际图标路径) # 按钮图标(需替换为实际图标路径)
start_icon_path = "img.png"
down_icon_path = "img.png" down_icon_path = "img.png"
error_icon_path = "img.png" error_icon_path = "img.png"
cancel_icon_path = "img.png" cancel_icon_path = "img.png"
self.startButton = QPushButton("下料完成") self.startFeedButton = QPushButton("开始下料")
self.restartButton = QPushButton("生产异常") self.finishButton = QPushButton("下料完成")
self.stopButton = QPushButton("生产取消") self.errorButton = QPushButton("生产异常")
self.cancelButton = QPushButton("生产取消")
# 设置按钮图标 # 设置按钮图标
self.startButton.setIcon(QIcon(down_icon_path)) self.startFeedButton.setIcon(QIcon(start_icon_path))
self.restartButton.setIcon(QIcon(error_icon_path)) self.finishButton.setIcon(QIcon(down_icon_path))
self.stopButton.setIcon(QIcon(cancel_icon_path)) self.errorButton.setIcon(QIcon(error_icon_path))
self.cancelButton.setIcon(QIcon(cancel_icon_path))
# 设置图标大小 # 设置图标大小
self.startButton.setIconSize(QSize(20, 20)) self.startFeedButton.setIconSize(QSize(20, 20))
self.restartButton.setIconSize(QSize(20, 20)) self.finishButton.setIconSize(QSize(20, 20))
self.stopButton.setIconSize(QSize(20, 20)) self.errorButton.setIconSize(QSize(20, 20))
self.cancelButton.setIconSize(QSize(20, 20))
button_base_style = """ button_base_style = """
QPushButton { 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 { QPushButton {
background-color: #00796B; background-color: #00796B;
color: white; color: white;
@ -302,7 +311,7 @@ class StatusMonitor(QWidget):
} }
""") """)
self.restartButton.setStyleSheet(button_base_style + """ self.errorButton.setStyleSheet(button_base_style + """
QPushButton { QPushButton {
background-color: #E65100; background-color: #E65100;
color: white; color: white;
@ -310,7 +319,7 @@ class StatusMonitor(QWidget):
} }
""") """)
self.stopButton.setStyleSheet(button_base_style + """ self.cancelButton.setStyleSheet(button_base_style + """
QPushButton { QPushButton {
background-color: #C62828; background-color: #C62828;
color: white; color: white;
@ -319,18 +328,27 @@ class StatusMonitor(QWidget):
""") """)
button_font = QFont("Microsoft YaHei", 12, QFont.Bold) button_font = QFont("Microsoft YaHei", 12, QFont.Bold)
self.startButton.setFont(button_font) self.startFeedButton.setFont(button_font)
self.restartButton.setFont(button_font) self.finishButton.setFont(button_font)
self.stopButton.setFont(button_font) self.errorButton.setFont(button_font)
self.cancelButton.setFont(button_font)
self.startButton.clicked.connect(self.on_start_clicked) self.startFeedButton.clicked.connect(self.on_start_clicked)
self.restartButton.clicked.connect(self.on_restart_clicked) self.finishButton.clicked.connect(self.on_finish_clicked)
self.stopButton.clicked.connect(self.on_stop_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.addStretch(1)
buttonLayout.addWidget(self.startButton) buttonLayout.addWidget(self.startFeedButton)
buttonLayout.addWidget(self.restartButton) buttonLayout.addWidget(self.finishButton)
buttonLayout.addWidget(self.stopButton) buttonLayout.addWidget(self.errorButton)
buttonLayout.addWidget(self.cancelButton)
buttonLayout.addStretch(1) buttonLayout.addStretch(1)
parent_layout.addWidget(buttonContainer) parent_layout.addWidget(buttonContainer)
@ -342,15 +360,14 @@ class StatusMonitor(QWidget):
self.time_timer.timeout.connect(self.update_time) self.time_timer.timeout.connect(self.update_time)
self.time_timer.start(1000) 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): def get_current_time(self):
"""获取格式化的当前时间""" """获取格式化的当前时间"""
return datetime.now().strftime('%Y-%m-%d %H:%M:%S') 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): def update_time(self):
"""更新时间显示并触发重绘""" """更新时间显示并触发重绘"""
self.current_datetime = self.get_current_time() self.current_datetime = self.get_current_time()
@ -380,29 +397,140 @@ class StatusMonitor(QWidget):
# 绘制文本 # 绘制文本
painter.drawText(x, y + text_rect.height(), text) painter.drawText(x, y + text_rect.height(), text)
def simulate_mcu_query(self): def _save_data_to_file(self, button_name):
"""模拟单片机状态查询""" """
import random # 仅用于模拟,实际项目替换为真实通信逻辑 将服务端数据+按钮操作信息保存到JSON文件
if self.is_running: 参数button_name:点击的按钮名称
"""
# 1、检查是否有服务端数据
if not self.latest_server_data:
print(f"⚠️ 未收到服务端数据,无法保存「{button_name}」操作记录")
return
# 2、构建完整数据服务端数据+按钮操作信息+存档时间)
save_data = {
"opration_button": button_name,
"save_time": self.get_current_time(),
"server_data":self.latest_server_data
}
# 3、生成唯一文件名按时间戳命名
file_name = f"operation_record_{self.get_timestamp()}.json"
file_path = os.path.join(SAVE_DIR, file_name)
# 4、写入JSON文件
try:
with open(file_path, "w", encoding="utf-8") as f:
json.dump(save_data, f, ensure_ascii=False, indent=4)
print(f"💾 保存「{button_name}」操作记录成功:{os.path.abspath(file_path)}")
except Exception as e:
print(f"💾 保存「{button_name}」操作记录失败:{str(e)}")
# ------------------
# 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
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: for widget in self.statusWidgets:
status = random.choice([True, False]) widget['indicator'].setStyleSheet("""
widget['status'] = status
if status:
# 状态为"是"时的样式 - 去除边框
widget['valueLabel'].setText("")
widget['valueLabel'].setStyleSheet("""
QLabel { QLabel {
font-size: 18px; background-color: #9E9E9E;
font-weight: bold; border-radius: 10px;
color: #00E676; border: 2px solid #555555;
background-color: #2D2D2D; /* 与父容器相同背景色 */
padding: 5px 10px;
border: none; /* 明确去除边框 */
min-width: 60px;
} }
""") """)
@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(""" widget['indicator'].setStyleSheet("""
QLabel { QLabel {
background-color: #00E676; background-color: #00E676;
@ -411,19 +539,7 @@ class StatusMonitor(QWidget):
} }
""") """)
else: 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(""" widget['indicator'].setStyleSheet("""
QLabel { QLabel {
background-color: #FF5252; background-color: #FF5252;
@ -432,50 +548,37 @@ class StatusMonitor(QWidget):
} }
""") """)
# ------------------
# 按钮点击事件
# ------------------
def on_start_clicked(self): def on_start_clicked(self):
"""开始按钮点击事件""" """点击“开始下料”:向服务端发送开始指令"""
if not self.is_running: print("🔘 点击「开始下料」按钮")
self.is_running = True if not self.is_tcp_connected:
print("系统已启动,开始监控状态...") print("TCP连接未建立尝试重新连接")
self.simulate_mcu_query() # 启动时立即更新一次状态 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_stop_clicked(self): def on_finish_clicked(self):
"""停止按钮点击事件""" """点击“下料完成”:向服务端发送完成指令"""
if self.is_running: print("🔘 点击「下料完成」按钮")
self.is_running = False self._send_tcp_request("finish_feed")
print("系统已停止,状态监控暂停...") self._save_data_to_file("finish_feed")
def on_restart_clicked(self): def on_error_clicked(self):
"""重启按钮点击事件""" """点击“生产异常”:向服务端发送异常指令"""
print("系统正在重启...") print("🔘 点击「生产异常」按钮")
self.is_running = False self._send_tcp_request("production_error")
# 重置所有状态为初始值和样式 self._save_data_to_file("production_error")
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;
}
""")
widget['indicator'].setStyleSheet("""
QLabel {
background-color: #9E9E9E;
border-radius: 10px;
border: 2px solid #555555;
}
""")
# 重启后自动开始
QTimer.singleShot(1000, self.on_start_clicked)
def on_cancel_clicked(self):
"""点击“生产取消”:向服务端发送取消指令"""
print("🔘 点击「生产取消」按钮")
self._send_tcp_request("cancel_feed")
self._save_data_to_file("cancel_feed")
if __name__ == "__main__": if __name__ == "__main__":
app = QApplication(sys.argv) app = QApplication(sys.argv)