- 一、Milo库
- 二、OPC UA服务端及客户端
- 三、Java连接OPC UA服务端
- 3.1 依赖
- 3.2 创建opc ua客户端
- 3.3 遍历树形节点
- 3.4 读取节点数据
- 3.5 写入节点数据
- 3.5 订阅(单个)
- 3.6 批量订阅
- 3.7 处理断线重连后的订阅问题
- 3.7.1 自定义实现SubscriptionListener
- 3.7.2 添加 SubscriptionListener
- 3.8 测试
本文使用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
以下代码都是在一个类中执行的,并使用main方法进行测试,因此所有方法都是private和static修饰的。
3.1 依赖Milo的依赖有2个,Client SDK,Server SDK。
客户端:
org.eclipse.milo sdk-client0.6.3
服务端:
3.2 创建opc ua客户端org.eclipse.milo sdk-server0.6.3
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() ); }
注意:
- new AnonymousProvider()表示使用匿名方式访问,也可以通过new UsernameProvider(userName, password)方式访问。
- 如果需要使用数字证书和秘钥的方式进行认证,请去官网查询相关资料。
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 读取节点数据
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())); }
- namespaceIndex可以通过UaExpert客户端去查询,一般来说这个值是2。
- identifier也可以通过UaExpert客户端去查询,这个值=通道名称.设备名称.标记名称
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 订阅(单个)
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); Listrequests = 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); }
订阅成功后,main线程会被阻塞,修改OPC UA中的变量值后,就可以即时查看到订阅的消息。订阅时间可以通过修改Thread.sleep的沉睡时间实现。
3.6 批量订阅private static void managedSubscriptionEvent(OpcUaClient client) throws Exception { final CountDownLatch eventLatch = new CountDownLatch(1); //处理订阅业务 handlerNode(client); //持续监听 eventLatch.await(); } private static void handlerNode(OpcUaClient client) { try { //创建订阅 ManagedSubscription subscription = ManagedSubscription.create(client); //你所需要订阅的key Listkey = new ArrayList<>(); key.add("TD-01.SB-01.AG-01"); key.add("TD-01.SB-01.AG-02"); List nodeIdList = new ArrayList<>(); for (String s : key) { nodeIdList.add(new NodeId(2, s)); } //监听 List 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 自定义实现SubscriptionListenerprivate 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"); } public void onSubscriptionTransferFailed(UaSubscription uaSubscription, StatusCode statusCode) { logger.debug("恢复订阅失败 需要重新订阅"); //在回调方法中重新订阅 handlerNode(client); } }3.7.2 添加 SubscriptionListener
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(); } }
欢迎分享,转载请注明来源:内存溢出
评论列表(0条)