3. OPC UA客户端连接与数据订阅
任务目标
本案例实现一个OPC UA客户端,完成以下闭环:连接OPC UA服务器、浏览指定节点、订阅数据变化并持续接收通知。客户端将订阅服务器端每秒更新的模拟温度值,并在控制台打印每次变化。
环境与依赖
使用Python与asyncua库(纯Python实现,跨平台)。安装命令:
pip install asyncua
服务器端使用asyncua自带的示例服务器(server_example.py),它默认在opc.tcp://localhost:4840暴露一个每秒更新的模拟量节点。若需手动启动:
python -m opcua.server.server_example
完整实现代码
import asyncio
from asyncua import Client, ua
# OPC UA服务器地址(asyncua自带示例服务器)
SERVER_URL = "opc.tcp://localhost:4840"
# 示例服务器中模拟温度节点的NodeId(字符串格式)
NODE_ID = "ns=2;i=5"
async def subscribe_data_change():
"""连接服务器,订阅节点数据变化并打印"""
client = Client(SERVER_URL)
try:
# 建立连接
await client.connect()
print(f"[INFO] 已连接服务器: {SERVER_URL}")
# 获取目标节点
node = client.get_node(NODE_ID)
# 读取一次当前值,确认节点可访问
current_val = await node.read_value()
print(f"[INFO] 节点 {NODE_ID} 当前值: {current_val}")
# 创建订阅对象
subscription = await client.create_subscription(100, handler) # 100ms轮询间隔
# 订阅节点数据变化
handle = await subscription.subscribe_data_change(node)
print("[INFO] 已订阅数据变化,等待通知... (Ctrl+C退出)")
# 持续运行,接收回调
while True:
await asyncio.sleep(1)
except KeyboardInterrupt:
print("\n[INFO] 用户中断,退出")
except Exception as e:
print(f"[ERROR] 连接或订阅失败: {e}")
finally:
# 清理订阅与连接
if 'subscription' in locals():
await subscription.delete()
await client.disconnect()
print("[INFO] 已断开连接")
class handler:
"""数据变化回调类(asyncua要求回调类实现datachange_notification方法)"""
def datachange_notification(self, node, value, data):
print(f"[DATA] 节点 {node} 新值: {value.value}")
if __name__ == "__main__":
asyncio.run(subscribe_data_change())
运行与验证
- 先启动服务器(若未运行):
python -m opcua.server.server_example - 运行客户端脚本:
python opcua_client.py - 预期输出(示例,实际值随时间变化):
[INFO] 已连接服务器: opc.tcp://localhost:4840
[INFO] 节点 ns=2;i=5 当前值: 21.4
[INFO] 已订阅数据变化,等待通知... (Ctrl+C退出)
[DATA] 节点 NodeId(ns=2, i=5) 新值: 21.5
[DATA] 节点 NodeId(ns=2, i=5) 新值: 21.7
[DATA] 节点 NodeId(ns=2, i=5) 新值: 21.9
...
通过判定标准:客户端能打印出服务器推送的连续变化数值(每秒约1次),且数值与服务器端模拟值一致。若10秒内无任何[DATA]输出,说明订阅未生效或节点ID不匹配,请检查服务器地址与节点ID。
关键说明
- 示例服务器中
ns=2;i=5是官方示例的模拟温度节点,若使用其他服务器,需通过client.get_children()或UA Expert工具浏览实际节点ID。 - 订阅轮询间隔设为100ms,实际通知频率由服务器端数据变化频率决定(示例服务器约1秒更新一次)。
- 回调类
handler必须实现datachange_notification方法,否则asyncua会静默丢弃通知。