Selaa lähdekoodia

NodesetCompiler: Correct inheritance of Variable DataType and ValueRank

Stefan Profanter 5 vuotta sitten
vanhempi
commit
71d206c202

+ 9 - 32
src/server/ua_nodes.c

@@ -312,16 +312,18 @@ copyCommonVariableAttributes(UA_VariableNode *node,
     node->arrayDimensionsSize = attr->arrayDimensionsSize;
 
     /* Data type and value rank */
-    retval |= UA_NodeId_copy(&attr->dataType, &node->dataType);
+    retval = UA_NodeId_copy(&attr->dataType, &node->dataType);
+	if(retval != UA_STATUSCODE_GOOD)
+		return retval;
     node->valueRank = attr->valueRank;
 
     /* Copy the value */
     node->valueSource = UA_VALUESOURCE_DATA;
     UA_NodeId extensionObject = UA_NODEID_NUMERIC(0, UA_NS0ID_STRUCTURE);
     /* If we have an extension object which is still encoded (e.g. from the
-     * nodeset compiler) we need to decode it and set the decoded value instead
-     * of the encoded object */
-    UA_Boolean valueSet = false;
+     * nodeset compiler) return an error.
+     * This was used in the old version of the nodeset compiler and is not
+     * needed anymore. */
     if(attr->value.type != NULL && UA_NodeId_equal(&attr->value.type->typeId, &extensionObject)) {
         /* Do nothing since we got an empty array of extension objects */
         if(attr->value.data == UA_EMPTY_ARRAY_SENTINEL)
@@ -329,38 +331,13 @@ copyCommonVariableAttributes(UA_VariableNode *node,
 
         const UA_ExtensionObject *obj = (const UA_ExtensionObject *)attr->value.data;
         if(obj && obj->encoding == UA_EXTENSIONOBJECT_ENCODED_BYTESTRING) {
-            /* TODO: Once we generate type description in the nodeset compiler,
-             * UA_findDatatypeByBinary can be made internal to the decoding
-             * layer. */
-            const UA_DataType *type = UA_findDataTypeByBinary(&obj->content.encoded.typeId);
-
-            if(type) {
-                void *dst = UA_Array_new(attr->value.arrayLength, type);
-                if (!dst) {
-                    return UA_STATUSCODE_BADOUTOFMEMORY;
-                }
-                uint8_t *tmpPos = (uint8_t *)dst;
-
-                for(size_t i=0; i<attr->value.arrayLength; i++) {
-                    size_t offset =0;
-                    const UA_ExtensionObject *curr = &((const UA_ExtensionObject *)attr->value.data)[i];
-                    UA_StatusCode ret = UA_decodeBinary(&curr->content.encoded.body, &offset, tmpPos, type, NULL);
-                    if(ret != UA_STATUSCODE_GOOD) {
-                        return ret;
-                    }
-                    tmpPos += type->memSize;
-                }
-
-                UA_Variant_setArray(&node->value.data.value.value, dst, attr->value.arrayLength, type);
-                valueSet = true;
-            }
+        	return UA_STATUSCODE_BADNOTSUPPORTED;
         }
     }
 
-    if(!valueSet)
-        retval |= UA_Variant_copy(&attr->value, &node->value.data.value.value);
+    retval = UA_Variant_copy(&attr->value, &node->value.data.value.value);
 
-    node->value.data.value.hasValue = true;
+    node->value.data.value.hasValue = node->value.data.value.value.type != NULL;
 
     return retval;
 }

+ 6 - 1
src/server/ua_services_nodemanagement.c

@@ -179,8 +179,13 @@ typeCheckVariableNode(UA_Server *server, UA_Session *session,
     if(retval != UA_STATUSCODE_GOOD)
         return retval;
 
+    UA_NodeId baseDataType = UA_NODEID_NUMERIC(0, UA_NS0ID_BASEDATATYPE);
+
     /* Check the datatype against the vt */
-    if(!compatibleDataType(server, &node->dataType, &vt->dataType, false)) {
+    /* If the node does not have any value and the dataType is BaseDataType,
+     * then it's also fine. This is the default for empty nodes. */
+    if(!compatibleDataType(server, &node->dataType, &vt->dataType, false) &&
+       (value.hasValue || !UA_NodeId_equal(&node->dataType, &baseDataType))) {
         UA_LOG_NODEID_WRAP(&node->nodeId, UA_LOG_INFO_SESSION(&server->config.logger, session,
                               "AddNodes: The value of %.*s is incompatible with "
                               "the datatype of the VariableType",

+ 1 - 4
tests/nodeset-compiler/check_nodeset_compiler_testnodeset.c

@@ -45,10 +45,7 @@ START_TEST(checkScalarValues) {
     UA_Variant_clear(&out);
     // Point_scalar_noInit
     UA_Server_readValue(server, UA_NODEID_NUMERIC(2, 10005), &out);
-    p = (UA_Point *)out.data;
-    ck_assert(UA_Variant_isScalar(&out));
-    ck_assert(p->x == (UA_Double)0.0);
-    ck_assert(p->y == (UA_Double)0.0);
+    ck_assert(out.data == NULL);
     UA_Variant_clear(&out);
 }
 END_TEST

+ 5 - 5
tools/nodeset_compiler/backend_open62541.py

@@ -227,18 +227,14 @@ _UA_END_DECLS
     logger.info("Writing code for nodes and references")
     functionNumber = 0
 
-    parentreftypes = getSubTypesOf(nodeset, nodeset.getNodeByBrowseName("HierarchicalReferences"))
-    parentreftypes = list(map(lambda x: x.id, parentreftypes))
-
     printed_ids = set()
     for node in sorted_nodes:
         printed_ids.add(node.id)
 
-        parentref = node.popParentRef(parentreftypes)
         if not node.hidden:
             writec("\n/* " + str(node.displayName) + " - " + str(node.id) + " */")
             code_global = []
-            code = generateNodeCode_begin(node, nodeset, parentref, code_global)
+            code = generateNodeCode_begin(node, nodeset, code_global)
             if code is None:
                 writec("/* Ignored. No parent */")
                 nodeset.hide_node(node.id)
@@ -258,6 +254,10 @@ _UA_END_DECLS
                 continue
             if node.hidden and nodeset.nodes[ref.target].hidden:
                 continue
+            if node.parent is not None and ref.target == node.parent.id \
+                and ref.referenceType == node.parentReference.id:
+                # Skip parent reference
+                continue
             writec(generateReferenceCode(ref))
 
         if node.hidden:

+ 91 - 60
tools/nodeset_compiler/backend_open62541_nodes.py

@@ -75,6 +75,39 @@ def generateObjectNodeCode(node):
         code.append("attr.eventNotifier = true;")
     return code
 
+def setNodeDatatypeRecursive(node, nodeset):
+
+    if not isinstance(node, VariableNode) and not isinstance(node, VariableTypeNode):
+        raise RuntimeError("DataType can only be set for VariableNode and VariableTypeNode")
+
+    if node.dataType is not None:
+        return
+
+    # If BaseVariableType
+    if node.id == NodeId("ns=0;i=62"):
+        if node.dataType is None:
+            # Set to default BaseDataType
+            node.dataType = NodeId("ns=0;i=24")
+        return
+
+    if isinstance(node, VariableNode) and not isinstance(node, VariableTypeNode):
+        typeDefNode = nodeset.getNodeTypeDefinition(node)
+        if typeDefNode is None:
+            # Use the parent type.
+            raise RuntimeError("Cannot get node for HasTypeDefinition of VariableNode " + node.browseName.name + " " + str(node.id))
+
+        setNodeDatatypeRecursive(typeDefNode, nodeset)
+
+        node.dataType = typeDefNode.dataType
+    else:
+        # Use the parent type.
+        if node.parent is None:
+            raise RuntimeError("Parent node not defined for " + node.browseName.name + " " + str(node.id))
+
+        setNodeDatatypeRecursive(node.parent, nodeset)
+        node.dataType = node.parent.dataType
+
+
 def generateCommonVariableCode(node, nodeset):
     code = []
     codeCleanup = []
@@ -92,41 +125,49 @@ def generateCommonVariableCode(node, nodeset):
                 for dim in range(0, node.valueRank):
                     code.append("arrayDimensions[{}] = 0;".format(dim))
             code.append("attr.arrayDimensions = &arrayDimensions[0];")
-
-    if node.dataType is not None:
-        dataTypeNode = nodeset.getBaseDataType(nodeset.getDataTypeNode(node.dataType))
-
-        if dataTypeNode is None:
-            raise RuntimeError("Cannot get BaseDataType for dataType : " + str(node.dataType) + " of node " + node.browseName.name + " " + str(node.id))
-
-        code.append("attr.dataType = %s;" % generateNodeIdCode(node.dataType))
-
-        if dataTypeNode.isEncodable():
-            if node.value is not None:
-                [code1, codeCleanup1, codeGlobal1] = generateValueCode(node.value, nodeset.nodes[node.id], nodeset)
-                code += code1
-                codeCleanup += codeCleanup1
-                codeGlobal += codeGlobal1
-                if node.valueRank is not None and node.valueRank > 0 and len(node.arrayDimensions) == node.valueRank and len(node.value.value) > 0:
-                    numElements = 1
-                    hasZero = False
-                    for v in node.arrayDimensions:
-                        dim = int(unicode(v))
-                        if dim > 0:
-                            numElements = numElements * dim
-                        else:
-                            hasZero = True
-                    if hasZero == False and len(node.value.value) == numElements:
-                        code.append("attr.value.arrayDimensionsSize = attr.arrayDimensionsSize;")
-                        code.append("attr.value.arrayDimensions = attr.arrayDimensions;")
-                        logger.warning("printing arrayDimensions")
+    else:
+        # Default value for the value rank as defined in the NodeSet2 xsd file
+        code.append("attr.valueRank = -1;")
+
+    if node.dataType is None:
+        # Inherit the datatype from the HasTypeDefinition reference, as stated in the OPC UA Spec:
+        # 6.4.2
+        # "Instances inherit the initial values for the Attributes that they have in common with the
+        # TypeDefinitionNode from which they are instantiated, with the exceptions of the NodeClass and
+        # NodeId."
+        setNodeDatatypeRecursive(node, nodeset)
+        code.append("/* DataType inherited */")
+
+    dataTypeNode = nodeset.getBaseDataType(nodeset.getDataTypeNode(node.dataType))
+
+    if dataTypeNode is None:
+        raise RuntimeError("Cannot get BaseDataType for dataType : " + str(node.dataType) + " of node " + node.browseName.name + " " + str(node.id))
+
+    code.append("attr.dataType = %s;" % generateNodeIdCode(node.dataType))
+
+    if dataTypeNode.isEncodable():
+        if node.value is not None:
+            [code1, codeCleanup1, codeGlobal1] = generateValueCode(node.value, nodeset.nodes[node.id], nodeset)
+            code += code1
+            codeCleanup += codeCleanup1
+            codeGlobal += codeGlobal1
+            if node.valueRank is not None and node.valueRank > 0 and len(node.arrayDimensions) == node.valueRank and len(node.value.value) > 0:
+                numElements = 1
+                hasZero = False
+                for v in node.arrayDimensions:
+                    dim = int(unicode(v))
+                    if dim > 0:
+                        numElements = numElements * dim
                     else:
-                        logger.error("Dimension with size 0 or value count mismatch detected, ArrayDimensions won't be copied to the Value attribute.")
-            elif not isinstance(node, VariableTypeNode): # Don't generate a dummy value for VariableType nodes
-                code += generateValueCodeDummy(dataTypeNode, nodeset.nodes[node.id], nodeset)
-        elif node.value is not None:
-            raise RuntimeError("Cannot encode dataTypeNode: " + dataTypeNode.browseName.name + " for value of node " + node.browseName.name + " " + str(node.id))
-
+                        hasZero = True
+                if hasZero == False and len(node.value.value) == numElements:
+                    code.append("attr.value.arrayDimensionsSize = attr.arrayDimensionsSize;")
+                    code.append("attr.value.arrayDimensions = attr.arrayDimensions;")
+                    logger.warning("printing arrayDimensions")
+                else:
+                    logger.error("Dimension with size 0 or value count mismatch detected, ArrayDimensions won't be copied to the Value attribute.")
+    elif node.value is not None:
+        raise RuntimeError("Cannot encode dataTypeNode: " + dataTypeNode.browseName.name + " for value of node " + node.browseName.name + " " + str(node.id))
 
     return [code, codeCleanup, codeGlobal]
 
@@ -145,6 +186,19 @@ def generateVariableNodeCode(node, nodeset):
     if node.valueRank == -2 and node.value is not None and len(node.value.value) == 1:
         node.valueRank = -1
 
+    if node.valueRank is None:
+        # If the VariableNode does not have any valueRank, use the one from the VariableType
+        # The type may define the valueRank to e.g. 1 which is not the default value for the variable itself.
+        # This is e.g. the case for 'ActiveBackground1' of the Adi.NodeSet2.xml
+        variableTypeNode = nodeset.getNodeTypeDefinition(node)
+
+        if variableTypeNode is None:
+            raise RuntimeError("Cannot get node for HasTypeDefinition of VariableNode " + node.browseName.name + " " + str(node.id))
+
+        if variableTypeNode.valueRank is not None and variableTypeNode.valueRank > -2:
+            code.append("attr.valueRank = %d; /* From VariableType */" % variableTypeNode.valueRank)
+
+
     [code1, codeCleanup1, codeGlobal1] = generateCommonVariableCode(node, nodeset)
     code += code1
     codeCleanup += codeCleanup1
@@ -251,22 +305,6 @@ def getTypeBrowseName(dataTypeNode):
         typeBrowseName = "String"
     return typeBrowseName
 
-def generateValueCodeDummy(dataTypeNode, parentNode, nodeset):
-    code = []
-    valueName = generateNodeIdPrintable(parentNode) + "_variant_DataContents"
-    typeBrowseName = getTypeBrowseName(dataTypeNode)
-    typeArr = dataTypeNode.typesArray + "[" + dataTypeNode.typesArray + "_" + typeBrowseName.upper() + "]"
-    typeStr = "UA_" + typeBrowseName
-
-    if parentNode.valueRank is not None and parentNode.valueRank > 0:
-        for i in range(0, parentNode.valueRank):
-            code.append("UA_Variant_setArray(&attr.value, NULL, (UA_Int32) " + "0, &" + typeArr + ");")
-    elif not dataTypeNode.isAbstract:
-        code.append("UA_STACKARRAY(" + typeStr + ", " + valueName + ", 1);")
-        code.append("UA_init(" + valueName + ", &" + typeArr + ");")
-        code.append("UA_Variant_setScalar(&attr.value, " + valueName + ", &" + typeArr + ");")
-    return code
-
 def getTypesArrayForValue(nodeset, value):
     typeNode = nodeset.getNodeByBrowseName(value.__class__.__name__)
     if typeNode is None or value.isInternal:
@@ -397,13 +435,6 @@ def generateViewNodeCode(node):
     code.append("attr.eventNotifier = (UA_Byte)%s;" % str(node.eventNotifier))
     return code
 
-def getNodeTypeDefinition(node):
-    for ref in node.references:
-        # 40 = HasTypeDefinition
-        if ref.referenceType.i == 40:
-            return ref.target
-    return None
-
 def generateSubtypeOfDefinitionCode(node):
     for ref in node.inverseReferences:
         # 45 = HasSubtype
@@ -411,7 +442,7 @@ def generateSubtypeOfDefinitionCode(node):
             return generateNodeIdCode(ref.target)
     return "UA_NODEID_NULL"
 
-def generateNodeCode_begin(node, nodeset, parentref, code_global):
+def generateNodeCode_begin(node, nodeset, code_global):
     code = []
     codeCleanup = []
     code.append("UA_StatusCode retVal = UA_STATUSCODE_GOOD;")
@@ -454,8 +485,8 @@ def generateNodeCode_begin(node, nodeset, parentref, code_global):
     code.append("retVal |= UA_Server_addNode_begin(server, UA_NODECLASS_{},".
             format(makeCIdentifier(node.__class__.__name__.upper().replace("NODE" ,""))))
     code.append(generateNodeIdCode(node.id) + ",")
-    code.append(generateNodeIdCode(parentref.target) + ",")
-    code.append(generateNodeIdCode(parentref.referenceType) + ",")
+    code.append(generateNodeIdCode(node.parent.id if node.parent else NodeId()) + ",")
+    code.append(generateNodeIdCode(node.parentReference.id if node.parent else NodeId()) + ",")
     code.append(generateQualifiedNameCode(node.browseName) + ",")
     if isinstance(node, VariableNode) or isinstance(node, ObjectNode):
         typeDefRef = node.popTypeDef()

+ 4 - 4
tools/nodeset_compiler/nodes.py

@@ -78,6 +78,8 @@ class Node(object):
         self.references = set()
         self.hidden = False
         self.modelUri = None
+        self.parent = None
+        self.parentReference = None
 
     def __str__(self):
         return self.__class__.__name__ + "(" + str(self.id) + ")"
@@ -142,17 +144,15 @@ class Node(object):
                     forward = not "false" in av.lower()
             self.references.add(Reference(source, reftype, target, forward))
 
-    def popParentRef(self, parentreftypes):
+    def getParentReference(self, parentreftypes):
         # HasSubtype has precedence
         for ref in self.references:
             if ref.referenceType == NodeId("ns=0;i=45") and not ref.isForward:
-                self.references.remove(ref)
                 return ref
         for ref in self.references:
             if ref.referenceType in parentreftypes and not ref.isForward:
-                self.references.remove(ref)
                 return ref
-        return Reference(NodeId(), NodeId(), NodeId(), False)
+        return None
 
     def popTypeDef(self):
         for ref in self.references:

+ 18 - 1
tools/nodeset_compiler/nodeset.py

@@ -316,7 +316,14 @@ class NodeSet(object):
             if ref.referenceType.i == 45:
                 return self.getBaseDataType(self.nodes[ref.target])
         return node
-                
+
+    def getNodeTypeDefinition(self, node):
+        for ref in node.references:
+            # 40 = HasTypeDefinition
+            if ref.referenceType.i == 40:
+                return self.nodes[ref.target]
+        return None
+
     def getDataTypeNode(self, dataType):
         if isinstance(dataType, string_types):
             if not valueIsInternalType(dataType):
@@ -348,3 +355,13 @@ class NodeSet(object):
             for ref in u.references:
                 back = Reference(ref.target, ref.referenceType, ref.source, not ref.isForward)
                 self.nodes[ref.target].references.add(back) # ref set does not make a duplicate entry
+
+    def setNodeParent(self):
+        parentreftypes = getSubTypesOf(self, self.getNodeByBrowseName("HierarchicalReferences"))
+        parentreftypes = list(map(lambda x: x.id, parentreftypes))
+
+        for node in self.nodes.values():
+            parentref = node.getParentReference(parentreftypes)
+            if parentref is not None:
+                node.parent = self.nodes[parentref.target]
+                node.parentReference = self.nodes[parentref.referenceType]

+ 2 - 0
tools/nodeset_compiler/nodeset_compiler.py

@@ -186,6 +186,8 @@ ns.allocateVariables()
 
 ns.addInverseReferences()
 
+ns.setNodeParent()
+
 logger.info("Generating Code for Backend: {}".format(args.backend))
 
 if args.backend == "open62541":