Search code examples
javaopc-uamilo

How do I reliably write to a OPC UA server?


I'm trying to write some values to a OPC UA server. In order to do that reliably, I propably need to know the DataType of the node that I'm trying to write to, otherwise it seems I'm prone to getting datatype mismatches.

Let's say my server has a node TestNodeOne with datatype Int16

My API gets told to write the value 3 to that node. Now I need to make a decision: Do I handle the 3 as an Integer or as an UShort? For this, it seems that I would need the data type of my node.

What I've tried

My approach was to just browse the server and create a cache of all it's nodes with the corresponding data type. Here's how that looks:

// Recursively browse entire server
@SuppressWarnings("deprecation")
private HashMap<String, NodeId> browseNode(String indent, OpcUaClient client, NodeId browseRoot, HashMap<String, NodeId> nodesMap) {

    BrowseDescription browse = new BrowseDescription(
            browseRoot,
            BrowseDirection.Forward,
            Identifiers.References,
            true, uint(NodeClass.Object.getValue() | NodeClass.Variable.getValue()),
            uint(BrowseResultMask.All.getValue()));

    try {
        BrowseResult browseResult = client.browse(browse).get();
        List<ReferenceDescription> references = toList(browseResult.getReferences());

        for (ReferenceDescription rd : references) {

            UShort namespaceIndex = rd.getNodeId().getNamespaceIndex();
            String identifier = rd.getNodeId().getIdentifier().toString();
            NodeId node = new NodeId(namespaceIndex, identifier);
            nodesMap.put(rd.getNodeId().getIdentifier().toString(), node);

            logger.info("----------------------------------------------------------------");
            logger.info(identifier);
            logger.info("TYPE " + rd.getTypeDefinition()); // Not the right node
            logger.info("TYPE " + rd.getTypeId().getIdentifier().toString()); // Not the right node
            logger.info("TYPE " + rd.getReferenceTypeId().getIdentifier().toString()); // Not the right node

            rd.getNodeId().local().ifPresent(nodeId -> {
                browseNode(indent + "  ", client, nodeId, nodesMap);
            });
        }

    } catch (InterruptedException | ExecutionException e) {
        logger.error("Browsing nodeId={} failed: {}", browseRoot, e.getMessage(), e);
    }

    return nodesMap;
}

I can't figure out how to get the datatype of a node. I think I'm on the wrong track here. How can I find out the data type of a node?


Solution

  • The easiest approach is to read the DataType attribute before each write so you know what kind of value to send.

    Once you have this working you can try something a little trickier, like caching the DataType so that subsequent writes to the same Node don't need to read the DataType attribute again, and then if a write ever fails with Bad_TypeMismatch you can invalidate the entry in the cache for that Node and then retry the read+write.