Преглед на файлове

fix handling of timestamps in read/write

Julius Pfrommer преди 8 години
родител
ревизия
e705eea331

+ 1 - 0
src/server/ua_server.c

@@ -1065,6 +1065,7 @@ UA_Server * UA_Server_new(const UA_ServerConfig config) {
     UA_Variant_setScalar(&state->value.data.value.value, UA_ServerState_new(),
                          &UA_TYPES[UA_TYPES_SERVERSTATE]);
     state->value.data.value.hasValue = true;
+    state->minimumSamplingInterval = 500.0f;
     addNodeInternalWithType(server, (UA_Node*)state, UA_NODEID_NUMERIC(0, UA_NS0ID_SERVER_SERVERSTATUS),
                             nodeIdHasComponent, UA_NODEID_NUMERIC(0, UA_NS0ID_BASEDATAVARIABLETYPE));
 

+ 112 - 84
src/server/ua_services_attribute.c

@@ -176,7 +176,8 @@ void Service_Read_single(UA_Server *server, UA_Session *session,
         break;
     case UA_ATTRIBUTEID_VALUE:
         CHECK_NODECLASS(UA_NODECLASS_VARIABLE | UA_NODECLASS_VARIABLETYPE);
-        retval = getVariableNodeValue(server, session, (const UA_VariableNode*)node, timestamps, id, v);
+        retval = getVariableNodeValue(server, session, (const UA_VariableNode*)node,
+                                      timestamps, id, v);
         break;
     case UA_ATTRIBUTEID_DATATYPE:
         CHECK_NODECLASS(UA_NODECLASS_VARIABLE | UA_NODECLASS_VARIABLETYPE);
@@ -226,7 +227,26 @@ void Service_Read_single(UA_Server *server, UA_Session *session,
         retval = UA_STATUSCODE_BADATTRIBUTEIDINVALID;
     }
 
-    if(retval != UA_STATUSCODE_GOOD) {
+    if(retval == UA_STATUSCODE_GOOD) {
+        /* create server timestamp */
+        if(timestamps == UA_TIMESTAMPSTORETURN_SERVER ||
+           timestamps == UA_TIMESTAMPSTORETURN_BOTH) {
+            v->serverTimestamp = UA_DateTime_now();
+            v->hasServerTimestamp = true;
+        }
+        /* create and suppress source timestamps in value attributes */
+        if(id->attributeId == UA_ATTRIBUTEID_VALUE) {
+            if (timestamps == UA_TIMESTAMPSTORETURN_SERVER ||
+                timestamps == UA_TIMESTAMPSTORETURN_NEITHER) {
+                v->hasSourceTimestamp = false;
+                v->hasSourcePicoseconds = false;
+            } else if(!v->hasSourceTimestamp) {
+                v->sourceTimestamp = UA_DateTime_now();
+                v->hasSourceTimestamp = true;
+            }
+        }
+    } else {
+        /* return error code */
         v->hasValue = false;
         v->hasStatus = true;
         v->status = retval;
@@ -253,8 +273,8 @@ void Service_Read(UA_Server *server, UA_Session *session,
         response->responseHeader.serviceResult = UA_STATUSCODE_BADOUTOFMEMORY;
         return;
     }
-
     response->resultsSize = size;
+
     if(request->maxAge < 0) {
         response->responseHeader.serviceResult = UA_STATUSCODE_BADMAXAGEINVALID;
         return;
@@ -422,6 +442,34 @@ findDataType(const UA_NodeId *typeId) {
     return NULL;
 }
 
+/* Test whether a valurank and the given arraydimensions are compatible. zero
+ * array dimensions indicate a scalar */
+static UA_StatusCode
+UA_matchValueRankArrayDimensions(UA_Int32 valueRank, size_t arrayDimensionsSize) {
+    switch(valueRank) {
+    case -3: /* the value can be a scalar or a one dimensional array */
+        if(arrayDimensionsSize > 1)
+            return UA_STATUSCODE_BADTYPEMISMATCH;
+        break;
+    case -2: /* the value can be a scalar or an array with any number of dimensions */
+        break;
+    case -1: /* the value is a scalar */
+        if(arrayDimensionsSize > 0)
+            return UA_STATUSCODE_BADTYPEMISMATCH;
+        break;
+    case 0: /* the value is an array with one or more dimensions */
+        if(arrayDimensionsSize < 1)
+            return UA_STATUSCODE_BADTYPEMISMATCH;
+        break;
+    default: /* >= 1: the value is an array with the specified number of dimensions */
+        if(valueRank < 0)
+            return UA_STATUSCODE_BADTYPEMISMATCH;
+        if(arrayDimensionsSize != (size_t)valueRank)
+            return UA_STATUSCODE_BADTYPEMISMATCH;
+    }
+    return UA_STATUSCODE_GOOD;
+}
+
 /* Tests whether the value matches a variable definition given by
  * - datatype
  * - valueranke
@@ -432,9 +480,12 @@ findDataType(const UA_NodeId *typeId) {
  * datatype constraints. */
 UA_StatusCode
 UA_Variant_matchVariableDefinition(UA_Server *server, const UA_NodeId *variableDataTypeId,
-                                   UA_Int32 variableValueRank, size_t variableArrayDimensionsSize,
-                                   const UA_UInt32 *variableArrayDimensions, const UA_Variant *value,
+                                   UA_Int32 variableValueRank,
+                                   size_t variableArrayDimensionsSize,
+                                   const UA_UInt32 *variableArrayDimensions,
+                                   const UA_Variant *value,
                                    const UA_NumericRange *range, UA_Variant *editableValue) {
+    size_t arrayDims;
     /* No content is only allowed for BaseDataType */
     const UA_NodeId *valueDataTypeId;
     UA_NodeId basedatatype = UA_NODEID_NUMERIC(0, UA_NS0ID_BASEDATATYPE);
@@ -493,25 +544,12 @@ UA_Variant_matchVariableDefinition(UA_Server *server, const UA_NodeId *variableD
 
  check_array:
     /* Check if the valuerank allows for the value dimension */
-    switch(variableValueRank) {
-    case -3: /* the value can be a scalar or a one dimensional array */
-        if(value->arrayDimensionsSize > 1)
-            return UA_STATUSCODE_BADTYPEMISMATCH;
-        break;
-    case -2: /* the value can be a scalar or an array with any number of dimensions */
-        break;
-    case -1: /* the value is a scalar */
-        if(!UA_Variant_isScalar(value))
-            return UA_STATUSCODE_BADTYPEMISMATCH;
-        break;
-    case 0: /* the value is an array with one or more dimensions */
-        if(UA_Variant_isScalar(value))
-            return UA_STATUSCODE_BADTYPEMISMATCH;
-        break;
-    default: /* >= 1: the value is an array with the specified number of dimensions */
-        if(value->arrayDimensionsSize != (size_t)variableValueRank)
-            return UA_STATUSCODE_BADTYPEMISMATCH;
-    }
+    arrayDims = value->arrayDimensionsSize;
+    if(value->arrayDimensionsSize == 0 && value->arrayLength > 0)
+        arrayDims = 1;
+    UA_StatusCode retval = UA_matchValueRankArrayDimensions(variableValueRank, arrayDims);
+    if(retval != UA_STATUSCODE_GOOD)
+        return retval;
 
     /* Ranges are checked in detail during writing into the variant */
     if(range)
@@ -556,7 +594,8 @@ UA_VariableNode_setDataType(UA_Server *server, UA_VariableNode *node,
     /* Check if the current value would match the new type */
     if(node->value.data.value.hasValue) {
         retval = UA_Variant_matchVariableDefinition(server, dataType, node->valueRank,
-                                                    node->arrayDimensionsSize, node->arrayDimensions,
+                                                    node->arrayDimensionsSize,
+                                                    node->arrayDimensions,
                                                     &node->value.data.value.value, NULL, NULL);
         if(retval != UA_STATUSCODE_GOOD)
             return retval;
@@ -601,15 +640,14 @@ UA_VariableNode_setValueRank(UA_Server *server, UA_VariableNode *node,
         return UA_STATUSCODE_BADTYPEMISMATCH;
     }
 
-    /* Check if the current value is compatible with the valueRank */
-    if(node->value.data.value.hasValue) {
-        UA_StatusCode retval =
-            UA_Variant_matchVariableDefinition(server, &node->dataType, valueRank,
-                                               node->arrayDimensionsSize, node->arrayDimensions,
-                                               &node->value.data.value.value, NULL, NULL);
-        if(retval != UA_STATUSCODE_GOOD)
-            return retval;
-    }
+    /* Check if the new value is compatible with the array dimensions */
+    size_t arrayDims = node->value.data.value.value.arrayDimensionsSize;
+    if(node->value.data.value.value.arrayDimensionsSize == 0 &&
+       node->value.data.value.value.arrayLength > 0)
+        arrayDims = 1;
+    UA_StatusCode retval = UA_matchValueRankArrayDimensions(valueRank, arrayDims);
+    if(retval != UA_STATUSCODE_GOOD)
+        return retval;
 
     /* Ok, apply */
     node->valueRank = valueRank;
@@ -626,30 +664,11 @@ UA_VariableNode_setArrayDimensions(UA_Server *server, UA_VariableNode *node,
        UA_Node_hasSubTypeOrInstances((const UA_Node*)node))
         return UA_STATUSCODE_BADINTERNALERROR;
 
-    /* Check if the array dimensions match with the valuerank */
-    switch(node->valueRank) {
-    case -3: /* the value can be a scalar or a one dimensional array */
-        if(arrayDimensionsSize > 1)
-            return UA_STATUSCODE_BADTYPEMISMATCH;
-        break;
-    case -2: /* the value can be a scalar or an array with any number of dimensions */
-        break;
-    case -1: /* the value is a scalar */
-        if(arrayDimensionsSize > 0)
-            return UA_STATUSCODE_BADTYPEMISMATCH;
-        break;
-    case 0: /* the value is an array with one or more dimensions */
-        /* no arraydimensions => array of dimension 1 */
-        break;
-    default: /* >= 1: the value is an array with the specified number of dimensions */
-        if(node->valueRank < 0)
-            return UA_STATUSCODE_BADTYPEMISMATCH;
-        if(arrayDimensionsSize == 0 && node->valueRank == 1)
-            /* no arraydimensions => array of dimension 1 */
-            break;
-        if(arrayDimensionsSize != (size_t)node->valueRank)
-            return UA_STATUSCODE_BADTYPEMISMATCH;
-    }
+    /* Check that the array dimensions match with the valuerank */
+    UA_StatusCode retval = UA_matchValueRankArrayDimensions(node->valueRank,
+                                                            arrayDimensionsSize);
+    if(retval != UA_STATUSCODE_GOOD)
+        return retval;
 
     /* Check if the array dimensions match with the wildcards in the
      * variabletype (dimension length 0) */
@@ -664,7 +683,6 @@ UA_VariableNode_setArrayDimensions(UA_Server *server, UA_VariableNode *node,
     }
 
     /* Check if the current value is compatible with the array dimensions */
-    UA_StatusCode retval;
     if(node->value.data.value.hasValue) {
         retval = UA_Variant_matchVariableDefinition(server, &node->dataType, node->valueRank,
                                                     arrayDimensionsSize, arrayDimensions,
@@ -700,24 +718,33 @@ UA_VariableNode_setValue(UA_Server *server, UA_VariableNode *node,
         rangeptr = &range;
     }
 
-    /* Check the type definition and use a possibly transformed variant that
-     * matches the node data type */
+    /* Copy the value into an editable "container" where e.g. the datatype can
+     * be adjusted */
     UA_DataValue editableValue = *value;
     editableValue.value.storageType = UA_VARIANT_DATA_NODELETE;
-    editableValue.serverTimestamp = UA_DateTime_now();
-    editableValue.hasServerTimestamp = true;
-    editableValue.hasServerPicoseconds = false;
+
+    /* Check the type definition and use a possibly transformed variant that
+     * matches the node data type */
     if(value->hasValue) {
         retval = UA_Variant_matchVariableDefinition(server, &node->dataType, node->valueRank,
-                                                    node->arrayDimensionsSize, node->arrayDimensions,
-                                                    &value->value, rangeptr, &editableValue.value);
+                                                    node->arrayDimensionsSize,
+                                                    node->arrayDimensions,
+                                                    &value->value, rangeptr,
+                                                    &editableValue.value);
         if(retval != UA_STATUSCODE_GOOD)
             goto cleanup;
     }
 
-    /* write the value */
+    /* Set the source timestamp if there is none */
+    if(!editableValue.hasSourceTimestamp) {
+        editableValue.sourceTimestamp = UA_DateTime_now();
+        editableValue.hasSourceTimestamp = true;
+    }
+
+    /* Write the value */
     if(node->valueSource == UA_VALUESOURCE_DATA) {
         if(!rangeptr) {
+            /* Replace the DataValue */
             UA_Variant old_value = node->value.data.value.value;
             retval = UA_DataValue_copy(&editableValue, &node->value.data.value);
             if(retval == UA_STATUSCODE_GOOD)
@@ -725,36 +752,37 @@ UA_VariableNode_setValue(UA_Server *server, UA_VariableNode *node,
             else
                 node->value.data.value.value = old_value; 
         } else {
-            if(!node->value.data.value.hasValue || !editableValue.hasValue) {
-                retval = UA_STATUSCODE_BADINDEXRANGEINVALID;
-                goto cleanup;
-            }
-            /* TODO: Catch error during setRangeCopy (make a copy of the value
-               in the server?) */
-            retval = UA_Variant_setRangeCopy(&node->value.data.value.value, editableValue.value.data,
-                                             editableValue.value.arrayLength, range);
+            /* Write with a range */
             node->value.data.value.hasStatus = editableValue.hasStatus;
             node->value.data.value.hasSourceTimestamp = editableValue.hasSourceTimestamp;
-            node->value.data.value.hasServerTimestamp = editableValue.hasServerTimestamp;
             node->value.data.value.hasSourcePicoseconds = editableValue.hasSourcePicoseconds;
-            node->value.data.value.hasServerPicoseconds = editableValue.hasServerPicoseconds;
             node->value.data.value.status = editableValue.status;
             node->value.data.value.sourceTimestamp = editableValue.sourceTimestamp;
             node->value.data.value.sourcePicoseconds = editableValue.sourcePicoseconds;
-            node->value.data.value.serverTimestamp = editableValue.serverTimestamp;
-            node->value.data.value.serverPicoseconds = editableValue.serverPicoseconds;
+            if(editableValue.status == UA_STATUSCODE_GOOD) {
+                if(!node->value.data.value.hasValue || !editableValue.hasValue) {
+                    retval = UA_STATUSCODE_BADINDEXRANGEINVALID;
+                    goto cleanup;
+                }
+                /* TODO: Catch error during setRangeCopy (make a copy of the value
+                   in the server?) */
+                retval = UA_Variant_setRangeCopy(&node->value.data.value.value,
+                                                 editableValue.value.data,
+                                                 editableValue.value.arrayLength, range);
+            }
         }
+
         /* post-write callback */
         if(retval == UA_STATUSCODE_GOOD && node->value.data.callback.onWrite)
             node->value.data.callback.onWrite(node->value.data.callback.handle, node->nodeId,
                                               &node->value.data.value.value, rangeptr);
     } else {
-        /* TODO: Don't make a copy of the node in the multithreaded case */
-        if(node->value.dataSource.write)
-            retval = node->value.dataSource.write(node->value.dataSource.handle, node->nodeId,
-                                                  &value->value, rangeptr);
-        else
+        /* write into a datasource */
+        if(!node->value.dataSource.write)
             retval = UA_STATUSCODE_BADWRITENOTSUPPORTED;
+        else
+            retval = node->value.dataSource.write(node->value.dataSource.handle,
+                                                  node->nodeId, &value->value, rangeptr);
     }
 
  cleanup:

+ 26 - 16
src/server/ua_services_nodemanagement.c

@@ -497,19 +497,19 @@ copyCommonVariableAttributes(UA_Server *server, UA_VariableNode *node,
     UA_StatusCode retval;
     if(!UA_NodeId_isNull(&attr->dataType))
         retval  = UA_VariableNode_setDataType(server, node, vt, &attr->dataType);
-    else
-        /* workaround common error where the datatype is left as NA_NODEID_NULL */
+    else /* workaround common error where the datatype is left as NA_NODEID_NULL */
         retval = UA_VariableNode_setDataType(server, node, vt, &vt->dataType);
-
-    if(attr->valueRank != 0 || !UA_Variant_isScalar(&attr->value))
-        retval |= UA_VariableNode_setValueRank(server, node, vt, attr->valueRank);
-    else
-        /* workaround common error where the valuerank is left as 0 */
-        retval |= UA_VariableNode_setValueRank(server, node, vt, vt->valueRank);
         
+    node->valueRank = -2; /* allow all dimensions first */
     retval |= UA_VariableNode_setArrayDimensions(server, node, vt,
                                                  attr->arrayDimensionsSize,
                                                  attr->arrayDimensions);
+
+    if(attr->valueRank != 0 || !UA_Variant_isScalar(&attr->value))
+        retval |= UA_VariableNode_setValueRank(server, node, vt, attr->valueRank);
+    else /* workaround common error where the valuerank is left as 0 */
+        retval |= UA_VariableNode_setValueRank(server, node, vt, vt->valueRank);
+
     /* Set the value */
     UA_DataValue value;
     UA_DataValue_init(&value);
@@ -838,15 +838,22 @@ UA_Server_addMethodNode(UA_Server *server, const UA_NodeId requestedNewNodeId,
         inputArgumentsVariableNode->displayName = UA_LOCALIZEDTEXT_ALLOC("en_US", "InputArguments");
         inputArgumentsVariableNode->description = UA_LOCALIZEDTEXT_ALLOC("en_US", "InputArguments");
         inputArgumentsVariableNode->valueRank = 1;
+
+        /* UAExport creates a monitoreditem on inputarguments ... */
+        inputArgumentsVariableNode->minimumSamplingInterval = 10000.0f;
+
         //TODO: 0.3 work item: the addMethodNode API does not have the possibility to set nodeIDs
         //actually we need to change the signature to pass UA_NS0ID_SERVER_GETMONITOREDITEMS_INPUTARGUMENTS
         //and UA_NS0ID_SERVER_GETMONITOREDITEMS_OUTPUTARGUMENTS into the function :/
-        if(newMethodId.namespaceIndex == 0 && newMethodId.identifierType == UA_NODEIDTYPE_NUMERIC &&
+        if(newMethodId.namespaceIndex == 0 &&
+           newMethodId.identifierType == UA_NODEIDTYPE_NUMERIC &&
            newMethodId.identifier.numeric == UA_NS0ID_SERVER_GETMONITOREDITEMS) {
-            inputArgumentsVariableNode->nodeId = UA_NODEID_NUMERIC(0, UA_NS0ID_SERVER_GETMONITOREDITEMS_INPUTARGUMENTS);
+            inputArgumentsVariableNode->nodeId =
+                UA_NODEID_NUMERIC(0, UA_NS0ID_SERVER_GETMONITOREDITEMS_INPUTARGUMENTS);
         }
-        UA_Variant_setArrayCopy(&inputArgumentsVariableNode->value.data.value.value, inputArguments,
-                                inputArgumentsSize, &UA_TYPES[UA_TYPES_ARGUMENT]);
+        UA_Variant_setArrayCopy(&inputArgumentsVariableNode->value.data.value.value,
+                                inputArguments, inputArgumentsSize,
+                                &UA_TYPES[UA_TYPES_ARGUMENT]);
         inputArgumentsVariableNode->value.data.value.hasValue = true;
         UA_RCU_LOCK();
         // todo: check if adding succeeded
@@ -864,12 +871,15 @@ UA_Server_addMethodNode(UA_Server *server, const UA_NodeId requestedNewNodeId,
         outputArgumentsVariableNode->description = UA_LOCALIZEDTEXT_ALLOC("en_US", "OutputArguments");
         outputArgumentsVariableNode->valueRank = 1;
         //FIXME: comment in line 882
-        if(newMethodId.namespaceIndex == 0 && newMethodId.identifierType == UA_NODEIDTYPE_NUMERIC &&
+        if(newMethodId.namespaceIndex == 0 &&
+           newMethodId.identifierType == UA_NODEIDTYPE_NUMERIC &&
            newMethodId.identifier.numeric == UA_NS0ID_SERVER_GETMONITOREDITEMS) {
-            outputArgumentsVariableNode->nodeId = UA_NODEID_NUMERIC(0, UA_NS0ID_SERVER_GETMONITOREDITEMS_OUTPUTARGUMENTS);
+            outputArgumentsVariableNode->nodeId =
+                UA_NODEID_NUMERIC(0, UA_NS0ID_SERVER_GETMONITOREDITEMS_OUTPUTARGUMENTS);
         }
-        UA_Variant_setArrayCopy(&outputArgumentsVariableNode->value.data.value.value, outputArguments,
-                                outputArgumentsSize, &UA_TYPES[UA_TYPES_ARGUMENT]);
+        UA_Variant_setArrayCopy(&outputArgumentsVariableNode->value.data.value.value,
+                                outputArguments, outputArgumentsSize,
+                                &UA_TYPES[UA_TYPES_ARGUMENT]);
         outputArgumentsVariableNode->value.data.value.hasValue = true;
         UA_RCU_LOCK();
         // todo: check if adding succeeded

+ 87 - 46
src/server/ua_services_subscription.c

@@ -15,8 +15,7 @@ setSubscriptionSettings(UA_Server *server, UA_Subscription *subscription,
                         UA_Double requestedPublishingInterval,
                         UA_UInt32 requestedLifetimeCount,
                         UA_UInt32 requestedMaxKeepAliveCount,
-                        UA_UInt32 maxNotificationsPerPublish, UA_Byte priority)
-{
+                        UA_UInt32 maxNotificationsPerPublish, UA_Byte priority) {
     /* deregister the job if required */
     UA_StatusCode retval = Subscription_unregisterPublishJob(server, subscription);
     if(retval != UA_STATUSCODE_GOOD)
@@ -53,12 +52,12 @@ setSubscriptionSettings(UA_Server *server, UA_Subscription *subscription,
 void
 Service_CreateSubscription(UA_Server *server, UA_Session *session,
                            const UA_CreateSubscriptionRequest *request,
-                           UA_CreateSubscriptionResponse *response)
-{
+                           UA_CreateSubscriptionResponse *response) {
     /* Create the subscription */
     UA_Subscription *newSubscription = UA_Subscription_new(session, response->subscriptionId);
     if(!newSubscription) {
-        UA_LOG_DEBUG_SESSION(server->config.logger, session, "Processing CreateSubscriptionRequest failed");
+        UA_LOG_DEBUG_SESSION(server->config.logger, session,
+                             "Processing CreateSubscriptionRequest failed");
         response->responseHeader.serviceResult = UA_STATUSCODE_BADOUTOFMEMORY;
         return;
     }
@@ -78,15 +77,18 @@ Service_CreateSubscription(UA_Server *server, UA_Session *session,
     response->revisedLifetimeCount = newSubscription->lifeTimeCount;
     response->revisedMaxKeepAliveCount = newSubscription->maxKeepAliveCount;
 
-    UA_LOG_DEBUG_SESSION(server->config.logger, session, "CreateSubscriptionRequest: Created Subscription %u "
+    UA_LOG_DEBUG_SESSION(server->config.logger, session,
+                         "CreateSubscriptionRequest: Created Subscription %u "
                          "with a publishing interval of %f ms", response->subscriptionId,
                          newSubscription->publishingInterval);
 }
 
-void Service_ModifySubscription(UA_Server *server, UA_Session *session,
-                                const UA_ModifySubscriptionRequest *request,
-                                UA_ModifySubscriptionResponse *response) {
-    UA_LOG_DEBUG_SESSION(server->config.logger, session, "Processing ModifySubscriptionRequest");
+void
+Service_ModifySubscription(UA_Server *server, UA_Session *session,
+                           const UA_ModifySubscriptionRequest *request,
+                           UA_ModifySubscriptionResponse *response) {
+    UA_LOG_DEBUG_SESSION(server->config.logger, session,
+                         "Processing ModifySubscriptionRequest");
     UA_Subscription *sub = UA_Session_getSubscriptionByID(session, request->subscriptionId);
     if(!sub) {
         response->responseHeader.serviceResult = UA_STATUSCODE_BADSUBSCRIPTIONIDINVALID;
@@ -103,10 +105,12 @@ void Service_ModifySubscription(UA_Server *server, UA_Session *session,
     return;
 }
 
-void Service_SetPublishingMode(UA_Server *server, UA_Session *session,
-                               const UA_SetPublishingModeRequest *request,
-                               UA_SetPublishingModeResponse *response) {
-    UA_LOG_DEBUG_SESSION(server->config.logger, session, "Processing SetPublishingModeRequest");
+void
+Service_SetPublishingMode(UA_Server *server, UA_Session *session,
+                          const UA_SetPublishingModeRequest *request,
+                          UA_SetPublishingModeResponse *response) {
+    UA_LOG_DEBUG_SESSION(server->config.logger, session,
+                         "Processing SetPublishingModeRequest");
     if(request->subscriptionIdsSize <= 0) {
         response->responseHeader.serviceResult = UA_STATUSCODE_BADNOTHINGTODO;
         return;
@@ -121,7 +125,8 @@ void Service_SetPublishingMode(UA_Server *server, UA_Session *session,
 
     response->resultsSize = size;
     for(size_t i = 0; i < size; i++) {
-        UA_Subscription *sub = UA_Session_getSubscriptionByID(session, request->subscriptionIds[i]);
+        UA_Subscription *sub =
+            UA_Session_getSubscriptionByID(session, request->subscriptionIds[i]);
         if(!sub) {
             response->results[i] = UA_STATUSCODE_BADSUBSCRIPTIONIDINVALID;
             continue;
@@ -139,38 +144,68 @@ void Service_SetPublishingMode(UA_Server *server, UA_Session *session,
 
 static void
 setMonitoredItemSettings(UA_Server *server, UA_MonitoredItem *mon,
-                         UA_MonitoringMode monitoringMode, UA_UInt32 clientHandle,
-                         UA_Double samplingInterval, UA_UInt32 queueSize,
-                         UA_Boolean discardOldest) {
+                         UA_MonitoringMode monitoringMode,
+                         const UA_MonitoringParameters *params) {
     MonitoredItem_unregisterSampleJob(server, mon);
     mon->monitoringMode = monitoringMode;
-    mon->clientHandle = clientHandle;
+
+    /* ClientHandle */
+    mon->clientHandle = params->clientHandle;
+
+    /* SamplingInterval */
+    UA_Double samplingInterval = params->samplingInterval;
+    if(mon->attributeID == UA_ATTRIBUTEID_VALUE) {
+        const UA_VariableNode *vn = (const UA_VariableNode*)
+            UA_NodeStore_get(server->nodestore, &mon->monitoredNodeId);
+        if(vn && vn->nodeClass == UA_NODECLASS_VARIABLE &&
+           samplingInterval <  vn->minimumSamplingInterval)
+            samplingInterval = vn->minimumSamplingInterval;
+    } else if(mon->attributeID == UA_ATTRIBUTEID_EVENTNOTIFIER) {
+        /* TODO: events should not need a samplinginterval */
+        samplingInterval = 10000.0f; // 10 seconds to reduce the load
+    }
     mon->samplingInterval = samplingInterval;
     UA_BOUNDEDVALUE_SETWBOUNDS(server->config.samplingIntervalLimits,
         samplingInterval, mon->samplingInterval);
-    /* Check for nan */
-    if(samplingInterval != samplingInterval)
+    if(samplingInterval != samplingInterval) /* Check for nan */
         mon->samplingInterval = server->config.samplingIntervalLimits.min;
+
+    /* Filter */
+    if(params->filter.encoding != UA_EXTENSIONOBJECT_DECODED ||
+       params->filter.content.decoded.type != &UA_TYPES[UA_TYPES_DATACHANGEFILTER]) {
+        /* Default: Trigger only on the value and the statuscode */
+        mon->trigger = UA_DATACHANGETRIGGER_STATUSVALUE;
+    } else {
+        UA_DataChangeFilter *filter = params->filter.content.decoded.data;
+        mon->trigger = filter->trigger;
+    }
+
+    /* QueueSize */
     UA_BOUNDEDVALUE_SETWBOUNDS(server->config.queueSizeLimits,
-                               queueSize, mon->maxQueueSize);
-    mon->discardOldest = discardOldest;
+                               params->queueSize, mon->maxQueueSize);
+
+    /* DiscardOldest */
+    mon->discardOldest = params->discardOldest;
+
+    /* Register sample job if reporting is enabled */
     if(monitoringMode == UA_MONITORINGMODE_REPORTING)
         MonitoredItem_registerSampleJob(server, mon);
 }
 
 static const UA_String binaryEncoding = {sizeof("Default Binary")-1, (UA_Byte*)"Default Binary"};
+
 static void
-Service_CreateMonitoredItems_single(UA_Server *server, UA_Session *session, UA_Subscription *sub,
+Service_CreateMonitoredItems_single(UA_Server *server, UA_Session *session,
+                                    UA_Subscription *sub,
                                     const UA_TimestampsToReturn timestampsToReturn,
                                     const UA_MonitoredItemCreateRequest *request,
                                     UA_MonitoredItemCreateResult *result) {
-    /* Make an example read to get errors in the itemToMonitor */
+    /* Make an example read to get errors in the itemToMonitor. Allow return
+     * codes "good" and "uncertain", as well as a list of statuscodes that might
+     * be repaired inside the data source. */
     UA_DataValue v;
     UA_DataValue_init(&v);
     Service_Read_single(server, session, timestampsToReturn, &request->itemToMonitor, &v);
-
-    /* Allow return codes "good" and "uncertain", as well as a list of
-       statuscodes that might be repaired by the data source. */
     if(v.hasStatus && (v.status >> 30) > 1 &&
        v.status != UA_STATUSCODE_BADRESOURCEUNAVAILABLE &&
        v.status != UA_STATUSCODE_BADCOMMUNICATIONERROR &&
@@ -190,7 +225,8 @@ Service_CreateMonitoredItems_single(UA_Server *server, UA_Session *session, UA_S
     }
 
     /* Check if the encoding is set for a value */
-    if(request->itemToMonitor.attributeId != UA_ATTRIBUTEID_VALUE && request->itemToMonitor.dataEncoding.name.length > 0){
+    if(request->itemToMonitor.attributeId != UA_ATTRIBUTEID_VALUE &&
+       request->itemToMonitor.dataEncoding.name.length > 0) {
         result->statusCode = UA_STATUSCODE_BADDATAENCODINGINVALID;
         return;
     }
@@ -201,7 +237,8 @@ Service_CreateMonitoredItems_single(UA_Server *server, UA_Session *session, UA_S
         result->statusCode = UA_STATUSCODE_BADOUTOFMEMORY;
         return;
     }
-    UA_StatusCode retval = UA_NodeId_copy(&request->itemToMonitor.nodeId, &newMon->monitoredNodeId);
+    UA_StatusCode retval = UA_NodeId_copy(&request->itemToMonitor.nodeId,
+                                          &newMon->monitoredNodeId);
     if(retval != UA_STATUSCODE_GOOD) {
         result->statusCode = retval;
         MonitoredItem_delete(server, newMon);
@@ -212,10 +249,7 @@ Service_CreateMonitoredItems_single(UA_Server *server, UA_Session *session, UA_S
     newMon->itemId = ++(sub->lastMonitoredItemId);
     newMon->timestampsToReturn = timestampsToReturn;
     setMonitoredItemSettings(server, newMon, request->monitoringMode,
-                             request->requestedParameters.clientHandle,
-                             request->requestedParameters.samplingInterval,
-                             request->requestedParameters.queueSize,
-                             request->requestedParameters.discardOldest);
+                             &request->requestedParameters);
     LIST_INSERT_HEAD(&sub->MonitoredItems, newMon, listEntry);
 
     /* Create the first sample */
@@ -233,9 +267,10 @@ void
 Service_CreateMonitoredItems(UA_Server *server, UA_Session *session,
                              const UA_CreateMonitoredItemsRequest *request,
                              UA_CreateMonitoredItemsResponse *response) {
-    UA_LOG_DEBUG_SESSION(server->config.logger, session, "Processing CreateMonitoredItemsRequest");
+    UA_LOG_DEBUG_SESSION(server->config.logger, session,
+                         "Processing CreateMonitoredItemsRequest");
 
-    /* check if the timestampstoreturn is valid */
+    /* Check if the timestampstoreturn is valid */
     if(request->timestampsToReturn > UA_TIMESTAMPSTORETURN_NEITHER) {
         response->responseHeader.serviceResult = UA_STATUSCODE_BADTIMESTAMPSTORETURNINVALID;
         return;
@@ -278,10 +313,7 @@ Service_ModifyMonitoredItems_single(UA_Server *server, UA_Session *session, UA_S
     }
 
     setMonitoredItemSettings(server, mon, mon->monitoringMode,
-                             request->requestedParameters.clientHandle,
-                             request->requestedParameters.samplingInterval,
-                             request->requestedParameters.queueSize,
-                             request->requestedParameters.discardOldest);
+                             &request->requestedParameters);
     result->revisedSamplingInterval = mon->samplingInterval;
     result->revisedQueueSize = mon->maxQueueSize;
 }
@@ -289,7 +321,8 @@ Service_ModifyMonitoredItems_single(UA_Server *server, UA_Session *session, UA_S
 void Service_ModifyMonitoredItems(UA_Server *server, UA_Session *session,
                                   const UA_ModifyMonitoredItemsRequest *request,
                                   UA_ModifyMonitoredItemsResponse *response) {
-    UA_LOG_DEBUG_SESSION(server->config.logger, session, "Processing ModifyMonitoredItemsRequest");
+    UA_LOG_DEBUG_SESSION(server->config.logger, session,
+                         "Processing ModifyMonitoredItemsRequest");
 
     /* check if the timestampstoreturn is valid */
     if(request->timestampsToReturn > UA_TIMESTAMPSTORETURN_NEITHER) {
@@ -297,6 +330,7 @@ void Service_ModifyMonitoredItems(UA_Server *server, UA_Session *session,
         return;
     }
 
+    /* Get the subscription */
     UA_Subscription *sub = UA_Session_getSubscriptionByID(session, request->subscriptionId);
     if(!sub) {
         response->responseHeader.serviceResult = UA_STATUSCODE_BADSUBSCRIPTIONIDINVALID;
@@ -347,12 +381,19 @@ void Service_SetMonitoringMode(UA_Server *server, UA_Session *session,
     response->resultsSize = request->monitoredItemIdsSize;
 
     for(size_t i = 0; i < response->resultsSize; i++) {
-        UA_MonitoredItem *mon = UA_Subscription_getMonitoredItem(sub, request->monitoredItemIds[i]);
-        if(mon)
-            setMonitoredItemSettings(server, mon, request->monitoringMode, mon->clientHandle,
-                                     mon->samplingInterval, mon->maxQueueSize, mon->discardOldest);
-        else
+        UA_MonitoredItem *mon =
+            UA_Subscription_getMonitoredItem(sub, request->monitoredItemIds[i]);
+        if(!mon) {
             response->results[i] = UA_STATUSCODE_BADMONITOREDITEMIDINVALID;
+            continue;
+        }
+        if(request->monitoringMode == mon->monitoringMode)
+            continue;
+        mon->monitoringMode = request->monitoringMode;
+        if(mon->monitoringMode == UA_MONITORINGMODE_REPORTING)
+            MonitoredItem_registerSampleJob(server, mon);
+        else
+            MonitoredItem_unregisterSampleJob(server, mon);
     }
 }
 

+ 45 - 14
src/server/ua_subscription.c

@@ -47,17 +47,20 @@ void MonitoredItem_delete(UA_Server *server, UA_MonitoredItem *monitoredItem) {
 void UA_MoniteredItem_SampleCallback(UA_Server *server, UA_MonitoredItem *monitoredItem) {
     UA_Subscription *sub = monitoredItem->subscription;
     if(monitoredItem->monitoredItemType != UA_MONITOREDITEMTYPE_CHANGENOTIFY) {
-        UA_LOG_DEBUG_SESSION(server->config.logger, sub->session, "Subscription %u | MonitoredItem %i | "
-                             "Cannot process a monitoreditem that is not a data change notification",
+        UA_LOG_DEBUG_SESSION(server->config.logger, sub->session,
+                             "Subscription %u | MonitoredItem %i | "
+                             "Cannot process a monitoreditem that is not "
+                             "a data change notification",
                              sub->subscriptionID, monitoredItem->itemId);
         return;
     }
 
     MonitoredItem_queuedValue *newvalue = UA_malloc(sizeof(MonitoredItem_queuedValue));
     if(!newvalue) {
-        UA_LOG_WARNING_SESSION(server->config.logger, sub->session, "Subscription %u | MonitoredItem %i | "
-                               "Skipped a sample due to lack of memory", sub->subscriptionID,
-                               monitoredItem->itemId);
+        UA_LOG_WARNING_SESSION(server->config.logger, sub->session,
+                               "Subscription %u | MonitoredItem %i | "
+                               "Skipped a sample due to lack of memory",
+                               sub->subscriptionID, monitoredItem->itemId);
         return;
     }
     UA_DataValue_init(&newvalue->value);
@@ -72,7 +75,22 @@ void UA_MoniteredItem_SampleCallback(UA_Server *server, UA_MonitoredItem *monito
     Service_Read_single(server, sub->session, monitoredItem->timestampsToReturn,
                         &rvid, &newvalue->value);
 
-    /* encode to see if the data has changed */
+    /* Apply Filter */
+    UA_Boolean hasValue = newvalue->value.hasValue;
+    UA_Boolean hasServerTimestamp = newvalue->value.hasServerTimestamp;
+    UA_Boolean hasServerPicoseconds = newvalue->value.hasServerPicoseconds;
+    UA_Boolean hasSourceTimestamp = newvalue->value.hasSourceTimestamp;
+    UA_Boolean hasSourcePicoseconds = newvalue->value.hasSourcePicoseconds;
+    newvalue->value.hasServerTimestamp = false;
+    newvalue->value.hasServerPicoseconds = false;
+    if(monitoredItem->trigger == UA_DATACHANGETRIGGER_STATUS)
+        newvalue->value.hasValue = false;
+    if(monitoredItem->trigger < UA_DATACHANGETRIGGER_STATUSVALUETIMESTAMP) {
+        newvalue->value.hasSourceTimestamp = false;
+        newvalue->value.hasSourcePicoseconds = false;
+    }
+
+    /* Encode to see if the data has changed */
     size_t binsize = UA_calcSizeBinary(&newvalue->value, &UA_TYPES[UA_TYPES_DATAVALUE]);
     UA_ByteString newValueAsByteString;
     UA_StatusCode retval = UA_ByteString_allocBuffer(&newValueAsByteString, binsize);
@@ -85,20 +103,28 @@ void UA_MoniteredItem_SampleCallback(UA_Server *server, UA_MonitoredItem *monito
     retval = UA_encodeBinary(&newvalue->value, &UA_TYPES[UA_TYPES_DATAVALUE],
                              NULL, NULL, &newValueAsByteString, &encodingOffset);
 
-    /* error or the content has not changed */
+    /* Restore the booleans changed for the filter */
+    newvalue->value.hasValue = hasValue;
+    newvalue->value.hasServerTimestamp = hasServerTimestamp;
+    newvalue->value.hasServerPicoseconds = hasServerPicoseconds;
+    newvalue->value.hasSourceTimestamp = hasSourceTimestamp;
+    newvalue->value.hasSourcePicoseconds = hasSourcePicoseconds;
+
+    /* Error or the value has not changed */
     if(retval != UA_STATUSCODE_GOOD ||
        (monitoredItem->lastSampledValue.data &&
         UA_String_equal(&newValueAsByteString, &monitoredItem->lastSampledValue))) {
         UA_ByteString_deleteMembers(&newValueAsByteString);
         UA_DataValue_deleteMembers(&newvalue->value);
         UA_free(newvalue);
-        UA_LOG_DEBUG_SESSION(server->config.logger, sub->session, "Subscription %u | "
+        UA_LOG_TRACE_SESSION(server->config.logger, sub->session, "Subscription %u | "
                              "MonitoredItem %u | Do not sample an unchanged value",
                              sub->subscriptionID, monitoredItem->itemId);
         return;
     }
 
-    UA_LOG_DEBUG_SESSION(server->config.logger, sub->session, "Subscription %u | MonitoredItem %u | "
+    UA_LOG_DEBUG_SESSION(server->config.logger, sub->session,
+                         "Subscription %u | MonitoredItem %u | "
                          "Sampling the value", sub->subscriptionID, monitoredItem->itemId);
 
     /* Do we have space in the queue? */
@@ -112,7 +138,8 @@ void UA_MoniteredItem_SampleCallback(UA_Server *server, UA_MonitoredItem *monito
         if(!queueItem) {
             UA_LOG_WARNING_SESSION(server->config.logger, sub->session, "Subscription %u | "
                                    "MonitoredItem %u | Cannot remove an element from the full "
-                                   "queue. Internal error!", sub->subscriptionID, monitoredItem->itemId);
+                                   "queue. Internal error!", sub->subscriptionID,
+                                   monitoredItem->itemId);
             UA_ByteString_deleteMembers(&newValueAsByteString);
             UA_DataValue_deleteMembers(&newvalue->value);
             UA_free(newvalue);
@@ -126,15 +153,18 @@ void UA_MoniteredItem_SampleCallback(UA_Server *server, UA_MonitoredItem *monito
     }
 
     /* If the read request returned a datavalue pointing into the nodestore, we
-       must make a copy to keep the datavalue across mainloop iterations */
-    if(newvalue->value.hasValue && newvalue->value.value.storageType == UA_VARIANT_DATA_NODELETE) {
+     * must make a deep copy to keep the datavalue across mainloop iterations */
+    if(newvalue->value.hasValue &&
+       newvalue->value.value.storageType == UA_VARIANT_DATA_NODELETE) {
         UA_Variant tempv = newvalue->value.value;
         UA_Variant_copy(&tempv, &newvalue->value.value);
     }
 
-    /* add the sample */
+    /* Replace the comparison bytestring with the current sample */
     UA_ByteString_deleteMembers(&monitoredItem->lastSampledValue);
     monitoredItem->lastSampledValue = newValueAsByteString;
+
+    /* Add the sample to the queue for publication */
     TAILQ_INSERT_TAIL(&monitoredItem->queue, newvalue, listEntry);
     monitoredItem->currentQueueSize++;
 }
@@ -142,7 +172,8 @@ void UA_MoniteredItem_SampleCallback(UA_Server *server, UA_MonitoredItem *monito
 UA_StatusCode MonitoredItem_registerSampleJob(UA_Server *server, UA_MonitoredItem *mon) {
     UA_Job job = {.type = UA_JOBTYPE_METHODCALL, .job.methodCall = {
             .method = (UA_ServerCallback)UA_MoniteredItem_SampleCallback, .data = mon} };
-    UA_StatusCode retval = UA_Server_addRepeatedJob(server, job, (UA_UInt32)mon->samplingInterval,
+    UA_StatusCode retval = UA_Server_addRepeatedJob(server, job,
+                                                    (UA_UInt32)mon->samplingInterval,
                                                     &mon->sampleJobGuid);
     if(retval == UA_STATUSCODE_GOOD)
         mon->sampleJobIsRegistered = true;

+ 1 - 0
src/server/ua_subscription.h

@@ -41,6 +41,7 @@ typedef struct UA_MonitoredItem {
     UA_Boolean discardOldest;
     UA_String indexRange;
     // TODO: dataEncoding is hardcoded to UA binary
+    UA_DataChangeTrigger trigger;
 
     /* Sample Job */
     UA_Guid sampleJobGuid;

+ 3 - 0
tools/schema/datatypes_minimal.txt

@@ -158,3 +158,6 @@ MonitoredItemModifyResult
 ModifyMonitoredItemsResponse
 SetMonitoringModeRequest
 SetMonitoringModeResponse
+DataChangeTrigger
+DeadbandType
+DataChangeFilter