2025-11-16 11:58:22 +08:00
|
|
|
|
#!/usr/bin/env python
|
|
|
|
|
|
# -*- coding: utf-8 -*-
|
|
|
|
|
|
'''
|
|
|
|
|
|
# @Time : 2025/11/13 11:05
|
|
|
|
|
|
# @Author : reenrr
|
|
|
|
|
|
# @File : tcp_client.py
|
|
|
|
|
|
'''
|
|
|
|
|
|
import json
|
|
|
|
|
|
from PySide6.QtCore import QTimer, Slot, Signal, QObject
|
|
|
|
|
|
from PySide6.QtNetwork import QTcpSocket, QAbstractSocket
|
|
|
|
|
|
from datetime import datetime
|
|
|
|
|
|
|
|
|
|
|
|
# -----------
|
|
|
|
|
|
# 参数配置
|
|
|
|
|
|
# -----------
|
|
|
|
|
|
tcp_server_host = "127.0.0.1"
|
|
|
|
|
|
tcp_server_port = 8888
|
|
|
|
|
|
|
|
|
|
|
|
RECONNECT_INTERVAL = 2000 # 重连间隔(毫秒)
|
|
|
|
|
|
|
|
|
|
|
|
class TcpClient(QObject):
|
|
|
|
|
|
# 信号,用于向主窗口/重量弹窗发送最新重量数据
|
|
|
|
|
|
weight_updated = Signal(dict) # 重量更新信号
|
|
|
|
|
|
connection_status_changed = Signal(bool) # 连接状态更新信号
|
2025-11-17 16:02:46 +08:00
|
|
|
|
upper_weight_error = Signal(bool) # 上料斗重量异常信号
|
|
|
|
|
|
down_weight_error = Signal(bool) # 下料斗重量异常信号
|
2025-11-16 11:58:22 +08:00
|
|
|
|
|
|
|
|
|
|
def __init__(self, parent=None):
|
|
|
|
|
|
super().__init__(parent)
|
|
|
|
|
|
|
|
|
|
|
|
self.is_running = False # 系统运行状态标记
|
|
|
|
|
|
self.latest_json_data = {} # 缓存服务端发送的最新JSON数据
|
|
|
|
|
|
|
|
|
|
|
|
self.statusWidgets = [] # 存储api_field + 对应的value标签
|
|
|
|
|
|
|
|
|
|
|
|
# ---------------
|
|
|
|
|
|
# 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连接状态标记
|
|
|
|
|
|
self.has_connected_once = False # 连接至服务器至少一次标记(区别首次连接和断开后重连)
|
|
|
|
|
|
self.reconnect_count = 0 # 重连次数计数器
|
|
|
|
|
|
|
|
|
|
|
|
# 重连定时器,每隔RECONNECT_INTERVAL毫秒重连一次
|
|
|
|
|
|
self.reconnect_timer = QTimer(self)
|
|
|
|
|
|
self.reconnect_timer.setInterval(RECONNECT_INTERVAL)
|
|
|
|
|
|
self.reconnect_timer.timeout.connect(self._reconnect_to_server) # 绑定重连函数
|
|
|
|
|
|
|
|
|
|
|
|
# 绑定TCP信号与槽(事件驱动)
|
|
|
|
|
|
self._bind_tcp_signals()
|
|
|
|
|
|
|
|
|
|
|
|
# ---------------------
|
|
|
|
|
|
# TCP连接相关函数
|
|
|
|
|
|
# ---------------------
|
|
|
|
|
|
def _connect_to_server(self):
|
|
|
|
|
|
"""主动发起连接(仅在未连接状态下有效"""
|
|
|
|
|
|
if not self.is_tcp_connected:
|
|
|
|
|
|
self.tcp_socket.abort() # 终止现有连接
|
|
|
|
|
|
self.tcp_socket.connectToHost(self.tcp_server_host, self.tcp_server_port)
|
|
|
|
|
|
print(f"发起连接请求:{self.tcp_server_host}:{self.tcp_server_port}")
|
|
|
|
|
|
|
|
|
|
|
|
def _reconnect_to_server(self):
|
|
|
|
|
|
"""重连执行函数:仅在未连接且未达最大次数时触发"""
|
|
|
|
|
|
if not self.is_tcp_connected:
|
|
|
|
|
|
self.reconnect_count += 1
|
|
|
|
|
|
# 日志优化:每10次重连提示一次,避免日志刷屏
|
|
|
|
|
|
if self.reconnect_count % 10 == 0:
|
|
|
|
|
|
print(f"已连续重连{self.reconnect_count}次,仍未连接服务端,请检查服务端状态...")
|
|
|
|
|
|
else:
|
|
|
|
|
|
print(f"第{self.reconnect_count}次重连请求:{self.tcp_server_host}:{self.tcp_server_port}")
|
|
|
|
|
|
self._connect_to_server()
|
|
|
|
|
|
|
|
|
|
|
|
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)
|
|
|
|
|
|
|
|
|
|
|
|
# ------------------
|
|
|
|
|
|
# TCP客户端核心功能
|
|
|
|
|
|
# ------------------
|
|
|
|
|
|
@Slot()
|
|
|
|
|
|
def _on_tcp_connected(self):
|
|
|
|
|
|
"""TCP连接成功回调"""
|
|
|
|
|
|
self.is_tcp_connected = True
|
|
|
|
|
|
self.has_connected_once = True
|
|
|
|
|
|
self.reconnect_timer.stop() # 停止重连定时器
|
|
|
|
|
|
self.reconnect_count = 0 # 重连计数器清零
|
|
|
|
|
|
self.is_running = True
|
|
|
|
|
|
print(f"TCP连接成功:{self.tcp_server_host}:{self.tcp_server_port}")
|
|
|
|
|
|
|
|
|
|
|
|
# 发送连接状态信号
|
|
|
|
|
|
self.connection_status_changed.emit(True)
|
2025-11-17 16:02:46 +08:00
|
|
|
|
# 重量错误状态信号(连接成功后恢复正常图标)
|
|
|
|
|
|
self.upper_weight_error.emit(False)
|
|
|
|
|
|
self.down_weight_error.emit(False)
|
2025-11-16 11:58:22 +08:00
|
|
|
|
# 连接成功后,向服务器发送“请求初始数据”指令
|
|
|
|
|
|
self._send_tcp_request("get_initial_data")
|
|
|
|
|
|
|
|
|
|
|
|
@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.connection_status_changed.emit(False)
|
|
|
|
|
|
|
|
|
|
|
|
# 发送重量清零信号
|
2025-11-17 16:02:46 +08:00
|
|
|
|
self.weight_updated.emit({"hopper_up_weight": 0, "hopper_down_weight": ""})
|
2025-11-16 11:58:22 +08:00
|
|
|
|
|
|
|
|
|
|
if not self.reconnect_timer.isActive(): # 未启动重连定时器时才启动
|
|
|
|
|
|
print(f"连接断开,将在{RECONNECT_INTERVAL / 1000}秒后启动重连...")
|
|
|
|
|
|
self.reconnect_timer.start()
|
|
|
|
|
|
|
|
|
|
|
|
@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_json_data = status_data
|
2025-11-17 16:02:46 +08:00
|
|
|
|
|
|
|
|
|
|
# 错误检测
|
|
|
|
|
|
up_weight = status_data.get("hopper_up_weight")
|
|
|
|
|
|
down_weight = status_data.get("hopper_down_weight")
|
|
|
|
|
|
|
|
|
|
|
|
# 顺序不能变
|
|
|
|
|
|
if up_weight == "error" and down_weight == "error":
|
|
|
|
|
|
self.weight_updated.emit({"hopper_up_weight": "", "hopper_down_weight": ""})
|
|
|
|
|
|
self.upper_weight_error.emit(True)
|
|
|
|
|
|
self.down_weight_error.emit(True)
|
|
|
|
|
|
elif up_weight == "error":
|
|
|
|
|
|
self.weight_updated.emit({"hopper_up_weight": "", "hopper_down_weight": down_weight})
|
|
|
|
|
|
self.upper_weight_error.emit(True)
|
|
|
|
|
|
elif down_weight == "error":
|
|
|
|
|
|
self.weight_updated.emit({"hopper_up_weight": up_weight, "hopper_down_weight": ""})
|
|
|
|
|
|
self.down_weight_error.emit(True)
|
|
|
|
|
|
else:
|
|
|
|
|
|
# 防止打乱服务端连接时的状态图标变换
|
|
|
|
|
|
self.weight_updated.emit(status_data)
|
|
|
|
|
|
self.upper_weight_error.emit(False)
|
|
|
|
|
|
self.down_weight_error.emit(False)
|
|
|
|
|
|
|
2025-11-16 11:58:22 +08:00
|
|
|
|
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错误回调"""
|
|
|
|
|
|
if not self.is_tcp_connected:
|
|
|
|
|
|
error_str = self.tcp_socket.errorString()
|
|
|
|
|
|
print(f"TCP错误:{error_str}")
|
|
|
|
|
|
self.is_tcp_connected = False
|
|
|
|
|
|
self.is_running = False
|
|
|
|
|
|
|
|
|
|
|
|
# 发送连接断开信号
|
|
|
|
|
|
self.connection_status_changed.emit(False)
|
2025-11-17 16:02:46 +08:00
|
|
|
|
# 错误时重置重量和错误状态
|
|
|
|
|
|
self.weight_updated.emit({"hopper_up_weight": "", "hopper_down_weight": ""})
|
|
|
|
|
|
self.weight_error_signal.emit(False)
|
2025-11-16 11:58:22 +08:00
|
|
|
|
|
|
|
|
|
|
# 首次连接失败时,启动重连定时器
|
|
|
|
|
|
if not self.reconnect_timer.isActive():
|
|
|
|
|
|
print(f"将在{RECONNECT_INTERVAL / 1000}秒后启动重连")
|
|
|
|
|
|
self.reconnect_timer.start()
|
|
|
|
|
|
|
|
|
|
|
|
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 get_current_time(self):
|
|
|
|
|
|
"""获取格式化的当前时间"""
|
|
|
|
|
|
return datetime.now().strftime('%Y-%m-%d %H:%M:%S')
|