Python-freeopcua客户端Subscription类使用疑问:如何访问节点变更值
嘿,我来帮你理清楚freeopcua里订阅和事件处理的逻辑,其实你的代码已经离目标很近啦!
先搞懂订阅的回调机制
freeopcua的订阅采用回调模式工作:
- 当你调用
client.create_subscription()创建订阅时,框架会在后台启动一个线程,专门监听OPC UA服务器发来的变更通知 - 你定义的
SubHandler类是一个“事件处理器”,框架会在检测到节点值变更(或者其他事件)时,自动调用对应方法(比如datachange_notification),并把相关数据作为参数传进来 - 你不需要主动“去拿”事件,只需要在这些回调方法里写好要执行的逻辑就行
你的代码里已经能拿到变更值啦
看你写的datachange_notification方法,它的三个参数已经包含了你需要的所有信息:
node: 触发值变更的节点对象,你可以通过它获取节点的ID、浏览名、显示名等元数据(比如node.nodeid.to_string()能拿到节点ID字符串)val: 节点变更后的新值,这就是你要找的核心数据!data: 包含更多上下文的对象,比如变更的时间戳、状态码,你可以通过data.monitored_item查看更详细的监控项信息
你的代码里已经在打印val了,其实已经实现了“节点变更时打印值”的需求。如果要自定义操作,直接在这个方法里加逻辑就行,比如:
def datachange_notification(self, node, val, data): node_name = node.get_browse_name().Name node_id = node.nodeid.to_string() # 自定义操作:打印更详细的变更信息 print(f"节点 [{node_name} ({node_id})] 的值更新为: {val}") # 还可以执行其他操作,比如写入日志、调用业务接口等 # write_to_log(node_id, val) # call_business_api(val)
扩展:把变更值传到其他业务逻辑
如果你的业务逻辑不在回调方法里(比如要在主线程处理数据),可以用队列来传递数据,避免阻塞回调线程:
from opcua import Client from queue import Queue import time class SubHandler(object): def __init__(self): # 用队列存储变更数据,供其他线程/模块获取 self.change_queue = Queue() def datachange_notification(self, node, val, data): change_info = { "node_id": node.nodeid.to_string(), "node_name": node.get_browse_name().Name, "new_value": val, "timestamp": data.monitored_item.Value.SourceTimestamp } print(f"Python: 收到数据变更通知: {change_info}") # 把数据放入队列 self.change_queue.put(change_info) def event_notification(self, event): print("Python: 收到新事件", event) if __name__ == "__main__": client = Client("opc.tcp://your-server-address:4840") try: client.connect() # 替换成你要订阅的节点ID target_node = client.get_node("ns=2;i=1005") handler = SubHandler() # 创建订阅,500是发布间隔(毫秒),表示服务器每隔500ms推送一次变更 sub = client.create_subscription(500, handler) # 订阅目标节点的数据变更 handle = sub.subscribe_data_change(target_node) # 主线程循环处理变更数据 print("开始监听节点变更...") while True: try: # 从队列获取变更数据,超时1秒避免卡死 change_data = handler.change_queue.get(timeout=1) # 这里执行你的主线程业务逻辑 print(f"主线程处理变更: {change_data}") except: continue time.sleep(0.1) finally: client.disconnect() print("客户端已断开连接")
几个注意事项
- 发布间隔:
create_subscription(500, handler)里的500是毫秒级的发布间隔,服务器会每隔这个时间把期间的变更打包推送。如果需要更高实时性,可以调小这个值,但不要太小(比如低于100),否则会增加服务器和客户端的负载 - 多节点订阅:如果要订阅多个节点,多次调用
sub.subscribe_data_change(node)即可,每个节点的变更都会触发datachange_notification - 避免耗时操作:回调方法是在框架的线程里执行的,不要在里面写耗时的代码(比如数据库写入、网络请求),否则会阻塞其他通知的处理。建议用队列把数据传到专门的线程处理
- 取消订阅:如果不需要监听某个节点了,可以调用
sub.unsubscribe(handle)取消订阅;要取消整个订阅,调用sub.delete()
内容的提问来源于stack exchange,提问作者Toni Cai
相关产品推荐
相关产品推荐

