diff --git a/config/config.json b/config/config.json deleted file mode 100644 index 68e270e..0000000 --- a/config/config.json +++ /dev/null @@ -1,4 +0,0 @@ -{ - "hopper_up_weight": "666666", - "hopper_down_weight": "5444444" -} diff --git a/config/tcp_server.py b/config/tcp_server.py deleted file mode 100644 index 5789f02..0000000 --- a/config/tcp_server.py +++ /dev/null @@ -1,157 +0,0 @@ -#!/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 = { - "hopper_up_weight": 0.0, # 上料斗重量 - "hopper_down_weight": 0.0 # 下料斗重量 - } - - 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} 不存在") - 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_window.py b/main_window.py index 7e65f2a..ed11c03 100644 --- a/main_window.py +++ b/main_window.py @@ -1,6 +1,6 @@ # coding: utf-8 from typing import List -from PySide6.QtCore import Qt, Signal, QEasingCurve, QUrl, QSize, QTimer, QEvent +from PySide6.QtCore import Qt, Signal, QEasingCurve, QUrl, QSize, QTimer, QEvent, QThread from PySide6.QtGui import QIcon, QDesktopServices, QColor, QPalette, QBrush, QImage from PySide6.QtWidgets import (QApplication, QHBoxLayout, QVBoxLayout, QFrame, QWidget, QSpacerItem, QSizePolicy, QMainWindow, QPushButton) @@ -8,7 +8,7 @@ from PySide6.QtWidgets import (QApplication, QHBoxLayout, QVBoxLayout, from system_nav_bar import SystemNavBar from bottom_control_widget import BottomControlWidget from weight_dialog import WeightDetailsDialog -from tcp_client import TcpClient +from opcua_client import OpcuaClient class MainWindow(QWidget): @@ -17,7 +17,7 @@ class MainWindow(QWidget): self.initWindow() self.createSubWidgets() # 创建子部件 self.setupLayout() # 设置布局 - self.tcp_client = TcpClient(self) # 创建 TCP 客户端 + self.init_opc_client() # 初始化OPC客户端 self.bind_signals() # 绑定信号槽 def initWindow(self): @@ -92,21 +92,32 @@ class MainWindow(QWidget): self.close() super().keyPressEvent(event) + # ------------------- + # OPC UA 客户端初始化 + # ------------------- + def init_opc_client(self): + """初始化 OPC UA 客户端并启动线程(避免阻塞UI)""" + self.Opcua_client = OpcuaClient(self) # 创建 OPC UA 客户端实例 + # 创建独立线程运行 OPC UA 客户端(避免阻塞UI线程) + self.opc_thread = QThread() + self.Opcua_client.moveToThread(self.opc_thread) + self.opc_thread.started.connect(self.Opcua_client.start_connect) # 线程启动后触发客户端连接 + self.opc_thread.start() # 启动线程 + # --------------- - # TCP更新重量信息 + # OPC UA 信号绑定与数据更新 # --------------- def bind_signals(self): """绑定TCP信号到弹窗更新函数""" - self.tcp_client.weight_updated.connect(self.update_weight_dialogs) # 绑定更新重量信息的信号槽 + self.Opcua_client.weight_updated.connect(self.update_weight_dialogs) # 绑定更新重量信息的信号槽 # 连接状态信号:更新状态图标 - self.tcp_client.connection_status_changed.connect(self.update_connection_status) + self.Opcua_client.connection_status_changed.connect(self.update_connection_status) # 上料斗重量信息更新失败信号:更新状态图标 - self.tcp_client.upper_weight_error.connect(self.upper_weight_status) # 重量信息更新失败信号 + self.Opcua_client.upper_weight_error.connect(self.upper_weight_status) # 重量信息更新失败信号 # 下料斗重量信息更新失败信号:更新状态图标 - self.tcp_client.down_weight_error.connect(self.down_weight_status) # 重量信息更新失败信号 + self.Opcua_client.down_weight_error.connect(self.down_weight_status) # 重量信息更新失败信号 # 启动 TCP 连接 - print(f"客户端启动,自动连接服务端{self.tcp_client.tcp_server_host}:{self.tcp_client.tcp_server_port}...") - self.tcp_client._connect_to_server() + print(f"客户端启动,自动连接服务端{self.Opcua_client.opc_server_url}") def down_weight_status(self, is_error): """发送上料斗错误信息,更新连接状态""" @@ -131,6 +142,14 @@ class MainWindow(QWidget): down_weight = data.get("hopper_down_weight", 0) self.weight_dialog2.update_weight(down_weight) + def closeEvent(self, event): + """窗口关闭时,停止 OPC UA 客户端和线程(避免资源泄露)""" + if hasattr(self, 'Opcua_client'): + self.Opcua_client.stop() # 停止客户端(断开连接、停止定时器) + if hasattr(self, 'opc_thread') and self.opc_thread.isRunning(): + self.opc_thread.quit() # 退出线程 + self.opc_thread.wait(2000) # 等待线程退出(最多2秒) + event.accept() if __name__ == "__main__": import sys diff --git a/opcua_client.py b/opcua_client.py new file mode 100644 index 0000000..ca4d7bb --- /dev/null +++ b/opcua_client.py @@ -0,0 +1,267 @@ +#!/usr/bin/env python +# -*- coding: utf-8 -*- +''' +# @Time : 2025/11/17 18:51 +# @Author : reenrr +# @File : opcua_client_test1111.py +''' +from PySide6.QtCore import QTimer, Slot, Signal, QObject, QThread, Qt +from opcua import Client, ua +from datetime import datetime + +# ----------- +# 参数配置 +# ----------- +OPC_SERVER_URL = "opc.tcp://localhost:4841/freeopcua/server/" +UPPER_WEIGHT_NODE_ID = "ns=2;s=upper_weight" +LOWER_WEIGHT_NODE_ID = "ns=2;s=lower_weight" + +RECONNECT_INTERVAL = 2000 # 重连间隔(毫秒) +DATA_READ_INTERVAL = 2000 # 数据读取间隔(毫秒,与服务端更新频率一致) + +class OpcuaClient(QObject): + # 客户端的信号接口 + weight_updated = Signal(dict) # 重量更新信号(传递 {"hopper_up_weight": 值, "hopper_down_weight": 值}) + connection_status_changed = Signal(bool) # 连接状态更新信号(True=连接,False=断开) + upper_weight_error = Signal(bool) # 上料斗重量异常信号(True=异常,False=正常) + down_weight_error = Signal(bool) # 下料斗重量异常信号(True=异常,False=正常) + + def __init__(self, parent=None): + super().__init__(parent) + + self.is_running = False # 系统运行状态标记 + self.latest_weight_data = {"hopper_up_weight": 0, "hopper_down_weight": 0} # 缓存最新重量数据 + + # --------------- + # OPC UA 客户端核心配置 + # --------------- + self.opc_client = None # OPC UA 客户端实例(延迟初始化) + self.opc_server_url = OPC_SERVER_URL + self.upper_weight_node = None # 上料斗重量节点对象 + self.lower_weight_node = None # 下料斗重量节点对象 + + self.is_opc_connected = False # OPC UA 连接状态标记 + self.has_connected_once = False # 至少连接成功一次标记 + self.reconnect_count = 0 # 重连次数计数器 + + # 重连定时器(连接断开时触发) + self.reconnect_timer = QTimer(self) + self.reconnect_timer.setInterval(RECONNECT_INTERVAL) + self.reconnect_timer.timeout.connect(self.reconnect_to_server) + + # 数据读取定时器(连接成功后触发,定时读取重量数据) + self.data_read_timer = QTimer(self) + self.data_read_timer.setInterval(DATA_READ_INTERVAL) + self.data_read_timer.timeout.connect(self._read_weight_data) + + # 初始化 OPC UA 客户端 + self._init_opc_client() + + # --------------------- + # OPC UA 初始化相关函数 + # --------------------- + def _init_opc_client(self): + """初始化 OPC UA 客户端实例""" + try: + self.opc_client = Client(self.opc_server_url) + print("OPC UA 客户端初始化成功") + except Exception as e: + print(f"OPC UA 客户端初始化失败:{e}") + self.opc_client = None + + def start_connect(self): + """启动连接(供外部调用,主动发起连接请求)""" + self._connect_to_server() + + # --------------------- + # 连接管理相关函数 + # --------------------- + def reconnect_to_server(self): + """重连执行函数(与原 TCP 客户端一致)""" + if not self.is_opc_connected: + self.reconnect_count += 1 + # 日志优化:每10次重连提示一次,避免日志刷屏 + if self.reconnect_count % 10 == 0: + print(f"已连续重连{self.reconnect_count}次,仍未连接服务端,请检查服务端状态...") + else: + print(f"第{self.reconnect_count}次重连请求:{self.opc_server_url}") + self._connect_to_server() + + def _connect_to_server(self): + """主动发起 OPC UA 连接(核心连接逻辑)""" + if self.is_opc_connected or not self.opc_client: + return + + try: + # 1. 连接到 OPC UA 服务器 + self.opc_client.connect() + print(f"OPC UA 连接成功:{self.opc_server_url}") + + # 2. 获取重量节点对象(仅连接成功时获取一次) + self.upper_weight_node = self.opc_client.get_node(UPPER_WEIGHT_NODE_ID) + self.lower_weight_node = self.opc_client.get_node(LOWER_WEIGHT_NODE_ID) + + # 3. 验证节点是否有效 + self._verify_nodes() + + # 4. 更新连接状态 + self.is_opc_connected = True + self.has_connected_once = True + self.reconnect_count = 0 + self.is_running = True + + # 5. 发送信号(连接成功+错误状态重置) + self.connection_status_changed.emit(True) + self.upper_weight_error.emit(False) + self.down_weight_error.emit(False) + + # 6. 启动数据读取定时器,停止重连定时器 + self.data_read_timer.start() + self.reconnect_timer.stop() + + # 7. 立即读取一次初始数据 + self._read_weight_data() + + except Exception as e: + print(f"OPC UA 连接失败:{e}") + self._handle_connect_failed() + + def _reconnect_to_server(self): + """重连执行函数""" + if self.is_opc_connected: + return + + self.reconnect_count += 1 + # 日志优化:每10次重连提示一次,避免日志刷屏 + if self.reconnect_count % 10 == 0: + print(f"已连续重连{self.reconnect_count}次,仍未连接服务端,请检查服务端状态...") + else: + print(f"第{self.reconnect_count}次重连请求:{self.opc_server_url}") + + self._connect_to_server() + + def _disconnect_from_server(self): + """主动断开 OPC UA 连接""" + if self.opc_client and self.is_opc_connected: + try: + self.opc_client.disconnect() + print(f"OPC UA 连接断开:{self.opc_server_url}") + except Exception as e: + print(f"OPC UA 断开连接失败:{e}") + + # 更新状态 + self.is_opc_connected = False + self.is_running = False + self.upper_weight_node = None + self.lower_weight_node = None + + # 停止数据读取定时器 + self.data_read_timer.stop() + + def _handle_connect_failed(self): + """处理连接失败逻辑""" + self.is_opc_connected = False + self.is_running = False + + # 发送连接断开信号 + self.connection_status_changed.emit(False) + # 发送重量清零信号 + self.weight_updated.emit({"hopper_up_weight": "", "hopper_down_weight": ""}) + # 标记重量异常 + self.upper_weight_error.emit(True) + self.down_weight_error.emit(True) + + # 启动重连定时器(首次连接失败或重连失败时) + if not self.reconnect_timer.isActive(): + print(f"将在{RECONNECT_INTERVAL / 1000}秒后启动重连...") + self.reconnect_timer.start() + + def _verify_nodes(self): + """验证重量节点是否有效(避免节点ID错误)""" + try: + # 读取节点的基本信息,验证节点存在 + upper_node_id = self.upper_weight_node.nodeid + lower_node_id = self.lower_weight_node.nodeid + print(f"节点验证成功:上料斗节点={upper_node_id}, 下料斗节点={lower_node_id}") + except Exception as e: + raise RuntimeError(f"节点验证失败(节点ID可能错误):{e}") + + # --------------------- + # 数据读取与处理相关函数 + # --------------------- + @Slot() + def _read_weight_data(self): + """定时读取重量数据(核心业务逻辑)""" + if not self.is_opc_connected or not self.upper_weight_node or not self.lower_weight_node: + return + + try: + # 读取 OPC UA 节点的重量值 + upper_weight = self.upper_weight_node.get_value() + lower_weight = self.lower_weight_node.get_value() + + # 数据有效性校验(过滤异常值) + upper_weight = round(float(upper_weight), 2) if self._is_valid_weight(upper_weight) else "error" + lower_weight = round(float(lower_weight), 2) if self._is_valid_weight(lower_weight) else "error" + + # 构建重量数据字典(与原 TCP 客户端格式一致) + weight_data = { + "hopper_up_weight": upper_weight, + "hopper_down_weight": lower_weight + } + self.latest_weight_data = weight_data + + # 错误状态判断(完全复用原 TCP 客户端逻辑) + if upper_weight == "error" and lower_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 upper_weight == "error": + self.weight_updated.emit({"hopper_up_weight": "", "hopper_down_weight": lower_weight}) + self.upper_weight_error.emit(True) + self.down_weight_error.emit(False) + elif lower_weight == "error": + self.weight_updated.emit({"hopper_up_weight": upper_weight, "hopper_down_weight": ""}) + self.upper_weight_error.emit(False) + self.down_weight_error.emit(True) + else: + self.weight_updated.emit(weight_data) + self.upper_weight_error.emit(False) + self.down_weight_error.emit(False) + + print(f"OPC UA 数据读取成功:上料斗={upper_weight} kg, 下料斗={lower_weight} kg") + + except Exception as e: + print(f"OPC UA 数据读取失败:{e}") + # 读取失败时标记为异常 + self.weight_updated.emit({"hopper_up_weight": "", "hopper_down_weight": ""}) + self.upper_weight_error.emit(True) + self.down_weight_error.emit(True) + # 读取失败可能是连接异常,触发重连 + self._disconnect_from_server() + if not self.reconnect_timer.isActive(): + self.reconnect_timer.start() + + def _is_valid_weight(self, weight_value): + """验证重量值是否有效""" + try: + weight = float(weight_value) + # 结合业务场景:重量值应大于0(模拟传感器有效范围) + return weight >= 0 + except (ValueError, TypeError): + return False + + # --------------------- + # 外部接口函数(与原 TCP 客户端一致) + # --------------------- + def stop(self): + """停止 OPC UA 客户端(断开连接、停止定时器)""" + self.reconnect_timer.stop() + self.data_read_timer.stop() + self._disconnect_from_server() + self.is_running = False + print("OPC UA 客户端已停止") + + def get_current_time(self): + """获取格式化的当前时间(保持原接口)""" + return datetime.now().strftime('%Y-%m-%d %H:%M:%S') \ No newline at end of file diff --git a/readme.md b/readme.md new file mode 100644 index 0000000..dc69840 --- /dev/null +++ b/readme.md @@ -0,0 +1,6 @@ +# 功能 +使用opc_tcp通讯,读取服务端的上料斗和下料斗重量的数值,并且显示在界面上。当服务端断开,状态图标会切换,重量值清零,会一直重连服务端。 +当服务端那边连接不上重量那边的客户端,上料斗或下料斗的值会变为"error",我这边客户端接收到"error"值时,会状态图标会切换,重量值清零,直到接收到正常值 +# 使用教程 +使用前需要修改的参数,opcua_client.py文件中,需要修改的地方有: +![img.png](参数修改.png) \ No newline at end of file diff --git a/tcp_client.py b/tcp_client.py deleted file mode 100644 index 558c239..0000000 --- a/tcp_client.py +++ /dev/null @@ -1,203 +0,0 @@ -#!/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) # 连接状态更新信号 - upper_weight_error = Signal(bool) # 上料斗重量异常信号 - down_weight_error = Signal(bool) # 下料斗重量异常信号 - - 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) - # 重量错误状态信号(连接成功后恢复正常图标) - self.upper_weight_error.emit(False) - self.down_weight_error.emit(False) - # 连接成功后,向服务器发送“请求初始数据”指令 - 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) - - # 发送重量清零信号 - self.weight_updated.emit({"hopper_up_weight": 0, "hopper_down_weight": ""}) - - 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 - - # 错误检测 - 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) - - 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) - # 错误时重置重量和错误状态 - self.weight_updated.emit({"hopper_up_weight": "", "hopper_down_weight": ""}) - self.weight_error_signal.emit(False) - - # 首次连接失败时,启动重连定时器 - 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') \ No newline at end of file diff --git a/weight_dialog.py b/weight_dialog.py index 29f5f56..8a27760 100644 --- a/weight_dialog.py +++ b/weight_dialog.py @@ -37,9 +37,11 @@ class WeightDetailsDialog(QDialog): self.check_label = None # 状态图标标签 self.is_running = False # 定时器是否运行 - self.latest_json_data = {} self.statusWidgets = [] # 状态显示控件列表 + # 存储当前重量值(用于处理空值/异常值) + self.current_weight = 0.0 + self._init_ui(title) # ----------------------- @@ -189,12 +191,21 @@ class WeightDetailsDialog(QDialog): def update_weight(self, weight_data): """根据TCP获取的数据更新重量显示""" # 更新6个数字标签(补零为6位,超过6位取后6位) - try: - weight_int = int(weight_data) - weight_str = f"{weight_int:06d}"[-6:] - except (ValueError, TypeError): + # 处理空值/异常值(客户端传递的 "" 或 "error") + if weight_data == "" or weight_data == "error" or weight_data is None: weight_str = "000000" + self.current_weight = 0 + else: + try: + # 支持浮点数(如 648.06 → 保留2位小数后转为整数显示,避免丢失精度) + weight_int = int(float(weight_data)) + self.current_weight = weight_int + weight_str = f"{weight_int:06d}"[-6:] # 超过6位取后6位 + except (ValueError, TypeError): + weight_str = "000000" + self.current_weight = 0 + # 更新6个数字标签 for i in range(min(len(self.digit_labels), 6)): self.digit_labels[i].setText(weight_str[i] if i < len(weight_str) else "0") @@ -227,10 +238,15 @@ class WeightDetailsDialog(QDialog): # -------------------- def _clear_ui_info(self): """清空管片ID和网格信息""" - if self.id_value_label: + # 修复:避免访问未定义的 self.id_value_label + if hasattr(self, 'id_value_label') and self.id_value_label: self.id_value_label.setText("") for widget in self.statusWidgets: - widget['valueLabel'].setText("") + if 'valueLabel' in widget and widget['valueLabel']: + widget['valueLabel'].setText("") + # 清空重量显示 + for label in self.digit_labels: + label.setText("0") print("ℹ️ 界面信息已清空") # 测试代码 diff --git a/参数修改.png b/参数修改.png new file mode 100644 index 0000000..92bade9 Binary files /dev/null and b/参数修改.png differ