You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.05.08 07:13:12