VWED_server/tests/test_opc_ua_server.py

331 lines
12 KiB
Python
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

#!/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()