前言
模拟OPC Server服务器的方法除了使用KEPServerEX6软件以外,还可以使用java代码模拟启动一个opc server。下文详细讲解,如何使用java代码,实现模拟一个或者多个opc server服务器。
OPC Server简介
OPC(OLE for Process Control)Server是一种用于实时数据通信的标准化软件接口,它允许不同厂商的设备和软件系统之间进行数据交换和集成。以下是对OPC Server的详细解释:
-
概述:OPC Server是一个在工业自动化领域中广泛应用的软件组件,它作为一个中间层,连接了底层的硬件设备(如传感器、控制器等)和上层的应用软件(如监控系统、数据采集系统等),实现实时数据的传输和共享。
-
标准化接口:OPC Server提供了一套标准化的接口和协议,使得不同的设备和软件系统可以通过统一的方式进行通信。这样,厂商可以开发符合OPC标准的设备和软件,并确保它们之间的互操作性和兼容性。
-
数据交换:OPC Server负责从底层设备读取实时数据,并将其转换成标准的OPC格式,然后通过网络或本地接口向上层应用软件提供数据。同时,OPC Server还可以接收来自应用软件的指令或配置信息,并将其传递给底层设备进行相应的操作和控制。
-
设备支持:OPC Server可以与各种不同类型的设备进行通信,包括传感器、执行器、PLC(可编程逻辑控制器)、DCS(分布式控制系统)等。通过OPC Server,这些设备可以实现与上层系统的无缝集成。
-
安全性和稳定性:OPC Server提供了安全机制,例如身份验证、权限管理等,以确保数据的安全性和系统的稳定性。此外,它还支持断线重连、故障恢复等功能,以保证通信的可靠性和持久性。
-
扩展性和灵活性:OPC Server的设计具有良好的扩展性和灵活性,允许用户根据需要添加或定制特定的功能模块。这使得用户能够根据应用场景的要求进行个性化的配置和扩展。
总结而言,OPC Server是一种用于工业自动化中实时数据通信的标准化软件接口。它通过提供统一的接口和协议,连接了底层设备和上层应用软件,实现了设备之间的数据交换和集成。通过使用OPC Server,企业可以实现设备和系统的互联互通,提高生产效率和管理水平。
引入依赖
首先在Maven项目的pom.xml文件中引入所需的依赖
<dependency><groupId>org.eclipse.milogroupId><artifactId>sdk-serverartifactId><version>0.6.9version>dependency><dependency><groupId>org.eclipse.milogroupId><artifactId>dictionary-managerartifactId><version>0.6.9version>dependency>
创建Server
创建opc server代码实现:
private OpcUaServer startServer(int port){ Set<EndpointConfiguration> endpointConfigurations=new HashSet<>(); EndpointConfiguration endpointConfiguration=EndpointConfiguration.newBuilder().setBindPort(port).build(); endpointConfigurations.add(endpointConfiguration); System.out.println(endpointConfiguration.getEndpointUrl()); OpcUaServerConfig serverConfig = OpcUaServerConfig.builder() .setApplicationName(LocalizedText.english("Server Application")) .setApplicationUri("urn:eclipse:milo:examples:server") .setProductUri("urn:eclipse:milo:examples:server") .setEndpoints(endpointConfigurations) .build(); OpcUaServer server = new OpcUaServer(serverConfig); server.startup(); return server; }
在EndpointConfiguration的newBuilder方法中,我看可以知道,如果我们不设置端口,默认就是 12685.
创建自定义Namespace
创建TestNamespace类,继承org.eclipse.milo sdk-server 中的ManagedNamespaceWithLifecycle类,并声明构造器,代码如下:
public static final String NAMESPACE_URI = "urn:eclipse:milo:opc"; private final Logger logger = LoggerFactory.getLogger(getClass()); private volatile Thread eventThread; private volatile boolean keepPostingEvents = true; private final DataTypeDictionaryManager dictionaryManager; private final SubscriptionModel subscriptionModel; public TestNamespace(OpcUaServer server) { super(server, NAMESPACE_URI); subscriptionModel = new SubscriptionModel(server, this); dictionaryManager = new DataTypeDictionaryManager(getNodeContext(), NAMESPACE_URI); getLifecycleManager().addLifecycle(dictionaryManager); getLifecycleManager().addLifecycle(subscriptionModel); getLifecycleManager().addLifecycle(new Lifecycle() { @Override public void startup() { startBogusEventNotifier(); } @Override public void shutdown() { try { keepPostingEvents = false; eventThread.interrupt(); eventThread.join(); } catch (InterruptedException ignored) { // ignored } } }); }
重载Lifecycle的方法
重载org.eclipse.milo.opcua.sdk.server.Lifecycle 的 startup方法和shutdown方法,启动时,创建事件通知器。代码如下:
private void startBogusEventNotifier() { // Set the EventNotifier bit on Server Node for Events. UaNode serverNode = getServer() .getAddressSpaceManager() .getManagedNode(Identifiers.Server) .orElse(null); if (serverNode instanceof ServerTypeNode) { ((ServerTypeNode) serverNode).setEventNotifier(ubyte(1)); // Post a bogus Event every couple seconds eventThread = new Thread(() -> { while (keepPostingEvents) { try { BaseEventTypeNode eventNode = getServer().getEventFactory().createEvent( newNodeId(UUID.randomUUID()), Identifiers.BaseEventType ); eventNode.setBrowseName(new QualifiedName(1, "foo")); eventNode.setDisplayName(LocalizedText.english("foo")); eventNode.setEventId(ByteString.of(new byte[]{0, 1, 2, 3})); eventNode.setEventType(Identifiers.BaseEventType); eventNode.setSourceNode(serverNode.getNodeId()); eventNode.setSourceName(serverNode.getDisplayName().getText()); eventNode.setTime(DateTime.now()); eventNode.setReceiveTime(DateTime.NULL_VALUE); eventNode.setMessage(LocalizedText.english("event message!")); eventNode.setSeverity(ushort(2)); //noinspection UnstableApiUsage getServer().getEventBus().post(eventNode); eventNode.delete(); } catch (Throwable e) { logger.error("Error creating EventNode: {}", e.getMessage(), e); } try { //noinspection BusyWait Thread.sleep(2_000); } catch (InterruptedException ignored) { // ignored } } }, "bogus-event-poster"); eventThread.start(); } }
创建opc ua 节点方法
public void addNodes(Set<String> keys) { // Create a "opc" folder and add it to the node manager NodeId folderNodeId = newNodeId("opc"); UaFolderNode folderNode = new UaFolderNode( getNodeContext(), folderNodeId, newQualifiedName("opc"), LocalizedText.english("opc") ); getNodeManager().addNode(folderNode); // Make sure our new folder shows up under the server's Objects folder. folderNode.addReference(new Reference( folderNode.getNodeId(), Identifiers.Organizes, Identifiers.ObjectsFolder.expanded(), false )); for (String key : keys) { NodeId typeId = Identifiers.Double; Variant variant = new Variant(0d); UaVariableNode node = new UaVariableNode.UaVariableNodeBuilder(getNodeContext()) .setNodeId(newNodeId(key)) .setAccessLevel(AccessLevel.READ_WRITE) .setUserAccessLevel(AccessLevel.READ_WRITE) .setBrowseName(newQualifiedName(key)) .setDisplayName(LocalizedText.english(key)) .setDataType(typeId) .setTypeDefinition(Identifiers.BaseDataVariableType) .build(); node.setValue(new DataValue(variant)); getNodeManager().addNode(node); folderNode.addOrganizes(node); } }
先创建一个“opc”文件夹并将其添加到节点管理器中,然后根据传入的节点名称,循环遍历,创建到“opc”文件夹下,生成变量类型的节点。
重载ManagedNamespaceWithLifecycle虚拟方法
重载继承类ManagedNamespaceWithLifecycle的虚拟方法
代码如下:
@Override public void onDataItemsCreated(List<DataItem> dataItems) { subscriptionModel.onDataItemsCreated(dataItems); } @Override public void onDataItemsModified(List<DataItem> dataItems) { subscriptionModel.onDataItemsModified(dataItems); } @Override public void onDataItemsDeleted(List<DataItem> dataItems) { subscriptionModel.onDataItemsDeleted(dataItems); } @Override public void onMonitoringModeChanged(List<MonitoredItem> monitoredItems) { subscriptionModel.onMonitoringModeChanged(monitoredItems); }
完整代码实现:
import org.eclipse.milo.opcua.sdk.core.AccessLevel;import org.eclipse.milo.opcua.sdk.core.Reference;import org.eclipse.milo.opcua.sdk.server.Lifecycle;import org.eclipse.milo.opcua.sdk.server.OpcUaServer;import org.eclipse.milo.opcua.sdk.server.api.DataItem;import org.eclipse.milo.opcua.sdk.server.api.ManagedNamespaceWithLifecycle;import org.eclipse.milo.opcua.sdk.server.api.MonitoredItem;import org.eclipse.milo.opcua.sdk.server.dtd.DataTypeDictionaryManager;import org.eclipse.milo.opcua.sdk.server.model.nodes.objects.BaseEventTypeNode;import org.eclipse.milo.opcua.sdk.server.model.nodes.objects.ServerTypeNode;import org.eclipse.milo.opcua.sdk.server.nodes.UaFolderNode;import org.eclipse.milo.opcua.sdk.server.nodes.UaNode;import org.eclipse.milo.opcua.sdk.server.nodes.UaVariableNode;import org.eclipse.milo.opcua.sdk.server.util.SubscriptionModel;import org.eclipse.milo.opcua.stack.core.Identifiers;import org.eclipse.milo.opcua.stack.core.types.builtin.*;import org.slf4j.Logger;import org.slf4j.LoggerFactory;import java.util.*;import static org.eclipse.milo.opcua.stack.core.types.builtin.unsigned.Unsigned.ubyte;import static org.eclipse.milo.opcua.stack.core.types.builtin.unsigned.Unsigned.ushort;public class TestNamespace extends ManagedNamespaceWithLifecycle { public static final String NAMESPACE_URI = "urn:eclipse:milo:opc"; private final Logger logger = LoggerFactory.getLogger(getClass()); private volatile Thread eventThread; private volatile boolean keepPostingEvents = true; private final DataTypeDictionaryManager dictionaryManager; private final SubscriptionModel subscriptionModel; public TestNamespace(OpcUaServer server) { super(server, NAMESPACE_URI); subscriptionModel = new SubscriptionModel(server, this); dictionaryManager = new DataTypeDictionaryManager(getNodeContext(), NAMESPACE_URI); getLifecycleManager().addLifecycle(dictionaryManager); getLifecycleManager().addLifecycle(subscriptionModel); getLifecycleManager().addLifecycle(new Lifecycle() { @Override public void startup() { startBogusEventNotifier(); } @Override public void shutdown() { try { keepPostingEvents = false; eventThread.interrupt(); eventThread.join(); } catch (InterruptedException ignored) { // ignored } } }); } public void addNodes(Set<String> keys) { // Create a "HelloWorld" folder and add it to the node manager NodeId folderNodeId = newNodeId("opc"); UaFolderNode folderNode = new UaFolderNode( getNodeContext(), folderNodeId, newQualifiedName("opc"), LocalizedText.english("opc") ); getNodeManager().addNode(folderNode); // Make sure our new folder shows up under the server's Objects folder. folderNode.addReference(new Reference( folderNode.getNodeId(), Identifiers.Organizes, Identifiers.ObjectsFolder.expanded(), false )); for (String key : keys) { NodeId typeId = Identifiers.Double; Variant variant = new Variant(0d); UaVariableNode node = new UaVariableNode.UaVariableNodeBuilder(getNodeContext()) .setNodeId(newNodeId(key)) .setAccessLevel(AccessLevel.READ_WRITE) .setUserAccessLevel(AccessLevel.READ_WRITE) .setBrowseName(newQualifiedName(key)) .setDisplayName(LocalizedText.english(key)) .setDataType(typeId) .setTypeDefinition(Identifiers.BaseDataVariableType) .build(); node.setValue(new DataValue(variant)); getNodeManager().addNode(node); folderNode.addOrganizes(node); } } private void startBogusEventNotifier() { // Set the EventNotifier bit on Server Node for Events. UaNode serverNode = getServer() .getAddressSpaceManager() .getManagedNode(Identifiers.Server) .orElse(null); if (serverNode instanceof ServerTypeNode) { ((ServerTypeNode) serverNode).setEventNotifier(ubyte(1)); // Post a bogus Event every couple seconds eventThread = new Thread(() -> { while (keepPostingEvents) { try { BaseEventTypeNode eventNode = getServer().getEventFactory().createEvent( newNodeId(UUID.randomUUID()), Identifiers.BaseEventType ); eventNode.setBrowseName(new QualifiedName(1, "foo")); eventNode.setDisplayName(LocalizedText.english("foo")); eventNode.setEventId(ByteString.of(new byte[]{0, 1, 2, 3})); eventNode.setEventType(Identifiers.BaseEventType); eventNode.setSourceNode(serverNode.getNodeId()); eventNode.setSourceName(serverNode.getDisplayName().getText()); eventNode.setTime(DateTime.now()); eventNode.setReceiveTime(DateTime.NULL_VALUE); eventNode.setMessage(LocalizedText.english("event message!")); eventNode.setSeverity(ushort(2)); //noinspection UnstableApiUsage getServer().getEventBus().post(eventNode); eventNode.delete(); } catch (Throwable e) { logger.error("Error creating EventNode: {}", e.getMessage(), e); } try { //noinspection BusyWait Thread.sleep(2_000); } catch (InterruptedException ignored) { // ignored } } }, "bogus-event-poster"); eventThread.start(); } } @Override public void onDataItemsCreated(List<DataItem> dataItems) { subscriptionModel.onDataItemsCreated(dataItems); } @Override public void onDataItemsModified(List<DataItem> dataItems) { subscriptionModel.onDataItemsModified(dataItems); } @Override public void onDataItemsDeleted(List<DataItem> dataItems) { subscriptionModel.onDataItemsDeleted(dataItems); } @Override public void onMonitoringModeChanged(List<MonitoredItem> monitoredItems) { subscriptionModel.onMonitoringModeChanged(monitoredItems); }}
创建OpcServerTest类,进行使用测试:
import org.eclipse.milo.opcua.sdk.server.OpcUaServer;import org.eclipse.milo.opcua.sdk.server.api.config.OpcUaServerConfig;import org.eclipse.milo.opcua.stack.core.types.builtin.LocalizedText;import org.eclipse.milo.opcua.stack.server.EndpointConfiguration;import java.io.*;import java.nio.charset.StandardCharsets;import java.util.HashSet;import java.util.Set;public class OpcServerTest { public static void main(String[] args) { OpcUaServer server=startServer(12688); TestNamespace namespace=new TestNamespace(server); Set<String> keys=getAllKeys("ehc.txt"); namespace.addNodes(keys); namespace.startup(); } private static OpcUaServer startServer(int port){ Set<EndpointConfiguration> endpointConfigurations=new HashSet<>(); EndpointConfiguration endpointConfiguration=EndpointConfiguration.newBuilder().setBindPort(port).build(); endpointConfigurations.add(endpointConfiguration); System.out.println(endpointConfiguration.getEndpointUrl()); OpcUaServerConfig serverConfig = OpcUaServerConfig.builder() .setApplicationName(LocalizedText.english("Server Application")) .setApplicationUri("urn:eclipse:milo:examples:server") .setProductUri("urn:eclipse:milo:examples:server") .setEndpoints(endpointConfigurations) .build(); OpcUaServer server = new OpcUaServer(serverConfig); server.startup(); return server; } private static Set<String> getAllKeys(String fileName){ Set<String> keys=new HashSet<>(50); try { InputStream is= OpcServerTest.class.getResourceAsStream("/points/"+fileName); InputStreamReader reader = new InputStreamReader(is, StandardCharsets.UTF_8); BufferedReader in = new BufferedReader(reader); String line; while ((line = in.readLine()) != null) { keys.add(line); } } catch (FileNotFoundException e) { e.printStackTrace(); } catch (IOException e) { e.printStackTrace(); } return keys; }}
我把需要创建的点位都放在java maven项目的resources文件下的ehc.txt文件中,通过getAllKeys方法拿到所有的需要创建的点位集合。
启动main方法,控制台输出如下:
我们可以看到控制台输出的 opc.tcp://localhost:12688 就是我们使用java启动opc ua server的连接地址,上面的启动server的代码中,没有设置用户,密码登录,我们在使用opc ua 客户端的时候,可以使用匿名登录访问。
OPC UA 客户端连接测试
使用java 代码连接我们刚才创建的 Opc Ua server,尝试读取我们创建的节点名称,代码如下:
import org.eclipse.milo.opcua.sdk.client.OpcUaClient;public class OpcUaClientTest { public static void main(String[] args) throws Exception { String endPointUrl="opc.tcp://localhost:12688"; OpcUaClient client=OpcUaUtil.createClient(endPointUrl,null,null); OpcUaUtil.browse(null,client); Thread.sleep(Integer.MAX_VALUE); }}
经测试,连接读取节点名称成功,控制台输出如下:
来源地址:https://blog.csdn.net/weixin_40986713/article/details/131513885