Quellcode durchsuchen

Merge branch '0.2'

Julius Pfrommer vor 7 Jahren
Ursprung
Commit
05820a2a1d

+ 6 - 0
CHANGELOG

@@ -0,0 +1,6 @@
+The changelog tracks changes to the public API.
+Internal refactorings and bug fixes are not reported here.
+
+2017-04-14 jpfr <julius.pfrommer@web.de>
+
+    * Auto-instantiate only child nodes marked as mandatory (fixes #1004)

+ 1 - 1
Dockerfile

@@ -1,5 +1,5 @@
 FROM alpine:3.3
-RUN apk add --no-cache cmake gcc musl-dev python make && rm -rf /var/cache/apk/*
+RUN apk add --no-cache cmake gcc g++ musl-dev python make && rm -rf /var/cache/apk/*
 ADD . /tmp/open62541
 WORKDIR /tmp/open62541/build
 RUN cmake -D UA_ENABLE_AMALGAMATION=true /tmp/open62541 && make

+ 1 - 1
TinyDockerfile

@@ -1,7 +1,7 @@
 FROM alpine:3.3
 ADD . /tmp/open62541
 WORKDIR /tmp/open62541/build
-RUN apk add --no-cache cmake gcc musl-dev python make && rm -rf /var/cache/apk/* && \
+RUN apk add --no-cache cmake gcc g++ musl-dev python make && rm -rf /var/cache/apk/* && \
     cmake -D UA_ENABLE_AMALGAMATION=true /tmp/open62541 && \
     make && \
     cp *.h /usr/include/ && \

+ 2 - 2
examples/client.c

@@ -65,12 +65,12 @@ int main(int argc, char *argv[]) {
         for (size_t j = 0; j < bResp.results[i].referencesSize; ++j) {
             UA_ReferenceDescription *ref = &(bResp.results[i].references[j]);
             if(ref->nodeId.nodeId.identifierType == UA_NODEIDTYPE_NUMERIC) {
-                printf("%-9d %-16d %-16.*s %-16.*s\n", ref->browseName.namespaceIndex,
+                printf("%-9d %-16d %-16.*s %-16.*s\n", ref->nodeId.nodeId.namespaceIndex,
                        ref->nodeId.nodeId.identifier.numeric, (int)ref->browseName.name.length,
                        ref->browseName.name.data, (int)ref->displayName.text.length,
                        ref->displayName.text.data);
             } else if(ref->nodeId.nodeId.identifierType == UA_NODEIDTYPE_STRING) {
-                printf("%-9d %-16.*s %-16.*s %-16.*s\n", ref->browseName.namespaceIndex,
+                printf("%-9d %-16.*s %-16.*s %-16.*s\n", ref->nodeId.nodeId.namespaceIndex,
                        (int)ref->nodeId.nodeId.identifier.string.length,
                        ref->nodeId.nodeId.identifier.string.data,
                        (int)ref->browseName.name.length, ref->browseName.name.data,

+ 22 - 9
examples/tutorial_server_object.c

@@ -133,7 +133,7 @@ manuallyDefinePump(UA_Server *server) {
  *
  *    { rank=same
  *      point_1 [shape=point]
- *      node_1 [label=< <I>VariableNode</I><BR/>ManufacturerName >] }
+ *      node_1 [label=< <I>VariableNode</I><BR/>ManufacturerName<BR/>(mandatory) >] }
  *    node_root -> point_1 [arrowhead=none]
  *    point_1 -> node_1 [label="hasComponent"]
  *
@@ -151,7 +151,7 @@ manuallyDefinePump(UA_Server *server) {
  *
  *    {  rank=same
  *       point_4 [shape=point]
- *       node_4 [label=< <I>VariableNode</I><BR/>Status >] }
+ *       node_4 [label=< <I>VariableNode</I><BR/>Status<BR/>(mandatory) >] }
  *    node_3 -> point_4 [arrowhead=none]
  *    point_4 -> node_4 [label="hasComponent"]
  *
@@ -163,7 +163,9 @@ manuallyDefinePump(UA_Server *server) {
  *
  *    }
  *
- */
+ * Children that are marked mandatory are automatically instantiated together
+ * with the parent object. This is indicated by a `hasModellingRule` reference
+ * to an object that representes the `mandatory` modelling rule. */
 
 /* predefined identifier for later use */
 UA_NodeId pumpTypeId = {1, UA_NODEIDTYPE_NUMERIC, {1001}};
@@ -184,10 +186,16 @@ defineObjectTypes(UA_Server *server) {
     UA_VariableAttributes mnAttr;
     UA_VariableAttributes_init(&mnAttr);
     mnAttr.displayName = UA_LOCALIZEDTEXT("en_US", "ManufacturerName");
+    UA_NodeId manufacturerNameId;
     UA_Server_addVariableNode(server, UA_NODEID_NULL, deviceTypeId,
                               UA_NODEID_NUMERIC(0, UA_NS0ID_HASCOMPONENT),
                               UA_QUALIFIEDNAME(1, "ManufacturerName"),
-                              UA_NODEID_NULL, mnAttr, NULL, NULL);
+                              UA_NODEID_NULL, mnAttr, NULL, &manufacturerNameId);
+    /* Make the manufacturer name mandatory */
+    UA_Server_addReference(server, manufacturerNameId,
+                           UA_NODEID_NUMERIC(0, UA_NS0ID_HASMODELLINGRULE),
+                           UA_EXPANDEDNODEID_NUMERIC(0, UA_NS0ID_MODELLINGRULE_MANDATORY), true);
+
 
     UA_VariableAttributes modelAttr;
     UA_VariableAttributes_init(&modelAttr);
@@ -210,10 +218,15 @@ defineObjectTypes(UA_Server *server) {
     UA_VariableAttributes_init(&statusAttr);
     statusAttr.displayName = UA_LOCALIZEDTEXT("en_US", "Status");
     statusAttr.valueRank = -1;
+    UA_NodeId statusId;
     UA_Server_addVariableNode(server, UA_NODEID_NULL, pumpTypeId,
                               UA_NODEID_NUMERIC(0, UA_NS0ID_HASCOMPONENT),
                               UA_QUALIFIEDNAME(1, "Status"),
-                              UA_NODEID_NULL, statusAttr, NULL, NULL);
+                              UA_NODEID_NULL, statusAttr, NULL, &statusId);
+    /* Make the status variable mandatory */
+    UA_Server_addReference(server, statusId,
+                           UA_NODEID_NUMERIC(0, UA_NS0ID_HASMODELLINGRULE),
+                           UA_EXPANDEDNODEID_NUMERIC(0, UA_NS0ID_MODELLINGRULE_MANDATORY), true);
 
     UA_VariableAttributes rpmAttr;
     UA_VariableAttributes_init(&rpmAttr);
@@ -227,10 +240,10 @@ defineObjectTypes(UA_Server *server) {
 
 /**
  * Now we add the derived ObjectType for the pump that inherits from the device
- * object type. The resulting object contains all four inherited child
- * variables. The object has a reference of type ``hasTypeDefinition`` to the
- * object type. Clients can browse this information at runtime and adjust
- * accordingly.
+ * object type. The resulting object contains all mandatory child variables.
+ * These are simply copied over from the object type. The object has a reference
+ * of type ``hasTypeDefinition`` to the object type, so that clients can detect
+ * the type-instance relation at runtime.
  */
 
 static void

+ 2 - 4
src/server/ua_server.c

@@ -275,12 +275,10 @@ UA_Server_new(const UA_ServerConfig config) {
     /* Initialize the handling of repeated jobs */
 #ifdef UA_ENABLE_MULTITHREADING
     UA_RepeatedJobsList_init(&server->repeatedJobs,
-                             (UA_RepeatedJobsListProcessCallback)UA_Server_dispatchJob,
-                             server);
+                             (UA_RepeatedJobsListProcessCallback)UA_Server_dispatchJob, server);
 #else
     UA_RepeatedJobsList_init(&server->repeatedJobs,
-                             (UA_RepeatedJobsListProcessCallback)UA_Server_processJob,
-                             server);
+                             (UA_RepeatedJobsListProcessCallback)UA_Server_processJob, server);
 #endif
 
     /* Initialized the linked list for delayed callbacks */

+ 13 - 0
src/server/ua_server_ns0.c

@@ -437,6 +437,9 @@ void UA_Server_createNS0(UA_Server *server) {
     UA_Server_addObjectTypeNode_begin(server, UA_NODEID_NUMERIC(0, UA_NS0ID_BASEOBJECTTYPE),
                                       UA_QUALIFIEDNAME(0, "BaseObjectType"), baseobj_attr, NULL);
 
+    addObjectTypeNode(server, "ModellingRuleType", UA_NS0ID_MODELLINGRULETYPE,
+                      false, UA_NS0ID_BASEOBJECTTYPE);
+
     addObjectTypeNode(server, "FolderType", UA_NS0ID_FOLDERTYPE,
                       false, UA_NS0ID_BASEOBJECTTYPE);
 
@@ -493,6 +496,16 @@ void UA_Server_createNS0(UA_Server *server) {
     addObjectNode(server, "Views", UA_NS0ID_VIEWSFOLDER, UA_NS0ID_ROOTFOLDER,
                   UA_NS0ID_ORGANIZES, UA_NS0ID_FOLDERTYPE);
 
+    /*******************/
+    /* Modelling Rules */
+    /*******************/
+
+    addObjectNode(server, "Mandatory", UA_NS0ID_MODELLINGRULE_MANDATORY,
+                  0, 0, UA_NS0ID_MODELLINGRULETYPE);
+
+    addObjectNode(server, "Optional", UA_NS0ID_MODELLINGRULE_OPTIONAL,
+                  0, 0, UA_NS0ID_MODELLINGRULETYPE);
+
     /*********************/
     /* The Server Object */
     /*********************/

+ 52 - 18
src/server/ua_services_nodemanagement.c

@@ -15,6 +15,11 @@
 static UA_StatusCode
 checkParentReference(UA_Server *server, UA_Session *session, UA_NodeClass nodeClass,
                      const UA_NodeId *parentNodeId, const UA_NodeId *referenceTypeId) {
+    /* Objects do not need a parent (e.g. mandatory/optional modellingrules) */
+    if(nodeClass == UA_NODECLASS_OBJECT && UA_NodeId_isNull(parentNodeId) &&
+       UA_NodeId_isNull(referenceTypeId))
+        return UA_STATUSCODE_GOOD;
+
     /* See if the parent exists */
     const UA_Node *parent = UA_NodeStore_get(server->nodestore, parentNodeId);
     if(!parent) {
@@ -89,8 +94,8 @@ typeCheckVariableNodeWithValue(UA_Server *server, UA_Session *session,
                                UA_DataValue *value) {
     /* Workaround: set a sane valueRank (the most permissive -2) */
     UA_Int32 valueRank = node->valueRank;
-    if(valueRank == 0 && value->hasValue && value->value.type &&
-       UA_Variant_isScalar(&value->value)) {
+    if(valueRank == 0 &&
+       (!value->hasValue || !value->value.type || UA_Variant_isScalar(&value->value))) {
         UA_LOG_INFO_SESSION(server->config.logger, session,
                             "AddNodes: Use a default ValueRank of -2");
         valueRank = -2;
@@ -99,7 +104,7 @@ typeCheckVariableNodeWithValue(UA_Server *server, UA_Session *session,
             return retval;
     }
 
-    /* If no value is set, see if the vt provides one and copy that. THis needs to be done
+    /* If no value is set, see if the vt provides one and copy that. This needs to be done
      * before copying the datatype from the vt. Setting the datatype triggers the
      * typecheck. Here, we have only a typecheck when the datatype is already not null. */
     if(!value->hasValue || !value->value.type) {
@@ -246,6 +251,30 @@ instanceFindAggregateByBrowsename(UA_Server *server, UA_Session *session,
     return retval;
 }
 
+static UA_Boolean
+mandatoryChild(UA_Server *server, UA_Session *session, const UA_NodeId *childNodeId) {
+    const UA_NodeId mandatoryId = UA_NODEID_NUMERIC(0, UA_NS0ID_MODELLINGRULE_MANDATORY);
+    const UA_NodeId hasModellingRuleId = UA_NODEID_NUMERIC(0, UA_NS0ID_HASMODELLINGRULE);
+
+    /* Get the child */
+    const UA_Node *child = UA_NodeStore_get(server->nodestore, childNodeId);
+    if(!child)
+        return false;
+
+    /* Look for the reference making the child mandatory */
+    for(size_t i = 0; i < child->referencesSize; ++i) {
+        UA_ReferenceNode *ref = &child->references[i];
+        if(!UA_NodeId_equal(&hasModellingRuleId, &ref->referenceTypeId))
+            continue;
+        if(!UA_NodeId_equal(&mandatoryId, &ref->targetId.nodeId))
+            continue;
+        if(ref->isInverse)
+            continue;
+        return true;
+    }
+    return false;
+}
+
 static UA_StatusCode
 copyChildNodes(UA_Server *server, UA_Session *session, 
                const UA_NodeId *sourceNodeId, const UA_NodeId *destinationNodeId, 
@@ -353,9 +382,18 @@ copyChildNodes(UA_Server *server, UA_Session *session,
   
     /* Copy all children from source to destination */
     UA_StatusCode retval = UA_STATUSCODE_GOOD;
-    for(size_t i = 0; i < br.referencesSize; ++i)
+    for(size_t i = 0; i < br.referencesSize; ++i) {
+        UA_ReferenceDescription *rd = &br.references[i];
+
+        /* Is the child mandatory? If not, skip */
+        if(!mandatoryChild(server, session, &rd->nodeId.nodeId))
+            continue;
+
+        /* TODO: If a child is optional, check whether optional children that
+         * were manually added fit the constraints. */
         retval |= copyChildNode(server, session, destinationNodeId, 
-                                &br.references[i], instantiationCallback);
+                                rd, instantiationCallback);
+    }
     UA_BrowseResult_deleteMembers(&br);
     return retval;
 }
@@ -665,17 +703,13 @@ Service_AddNode_finish(UA_Server *server, UA_Session *session, const UA_NodeId *
     }
 
     /* Check parent reference. Objects may have no parent. */
-    UA_StatusCode retval = UA_STATUSCODE_GOOD;
-    if(node->nodeClass != UA_NODECLASS_OBJECT || !UA_NodeId_isNull(parentNodeId) ||
-       !UA_NodeId_isNull(referenceTypeId)) {
-        retval = checkParentReference(server, session, node->nodeClass,
-                                      parentNodeId, referenceTypeId);
-        if(retval != UA_STATUSCODE_GOOD) {
-            UA_LOG_INFO_SESSION(server->config.logger, session,
-                                "AddNodes: The parent reference is invalid");
-            Service_DeleteNodes_single(server, &adminSession, nodeId, true);
-            return retval;
-        }
+    UA_StatusCode retval = checkParentReference(server, session, node->nodeClass,
+                                                parentNodeId, referenceTypeId);
+    if(retval != UA_STATUSCODE_GOOD) {
+        UA_LOG_INFO_SESSION(server->config.logger, session,
+                            "AddNodes: The parent reference is invalid");
+        Service_DeleteNodes_single(server, &adminSession, nodeId, true);
+        return retval;
     }
 
     /* Instantiate node. We need the variable type for type checking (e.g. when
@@ -693,8 +727,7 @@ Service_AddNode_finish(UA_Server *server, UA_Session *session, const UA_NodeId *
     /* Type check node */
     if(node->nodeClass == UA_NODECLASS_VARIABLE ||
        node->nodeClass == UA_NODECLASS_VARIABLETYPE) {
-        retval = typeCheckVariableNode(server, session, (const UA_VariableNode*)node,
-                                       typeDefinition);
+        retval = typeCheckVariableNode(server, session, (const UA_VariableNode*)node, typeDefinition);
         if(retval != UA_STATUSCODE_GOOD) {
             UA_LOG_INFO_SESSION(server->config.logger, session,
                                 "AddNodes: Type checking failed with error code %s",
@@ -1288,6 +1321,7 @@ Service_DeleteNodes_single(UA_Server *server, UA_Session *session,
         return UA_STATUSCODE_BADNODEIDUNKNOWN;
 
     /* TODO: check if the information model consistency is violated */
+    /* TODO: Check if the node is a mandatory child of an object */
 
     /* Destroy an object before removing it */
     if(node->nodeClass == UA_NODECLASS_OBJECT) {