前言
模拟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.

创建自定义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方法拿到所有的
需要创建的点位集合。

启动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;
/**
* @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);
}
}
经测试,连接读取节点名称成功,控制台输出如下:

————————————————
版权声明:本文为CSDN博主「洛阳泰山」的原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接及本声明。
原文链接:https://blog.csdn.net/weixin_40986713/article/details/131513885