from opcua import Client, ua import traceback import threading import time class OPCAngleUploader: """下料斗夹爪角度上传OPC""" def __init__(self, opc_server_url="opc.tcp://localhost:4840/zjsh_feed/server/", angle_node_id = "ns=2;i=13", upload_interval=0.5, angle_threshold=5.0, angle_min = 3.0): # OPC服务器配置 self.opc_server_url = opc_server_url self.upload_interval = upload_interval # 固定0.5秒上传 self.angle_threshold = angle_threshold # 角度变化阈值(超过阈值才更新角度) self.angle_min = angle_min # 角度最小值(低于该值为误判的角度,不更新) # 配置 lower_angle 节点ID(ns=2;i=13) self.angle_node_id = angle_node_id # lower_angle节点ID self.angle_node = None # 缓存节点对象,避免重复获取 # 线程安全相关 self.latest_angle = None # 最新角度缓存 self.last_uploaded_angle = None # 上一次成功上传opc的角度 self.angle_lock = threading.Lock() # 角度读写锁 self.exit_event = threading.Event() # 线程退出信号 self.upload_thread = None # 上传线程 # OPC客户端状态 self.opc_client = None self.is_connected = False def connect_opc(self): """连接OPC服务器, 获取lower_angle节点对象""" try: # 关闭旧连接 if self.opc_client: try: self.opc_client.disconnect() except: pass # 初始化客户端并连接 self.opc_client = Client(self.opc_server_url) self.opc_client.connect() # 获取目标节点(仅获取一次,提升效率) self.angle_node = self.opc_client.get_node(self.angle_node_id) self.is_connected = True # print(f"[OPC] 连接成功,已获取节点:{self.angle_node_id}(lower_angle)") return True except Exception as e: print(f"[OPC] 连接/获取节点失败:{e}") self.is_connected = False self.angle_node = None return False def update_angle(self, angle): """线程安全更新最新角度值""" with self.angle_lock: self.latest_angle = angle def _upload_worker(self): """上传线程核心: 每隔0.5秒写入角度节点""" # 初始连接(失败则5秒重试) while not self.exit_event.is_set() and not self.connect_opc(): # print(f"[OPC] 初始连接失败,5秒后重试...") time.sleep(5) # 循环上传 while not self.exit_event.is_set(): try: # 连接失效则重连 if not self.is_connected or not self.angle_node: self.connect_opc() time.sleep(1) continue # 线程安全读取角度 with self.angle_lock: current_angle = self.latest_angle last_angle = self.last_uploaded_angle if current_angle is None: time.sleep(self.upload_interval) continue need_upload = False if last_angle is None: need_upload = True else: angle_diff = abs(float(current_angle) - float(last_angle)) if angle_diff >= self.angle_threshold: need_upload = True # 写入角度到OPC节点 if need_upload and current_angle > self.angle_min: angle_variant = ua.Variant(float(current_angle), ua.VariantType.Float) self.angle_node.set_value(angle_variant) # 更新上一次上传的角度 with self.angle_lock: self.last_uploaded_angle = current_angle # 0.5秒间隔 time.sleep(self.upload_interval) except Exception as e: print(f"[OPC] 上传异常:{e}") traceback.print_exc() self.is_connected = False # 标记连接失效,触发重连 time.sleep(1) def start_upload(self): """启动上传线程(守护线程,随进程退出)""" if not self.upload_thread or not self.upload_thread.is_alive(): self.exit_event.clear() self.upload_thread = threading.Thread(target=self._upload_worker, daemon=True) self.upload_thread.start() def stop_upload(self): """停止线程并关闭OPC连接""" self.exit_event.set() if self.upload_thread and self.upload_thread.is_alive(): self.upload_thread.join(timeout=2) if self.opc_client: try: self.opc_client.disconnect() except: pass self.is_connected = False