参考链接
前置条件
下载 KEPServer V6 测试 参考连接有地址,这里摘抄下
项目使用KEPServer V6(427M,中文):百度网盘 ,密码: ykj2
软件操作
下载完 KEPServer V6
打开
界面如下 Default User
是默认用户名 点击连接
连接成功后点击 通道 1 设备 1 可以看到默认配置的信息
如果使用 Opc Ua 无密码登录,需要配置opc属性
右键点击项目,点击属性
选择 opc UA 选择允许匿名登录
配置完毕 点击应用 确定
配置Opc UA服务端属性
选择仅主机
点击客户端 查看数据
启动测试类 看看有没有消息
opc介绍
ocp连接可分为ua
、da
模式
ua 连接
添加依赖 这里使用 miilo
依赖
<properties>
<miilo.version>0.6.9</miilo.version>
</properties>
<dependency>
<groupId>org.eclipse.milo</groupId>
<artifactId>sdk-client</artifactId>
<version>${miilo.version}</version>
</dependency>
<!--Milo服务端的依赖-->
<dependency>
<groupId>org.eclipse.milo</groupId>
<artifactId>sdk-server</artifactId>
<version>${miilo.version}</version>
</dependency>
<dependency>
<groupId>org.bouncycastle</groupId>
<artifactId>bcpkix-jdk15on</artifactId>
<version>1.57</version>
</dependency>
创建客户端
创建客户端并测试
@Slf4j
public class OpcUaClientTest {
private static AtomicInteger atomic = new AtomicInteger(1);
// 定义服务端地址
private final static String endPointUrl = "opc.tcp://127.0.0.1:49320";
public static void main(String[] args) throws Exception {
//创建OPC UA客户端
OpcUaClient opcUaClient = createClient();
//开启连接
opcUaClient.connect().get();
//遍历节点
browseNode(opcUaClient, null);
//读
readNode(opcUaClient);
//写
writeNodeValue(opcUaClient);
//订阅
subscribe(opcUaClient);
//批量订阅
//managedSubscriptionEvent(opcUaClient);
//关闭连接
opcUaClient.disconnect().get();
}
/**
* 订阅(单个)
*
* @param client
* @throws Exception
*/
private static void subscribe(OpcUaClient client) throws Exception {
//创建发布间隔1000ms的订阅对象
client
.getSubscriptionManager()
.createSubscription(1000.0)
.thenAccept(t -> {
//节点
NodeId nodeId = new NodeId(2, "TD-01.SB-01.AG-01");
ReadValueId readValueId = new ReadValueId(nodeId, AttributeId.Value.uid(), null, null);
//创建监控的参数
MonitoringParameters parameters = new MonitoringParameters(UInteger.valueOf(atomic.getAndIncrement()), 1000.0, null, UInteger.valueOf(10), true);
//创建监控项请求
//该请求最后用于创建订阅。
MonitoredItemCreateRequest request = new MonitoredItemCreateRequest(readValueId, MonitoringMode.Reporting, parameters);
List<MonitoredItemCreateRequest> requests = new ArrayList<>();
requests.add(request);
//创建监控项,并且注册变量值改变时候的回调函数。
t.createMonitoredItems(
TimestampsToReturn.Both,
requests,
(item, id) -> item.setValueConsumer((it, val) -> {
System.out.println("nodeid :" + it.getReadValueId().getNodeId());
System.out.println("value :" + val.getValue().getValue());
})
);
}).get();
//持续订阅
Thread.sleep(Long.MAX_VALUE);
}
/**
* 写入节点数据
*
* @param client
* @throws Exception
*/
private static void writeNodeValue(OpcUaClient client) throws Exception {
//节点
NodeId nodeId = new NodeId(2, "TD-01.SB-01.AG-01");
short i = 3;
//创建数据对象,此处的数据对象一定要定义类型,不然会出现类型错误,导致无法写入
DataValue nowValue = new DataValue(new Variant(i), null, null);
//写入节点数据
StatusCode statusCode = client.writeValue(nodeId, nowValue).join();
System.out.println("结果:" + statusCode.isGood());
}
/**
* 读取节点数据
*
* @param client OPC UA客户端
* @throws Exception
*/
private static void readNode(OpcUaClient client) throws Exception {
int namespaceIndex = 2;
String identifier = "通道 1.设备 1.标记 1";
//节点
NodeId nodeId = new NodeId(namespaceIndex, identifier);
//读取节点数据
DataValue value = client.readValue(0.0, TimestampsToReturn.Neither, nodeId).get();
//标识符
identifier = String.valueOf(nodeId.getIdentifier());
log.info("读取数据" + identifier + ": " + String.valueOf(value.getValue().getValue()));
}
/**
* 遍历树形节点
*
* @param client OPC UA客户端
* @param uaNode 节点
* @throws Exception
*/
private static void browseNode(OpcUaClient client, UaNode uaNode) throws Exception {
List<? extends UaNode> nodes;
if (uaNode == null) {
nodes = client.getAddressSpace().browseNodes(Identifiers.ObjectsFolder);
} else {
nodes = client.getAddressSpace().browseNodes(uaNode);
}
for (UaNode nd : nodes) {
//排除系统行性节点,这些系统性节点名称一般都是以"_"开头
if (Objects.requireNonNull(nd.getBrowseName().getName()).contains("_")) {
continue;
}
System.out.println("Node= " + nd.getBrowseName().getName());
browseNode(client, nd);
}
}
/**
* 创建OPC UA客户端
* @return
* @throws Exception
*/
private static OpcUaClient createClient() throws Exception {
//opc ua服务端地址
Path securityTempDir = Paths.get(System.getProperty("java.io.tmpdir"), "security");
Files.createDirectories(securityTempDir);
if (!Files.exists(securityTempDir)) {
throw new Exception("unable to create security dir: " + securityTempDir);
}
return OpcUaClient.create(endPointUrl, endpoints ->
final Optional<EndpointDescription> endpoint = endpoints
.stream()
.filter(e -> e.getSecurityPolicyUri().equals(SecurityPolicy.None.getUri()))
.findFirst();
EndpointDescription newEndpoint=new EndpointDescription(endPointUrl, endpoint.get().getServer(), endpoint.get().getServerCertificate(),
endpoint.get().getSecurityMode(),endpoint.get().getSecurityPolicyUri(),endpoint.get().getUserIdentityTokens(),
endpoint.get().getTransportProfileUri(), endpoint.get().getSecurityLevel());
return Optional.of(newEndpoint);
configBuilder ->
configBuilder
.setApplicationName(LocalizedText.english("eclipse milo opc-ua client"))
.setApplicationUri("urn:eclipse:milo:examples:client")
//访问方式
.setIdentityProvider(new AnonymousProvider())
.setRequestTimeout(UInteger.valueOf(5000))
.build()
);
}
}
创建连接时得使用这个,不然连接错误 参考连接
final Optional<EndpointDescription> endpoint = endpoints
.stream()
.filter(e -> e.getSecurityPolicyUri().equals(SecurityPolicy.None.getUri()))
.findFirst();
EndpointDescription newEndpoint=new EndpointDescription(endPointUrl, endpoint.get().getServer(), endpoint.get().getServerCertificate(),
endpoint.get().getSecurityMode(),endpoint.get().getSecurityPolicyUri(),endpoint.get().getUserIdentityTokens(),
endpoint.get().getTransportProfileUri(), endpoint.get().getSecurityLevel());
return Optional.of(newEndpoint);
线上代码参考
package com.ruoyi.business.opc.config;
import com.google.common.collect.Maps;
import com.ruoyi.business.domain.BizOpcInfo;
import com.ruoyi.business.mapper.BizOpcLogMapper;
import com.ruoyi.business.opc.util.OpcDataUtils;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.eclipse.milo.opcua.sdk.client.OpcUaClient;
import org.eclipse.milo.opcua.sdk.client.api.UaClient;
import org.eclipse.milo.opcua.sdk.client.api.identity.AnonymousProvider;
import org.eclipse.milo.opcua.sdk.client.model.nodes.objects.ServerTypeNode;
import org.eclipse.milo.opcua.sdk.client.nodes.UaNode;
import org.eclipse.milo.opcua.stack.core.Identifiers;
import org.eclipse.milo.opcua.stack.core.security.SecurityPolicy;
import org.eclipse.milo.opcua.stack.core.types.builtin.*;
import org.eclipse.milo.opcua.stack.core.types.builtin.unsigned.UInteger;
import org.eclipse.milo.opcua.stack.core.types.builtin.unsigned.Unsigned;
import org.eclipse.milo.opcua.stack.core.types.enumerated.TimestampsToReturn;
import org.eclipse.milo.opcua.stack.core.types.structured.EndpointDescription;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.stereotype.Component;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.util.Collections;
import java.util.List;
import java.util.Map;
import java.util.Optional;
import java.util.stream.Collectors;
/**
* @author
*/
@Component
@Slf4j
@RequiredArgsConstructor
public class OpcUaConfig {
public static UaClient opcLink = null;
private static final Logger logger = LoggerFactory.getLogger("sys-commun");
private final OpcDataUtils opcDataUtils;
// 将打开的opc客户端放在map中 防止重复打开
public final static Map<String, UaClient> OPC_MAP = Maps.newHashMap();
/**
* 创建订阅
* opc ua 打开连接订阅
* res 是否需要重复连接
*
* @param info 信息
*/
public void createSubscription(BizOpcInfo info) {
try {
//等待三秒(可要可不要)
Thread.sleep(3 * 1000);
UaClient uaClient = null;
Path securityTempDir = Paths.get(System.getProperty("java.io.tmpdir"), "security");
Files.createDirectories(securityTempDir);
if (!Files.exists(securityTempDir)) {
throw new Exception("unable to create security dir: " + securityTempDir);
}
String endPointUrl = info.getOpcUrl();
OpcUaClient opcClient = OpcUaClient.create(endPointUrl,
endpoints -> {
final Optional<EndpointDescription> endpoint = endpoints
.stream()
.filter(e -> e.getSecurityPolicyUri().equals(SecurityPolicy.None.getUri()))
.findFirst();
EndpointDescription newEndpoint=new EndpointDescription(endPointUrl, endpoint.get().getServer(), endpoint.get().getServerCertificate(),
endpoint.get().getSecurityMode(),endpoint.get().getSecurityPolicyUri(),endpoint.get().getUserIdentityTokens(),
endpoint.get().getTransportProfileUri(), endpoint.get().getSecurityLevel());
return Optional.of(newEndpoint);
},
configBuilder ->
configBuilder
.setApplicationName(LocalizedText.english("eclipse milo opc-ua client"))
.setApplicationUri("urn:eclipse:milo:examples:client")
//访问方式
.setIdentityProvider(new AnonymousProvider())
.setRequestTimeout(UInteger.valueOf(50000))
.build()
);
uaClient = opcClient.connect().get();
if (opcLink == null){
opcLink = uaClient;
OPC_MAP.put(endPointUrl, uaClient);
}else {
uaClient.disconnect().get();
}
logger.info(String.format("opc链接%s打开成功", info.getOpcUrl()));
} catch (Exception e) {
logger.error(String.format("opc链接%s打开失败", info.getOpcUrl()));
// 保存错误日志
opcDataUtils.saveError(info, e);
}
}
public static List<? extends UaNode> browseNode(UaNode uaNode) throws Exception {
List<? extends UaNode> nodes;
if (uaNode == null) {
nodes = opcLink.getAddressSpace().browseNodes(Identifiers.ObjectsFolder);
} else {
nodes = opcLink.getAddressSpace().browseNodes(uaNode);
}
nodes = nodes.stream().filter(t->!(t instanceof ServerTypeNode || t.getBrowseName().getName().startsWith("_"))).collect(Collectors.toList());
return nodes;
}
/**
* 读取节点数据
*
* @throws Exception
*/
public static Object readNode(String key) throws Exception {
int namespaceIndex = 2;
//节点
NodeId nodeId = new NodeId(namespaceIndex, key);
//读取节点数据
DataValue value = opcLink.readValue(0.0, TimestampsToReturn.Neither, nodeId).get();
//标识符
String identifier1 = String.valueOf(nodeId.getIdentifier());
return value.getValue().getValue();
}
/**
* 写入节点数据
*
* @param client
* @throws Exception
*/
public static boolean writeNodeValue(UaClient client,String key ,String value,String valueType) {
//节点
NodeId nodeId = new NodeId(2, key);
//创建数据对象,此处的数据对象一定要定义类型,不然会出现类型错误,导致无法写入
Object v = value;
switch (valueType){
case "short":
v = Short.valueOf(value);
break;
case "int":
case "long":
v = Integer.valueOf(value);
break;
case "float":
v = Float.valueOf(value);
break;
case "double":
v = Double.valueOf(value);
break;
case "bool":
v = value.equals("1")? Boolean.valueOf("true"):Boolean.valueOf("false");
break;
case "string":
v = String.valueOf(value);
break;
case "word":
v = Unsigned.ushort(value);
break;
default:
v = String.valueOf(v);
break;
}
DataValue nowValue = new DataValue(new Variant(v), null, null);
//写入节点数据
StatusCode statusCode = client.writeValue(nodeId, nowValue).join();
return statusCode.isGood();
}
public Boolean exist(List<String> list1, List<String> list2) {
Collections.sort(list1);
Collections.sort(list2);
return list1.equals(list2);
}
}
异常
- Thread.sleep(2000); // 让子弹飞一会
UaException: status=Bad_SessionClosed, message=The session was closed by the client.
原因分析: opcUaClient.connect().get(); 是一个异步的过程,可能在读写的时候,连接还没有创建好。
解决方法: Thread.sleep(2000); // 线程休眠一下再返回对象,给创建过程一个时间。