This commit is contained in:
2025-11-21 14:55:52 +08:00
parent e3ecd0550f
commit 26ed8df502
36 changed files with 908 additions and 433 deletions

View File

@ -0,0 +1,203 @@
#!/usr/bin/env python3
"""
OPC UA 客户端订阅模式示例
使用订阅机制实现实时数据更新
"""
from opcua import Client, Subscription
from opcua.ua import DataChangeNotification
import time
import sys
import threading
class SubHandler:
"""
订阅处理器,处理数据变化通知
"""
def __init__(self):
self.data_changes = {}
self.change_count = 0
def datachange_notification(self, node, val, data):
"""
数据变化时的回调函数
"""
self.change_count += 1
node_name = node.get_display_name().Text
self.data_changes[node_name] = {
'value': val,
'timestamp': time.strftime('%Y-%m-%d %H:%M:%S'),
'node_id': str(node)
}
print(f"🔔 数据变化 #{self.change_count}")
print(f" 节点: {node_name}")
print(f" 数值: {val}")
print(f" 时间: {self.data_changes[node_name]['timestamp']}")
print(f" 节点ID: {node}")
print("-" * 50)
class OPCUAClientSubscription:
"""
使用订阅机制的 OPC UA 客户端
"""
def __init__(self, server_url="opc.tcp://localhost:4840/zjsh_feed/server/"):
self.client = Client(server_url)
self.connected = False
self.subscription = None
self.handler = SubHandler()
self.monitored_nodes = {}
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:
self.subscription.delete()
print("🗑️ 已删除订阅")
if self.connected:
self.client.disconnect()
self.connected = False
print("🔌 已断开与 OPC UA 服务器的连接")
def setup_subscription(self, publishing_interval=500):
"""
设置订阅
Args:
publishing_interval: 发布间隔(毫秒)
"""
if not self.connected:
print("请先连接到服务器")
return False
try:
# 创建订阅
self.subscription = self.client.create_subscription(publishing_interval, self.handler)
print(f"📡 已创建订阅,发布间隔: {publishing_interval}ms")
# 获取要监控的节点
objects = self.client.get_objects_node()
upper_device = objects.get_child("2:upper")
lower_device = objects.get_child("2:lower")
# 订阅重量数据
upper_weight_node = upper_device.get_child("2:upper_weight")
lower_weight_node = lower_device.get_child("2:lower_weight")
# 开始监控
upper_handle = self.subscription.subscribe_data_change(upper_weight_node)
lower_handle = self.subscription.subscribe_data_change(lower_weight_node)
self.monitored_nodes = {
'upper_weight': {'node': upper_weight_node, 'handle': upper_handle},
'lower_weight': {'node': lower_weight_node, 'handle': lower_handle}
}
print(f"📊 已订阅 {len(self.monitored_nodes)} 个数据节点")
return True
except Exception as e:
print(f"❌ 设置订阅失败: {e}")
return False
def get_current_values(self):
"""获取当前值"""
if not self.monitored_nodes:
return {}
values = {}
for name, info in self.monitored_nodes.items():
try:
values[name] = info['node'].get_value()
except Exception as e:
values[name] = f"读取失败: {e}"
return values
def run_subscription_test(self, duration=30):
"""
运行订阅测试
Args:
duration: 测试持续时间(秒)
"""
if not self.setup_subscription():
return
print(f"\n🚀 开始订阅模式测试,持续 {duration} 秒...")
print("💡 等待数据变化通知...")
print("=" * 60)
start_time = time.time()
last_stats_time = start_time
try:
while time.time() - start_time < duration:
current_time = time.time()
# 每5秒显示一次统计信息
if current_time - last_stats_time >= 5:
elapsed = current_time - start_time
changes_per_minute = (self.handler.change_count / elapsed) * 60
print(f"\n📈 统计信息 (运行时间: {elapsed:.1f}s)")
print(f" 总变化次数: {self.handler.change_count}")
print(f" 变化频率: {changes_per_minute:.1f} 次/分钟")
if self.handler.data_changes:
print(f" 最新数据:")
for name, data in list(self.handler.data_changes.items())[-2:]: # 显示最后2个
print(f" {name}: {data['value']} ({data['timestamp']})")
last_stats_time = current_time
time.sleep(0.1) # 小延迟避免CPU占用过高
except KeyboardInterrupt:
print("\n⏹️ 测试被用户中断")
finally:
print(f"\n🏁 测试完成")
print(f"📊 总变化次数: {self.handler.change_count}")
print(f"⏱️ 平均变化频率: {(self.handler.change_count / duration) * 60:.1f} 次/分钟")
def main():
"""主函数"""
client = OPCUAClientSubscription("opc.tcp://localhost:4840/zjsh_feed/server/")
try:
# 连接到服务器
if not client.connect():
return
# 运行订阅测试
client.run_subscription_test(duration=60) # 运行60秒
except Exception as e:
print(f"❌ 客户端运行错误: {e}")
finally:
client.disconnect()
if __name__ == "__main__":
if len(sys.argv) > 1:
server_url = sys.argv[1]
client = OPCUAClientSubscription(server_url)
else:
client = OPCUAClientSubscription()
try:
main()
except KeyboardInterrupt:
print("\n👋 用户中断程序")
sys.exit(0)

View File

@ -52,36 +52,30 @@ class OPCUAClientTest:
objects = self.client.get_objects_node()
print(f"对象节点: {objects}")
# 浏览 IndustrialDevice 节点
upper_device = objects.get_child("2:upper")
print(f"\n工业设备节点: {upper_device}")
print(f"\n上料斗对象: {upper_device}")
# 获取传感器节点
lower_device = objects.get_child("2:lower")
print(f"传感器节点: {lower_device}")
print(f"下料斗对象: {lower_device}")
print(f"温度传感器: {upper_device}")
print(f"压力传感器: {lower_device}")
# 获取变量值
print("\n=== 当前传感器数据 ===")
self.read_sensor_values(upper_device, lower_device)
print("\n=== 当前对象属性===")
self.read_object_properties(upper_device, lower_device)
except Exception as e:
print(f"浏览节点时出错: {e}")
def read_sensor_values(self, upper_device, lower_device):
"""读取传感器数值"""
def read_object_properties(self, upper_device, lower_device):
"""读取重量数值"""
try:
# 读取温度
temp_value = upper_device.get_child("2:upper_weight").get_value()
temp_unit = upper_device.get_child("2:lower_weight").get_value()
print(f"温度: {temp_value} {temp_unit}")
# 读取重量
upper_weight = upper_device.get_child("2:upper_weight").get_value()
lower_weight = lower_device.get_child("2:lower_weight").get_value()
print(f"上料斗重量: {upper_weight}")
print(f"下料斗重量: {lower_weight}")
except Exception as e:
print(f"读取传感器数据时出错: {e}")
print(f"读取数据时出错: {e}")
def monitor_data(self, duration=30):
"""监控数据变化"""

View File

@ -10,9 +10,10 @@ import random
import threading
from datetime import datetime
from core.system import SystemState
from config.ini_manager import ini_manager
class SimpleOPCUAServer:
def __init__(self, state, endpoint="opc.tcp://0.0.0.0:4840/zjsh_feed/server/", name="Feed_Server"):
def __init__(self, state, endpoint=ini_manager.opcua_endpoint, name="Feed_Server"):
"""
初始化OPC UA服务器
@ -54,8 +55,8 @@ class SimpleOPCUAServer:
self.lower_weight = self.lower.add_variable(self.namespace, "lower_weight", 0.0)
# 设置变量为可写
self.upper_weight.set_writable()
self.lower_weight.set_writable()
# self.upper_weight.set_writable()
# self.lower_weight.set_writable()
def setup_state_listeners(self):
"""设置状态监听器 - 事件驱动更新"""
@ -83,9 +84,7 @@ class SimpleOPCUAServer:
try:
self.server.start()
self.running = True
print(f"OPC UA服务器启动成功!")
print(f"服务器端点: opc.tcp://0.0.0.0:4840/freeopcua/server/")
print(f"命名空间: {self.namespace}")
# 初始化当前值
if self.state:
@ -109,8 +108,6 @@ class SimpleOPCUAServer:
def stop(self):
"""停止服务器"""
self.running = False
if hasattr(self, 'simulation_thread'):
self.simulation_thread.join(timeout=2)
self.server.stop()
print("OPC UA服务器已停止")
@ -121,21 +118,6 @@ class SimpleOPCUAServer:
except:
pass
def simulate_data(self):
"""模拟数据更新"""
while self.running:
try:
# 更新变量值
self.upper_weight.set_value(self.state.upper_weight)
self.lower_weight.set_value(self.state.lower_weight)
# 模拟延迟
time.sleep(1)
except Exception as e:
print(f"数据更新错误: {e}")
continue
def main():
"""主函数"""