4. OPC UA 客户端数据订阅

任务目标

编写一个 OPC UA 客户端程序,连接到指定的 OPC UA 服务器,订阅服务器上某个节点的数据变化,并在数据值发生变化时实时打印新值。本案例使用 asyncua 库实现,服务器地址与节点 ID 作为可配置参数。

环境依赖

  • Python 3.8+
  • 安装依赖:pip install asyncua
  • 需要一个可访问的 OPC UA 服务器。若本地无服务器,可使用 asyncua 自带的示例服务器(见下文验证方法)

关键完整代码

import asyncio
import sys
from asyncua import Client, ua

# 配置参数
SERVER_URL = "opc.tcp://localhost:4840"  # 服务器地址
NODE_ID = "ns=2;i=2"                     # 要订阅的节点 ID

async def subscribe_data_change():
    """连接服务器并订阅指定节点的数据变化"""
    client = Client(SERVER_URL)
    try:
        await client.connect()
        print(f"已连接服务器: {SERVER_URL}")

        # 获取目标节点
        node = client.get_node(NODE_ID)
        # 验证节点是否存在
        await node.read_value()
        print(f"订阅节点: {NODE_ID}")

        # 创建订阅
        subscription = await client.create_subscription(100, handler)  # 100ms 推送间隔
        await subscription.subscribe_data_change(node)

        # 保持运行,等待数据变化
        print("等待数据变化... (Ctrl+C 退出)")
        while True:
            await asyncio.sleep(1)
    except KeyboardInterrupt:
        print("\n用户中断,退出订阅")
    except Exception as e:
        print(f"错误: {e}")
    finally:
        await client.disconnect()
        print("已断开连接")

class handler:
    """数据变化回调处理类"""
    def datachange_notification(self, node, val, data):
        """当订阅的节点数据变化时,此方法被调用"""
        print(f"[{node}] 新值: {val}")

if __name__ == "__main__":
    try:
        asyncio.run(subscribe_data_change())
    except KeyboardInterrupt:
        pass

运行方式

  1. 确保 OPC UA 服务器已启动并监听 opc.tcp://localhost:4840,且 ns=2;i=2 节点存在且可读。
  2. 将上述代码保存为 opcua_subscriber.py。
  3. 运行:python opcua_subscriber.py

验证方法与判定标准

验证步骤:

  1. 若本地没有 OPC UA 服务器,可先启动 asyncua 自带的示例服务器(在另一个终端运行 python -m asyncua.server),该服务器默认在 opc.tcp://localhost:4840 监听,并包含 ns=2;i=2 节点(该节点值会周期性变化)。
  2. 运行订阅客户端程序。
  3. 观察控制台输出。

通过条件:

  • 程序输出“已连接服务器”和“订阅节点”信息,说明连接与节点访问成功。
  • 当服务器端节点值变化时(示例服务器约每 1 秒变化一次),客户端实时打印类似 [Node(...)] 新值: 42 的信息,且数值随服务器端变化而更新。
  • 按 Ctrl+C 能正常退出并显示“已断开连接”。

预期输出示例(与代码逻辑一致,非实测结果):

已连接服务器: opc.tcp://localhost:4840
订阅节点: ns=2;i=2
等待数据变化... (Ctrl+C 退出)
[Node(...)] 新值: 1
[Node(...)] 新值: 2
[Node(...)] 新值: 3

常见问题

  • 连接失败:确认服务器地址正确且服务器已启动;检查防火墙是否阻止 4840 端口。
  • 节点不存在:若报 BadNodeIdUnknown,请将 NODE_ID 改为服务器上实际存在的节点 ID。
  • 无数据变化输出:确认所订阅的节点值确实在变化;若节点值不变,订阅自然不会触发回调。