Java实现OPC UA客户端

743 阅读4分钟

一、Milo库

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

官网:Milo Github链接

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

相关博客资料(转载):blog.csdn.net/q932104843/…

二、OPC UA服务端及客户端

OPC UA服务端:KEPServerEX 6

UI客户端:

三、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();

    //持续订阅
    Thread.sleep(Long.MAX_VALUE);
}

3.6 批量订阅

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

  //持续监听
  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();
    }
}

OPC UA常见故障信息代码(转载):blog.csdn.net/ymfz001/art…