#!/usr/bin/env python3 from PySide6.QtCore import QObject, Signal from opcua import Client, ua import time from datetime import datetime class OpcuaUiSignal(QObject): # 定义值变化信号:参数为(node_id, var_name, new_value) value_changed = Signal(str, str, object) # Opcua回调处理器 class SubscriptionHandler: def __init__(self, opc_signal:OpcuaUiSignal): # 初始化nodeid→变量名映射表 self.node_id_to_name = {} self.opc_signal = opc_signal def datachange_notification(self, node, val, data): """ python-opcua标准的回调函数 :param node: 变化的节点对象 :param val: 节点新值 :param data: 附加数据(包含时间戳等) """ try: # 1. 解析时间戳 # time_str = datetime.now().strftime('%Y-%m-%d %H:%M:%S') # try: # utc_time = data.monitored_item.Value.SourceTimestamp # beijing_time = utc_time + datetime.timedelta(hours=8) # time_str = beijing_time.strftime('%Y-%m-%d %H:%M:%S') # except: # pass # 2. 获取nodeid并解析变量名 node_id = node.nodeid.to_string() # 从映射表获取可读变量名 var_name = self.node_id_to_name.get(node_id) # 3. 打印变化通知 # print(f"\n 节点值发生变化!") # print(f" 节点ID: {node_id}") # print(f" 变量名称: {var_name}") # print(f" 时间: {time_str}") # print(f" 新值: {val}") self.opc_signal.value_changed.emit(node_id, var_name, val) except Exception as e: print(f"解析值变化事件失败: {e}") import traceback traceback.print_exc() class OpcuaUiClient: def __init__(self, server_url="opc.tcp://localhost:4840/zjsh_feed/server/"): """初始化 OPC UA 客户端""" self.client = Client(server_url) self.connected = False self.subscription = None self.monitored_items = [] # 创建Qt信号对象(用于跨线程传递数据) self.opc_signal = OpcuaUiSignal() self.handler = SubscriptionHandler(self.opc_signal) # 回调处理器 # 定义需要监控的变量路径(object_name + var_name) # 格式:(变量可读名称, [object路径, 变量路径]) self.target_var_paths = [ ("upper_weight", ["2:upper", "2:upper_weight"]), ("lower_weight", ["2:lower", "2:lower_weight"]) ] self.node_id_mapping = {} # 存储nodeid→变量名的映射表 def connect(self): """连接到服务器""" try: self.client.connect() self.connected = True print(f"成功连接到 OPC UA 服务器: {self.client.server_url}") return True except Exception as e: print(f"连接服务器失败: {e}") return False def disconnect(self): """断开连接(包含取消订阅)""" if self.subscription: try: self.subscription.delete() print("已取消节点订阅") except: pass if self.connected: self.client.disconnect() self.connected = False print("已断开与 OPC UA 服务器的连接") def build_node_id_mapping(self): """ 根据object_name+var_name路径获取nodeid,建立映射表 """ if not self.connected: print("请先连接到服务器") return False try: print("\n 开始构建nodeid映射表...") objects_node = self.client.get_objects_node() # 获取根Objects节点 for var_name, path_list in self.target_var_paths: # 根据层级路径找到目标节点 target_node = objects_node.get_child(path_list) # 提取nodeid字符串 node_id = target_node.nodeid.to_string() # 存入映射表 self.node_id_mapping[node_id] = var_name print(f"映射成功: {node_id} → {var_name}") # 将映射表传给回调处理器 self.handler.node_id_to_name = self.node_id_mapping return True except Exception as e: print(f"构建映射表失败: {e}") import traceback traceback.print_exc() return False def create_multi_subscription(self, interval=500): """订阅多个变量(基于映射表的nodeid)""" if not self.connected: print("请先连接到服务器") return # 先构建映射表,失败则直接返回 if not self.node_id_mapping: if not self.build_node_id_mapping(): return try: interval = int(interval) # 1. 创建订阅 self.subscription = self.client.create_subscription(interval, self.handler) print(f"\n订阅创建成功(间隔:{interval}ms)") # 2. 遍历映射表,为每个nodeid创建监控项 for node_id, var_name in self.node_id_mapping.items(): var_node = self.client.get_node(node_id) monitored_item = self.subscription.subscribe_data_change(var_node) self.monitored_items.append(monitored_item) print(f"已订阅变量: {var_name} (nodeid: {node_id})") except Exception as e: print(f"创建批量订阅失败: {e}") import traceback traceback.print_exc() def write_value_by_name(self, var_readable_name, value): """ 根据变量可读名称写入值(主要用于修改方量, 类型为 Double类型) :param var_readable_name: 变量可读名称(如"upper_weight") :param value: 要写入的值 """ if not self.connected: print("请先连接到服务器") return # 反向查找:通过变量名找nodeid target_node_id = None for node_id, name in self.node_id_mapping.items(): if name == var_readable_name: target_node_id = node_id break if not target_node_id: print(f"未找到变量名 {var_readable_name} 对应的nodeid") return try: target_node = self.client.get_node(target_node_id) # 明确指定值类型为Double,避免类型错误 variant = ua.Variant(float(value), ua.VariantType.Double) target_node.set_value(variant) except Exception as e: print(f"写入值失败: {e}")