VWED_server/tests/test_opc_ua_server.py

331 lines
12 KiB
Python
Raw Normal View History

2025-09-30 13:52:36 +08:00
#!/usr/bin/env python
# -*- coding: utf-8 -*-
"""
OPC UA 模拟服务器
用于测试在线脚本中的OPC UA通信功能
"""
import time
import threading
import logging
from typing import Dict, Any
from opcua import Server, ua
# 配置日志
logging.basicConfig(level=logging.INFO)
logger = logging.getLogger(__name__)
class OpcUaTestServer:
"""OPC UA 测试服务器"""
def __init__(self, host='localhost', port=4840):
self.host = host
self.port = port
self.endpoint = f"opc.tcp://{host}:{port}/freeopcua/server/"
self.server = None
self.server_thread = None
self.update_thread = None
self.running = False
self.nodes: Dict[str, Any] = {}
# 初始化服务器
self._setup_server()
def _setup_server(self):
"""设置OPC UA服务器"""
self.server = Server()
self.server.set_endpoint(self.endpoint)
# 设置服务器名称和应用URI
self.server.set_server_name("VWED OPC UA Test Server")
# 设置安全策略
self.server.set_security_policy([
ua.SecurityPolicyType.NoSecurity,
ua.SecurityPolicyType.Basic256Sha256_SignAndEncrypt,
ua.SecurityPolicyType.Basic256Sha256_Sign
])
# 创建命名空间
uri = "http://vwed.test.opcua.server"
self.namespace_idx = self.server.register_namespace(uri)
# 获取根节点
root = self.server.get_root_node()
# 创建对象节点
myobj = root.add_object(self.namespace_idx, "VWEDTestObject")
# 创建各种类型的变量节点用于测试
self._create_test_variables(myobj)
def _create_test_variables(self, parent_node):
"""创建测试变量节点"""
# 1. 基本数据类型变量
self.nodes['boolean_var'] = parent_node.add_variable(
self.namespace_idx, "BooleanVariable", True
)
self.nodes['boolean_var'].set_writable()
self.nodes['int_var'] = parent_node.add_variable(
self.namespace_idx, "IntegerVariable", 42
)
self.nodes['int_var'].set_writable()
self.nodes['float_var'] = parent_node.add_variable(
self.namespace_idx, "FloatVariable", 3.14159
)
self.nodes['float_var'].set_writable()
self.nodes['string_var'] = parent_node.add_variable(
self.namespace_idx, "StringVariable", "Hello VWED"
)
self.nodes['string_var'].set_writable()
# 2. 动态变化的变量(模拟传感器数据)
self.nodes['temperature'] = parent_node.add_variable(
self.namespace_idx, "Temperature", 25.0
)
self.nodes['temperature'].set_writable()
self.nodes['pressure'] = parent_node.add_variable(
self.namespace_idx, "Pressure", 101.3
)
self.nodes['pressure'].set_writable()
self.nodes['counter'] = parent_node.add_variable(
self.namespace_idx, "Counter", 0
)
self.nodes['counter'].set_writable()
# 3. 只读变量
self.nodes['readonly_var'] = parent_node.add_variable(
self.namespace_idx, "ReadOnlyVariable", "Read Only Value"
)
# 不设置writable默认为只读
# 4. 数组变量
self.nodes['array_var'] = parent_node.add_variable(
self.namespace_idx, "ArrayVariable", [1, 2, 3, 4, 5]
)
self.nodes['array_var'].set_writable()
# 5. 创建不同NodeId类型的节点进行测试
# 字符串NodeId: s=StringNodeId
self.nodes['string_node'] = parent_node.add_variable(
ua.NodeId("StringNodeId", self.namespace_idx), "StringNodeVariable", "String Node Value"
)
self.nodes['string_node'].set_writable()
# 整数NodeId: i=1001
self.nodes['int_node'] = parent_node.add_variable(
ua.NodeId(1001, self.namespace_idx), "IntNodeVariable", 1001
)
self.nodes['int_node'].set_writable()
# GUID NodeId
import uuid
guid = uuid.UUID('550e8400-e29b-41d4-a716-446655440000')
self.nodes['guid_node'] = parent_node.add_variable(
ua.NodeId(guid, self.namespace_idx), "GuidNodeVariable", "GUID Node Value"
)
self.nodes['guid_node'].set_writable()
# 字节NodeId
byte_id = b'ByteNodeId'
self.nodes['byte_node'] = parent_node.add_variable(
ua.NodeId(byte_id, self.namespace_idx), "ByteNodeVariable", "Byte Node Value"
)
self.nodes['byte_node'].set_writable()
logger.info("创建测试变量节点完成")
def start_server(self):
"""启动OPC UA服务器"""
if self.running:
logger.warning("服务器已经在运行")
return
logger.info(f"启动OPC UA服务器: {self.endpoint}")
try:
self.server.start()
self.running = True
# 启动数据更新线程
self.update_thread = threading.Thread(target=self._update_data_loop, daemon=True)
self.update_thread.start()
logger.info("OPC UA服务器启动成功")
except Exception as e:
logger.error(f"启动OPC UA服务器失败: {e}")
raise
def _update_data_loop(self):
"""更新测试数据的循环"""
counter = 0
while self.running:
try:
# 更新计数器
self.nodes['counter'].set_value(counter)
# 更新温度模拟传感器数据20-30度之间波动
import math
temperature = 25.0 + 5.0 * math.sin(counter * 0.1)
self.nodes['temperature'].set_value(temperature)
# 更新压力模拟传感器数据100-102之间波动
pressure = 101.0 + math.cos(counter * 0.05)
self.nodes['pressure'].set_value(pressure)
# 更新布尔值每5秒切换一次
boolean_value = (counter // 5) % 2 == 0
self.nodes['boolean_var'].set_value(boolean_value)
# 更新数组(循环移位)
current_array = self.nodes['array_var'].get_value()
if isinstance(current_array, list) and len(current_array) > 0:
# 循环移位
new_array = current_array[1:] + [current_array[0] + counter]
self.nodes['array_var'].set_value(new_array)
counter += 1
time.sleep(1) # 每秒更新一次
except Exception as e:
logger.error(f"数据更新错误: {e}")
time.sleep(1)
def stop_server(self):
"""停止服务器"""
logger.info("正在停止OPC UA服务器...")
self.running = False
if self.server:
try:
self.server.stop()
logger.info("OPC UA服务器已停止")
except Exception as e:
logger.error(f"停止服务器时出错: {e}")
def get_server_info(self):
"""获取服务器信息"""
return {
'host': self.host,
'port': self.port,
'endpoint': self.endpoint,
'running': self.running,
'namespace_index': self.namespace_idx,
'test_nodes': list(self.nodes.keys()),
}
def get_node_info(self):
"""获取节点信息(用于测试参考)"""
if not self.running:
return None
node_info = {}
try:
for name, node in self.nodes.items():
node_id = node.nodeid
print("node_id::::", node_id)
# node
value = node.get_value()
# 解析NodeId类型
node_id_str = ""
identifier = node_id.identifier
if isinstance(identifier, int):
node_id_str = f"i={identifier}"
elif isinstance(identifier, str):
node_id_str = f"s={identifier}"
elif hasattr(identifier, 'hex'): # UUID
node_id_str = f"g={identifier}"
elif isinstance(identifier, bytes):
import base64
node_id_str = f"b={base64.b64encode(identifier).decode()}"
else:
node_id_str = f"i={identifier}"
node_info[name] = {
'node_id': node_id_str,
'namespace_index': node_id.namespace_index,
'value': value,
'data_type': type(value).__name__
}
except Exception as e:
logger.error(f"获取节点信息失败: {e}")
return node_info
def set_node_value(self, node_name: str, value: Any):
"""设置节点值(用于测试)"""
try:
if node_name in self.nodes:
self.nodes[node_name].set_value(value)
logger.info(f"设置节点值: {node_name} = {value}")
else:
logger.warning(f"节点不存在: {node_name}")
except Exception as e:
logger.error(f"设置节点值失败: {e}")
def main():
"""主函数 - 启动测试服务器"""
print("=" * 60)
print("VWED OPC UA 测试服务器")
print("=" * 60)
# 创建并启动服务器
server = OpcUaTestServer(host='localhost', port=4840)
server.start_server()
server_info = server.get_server_info()
print(f"\n服务器信息:")
print(f" 端点: {server_info['endpoint']}")
print(f" 命名空间索引: {server_info['namespace_index']}")
print(f" 运行状态: {server_info['running']}")
# 显示节点信息
print(f"\n测试节点列表:")
node_info = server.get_node_info()
if node_info:
for name, info in node_info.items():
print(f" {name}:")
print(f" NodeId: ns={info['namespace_index']};{info['node_id']}")
print(f" Value: {info['value']} ({info['data_type']})")
print(f"\n测试示例:")
print("python -c \"")
print("from services.online_script.built_in_modules.opc_ua_module import VWEDOpcUaModule")
print("m = VWEDOpcUaModule('test')")
print(f"print(m.read_opc_value_with_namespace({server_info['namespace_index']}, 'i=1001'))")
print("\"")
try:
while True:
time.sleep(10)
# 显示当前动态数据状态
node_info = server.get_node_info()
if node_info:
print(f"\n[{time.strftime('%H:%M:%S')}] 动态数据:")
dynamic_nodes = ['counter', 'temperature', 'pressure', 'boolean_var']
for node_name in dynamic_nodes:
if node_name in node_info:
value = node_info[node_name]['value']
print(f" {node_name}: {value}")
except KeyboardInterrupt:
print("\n\n正在关闭服务器...")
server.stop_server()
print("服务器已关闭")
if __name__ == "__main__":
main()