1

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?

Necrophades
  • 605
  • 7
  • 21

3 Answers3

1

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.

Kevin Herron
  • 6,500
  • 3
  • 26
  • 35
  • I kind of hoped that I could get the datatypes of my nodes during browse... Is there a difference between using a ReadRequest and doing client.readValues? And how do GUI clients that display datatypes get the datatype info? Thanks for your suggestion, I'll try and implement it. – Necrophades Oct 10 '19 at 09:14
  • DataType information is not part of the browse response. GUI clients read the DataType attribute (and usually all the other attributes as well). – Kevin Herron Oct 10 '19 at 12:58
  • 1
    OpcUaClient#readValue(s) is specifically for the Value attribute, you need to use OpcUaClient#read for other attributes. Both these methods are sending a ReadRequest underneath and using the Read service. – Kevin Herron Oct 10 '19 at 12:59
0

The easiest way to know which Datatype to write in the Value of a Node will be to send a Read Request Value before the Write.

ReadRequest
  -  ...
  -  NodesToRead [ReadValueId]
    - ReadValue[0]
      - NodeId:       {NodeId of your Node}
      - AttribueId:   Value (0x0d)
      - IndexRange:   Null
      - DataEncoding: Null

In the Read Response, you will get the current value of the Node as well as its OPC UA Datatype.

ReadResponse
  - ResponseHeader
  - Results [DataValue]
     - DataValue[0]
       - EncodingMask
       - Value 
         - VariantType: Int16
         - Int16:       3

Edit : As noted in the comment, it might be also possible that the current Value is Null. So instead of reading the Value attribute of the Node you could also read the DataType Attribute.

In the case of a "not Built-in" OPC UA DataTypes, you will have to either :

  • Already know the Datatype and its structure to allow you to Write the correct information

  • Browse the TargetNode of the HasTypeDefinition.

ie: Server_ServerStatus (NodeId [i=2256]) has a DataType ServerStatusDataType (NodeId [i=862]) and a HasTypeDefinition ServerStatusType (NodeId [i=2138]). By Browsing the ServerStatusType you will get the structure of this complex DataType. In this case:

 - ServerStatusType      [ServerStatusDataType]
   - BuildInfo           [BuildInfoType] -> Also a complex Datatype
   - CurrentTime         [UtcTime]
   - SecondsTillShutdown [UInt32]
   - ShutdownReason      [LocalizedText]
   - StartTime           [UtcTime]
   - State               [ServerState] -> Also a complex Datatype
Camille G.
  • 3,058
  • 1
  • 25
  • 41
  • This is not right. You should not read the Value attribute. You should read the DataType attribute. The Value may be have no type (null), or it can be a specific subtype of the allowed DataType. In addition, reading the Value may involve slow communication with the target device, while reading DataType usually does not. – ZbynekZ Oct 10 '19 at 05:59
0

Firstly, I think you should read value type, such as

DataValue value = opcUaClient.readValue(0.0, TimestampsToReturn.Neither, nodeId).get();
Optional<ExpandedNodeId> dataType = value.getValue().getDataType();

Secondly, use Unsigned class methods to find what you want type:

Variant v = new Variant(Unsigned.ushort(22));
janw
  • 8,758
  • 11
  • 40
  • 62