Java使用Milo实现OPC UA客户端

1 篇文章 0 订阅
1 篇文章 0 订阅

文章目录

一、前置条件
二、KEPServerEX 6的配置
三、OPC 客户端开发
四、测试
五、注意点

一、前置条件

jdk:1.8、maven:3.8.1、OPC服务端:KEPServerEX 6Milo

二、KEPServerEX 6的配置

若有下角没有图中的图标,运行KEPServerEX 6 Administration

维护服务器端点信息:

        指定url和相应的安全策略

url:opc.tcp://127.0.0.1:49320
安全策略:None、Basic128Rsa15、Basic256、Basic256Sha256、Aes128_Sha256_RsaOaep、Aes256_Sha256_RsaPss

 添加完指定的用户信息,如下图:

运行项目后选择允许访问的客户端名称对应的证书,点击信任后如下:

 

创建通道->添加设备->维护相应的标记(根据项目需要指定相应的类型和访问权限)

三、OPC 客户端开发

        1 引入Milo库


		<dependency>
			<groupId>org.eclipse.milo</groupId>
			<artifactId>sdk-client</artifactId>
			<version>0.6.10</version>
		</dependency>
		<dependency>
			<groupId>org.bouncycastle</groupId>
			<artifactId>bcpkix-jdk15on</artifactId>
			<version>1.69</version>
		</dependency>
		<dependency>
			<groupId>org.eclipse.milo</groupId>
			<artifactId>sdk-server</artifactId>
			<version>0.6.10</version>
		</dependency>

         2.创建OPC客户端

package com.example.opc;

import com.example.opc.service.OpcUAClientService;
import lombok.extern.slf4j.Slf4j;
import org.eclipse.milo.opcua.sdk.client.OpcUaClient;
import org.eclipse.milo.opcua.stack.core.Stack;
import org.eclipse.milo.opcua.stack.core.types.builtin.LocalizedText;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.stereotype.Service;

import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.TimeUnit;

import static org.eclipse.milo.opcua.stack.core.types.builtin.unsigned.Unsigned.uint;

/**
 * @License : (C) Copyright, MaxSense (Shanghai) Technology Co., Ltd.
 *
 * @file    : OpcUAClientRunner.java
 *
 * @desc    : 
 *
 * @author  : xxx
 *
 * @date    : 2023/8/3 11:44
 *
 * @version : V1.0
 *
 */
@Slf4j
@Service("OpcUAClientRunner")
public class OpcUAClientRunner {
    private final Logger logger = LoggerFactory.getLogger(getClass());
    private final CompletableFuture<OpcUaClient> future = new CompletableFuture<>();
    private final OpcUAClientService opcUAClientService;

    public OpcUAClientRunner(OpcUAClientService opcUAClientService) {
        this.opcUAClientService = opcUAClientService;
    }

    /**
     * OPC UA的运行入口程序
     */
    public void run() {
        try {
            // 创建OPC UA客户端
            OpcUaClient opcUaClient = createClient();
            // future执行完毕后,异步判断状态
            future.whenCompleteAsync((c, ex) -> {
                if (ex != null) {
                    logger.error("连接OPC UA服务错误: {}", ex.getMessage(), ex);
                }
                // 关闭OPC UA客户端
                try {
                    opcUaClient.disconnect().get();
                    Stack.releaseSharedResources();
                } catch (InterruptedException | ExecutionException e) {
                    logger.error("OPC UA服务关闭错误: {}", e.getMessage(), e);
                }
            });
            try {
                // 获取OPC UA服务器的数据
                opcUAClientService.run(opcUaClient, future);
                future.get(5, TimeUnit.SECONDS);
            } catch (Throwable t) {
                logger.error("OPC UA客户端运行错误: {}", t.getMessage(), t);
                future.completeExceptionally(t);
            }
        } catch (Throwable t) {
            logger.error("OPC UA客户端创建错误: {}", t.getMessage(), t);
            future.completeExceptionally(t);
        }
    }

    /**
     * 创建OPC UA的服务连接对象
     */
    private OpcUaClient createClient() throws Exception {
        Path securityTempDir = Paths.get(System.getProperty("java.io.tmpdir"), "client", "security");
        Files.createDirectories(securityTempDir);
        if (!Files.exists(securityTempDir)) {
            throw new Exception("unable to create security dir: " + securityTempDir);
        }
        KeyStoreLoader loader = new KeyStoreLoader().load(securityTempDir);
        // 创建OPC UA客户端
        return OpcUaClient.create(
                opcUAClientService.getEndpointUrl(),
                endpoints ->
                        endpoints.stream()
                                .filter(opcUAClientService.endpointFilter())
                                .findFirst(),
                configBuilder ->
                        configBuilder
                                .setApplicationName(LocalizedText.english("eclipse milo opc-ua client"))
                                .setApplicationUri("urn:eclipse:milo:client")
                                .setKeyPair(loader.getClientKeyPair())
                                .setCertificate(loader.getClientCertificate())
                                .setCertificateChain(loader.getClientCertificateChain())
//                                .setCertificateValidator(certificateValidator)
                                .setIdentityProvider(opcUAClientService.getIdentityProvider())
                                .setRequestTimeout(uint(5000))
                                .build()
        );
    }
}


         3.创建opc客户端证书

package com.example.opc;

import lombok.extern.slf4j.Slf4j;
import org.eclipse.milo.opcua.sdk.server.util.HostnameUtil;
import org.eclipse.milo.opcua.stack.core.util.SelfSignedCertificateBuilder;
import org.eclipse.milo.opcua.stack.core.util.SelfSignedCertificateGenerator;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.io.InputStream;
import java.io.OutputStream;
import java.nio.file.Files;
import java.nio.file.Path;
import java.security.*;
import java.security.cert.X509Certificate;
import java.util.Arrays;
import java.util.regex.Pattern;

/**
 * @License : (C) Copyright, MaxSense (Shanghai) Technology Co., Ltd.
 *
 * @file    : KeyStoreLoader.java
 *
 * @desc    :
 *
 * @author  : xxx
 *
 * @date    : 2023/8/3 11:45
 *
 * @version : V1.0
 *
 */
@Slf4j
public class KeyStoreLoader {

    private static final Pattern IP_ADDR_PATTERN = Pattern.compile(
            "^(([01]?\\d\\d?|2[0-4]\\d|25[0-5])\\.){3}([01]?\\d\\d?|2[0-4]\\d|25[0-5])$");

    private static final String CLIENT_ALIAS = "client-ai";
    private static final char[] PASSWORD = "milo".toCharArray();

    private final Logger logger = LoggerFactory.getLogger(getClass());

    private X509Certificate[] clientCertificateChain;
    private X509Certificate clientCertificate;
    private KeyPair clientKeyPair;

    KeyStoreLoader load(Path baseDir) throws Exception {
        KeyStore keyStore = KeyStore.getInstance("PKCS12");

        Path serverKeyStore = baseDir.resolve("milo-client.pfx");

        logger.info("Loading KeyStore at {}", serverKeyStore);

        if (!Files.exists(serverKeyStore)) {
            keyStore.load(null, PASSWORD);

            KeyPair keyPair = SelfSignedCertificateGenerator.generateRsaKeyPair(2048);

            SelfSignedCertificateBuilder builder = new SelfSignedCertificateBuilder(keyPair)
                    .setCommonName("Eclipse Milo Client")
                    .setOrganization("digitalpetri")
                    .setOrganizationalUnit("dev")
                    .setLocalityName("Folsom")
                    .setStateName("CA")
                    .setCountryCode("CN")
                    .setApplicationUri("urn:eclipse:milo:client")
                    .addDnsName("localhost")
                    .addIpAddress("127.0.0.1");

            // Get as many hostnames and IP addresses as we can listed in the certificate.
            for (String hostname : HostnameUtil.getHostnames("0.0.0.0")) {
                if (IP_ADDR_PATTERN.matcher(hostname).matches()) {
                    builder.addIpAddress(hostname);
                } else {
                    builder.addDnsName(hostname);
                }
            }

            X509Certificate certificate = builder.build();

            keyStore.setKeyEntry(CLIENT_ALIAS, keyPair.getPrivate(), PASSWORD, new X509Certificate[]{certificate});
            try (OutputStream out = Files.newOutputStream(serverKeyStore)) {
                keyStore.store(out, PASSWORD);
            }
        } else {
            try (InputStream in = Files.newInputStream(serverKeyStore)) {
                keyStore.load(in, PASSWORD);
            }
        }

        Key clientPrivateKey = keyStore.getKey(CLIENT_ALIAS, PASSWORD);
        if (clientPrivateKey instanceof PrivateKey) {
            clientCertificate = (X509Certificate) keyStore.getCertificate(CLIENT_ALIAS);

            clientCertificateChain = Arrays.stream(keyStore.getCertificateChain(CLIENT_ALIAS))
                    .map(X509Certificate.class::cast)
                    .toArray(X509Certificate[]::new);

            PublicKey serverPublicKey = clientCertificate.getPublicKey();
            clientKeyPair = new KeyPair(serverPublicKey, (PrivateKey) clientPrivateKey);
        }

        return this;
    }

    X509Certificate getClientCertificate() {
        return clientCertificate;
    }

    public X509Certificate[] getClientCertificateChain() {
        return clientCertificateChain;
    }

    KeyPair getClientKeyPair() {
        return clientKeyPair;
    }
}

        四.opc客户端服务类

package com.example.opc.service;

import org.eclipse.milo.opcua.sdk.client.OpcUaClient;
import org.eclipse.milo.opcua.sdk.client.api.identity.IdentityProvider;
import org.eclipse.milo.opcua.sdk.client.api.identity.UsernameProvider;
import org.eclipse.milo.opcua.stack.core.UaException;
import org.eclipse.milo.opcua.stack.core.security.SecurityPolicy;
import org.eclipse.milo.opcua.stack.core.types.structured.EndpointDescription;

import java.util.concurrent.CompletableFuture;
import java.util.function.Predicate;

/**
 * @License : (C) Copyright, MaxSense (Shanghai) Technology Co., Ltd.
 *
 * @file    : OpcUAClientService.java
 *
 * @desc    : 
 *
 * @author  : xxx
 *
 * @date    : 2023/8/3 12:24
 *
 * @version : V1.0
 *
 */
public interface OpcUAClientService {

    /**
     * OPC UA服务器地址和接口
     */
    default String getEndpointUrl() {
        return "opc.tcp://127.0.0.1:49320";
    }

    /**
     * 过滤返回的server endpoint
     */
    default Predicate<EndpointDescription> endpointFilter() {
        return e -> getSecurityPolicy().getUri().equals(e.getSecurityPolicyUri());
    }

    /**
     * 连接服务器的安全策略
     * <p>
     *     opc客户端指定的安全策略必须被包含在opc服务端指定安全策略中
     *     否则抛{@link UaException} no endpoint selected
     * </p>
     * None、Basic128Rsa15、Basic256、Basic256Sha256、Aes128_Sha256_RsaOaep、Aes256_Sha256_RsaPss
     */
    default SecurityPolicy getSecurityPolicy() {
        return SecurityPolicy.Basic256;
    }

    /**
     * 提供身份验证
     * <p>
     *     username和password需求服务端创建然后提供
     * </p>
     */
    default IdentityProvider getIdentityProvider() {
        return new UsernameProvider("jfy", "123456");
    }

    /**
     * 实际操作服务、由实现类重写实现
     */
    void run(OpcUaClient client, CompletableFuture<OpcUaClient> future) throws Exception;
}

        五.opc客户端实现类

package com.example.opc.service.impl;

import com.example.opc.service.OpcUAClientService;
import com.google.common.collect.ImmutableList;
import lombok.extern.slf4j.Slf4j;
import org.eclipse.milo.opcua.sdk.client.OpcUaClient;
import org.eclipse.milo.opcua.sdk.client.nodes.UaNode;
import org.eclipse.milo.opcua.sdk.client.nodes.UaVariableNode;
import org.eclipse.milo.opcua.stack.core.Identifiers;
import org.eclipse.milo.opcua.stack.core.UaException;
import org.eclipse.milo.opcua.stack.core.types.builtin.DataValue;
import org.eclipse.milo.opcua.stack.core.types.builtin.NodeId;
import org.eclipse.milo.opcua.stack.core.types.enumerated.ServerState;
import org.eclipse.milo.opcua.stack.core.types.enumerated.TimestampsToReturn;
import org.springframework.stereotype.Service;

import java.util.ArrayList;
import java.util.List;
import java.util.Objects;
import java.util.concurrent.CompletableFuture;

import static com.example.opc.opcenum.UaNodeEnum.CHANNEL_1;

/**
 * @License : (C) Copyright, MaxSense (Shanghai) Technology Co., Ltd.
 *
 * @file    : OpcUAClientServiceImpl.java
 *
 * @desc    : 
 *
 * @author  : xxx
 *
 * @date    : 2023/8/3 12:24
 *
 * @version : V1.0
 *
 */
@Slf4j
@Service("OpcUAClientService")
public class OpcUAClientServiceImpl implements OpcUAClientService {

    /**
     * 覆盖接口的方法,建立和OPC UA的服务
     */
    @Override
    public void run(OpcUaClient client, CompletableFuture<OpcUaClient> future) throws Exception {
        // 同步建立连接
        client.connect().get();
        // synchronous read request via VariableNode
        UaVariableNode node = client.getAddressSpace().getVariableNode(Identifiers.Server_ServerStatus_StartTime);
        DataValue value = node.readValue();

        log.info("StartTime={}", value.getValue().getValue());

        // 异步读取数据
        readTagData(client).thenAccept(values -> {
            DataValue nodeId_Tag1 = values.get(0);
            DataValue nodeId_Tag2 = values.get(1);

            System.out.println("#########Tag1=" + nodeId_Tag1.getValue().getValue());
            System.out.println("#########Tag2=" + nodeId_Tag2.getValue().getValue());

            log.info("State={}", ServerState.from((int) nodeId_Tag1.getStatusCode().getValue()));
            log.info("CurrentTime={}", nodeId_Tag1.getServerTime().getJavaDate());

            future.complete(client);
        });
    }

    /**
     * 读取标签点的数据
     */
    private CompletableFuture<List<DataValue>> readTagData(OpcUaClient client) throws UaException {
        // 获取节点列表
        // Identifiers.RootFolder-->Milo库预定义的根目录
        final List<? extends UaNode> nodes = new ArrayList<>();
        browseNode(0, "", client, Identifiers.RootFolder, (List<UaNode>) nodes);

        NodeId nodeId_Tag1 = new NodeId(2, "通道 1.Device1.Tag1");
        NodeId nodeId_Tag2 = new NodeId(2, "通道 1.Device1.Tag2");
        List<NodeId> nodeIds = ImmutableList.of(nodeId_Tag1, nodeId_Tag2);
        return client.readValues(0.0, TimestampsToReturn.Both, nodeIds);
    }

    /**
     * 遍历树形节点
     * @param deep
     * @param indent
     * @param client
     * @param browseRoot
     * @param nodes
     * @return
     */
    private List<? extends UaNode> browseNode(final int deep, final String indent, final OpcUaClient client, final NodeId browseRoot, final List<UaNode> nodes) {
        try {
            List<? extends UaNode> nodeList = client.getAddressSpace().browseNodes(browseRoot);
            for (final UaNode node : nodeList) {
                //排除系统性节点,这些系统性节点名称一般都是以"_"开头
                if (Objects.requireNonNull(node.getBrowseName().getName()).contains("_")) continue;
                // 匹配指定节点的数据(包含对应的子节点)
                log.info("{}--{} Node={}", deep, indent, node.getBrowseName().getName());
                if (deep == 1 && !CHANNEL_1.getName().equals(node.getBrowseName().getName())) continue;
                nodes.add(node);
                // recursively browse to children
                browseNode(deep + 1, indent + "  ", client, node.getNodeId(), nodes);
            }
        } catch (UaException e) {
            log.error("Browsing nodeId={} failed: {}", browseRoot, e.getMessage(), e);
        }
        return nodes;
    }

    private CompletableFuture<List<DataValue>> readServerStateAndTime(OpcUaClient client) {
        List<NodeId> nodeIds = ImmutableList.of(
                Identifiers.Server_ServerStatus_State,
                Identifiers.Server_ServerStatus_CurrentTime);
        return client.readValues(0.0, TimestampsToReturn.Both, nodeIds);
    }
}

 四、测试

五、注意点

1、opc服务端更新过配置后需要初始化

 2、创建OPC客户端时,下图中的ApplicationUri需和KeyStoreLoader中的保持一致,不然抛异常,

 3、url、安全策略、用户名和密码需要配置正确

  • 5
    点赞
  • 13
    收藏
    觉得还不错? 一键收藏
  • 8
    评论
下面是一个简单的 Java 代码示例,用于连接 OPC UA 服务器和读取其中的数据。这个代码示例使用 Eclipse Milo 库来实现 OPC UA 功能。 ```java import org.eclipse.milo.opcua.sdk.client.OpcUaClient; import org.eclipse.milo.opcua.sdk.client.api.identity.UsernameProvider; import org.eclipse.milo.opcua.sdk.client.model.nodes.objects.ServerNode; import org.eclipse.milo.opcua.stack.core.types.builtin.DataValue; import org.eclipse.milo.opcua.stack.core.types.builtin.NodeId; import org.eclipse.milo.opcua.stack.core.types.builtin.Variant; import java.util.concurrent.CompletableFuture; import java.util.concurrent.ExecutionException; public class OpcUaClientExample { public static void main(String[] args) throws InterruptedException, ExecutionException { // 创建 OPC UA 客户端 OpcUaClient client = OpcUaClient.create("opc.tcp://localhost:12686/UA/MyLittleServer"); // 设置用户凭证(如果需要) client.setIdentityProvider(new UsernameProvider("user", "password".toCharArray())); // 连接 OPC UA 服务器 CompletableFuture<OpcUaClient> future = client.connect(); future.get(); // 读取 OPC UA 服务器上的节点数据,例如服务器版本号 NodeId nodeId = new NodeId(2, "i=2261"); CompletableFuture<DataValue> readFuture = client.readValue(0, null, nodeId); DataValue dataValue = readFuture.get(); Variant variant = dataValue.getValue(); Object value = variant.getValue(); System.out.println("Server version: " + value); // 关闭 OPC UA 客户端 client.disconnect().get(); } } ``` 在上面的代码示例中,我们首先创建了一个 OPC UA 客户端对象,然后设置了 OPC UA 服务器的 URL 地址和用户凭证(如果需要)。接着,我们使用 `connect()` 方法连接 OPC UA 服务器,这个方法返回一个 `CompletableFuture<OpcUaClient>` 对象,我们需要使用 `get()` 方法等待连接成功。连接成功后,我们就可以使用客户端对象的各种方法来读取 OPC UA 服务器上的节点数据。在上面的代码示例中,我们读取了服务器版本号,并将其打印到控制台上。最后,我们使用 `disconnect()` 方法关闭了 OPC UA 客户端

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 8
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值