Java代码实现OPC UA Client直接与PLC通讯

介绍

在现代制造业中,PLC(可编程逻辑控制器)被广泛应用于控制工厂设备和流程。而OPC UA(OLE for Process Control Unified Architecture)则成为了工业自动化领域中的通信协议标准。本教程将教你如何使用Java编写一个OPC UA Client来实现直接与PLC通讯。

准备工作

在开始编写代码之前,我们需要准备以下工作:

  1. 安装Java开发环境(JDK)
  2. 从OPC Foundation官网下载并安装OPC UA库(org.eclipse.milo)

步骤

步骤1:创建一个Java项目

首先,在你的开发环境中创建一个新的Java项目。

步骤2:添加OPC UA库依赖

普通方式
将下载的OPC UA库(org.eclipse.milo)导入到你的Java项目中。具体的导入方式根据你使用的开发环境而定。
maven方式

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

步骤3:代码教程

在你的Java项目中创建一个新的类,命名为OpcUaUtil。这个类将包含与PLC通讯的所有代码方法。

创建客户端

传入opc地址,和用户名、密码,创建一个opc ua 客户端,代码实现如下:

  /*** 方法描述: 创建客户端** @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;}

遍历节点

   /*** 方法描述: 查询某个节点下的所有节点** @param identifier 节点名* @param client opc ua客户端* @return* @throws*/public static void browse(String identifier, OpcUaClient client) throws Exception {NodeId nodeId = Identifiers.ObjectsFolder;if(!StringUtils.isEmpty(identifier)){nodeId = new NodeId(2, identifier);}BrowseDescription browse = new BrowseDescription(nodeId,BrowseDirection.Forward,Identifiers.References,true,UInteger.valueOf(NodeClass.Object.getValue() | NodeClass.Variable.getValue()),UInteger.valueOf(BrowseResultMask.All.getValue()));BrowseResult browseResult = client.browse(browse).get();ReferenceDescription[] references = browseResult.getReferences();for (ReferenceDescription reference : references) {System.out.println(reference.getNodeId().getIdentifier().toString()+" "+reference.getNodeClass().getValue());if(reference.getNodeClass().getValue()==NodeClass.Object.getValue()){browse(reference.getNodeId().getIdentifier().toString(),client);}}}

注意:这里用BrowseDescription对象过滤了节点类型为Object和Variable的节点,其他类型的不予查询!

读取单个节点的值

    public static Object readValue(String identifier, OpcUaClient client){log.info("点位数据:"+ identifier);NodeId nodeId = new NodeId(2, identifier);DataValue value = null;try {value = client.readValue(0.0, TimestampsToReturn.Both, nodeId).get();} catch (InterruptedException | ExecutionException e) {e.printStackTrace();}if(Objects.nonNull(value)&&Objects.nonNull(value.getValue())&&Objects.nonNull(value.getValue().getValue())) {return value.getValue().getValue();}return null;}

读取多个节点的值

       /*** 方法描述: 读取多个点位的值** @param keys 点位集合* @param client 客户端* @return {@link List< DataValue>}* @throws*/public  List<DataValue> readValues(Set<String> keys, OpcUaClient client){List<NodeId> nodeIdList=new ArrayList<>(500);keys.forEach(e->{NodeId nodeId = new NodeId(2, e);nodeIdList.add(nodeId);});try {List<DataValue> dataValues=client.readValues(0.0, TimestampsToReturn.Both,nodeIdList).get();return dataValues;} catch (InterruptedException | ExecutionException e) {e.printStackTrace();}return null;};}

注意:返回的数据值集合的顺序和传入的点位集合的顺序是一致的,我们可能根据传入点位的的顺序,匹配对应的值

写入单个节点的值

    /*** 写入值** @param identifier* @param value* @throws Exception*/public static void writeValue(String identifier, Object value) throws Exception {//创建变量节点NodeId nodeId = new NodeId(2, identifier);//创建Variant对象和DataValue对象Variant v = new Variant(value);DataValue dataValue = new DataValue(v, null, null);StatusCode statusCode = OPC_UA_CLIENT.writeValue(nodeId, dataValue).get();if(statusCode.isGood()){log.info("数据写入成功");}else {log.error("数据写入失败");}}

写入多个节点的值

/*** 方法描述:  写入多个节点的值** @param keys  节点集合* @param values  值集合* @param client  客户端* @return {@link Object}* @throws*/public  Object writeValues(Set<String> keys,List<Object> values,OpcUaClient client){List<NodeId> nodeIs=new ArrayList<>(keys.size());keys.forEach(e->{NodeId nodeId = new NodeId(2, e);nodeIs.add(nodeId);});List<DataValue> dataValues=new ArrayList<>(values.size());values.forEach(e->{Variant value=new Variant(Double.parseDouble(e.toString()));DataValue dataValue=new DataValue(value);dataValues.add(dataValue);});try {client.writeValues(nodeIs,dataValues).get();} catch (InterruptedException | ExecutionException e) {e.printStackTrace();}return null;}

注意:传入节点集合的顺序要和传入值的集合的顺序一一对应,才能存入和点位匹配的值。

断开连接

  /*** 方法描述: 断开连接** @param client* @return* @throws*/public void disconnect(OpcUaClient client){try {client.disconnect().get();} catch (InterruptedException e) {e.printStackTrace();} catch (ExecutionException e) {e.printStackTrace();}}

完整代码:


import lombok.extern.slf4j.Slf4j;
import org.eclipse.milo.opcua.sdk.client.OpcUaClient;
import org.eclipse.milo.opcua.sdk.client.api.config.OpcUaClientConfig;
import org.eclipse.milo.opcua.sdk.client.api.identity.AnonymousProvider;
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.client.DiscoveryClient;
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.enumerated.BrowseDirection;
import org.eclipse.milo.opcua.stack.core.types.enumerated.BrowseResultMask;
import org.eclipse.milo.opcua.stack.core.types.enumerated.NodeClass;
import org.eclipse.milo.opcua.stack.core.types.enumerated.TimestampsToReturn;
import org.eclipse.milo.opcua.stack.core.types.structured.BrowseDescription;
import org.eclipse.milo.opcua.stack.core.types.structured.BrowseResult;
import org.eclipse.milo.opcua.stack.core.types.structured.EndpointDescription;
import org.eclipse.milo.opcua.stack.core.types.structured.ReferenceDescription;
import org.springframework.util.StringUtils;import java.util.*;
import java.util.concurrent.ExecutionException;/*** @author TARZAN*/
@Slf4j
public class OpcUaUtil {/*** 方法描述: 创建客户端** @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;}/*** 方法描述: 查询所有节点** @param client* @return {@link Set<String>}* @throws* @author liwenbin* @date 2023年07月03日 13:05:55*/public static Set<String> getAllKeys(OpcUaClient client) throws Exception {return browse(null,client);}/*** 方法描述: 查询某个节点下的所有节点** @param identifier* @param client* @return* @throws*/public static Set<String> browse(String identifier, OpcUaClient client) throws Exception {Set<String> keys=new HashSet<>(500);browse(identifier,keys,client);return keys;}/*** 方法描述: 查询某个节点下的所有节点** @param identifier* @param client* @return* @throws*/private static Set<String> browse(String identifier, Set<String> keys, OpcUaClient client) throws Exception {NodeId nodeId = Identifiers.ObjectsFolder;if(!StringUtils.isEmpty(identifier)){nodeId = new NodeId(2, identifier);}BrowseDescription browse = new BrowseDescription(nodeId,BrowseDirection.Forward,Identifiers.References,true,UInteger.valueOf(NodeClass.Object.getValue() | NodeClass.Variable.getValue()),UInteger.valueOf(BrowseResultMask.All.getValue()));BrowseResult browseResult = client.browse(browse).get();ReferenceDescription[] references = browseResult.getReferences();for (ReferenceDescription reference : references) {System.out.println(reference.getNodeId().getIdentifier().toString()+" "+reference.getNodeClass().getValue());keys.add(identifier);if(reference.getNodeClass().getValue()==NodeClass.Object.getValue()){browse(reference.getNodeId().getIdentifier().toString(),keys,client);}}return keys;}/*** 方法描述: 读取单个节点值** @param identifier* @param client* @return {@link Object}* @throws*/public static Object readValue(String identifier, OpcUaClient client){NodeId nodeId = new NodeId(2, identifier);DataValue value = null;try {client.readValue(0.0, TimestampsToReturn.Both,nodeId);value = client.readValue(0.0, TimestampsToReturn.Both, nodeId).get();} catch (InterruptedException | ExecutionException e) {e.printStackTrace();}if(Objects.nonNull(value)&&Objects.nonNull(value.getValue())&&Objects.nonNull(value.getValue().getValue())) {return value.getValue().getValue();}return null;}/*** 方法描述: 读取多个点位的值** @param keys 点位集合* @param client 客户端* @return {@link List<DataValue>}* @throws*/public static List<DataValue> readValues(Set<String> keys, OpcUaClient client){List<NodeId> nodeIdList=new ArrayList<>(500);keys.forEach(e->{NodeId nodeId = new NodeId(2, e);nodeIdList.add(nodeId);});try {List<DataValue> dataValues=client.readValues(0.0, TimestampsToReturn.Both,nodeIdList).get();return dataValues;} catch (InterruptedException | ExecutionException e) {e.printStackTrace();}return null;}/*** 写入值** @param identifier* @param value* @throws Exception*/public static void writeValue(String identifier, Object value,OpcUaClient client) throws Exception {//创建变量节点NodeId nodeId = new NodeId(2, identifier);//创建Variant对象和DataValue对象Variant v = new Variant(value);DataValue dataValue = new DataValue(v, null, null);StatusCode statusCode = client.writeValue(nodeId, dataValue).get();if(statusCode.isGood()){log.info("数据写入成功");}else {log.error("数据写入失败");}}/*** 方法描述:  写入多个节点的值** @param keys  节点集合* @param values  值集合* @param client  客户端* @return {@link Object}* @throws*/public static Object writeValues(Set<String> keys,List<Object> values,OpcUaClient client){List<NodeId> nodeIs=new ArrayList<>(keys.size());keys.forEach(e->{NodeId nodeId = new NodeId(2, e);nodeIs.add(nodeId);});List<DataValue> dataValues=new ArrayList<>(values.size());values.forEach(e->{Variant value=new Variant(Double.parseDouble(e.toString()));DataValue dataValue=new DataValue(value);dataValues.add(dataValue);});try {client.writeValues(nodeIs,dataValues).get();} catch (InterruptedException | ExecutionException e) {e.printStackTrace();}return null;}/*** 方法描述: 断开连接** @param client* @return*/public static void disconnect(OpcUaClient client){try {client.disconnect().get();} catch (InterruptedException | ExecutionException e) {e.printStackTrace();}}}

步骤4:使用OPCUAClient类

现在,在你的Java项目中创建一个OpcUaClientTest 类,并使用OPCUAClient类来实现与PLC的通讯。

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:12686";OpcUaClient client=OpcUaUtil.createClient(endPointUrl,null,null);OpcUaUtil.browse(null,client);OpcUaUtil.disconnect(client);Thread.sleep(Integer.MAX_VALUE);}
}

运行测试,控制台输出结果
在这里插入图片描述

结论

通过本教程,你已经学会了使用Java编写一个OPC UA Client来实现直接与PLC通讯。你可以根据自己的需求对OPCUAClient类进行扩展和改进。希望本教程对你有所帮助!

注意:以上只是一个简单的示例,实际使用中可能还需要处理异常,添加更多的读写功能等。此外,需要根据具体的PLC类型和配置,设置正确的连接参数和节点ID。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.hqwc.cn/news/5992.html

如若内容造成侵权/违法违规/事实不符,请联系编程知识网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

(1)深度学习学习笔记-数据操作和处理

文章目录 前言一、张量操作二、csv文件数据操作数据预处理(读入csv文件作为pytorch能处理的) 来源 前言 张量的一些处理和操作 csv文件数据操作 一、张量操作 [&#xff1a;&#xff0c;1]表示全部行 第二列 [&#xff1a;]是全取 [1:3,1&#xff1a;]&#xff1a;1:3表示1~3的…

基于Java+Vue前后端分离宠物领养系统设计实现(源码+lw+部署文档+讲解等)

博主介绍&#xff1a;✌全网粉丝30W,csdn特邀作者、博客专家、CSDN新星计划导师、Java领域优质创作者,博客之星、掘金/华为云/阿里云/InfoQ等平台优质作者、专注于Java技术领域和毕业项目实战✌ &#x1f345;文末获取源码联系&#x1f345; &#x1f447;&#x1f3fb; 精彩专…

微信为什么使用 SQLite 保存聊天记录?

概要 SQLite 是一个被大家低估的数据库&#xff0c;但有些人认为它是一个不适合生产环境使用的玩具数据库。事实上&#xff0c;SQLite 是一个非常可靠的数据库&#xff0c;它可以处理 TB 级的数据&#xff0c;但它没有网络层。接下来&#xff0c;本文将与大家共同探讨 SQLite 在…

【每日一题】Leetcode - 10. 正则表达式匹配

题目 Leetcode - 10. 正则表达式匹配 解题思路 预处理正则表达式&#xff0c;合并同项&#xff0c;比如: "a * b * c * . * " -> " . * "更加预处理后的正则表达式&#xff0c;构建NFA通过NFA状态转移条件一个一个匹配字符串字符不匹配的状态要回退匹…

13年12月CCF计算机软件能力认证(csp认证) 内有c++和Python代码

第一题&#xff1a;出现次数最多的数 给定 n 个正整数&#xff0c;找出它们中出现次数最多的数。 如果这样的数有多个&#xff0c;请输出其中最小的一个。 输入格式 输入的第一行只有一个正整数 n&#xff0c;表示数字的个数。 输入的第二行有 n 个整数 s1,s2,…,sn。 相邻的数…

3.FreeRTOS系统源码移植

目录 一、获取FreeRTOS源代码 二、FreeRTOS系统源码内容 三、FreeRTOS系统源码移植 一、获取FreeRTOS源代码 来FreeRTOS官方网站:https://www.freertos.org/ 我这里主要提供的是例程为FreeRTOS的V10.4.6版本 1、进入官网&#xff0c;点击Download FreeRTOS 2、点击Downl…

浅析厂房仓库电气火灾的成因及对策

贾丽丽 安科瑞电气股份有限公司 上海嘉定201801 摘 要: 文章分析了厂房仓库电气火灾的成因及火灾特点 ,并有针对性地提出了预防火灾的对策。 关键词: 厂房仓库&#xff1b;电气火灾&#xff1b;成因&#xff1b;预防对策 0 前 言 随着国际经济的全球化,国内经济建设迅猛发…

时间序列分解 | Matlab改进的自适应噪声完备集合经验模态分解ICEEMDAN

文章目录 效果一览文章概述部分源码参考资料效果一览 文章概述 时间序列分解 | Matlab改进的自适应噪声完备集合经验模态分解ICEEMDAN 部分源码 %--------------------

Hadoop 集群如何升级?

前言 本文隶属于专栏《大数据技术体系》&#xff0c;该专栏为笔者原创&#xff0c;引用请注明来源&#xff0c;不足和错误之处请在评论区帮忙指出&#xff0c;谢谢&#xff01; 本专栏目录结构和参考文献请见大数据技术体系 正文 升级 Hadoop 集群需要细致的规划&#xff0c;特…

手把手教你搭建SpringCloudAlibaba之Sentinel规则持久化

SpringCloud Alibaba全集文章目录&#xff1a; 零、手把手教你搭建SpringCloudAlibaba项目 一、手把手教你搭建SpringCloud Alibaba之生产者与消费者 二、手把手教你搭建SpringCloudAlibaba之Nacos服务注册中心 三、手把手教你搭建SpringCloudAlibaba之Nacos服务配置中心 …

IIC(I2C)协议

I2C&#xff08;Inter-Integrated Circuit&#xff09;:是一种串行通信协议&#xff0c;用于在集成电路之间进行数据传输。它由飞利浦公司开发&#xff0c;并广泛应用在各种电子设备和传感器之间进行通信。 I2C通信协议由两根线组成&#xff1a; 一个是用于数据传输的串行数据线…

MySQL 主从复制与读写分离

概念 主从复制与读写分离的意义 企业中的业务通常数据量都比较大&#xff0c;而单台数据库在数据存储、安全性和高并发方面都无法满足实际的需求&#xff0c;所以需要配置多台主从数据服务器&#xff0c;以实现主从复制&#xff0c;增加数据可靠性&#xff0c;读写分离&#x…