Java使用Milo实现OPC UA客户端

一、Milo库

本文使用Milo库实现OPC UA客户端,以达到通过java读、写、订阅变量的目的。

官网:Milo Github链接

官网地址有时候访问很慢,也可以使用国内的地址:https://gitcode.net/mirrors/eclipse/milo

二、OPC UA服务端及客户端

OPC UA服务端:KEPServerEX 6

  • 下载地址:http://www.sibotech.net/SiboKepware/downloadInfo/

UI客户端:

  • UaExpert 下载地址:https://www.unified-automation.com/
  • UaExpert使用教程(转载):https://www.cnblogs.com/water-sea/p/12863111.html

三、Java连接OPC UA服务端

以下代码都是在一个类中执行的,并使用main方法进行测试,因此所有方法都是private和static修饰的。

3.1 依赖

Milo的依赖有2个,Client SDK,Server SDK。

客户端:

<!--Milo客户端的依赖-->
<dependency>
  <groupId>org.eclipse.milo</groupId>
  <artifactId>sdk-client</artifactId>
  <version>0.6.3</version>
</dependency>

服务端:

<!--Milo服务端的依赖-->
<dependency>
  <groupId>org.eclipse.milo</groupId>
  <artifactId>sdk-server</artifactId>
  <version>0.6.3</version>
</dependency>

3.2 创建opc ua客户端

/**
  * 创建OPC UA客户端
  * @return
  * @throws Exception
  */
private static OpcUaClient createClient() throws Exception {
  //opc ua服务端地址
  private final static String endPointUrl = "opc.tcp://192.168.0.169:49320";
  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 ->
                            endpoints.stream()
                            .filter(e -> e.getSecurityPolicyUri().equals(SecurityPolicy.None.getUri()))
                            .findFirst(),
                            configBuilder ->
                            configBuilder
                            .setApplicationName(LocalizedText.english("eclipse milo opc-ua client"))
                            .setApplicationUri("urn:eclipse:milo:examples:client")
                            //访问方式
                            .setIdentityProvider(new AnonymousProvider())
                            .setRequestTimeout(UInteger.valueOf(5000))
                            .build()
                           );
}

注意:

  1. new AnonymousProvider()表示使用匿名方式访问,也可以通过new UsernameProvider(userName, password)方式访问。
  2. 如果需要使用数字证书和秘钥的方式进行认证,请去官网查询相关资料。

3.3 遍历树形节点

/**
  * 遍历树形节点
  *
  * @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);
  }
}

3.4 读取节点数据

/**
  * 读取节点数据
  *
  * @param client OPC UA客户端
  * @throws Exception
  */
private static void readNode(OpcUaClient client) throws Exception {
  int namespaceIndex = 2;
  String identifier = "TD-01.SB-01.AG-01";
  //节点
  NodeId nodeId = new NodeId(namespaceIndex, identifier);
  //读取节点数据
  DataValue value = client.readValue(0.0, TimestampsToReturn.Neither, nodeId).get();
  //标识符
  String identifier = String.valueOf(nodeId.getIdentifier());
  System.out.println(identifier + ": " + String.valueOf(value.getValue().getValue()));
}
  1. namespaceIndex可以通过UaExpert客户端去查询,一般来说这个值是2。
  2. identifier也可以通过UaExpert客户端去查询,这个值=通道名称.设备名称.标记名称

3.5 写入节点数据

/**
 * 写入节点数据
 *
 * @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());
}

3.5 订阅(单个)

/**
 * 订阅(单个)
 *
 * @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();

    //持续订阅,main方法测试需要加上,不然主线程直接结束了就没办法持续监听了。如果是springboot中调用就不需要了。
    Thread.sleep(Long.MAX_VALUE);
}

订阅成功后,main线程会被阻塞,修改OPC UA中的变量值后,就可以即时查看到订阅的消息。订阅时间可以通过修改Thread.sleep的沉睡时间实现。

3.6 批量订阅

/**
  * 批量订阅
  *
  * @param client
  * @throws Exception
  */
private static void managedSubscriptionEvent(OpcUaClient client) throws Exception {
  final CountDownLatch eventLatch = new CountDownLatch(1);
	
  //处理订阅业务
  handlerNode(client);

  //持续监听,main方法测试需要加上,不然主线程直接结束了就没办法持续监听了。如果是springboot中调用就不需要了。
  eventLatch.await();
}

/**
  * 处理订阅业务
  *
  * @param client OPC UA客户端
  */
private static void handlerNode(OpcUaClient client) {
  try {
    //创建订阅
    ManagedSubscription subscription = ManagedSubscription.create(client);

    //你所需要订阅的key
    List<String> key = new ArrayList<>();
    key.add("TD-01.SB-01.AG-01");
    key.add("TD-01.SB-01.AG-02");

    List<NodeId> nodeIdList = new ArrayList<>();
    for (String s : key) {
      nodeIdList.add(new NodeId(2, s));
    }

    //监听
    List<ManagedDataItem> dataItemList = subscription.createDataItems(nodeIdList);
    for (ManagedDataItem managedDataItem : dataItemList) {
      managedDataItem.addDataValueListener((t) -> {
        System.out.println(managedDataItem.getNodeId().getIdentifier().toString() + ":" + t.getValue().getValue().toString());
      });
    }
  } catch (Exception e) {
    e.printStackTrace();
  }
}

注意:正常情况下通过OpcUaClient去订阅,没问题,但是当服务端断开之后,milo会抛出异常,当服务端重新启动成功后,OpcUaClient可以自动断线恢复,但是恢复之后会发现之前订阅的数据没法访问了。要解决这个问题,只需要断线重连后重新订阅即可。

3.7 处理断线重连后的订阅问题

3.7.1 自定义实现SubscriptionListener

/**
  * 自定义订阅监听
  */
private static class CustomSubscriptionListener implements UaSubscriptionManager.SubscriptionListener {

  private OpcUaClient client;

  CustomSubscriptionListener(OpcUaClient client) {
    this.client = client;
  }

  public void onKeepAlive(UaSubscription subscription, DateTime publishTime) {
    logger.debug("onKeepAlive");
  }

  public void onStatusChanged(UaSubscription subscription, StatusCode status) {
    logger.debug("onStatusChanged");
  }

  public void onPublishFailure(UaException exception) {
    logger.debug("onPublishFailure");
  }

  public void onNotificationDataLost(UaSubscription subscription) {
    logger.debug("onNotificationDataLost");
  }

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

3.7.2 添加 SubscriptionListener

/**
  * 批量订阅
  *
  * @param client
  * @throws Exception
  */
private static void managedSubscriptionEvent(OpcUaClient client) throws Exception {
  final CountDownLatch eventLatch = new CountDownLatch(1);

  //添加订阅监听器,用于处理断线重连后的订阅问题
  client.getSubscriptionManager().addSubscriptionListener(new CustomSubscriptionListener(client));

  //处理订阅业务
  handlerNode(client);

  //持续监听
  eventLatch.await();
}

3.8 测试

public class OpcUaClientTest {

    private static Logger logger = LoggerFactory.getLogger(OpcUaClientTest.class);
    private final static String endPointUrl = "opc.tcp://192.168.0.169:49320";
    private static AtomicInteger atomic = new AtomicInteger(1);

    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();
    }
}
  • 27
    点赞
  • 161
    收藏
    觉得还不错? 一键收藏
  • 90
    评论
Java Eclipse环境下实现OPC UA客户端代码,可以使用Milo库。以下是一个简单实现的示例代码: 1. 导入Milo库和其他必要的依赖: ``` <dependency> <groupId>org.eclipse.milo</groupId> <artifactId>milo-client-sdk</artifactId> <version>x.x.x</version> </dependency> ``` 2. 创建一个OPC UA客户端: ```java OpcUaClient client; try { // 连接到OPC UA服务器 OpcUaClientConfig config = OpcUaClientConfig.builder() .setEndpoint(endpointUrl) // OPC UA服务器的URL .setRequestTimeout(uint(5000)) // 请求超时时间 .build(); client = new OpcUaClient(config); client.connect().get(); // 建立连接 // 连接成功后,进行其他操作,例如读取或写入节点值 } catch (Exception e) { e.printStackTrace(); } ``` 3. 读取节点值: ```java try { // 读取单个节点值 NodeId nodeId = new NodeId(namespaceIndex, identifier); // 根据节点的命名空间和标识符创建节点ID DataValue value = client.readValue(Duration.ofMillis(5000), TimestampsToReturn.Both, nodeId).get(); // 处理读取到的值 System.out.println(value.getValue().getValue()); } catch (Exception e) { e.printStackTrace(); } ``` 4. 写入节点值: ```java try { // 写入单个节点值 NodeId nodeId = new NodeId(namespaceIndex, identifier); // 根据节点的命名空间和标识符创建节点ID Variant variant = new Variant(writeValue); // 写入的值 StatusCode statusCode = client.writeValue(nodeId, variant).get(); // 处理写入结果 System.out.println("Write status: " + statusCode); } catch (Exception e) { e.printStackTrace(); } ``` 需要注意的是,以上代码只是一个简单的示例,实际使用中可能会涉及更多的细节和功能。此外,还需要根据具体的OPC UA服务器配置和节点信息进行调整。详细的使用指南可以参考Milo库的文档和示例代码。

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值