前言

模拟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.milo</groupId>
    <artifactId>sdk-server</artifactId>
    <version>0.6.9</version>
</dependency>
<dependency>
    <groupId>org.eclipse.milo</groupId>
    <artifactId>dictionary-manager</artifactId>
    <version>0.6.9</version>
</dependency>

创建Server

创建opc server代码实现:

   /**
     * 方法描述: 创建opcUaServer
     *
     * @param port 端口
     * @return {@link OpcUaServer}
     * @throws
     */
    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.

image.png

创建自定义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 节点方法

  /**
     * 方法描述: 创建节点方法
     *
     * @param keys 节点名称集合
     * @return
     * @throws
     */
    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;

/**
 * @author Lenovo
 */
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
                }
            }
        });
    }

    /**
     * 方法描述: 创建节点方法
     *
     * @param keys
     * @return
     * @throws
     */
    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;

/**
 * @author tarzaqn
 */
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();
    }


    /**
     * 方法描述: 创建opcUaServer
     *
     * @param port 端口
     * @return {@link OpcUaServer}
     * @throws
     */
    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方法拿到所有的

需要创建的点位集合。

image.png


启动main方法,控制台输出如下:

image.png



我们可以看到控制台输出的 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;

/**
 * @author tarzan
 */
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);
    }
}


 

经测试,连接读取节点名称成功,控制台输出如下:

image.png



————————————————

版权声明:本文为CSDN博主「洛阳泰山」的原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接及本声明。

原文链接:https://blog.csdn.net/weixin_40986713/article/details/131513885