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