OPCUA实现eclipse milo client,java工具类,所需jar

我这边实现了一个可以直接使用的工具类,做个记录

eclipse milo 源码在这里

所需jar包

如果是在线使用的话,用到这些就可以

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

如果离线使用的话,那就需要这些,不想手动一个个下载的话可以给我留言邮箱,我会尽快发给你(不好意思现在这些jar包我也没有现成的了 2021.9.27)
离线所需jar包

java工具类

/**
 * @Author ws
 * @Date 2020/06/08
 */
@Slf4j
public class UaClient {

    private OpcUaClient client;
    private String url;
    private String userName;
    private String passWord;
    private Map<NodeId, String> nodeValues;
    private boolean runningState;

    public UaClient(String url, @Nullable String userName, @Nullable String passWord) {
        this.url = url;
        this.userName = userName;
        this.passWord = passWord;
    }

    public boolean createConnection() {
        try {
            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);
            }

            KeyStoreLoader loader = new KeyStoreLoader().load(securityTempDir);

            client = OpcUaClient.create(url,
                    endpoints ->
                            endpoints.stream()
                                    .filter(endpointFilter())
                                    .findFirst(),
                    configBuilder ->
                            configBuilder
                                    .setApplicationName(LocalizedText.english("eclipse milo opc-ua client"))
                                    .setApplicationUri("urn:eclipse:milo:examples:client")
                                    .setCertificate(loader.getClientCertificate())
                                    .setKeyPair(loader.getClientKeyPair())
                                    .setIdentityProvider(getIdentityProvider())
                                    .setRequestTimeout(uint(5000))
                                    .build()
            );

            client.connect().get();
            client.addFaultListener(new UaFaultListener());
        } catch (Exception e) {
            log.info("创建ua客户端时发生了错误", e);
            return false;
        }

        nodeValues = new HashMap<>();
        runningState = true;
        return true;
    }

    public void disconnect() {
        runningState = false;
        try {
            client.disconnect().get();
            Stack.releaseSharedResources();
        } catch (InterruptedException | ExecutionException e) {
            log.error("设备断开连接的时候发生了错误: {}", e.getMessage(), e);
        }
    }


    public Map<NodeId, String> read() throws ConnectionInterruptException {
        if (!runningState) {
            throw new ConnectionInterruptException();
        }
        return this.nodeValues;
    }

    public void subscription(List<NodeId> nodeIds, double sf) {
        try {
            UaSubscription subscription = client.getSubscriptionManager().createSubscription(sf).get();

            List<MonitoredItemCreateRequest> requests = new ArrayList<>();
            for (NodeId nodeId : nodeIds) {
                ReadValueId readValueId = new ReadValueId(
                        nodeId, AttributeId.Value.uid(), null, QualifiedName.NULL_VALUE
                );

                UInteger clientHandle = subscription.nextClientHandle();

                MonitoringParameters parameters = new MonitoringParameters(
                        clientHandle, sf, null, uint(10), true
                );

                MonitoredItemCreateRequest request = new MonitoredItemCreateRequest(
                        readValueId, MonitoringMode.Reporting, parameters
                );

                requests.add(request);
            }

            BiConsumer<UaMonitoredItem, Integer> onItemCreated =
                    (item, id) -> item.setValueConsumer(this::onSubscriptionValue);


            List<UaMonitoredItem> items = subscription.createMonitoredItems(
                    TimestampsToReturn.Both,
                    requests,
                    onItemCreated
            ).get();
        } catch (InterruptedException | ExecutionException e) {
            log.info("订阅点位时发生了错误", e);
            disconnect();
        }
    }

    private void onSubscriptionValue(UaMonitoredItem item, DataValue value) {
        if (item.getStatusCode().isGood()) {
            nodeValues.put(
                    item.getReadValueId().getNodeId(),
                    value.getValue().getValue().toString()
            );
        } else {
            log.info("点位[{}]读到了脏数据[{}]",
                    item.getReadValueId().getNodeId(),
                    value.getValue().getValue().toString()
            );
        }

    }

    public boolean write(Map<NodeId, String> writeNodes) {
        try {

            List<NodeId> nodeIds = new ArrayList<>();
            List<DataValue> dataValues = new ArrayList<>();
            for (NodeId nodeId : writeNodes.keySet()) {
                nodeIds.add(nodeId);
                Variant v = new Variant(writeNodes.get(nodeId));
                dataValues.add(new DataValue(v, null, null));
            }
            // write asynchronously....
            CompletableFuture<List<StatusCode>> f =
                    client.writeValues(nodeIds, dataValues);

            // ...but block for the results so we write in order
            List<StatusCode> statusCodes = f.get();
            for (int i = 0; i < statusCodes.size(); i++) {
                if (!statusCodes.get(i).isGood()) {
                    log.info("向点位(NodeId={})写值失败", nodeIds.get(i));
                    return false;
                }
            }
        } catch (ExecutionException | InterruptedException e) {
            log.info("向点位写值时发生了错误", e);
            disconnect();
            return false;
        }
        return true;
    }

    public TypeValue browseTree(@Nullable NodeId startNode) {

        // start browsing at root folder
        TypeValue typeValue = new TypeValue();
        try {

            typeValue.setData(browseNode(client,
                    startNode == null ? Identifiers.RootFolder : startNode));

        } catch (ExecutionException | InterruptedException e) {
            log.info("遍历树的时候发生了错误", e);
            disconnect();
        }
        return typeValue;
    }

    private List<TypeValueData> browseNode(OpcUaClient client, NodeId browseRoot) throws ExecutionException, InterruptedException {
        List<Node> nodes = client.getAddressSpace().browse(browseRoot).get();

        if (nodes.size() == 0) {
            return null;
        }
        List<TypeValueData> dataList = new ArrayList<>();

        for (Node node : nodes) {
            TypeValueData data = new TypeValueData();
            data.setKey(node.getBrowseName().get().getName());
            NodeId nodeId = node.getNodeId().get();
            data.setValue(nodeId.getNamespaceIndex() + ";" + nodeId.getIdentifier());

            // recursively browse to children
            List<TypeValueData> list = browseNode(client, node.getNodeId().get());
            if (list != null) {
                data.setChildren(list);
            }
            dataList.add(data);
        }
        return dataList;
    }

    // 过滤安全验证,使用None
    private Predicate<EndpointDescription> endpointFilter() {
        return e -> e.getSecurityPolicyUri().equals(SecurityPolicy.None.getUri());
    }


    // 获取登录验证,没有输入用户名密码就匿名登录
    private IdentityProvider getIdentityProvider() {
        if (userName != null && passWord != null) {
            return new UsernameProvider(userName, passWord);
        }

        return new AnonymousProvider();
    }

    public class UaFaultListener implements ServiceFaultListener {

        @Override
        public void onServiceFault(ServiceFault serviceFault) {
            log.info("UA的连接发生了错误,即将断开连接");
            disconnect();
        }
    }
}

这其中包括的功能(按照一般使用顺序):

  • 连接connect,我这里把milo里面的每个模块整理了一下,把确实的连接的模块放在了一起,也就是说,返回值为true的时候就真的代表已经连接上了,可以直接进行其他操作了;
  • 断开连接disconnect
  • 读节点,先订阅subscription,再读map,我直接就没有实现轮询读数的功能了,多点位场景下轮询有点鸡肋;
  • 写节点write
  • 遍历节点browseTree,这里有点特殊,挨个说吧。
    • 返回值类型是我这边私有的一个类型,也就是{节点名,节点值,子节点}的结构罢了,调用的时候按需更改吧,哦对了我有一篇关于opc的文章里面实现了一个简易的这样的结构类型(为什么不调用java已有的?因为我觉得我可能会对tree进行一些特殊的更改,我这样自己实现的话改起来会很方便);
    • 参数是一个节点NodeId,可以直接 new NodeId(namespaceIndex,Identifier),这两个参数可以在OPCUA Client这些客户端工具上看到,一般来说,namespaceIndex为1,Identifier为服务的程序id+Root(如ControlEase.TNT.OPC.2Root或Matrikon.OPC.Simulation.1Root);如果不选择开始节点的话,遍历会从根节点进行遍历,会多出很多无用的点位;
    • 这边是按层序索引的,我是没找到列表索引的,虽然不用,但是好奇为啥没有,难道ua直接把列表索引的功能淘汰了?

另外,这里面还引用了KeyStoreLoader,这个类没什么特殊的,直接用milo库里的就成,我就不沾上来了。

  • 1
    点赞
  • 12
    收藏
    觉得还不错? 一键收藏
  • 12
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值