Java实现OPC UA断连重连和数据监听

简介

本文将以Java语言为例,详细解读如何在OPC UA通信中实现断连重连和数据监听的技术。首先,分析了为何断连重连和数据监听在OPC UA应用中至关重要,以及传统方法的局限性。随后,引入了Java的开源框架和库,如Eclipse Milo和Apache Camel,以优雅且高效的方式处理连接管理和数据流。同时,结合实际案例,详细演示了如何利用这些技术代码实现OPC UA断连重连和数据监听,从而实现系统的稳定性和实时性。无论您是OPC UA初学者还是有一定经验的开发者,本文都将为您提供宝贵的技术指导,助力您实现高效稳定的OPC UA通信。

引入依赖

首先在maven项目中,引入org.eclipse.milojar包依赖

        <dependency>
            <groupId>org.eclipse.milo</groupId>
            <artifactId>sdk-client</artifactId>
            <version>0.6.7</version>
        </dependency>

Milo库
org.eclipse.milo是一个基于Java的开源OPC UA(开放型通讯联盟)实现。OPC UA是一种用于工业自动化领域的开放标准通信协议,它提供了可互操作的数据交换和设备管理能力。

org.eclipse.milo项目旨在提供一个完善的OPC UA实现,以便开发者可以轻松地创建和管理OPC UA服务器和客户端。它的设计目标是在性能和功能方面提供高度的可扩展性和灵活性。

该项目基于Eclipse IoT项目中的Eclipse Milo子项目进行开发和维护。Eclipse Milo提供一系列的OPC UA库和工具,可以帮助开发者在Java平台上构建OPC UA应用程序。org.eclipse.milo扩展了Eclipse Milo,提供了更多的功能和集成选项。

通过org.eclipse.milo,开发者可以轻松地创建基于OPC UA的应用程序,包括OPC UA服务器和客户端。它提供了一组API,可以处理与OPC UA通信相关的任务,如创建和管理节点,读写变量值,订阅和发布事件等。

org.eclipse.milo还提供了一些示例应用程序和工具,可以帮助开发者入门并快速开始开发OPC UA应用程序。

总结来说,org.eclipse.milo是一个功能强大的基于Java的OPC UA实现,为开发者提供了构建和管理OPC UA应用程序的工具和API。它是一个开源项目,可以根据需要进行定制和扩展。

OPC UA断开重连

创建订阅事件监听器SubscriptionListener实现implements UaSubscriptionManager.SubscriptionListener 接口的方法,代码如下:


import com.tarzan.opcua.util.OpcUaUtil;
import lombok.extern.slf4j.Slf4j;
import org.eclipse.milo.opcua.sdk.client.OpcUaClient;
import org.eclipse.milo.opcua.sdk.client.api.subscriptions.UaSubscription;
import org.eclipse.milo.opcua.sdk.client.api.subscriptions.UaSubscriptionManager;
import org.eclipse.milo.opcua.stack.core.UaException;
import org.eclipse.milo.opcua.stack.core.types.builtin.DateTime;
import org.eclipse.milo.opcua.stack.core.types.builtin.StatusCode;

import java.util.Set;


/**
 * @author tarzan
 */
@Slf4j
public class SubscriptionListener implements UaSubscriptionManager.SubscriptionListener {
    
    


    private Set<String> keys;
    private OpcUaClient client;

    public SubscriptionListener(Set<String> keys, OpcUaClient client) {
    
    
        this.keys = keys;
        this.client=client;
    }

    @Override
    public void onKeepAlive(UaSubscription subscription, DateTime publishTime) {
    
    
        log.info("onKeepAlive");
    }

    @Override
    public void onStatusChanged(UaSubscription subscription, StatusCode status) {
    
    
        log.info("onStatusChanged");
    }

    @Override
    public void onPublishFailure(UaException exception) {
    
    
        log.info("onPublishFailure");
    }

    @Override
    public void onNotificationDataLost(UaSubscription subscription) {
    
    
        log.info("onNotificationDataLost");
    }

    /**
     * 重连时 尝试恢复之前的订阅失败时 会调用此方法
     * @param uaSubscription 订阅
     * @param statusCode 状态
     */
    @Override
    public void onSubscriptionTransferFailed(UaSubscription uaSubscription, StatusCode statusCode) {
    
    
        log.info("恢复订阅失败 需要重新订阅");
        //在回调方法中重新订阅
        OpcUaUtil.handlerNode(keys,client);
    }
}

其中 keys是需要订阅点位集合,client是OpcUa客户端。 onSubscriptionTransferFailed订阅转移失败时触发,通过调用OpcUaUtil.handlerNode方法,重新批量订阅点位。handlerNode方法实现代码如下:


    /**
     * 处理订阅业务
     *
     * @param keys
     */
    public static void handlerNode(Set<String> keys, OpcUaClient client) {
    
    
        try {
    
    
            //创建订阅
            ManagedSubscription subscription = ManagedSubscription.create(client);
            List<NodeId> nodeIdList = new ArrayList<>();
            for (String key : keys) {
    
    
                nodeIdList.add(new NodeId(2, key));
            }
            //监听
            List<ManagedDataItem> dataItemList = subscription.createDataItems(nodeIdList);
            for (ManagedDataItem managedDataItem : dataItemList) {
    
    
                managedDataItem.addDataValueListener(new MyDataValueListener());
            }
        } catch (Exception e) {
    
    
            e.printStackTrace();
        }
    }

其中 MyDataValueListener 是自定义的opc ua 数据值变化监听器。

数据监听

创建一个MyDataValueListener 类,实现ManagedDataItem.DataValueListener接口的onDataValueReceived的方法。


import lombok.AllArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.eclipse.milo.opcua.sdk.client.subscriptions.ManagedDataItem;
import org.eclipse.milo.opcua.stack.core.types.builtin.DataValue;

/**
 * @author tarzan
 */
@Slf4j
@AllArgsConstructor
public class MyDataValueListener implements ManagedDataItem.DataValueListener {
    
    


    @Override
    public void onDataValueReceived(ManagedDataItem managedDataItem, DataValue dataValue) {
    
    
        log.info(managedDataItem.getNodeId().getIdentifier().toString() + ":" + dataValue.getValue().getValue());
        //do some something
    }
}

订阅使用

创建一个批量订阅的方法,传入需要订阅的点位名集合和OpcUaClient客户端


    /**
     * 批量订阅
     *
     * @param keys 订阅
     * @throws Exception
     */
    public  void subscribeEvent(Set<String> keys,OpcUaClient client) throws Exception {
    
    
        final CountDownLatch eventLatch = new CountDownLatch(1);
        //处理订阅业务
        handlerNode(keys,client);
        //添加订阅监听器,用于处理断线重连后的订阅问题
        client.getSubscriptionManager().addSubscriptionListener(new SubscriptionListener(keys,client));
        //持续监听
        eventLatch.await();
    }

进一步封装方法,在service层,创建订阅方法,代码如下:

 public void subscribe(Set<String> keys) throws Exception {
    
    
        OpcUaClient client= OpcUaUtil.createClient(endPointUrl);
        opcUaService.subscribeEvent(keys,client);
  }

然后通过controller接口,调用service层subscribe订阅方法,或者在其他业务层里调用即可。这样就可以实现,订阅断开重连和订阅点位值变化监听的功能了,代码比较简单,需要加入自己的业务处理,请在原有的代码上加以改动!下面补充一下,OpcUaUtil.createClient创建客户端的方法,代码如下:


    /**
     * 方法描述: 创建客户端
     *
     * @param endPointUrl
     * @return {@link OpcUaClient}
     * @throws
     */
    public static OpcUaClient createClient(String endPointUrl){
    
    
        return createClient(endPointUrl,null,null);
    }

    /**
     * 方法描述: 创建客户端
     *
     * @param endPointUrl
     * @param username
     * @param password
     * @return {@link OpcUaClient}
     * @throws
     */
    public static OpcUaClient createClient(String endPointUrl,String username,String password){
    
    
        log.info(endPointUrl);
        try {
    
    
            //获取安全策略
            List<EndpointDescription> endpointDescription = DiscoveryClient.getEndpoints(endPointUrl).get();
            //过滤出一个自己需要的安全策略
            EndpointDescription endpoint = endpointDescription.stream()
                    .filter(e -> e.getSecurityPolicyUri().equals(SecurityPolicy.None.getUri()))
                    .findFirst().orElse(null);
            IdentityProvider identityProvider=new AnonymousProvider();
            if(!StringUtils.isEmpty(username)||!StringUtils.isEmpty(password)){
    
    
                identityProvider=new UsernameProvider(username,password);
            }
            // 设置配置信息
            OpcUaClientConfig config = OpcUaClientConfig.builder()
                    // opc ua 自定义的名称
                    .setApplicationName(LocalizedText.english("plc"))
                    // 地址
                    .setApplicationUri(endPointUrl)
                    // 安全策略等配置
                    .setEndpoint(endpoint)
                    .setIdentityProvider(identityProvider)
                    //等待时间
                    .setRequestTimeout(UInteger.valueOf(5000))
                    .build();
            // 准备连接
            OpcUaClient opcClient =OpcUaClient.create(config);
            //开启连接
            opcClient.connect().get();
            log.info("连接成功。。。success");
            return opcClient;
        } catch (Exception e) {
    
    
            e.printStackTrace();
            log.error("======== opc connection fail ========");
        }
        return null;
    }

结语

在我做的项目中,一开始使用的这种监听点位值变化的代码实现,但是我这边的需求是要求实时输出点位值。如果用监听值的办法的话,可会好久不输出,因为点位值没有变化,就不会触发监听事件方法。我这改成了定时一次性读取多个点位值的代码实现。关于断开重连的解决方案,因为我设置的是定时任务,1秒执行一次。在OpcUaUtil.createClient的方法上又加一个获取Opc Ua客户端的方法,当createClient方法返回为null时,我就再次调用创建客户端的方法,代码如下:

   public  OpcUaClient getClient(){
    
    
        if(client==null){
    
    
            client= OpcUaUtil.createClient(endPointUrl);
        }
        return client;
    }

在service层,创建订阅方法修改,代码如下:

 public void subscribe(Set<String> keys) throws Exception {
    
    
        OpcUaClient client= OpcUaUtil.getClient(endPointUrl);
        opcUaService.subscribeEvent(keys,client);
  }

猜你喜欢

转载自blog.csdn.net/weixin_40986713/article/details/131529181