Ver código fonte

Added highlevel client API for reading historical data

The following commits where squashed in this commit:
* Added highlevel client API for reading historical values
* Added example for reading historical values with client
    It requires the Unified Automation demo server.
    It starts data logging and reads the values afterwards.
* Adapted API to allow readModified
* Changes order of arguments to prevent mistakes
* Added separat methods for raw / modified / prepared events
* Reused "service" for multiple DetailTypes / simplyfied
* Removed deletion of local variables
* Moved / added missing functions in header file
* Missed to commit changes to UA_Client_readHistorical_events()
* Added __UA_Client_readHistorical() to header
    .. sry, unit tests wont run on this machine, while building works like a
    charme
* My git client skips content.. :-1:
* Fixed example warnings
* Fixed prints of example / added _BSD_SOURCE for usleep()
* Fixed POSIX features again
Fabian Arndt 6 anos atrás
pai
commit
8577f8773e

+ 2 - 0
examples/CMakeLists.txt

@@ -90,6 +90,8 @@ install(PROGRAMS $<TARGET_FILE:client>
 
 add_example(client_async client_async.c)
 
+add_example(client_historical client_historical.c)
+
 add_example(client_connect_loop client_connect_loop.c)
 
 add_example(client_connectivitycheck_loop client_connectivitycheck_loop.c)

+ 160 - 0
examples/client_historical.c

@@ -0,0 +1,160 @@
+/* This work is licensed under a Creative Commons CCZero 1.0 Universal License.
+ * See http://creativecommons.org/publicdomain/zero/1.0/ for more information. */
+
+/* Enable POSIX features */
+#if !defined(_XOPEN_SOURCE) && !defined(_WRS_KERNEL)
+# define _XOPEN_SOURCE 600
+#endif
+#ifndef _DEFAULT_SOURCE
+# define _DEFAULT_SOURCE
+#endif
+/* On older systems we need to define _BSD_SOURCE.
+ * _DEFAULT_SOURCE is an alias for that. */
+#ifndef _BSD_SOURCE
+# define _BSD_SOURCE
+#endif
+
+#include <stdio.h>
+#include "open62541.h"
+
+#ifdef _WIN32
+# include <windows.h>
+# define UA_sleep_ms(X) Sleep(X)
+#else
+# include <unistd.h>
+# define UA_sleep_ms(X) usleep(X * 1000)
+#endif
+
+static UA_Boolean
+readHist(const UA_NodeId nodeId, const UA_Boolean isInverse,
+         const UA_Boolean moreDataAvailable,
+         const UA_HistoryData *data, void *isDouble) {
+
+    printf("\nRead historical callback:\n");
+    printf("\tValue count:\t%u\n", (UA_UInt32)data->dataValuesSize);
+    printf("\tIs inverse:\t%d\n", isInverse);
+    printf("\tHas more data:\t%d\n\n", moreDataAvailable);
+
+    /* Iterate over all values */
+    for (UA_UInt32 i = 0; i < data->dataValuesSize; ++i)
+    {
+        UA_DataValue val = data->dataValues[i];
+        
+        /* If there is no value, we are out of bounds or something bad hapened */
+        if (!val.hasValue) {
+            if (val.hasStatus) {
+                if (val.status == UA_STATUSCODE_BADBOUNDNOTFOUND)
+                    printf("Skipping bounds (i=%u)\n\n", i);
+                else
+                    printf("Skipping (i=%u) (status=0x%08x -> %s)\n\n", i, val.status, UA_StatusCode_name(val.status));
+            }
+
+            continue;
+        }
+        
+        /* The handle is used to determine double and byte request */
+        if ((UA_Boolean)isDouble) {
+            UA_Double hrValue = *(UA_Double *)val.value.data;
+            printf("ByteValue (i=%u) %f\n", i, hrValue);
+        }
+        else {
+            UA_Byte hrValue = *(UA_Byte *)val.value.data;
+            printf("DoubleValue (i=%u) %d\n", i, hrValue);
+        }
+
+        /* Print status and timestamps */
+        if (val.hasStatus)
+            printf("Status 0x%08x\n", val.status);
+        if (val.hasServerTimestamp) {
+            UA_DateTimeStruct dts = UA_DateTime_toStruct(val.serverTimestamp);
+            printf("ServerTime: %02u-%02u-%04u %02u:%02u:%02u.%03u\n",
+                dts.day, dts.month, dts.year, dts.hour, dts.min, dts.sec, dts.milliSec);
+        }
+        if (val.hasSourceTimestamp) {
+            UA_DateTimeStruct dts = UA_DateTime_toStruct(val.sourceTimestamp);
+            printf("ServerTime: %02u-%02u-%04u %02u:%02u:%02u.%03u\n",
+                dts.day, dts.month, dts.year, dts.hour, dts.min, dts.sec, dts.milliSec);
+        }
+        printf("\n");
+    }
+
+    /* We want more data! */
+    return true;
+}
+
+int main(int argc, char *argv[]) {
+    UA_Client *client = UA_Client_new(UA_ClientConfig_default);
+
+    /* Connect to the Unified Automation demo server */
+    UA_StatusCode retval = UA_Client_connect(client, "opc.tcp://localhost:48020");
+    if(retval != UA_STATUSCODE_GOOD) {
+        UA_Client_delete(client);
+        return (int)retval;
+    }
+
+    /* Read, if data logging is active */
+    UA_Boolean active = UA_FALSE;
+    printf("Reading, if data logging is active (4, \"Demo.History.DataLoggerActive\"):\n");
+    UA_Variant *val = UA_Variant_new();
+    retval = UA_Client_readValueAttribute(client, UA_NODEID_STRING(4, "Demo.History.DataLoggerActive"), val);
+    if (retval == UA_STATUSCODE_GOOD && UA_Variant_isScalar(val) &&
+        val->type == &UA_TYPES[UA_TYPES_BOOLEAN]) {
+        active = *(UA_Boolean*)val->data;
+        if (active) 
+            printf("The data logging is active. Continue.\n");
+        else
+            printf("The data logging is not active!\n");
+    }
+    else {
+        printf("Failed to read data logging status.\n");
+        UA_Variant_delete(val);
+        goto cleanup;
+    }
+    UA_Variant_delete(val);
+
+#ifdef UA_ENABLE_METHODCALLS
+    /* Active server side data logging via remote method call */
+    if (!active) {
+        printf("Activate data logging.\n");
+        retval = UA_Client_call(client, UA_NODEID_NUMERIC(0, UA_NS0ID_OBJECTSFOLDER),
+            UA_NODEID_STRING(4, "Demo.History.StartLogging"), 0, NULL, NULL, NULL);
+        if (retval != UA_STATUSCODE_GOOD) {
+            printf("Start method call failed.\n");
+            goto cleanup;
+        }
+
+        /* The server logs a value every 50ms by default */
+        printf("Successfully started data logging. Let the server collect data for 2000ms..\n");
+        UA_sleep_ms(2000);
+    }
+#else
+    if (!active) {
+        printf("Method calling is not allowed, you have to active the data logging manually.\n");
+        goto cleanup;
+    }
+#endif
+
+    /* Read historical values (Byte) */
+    printf("\nStart historical read (4, \"Demo.History.ByteWithHistory\"):\n");
+    retval = UA_Client_readHistorical_raw(client, UA_NODEID_STRING(4, "Demo.History.ByteWithHistory"), readHist,
+        UA_DateTime_fromUnixTime(0), UA_DateTime_now(), false, 10, UA_TIMESTAMPSTORETURN_BOTH, (void *)UA_FALSE);
+    
+    if (retval != UA_STATUSCODE_GOOD) {
+        printf("Failed.\n");
+        goto cleanup;
+    }
+
+    /* Read historical values (Double) */
+    printf("\nStart historical read (4, \"Demo.History.DoubleWithHistory\"):\n");
+    retval = UA_Client_readHistorical_raw(client, UA_NODEID_STRING(4, "Demo.History.DoubleWithHistory"), readHist,
+        UA_DateTime_fromUnixTime(0), UA_DateTime_now(), false, 10, UA_TIMESTAMPSTORETURN_BOTH, (void *)UA_TRUE);
+
+    if (retval != UA_STATUSCODE_GOOD) {
+        printf("Failed.\n");
+    }
+
+cleanup:
+    UA_Client_disconnect(client);
+    UA_Client_delete(client);
+    return (int) retval;
+}

+ 12 - 1
include/ua_client.h

@@ -257,7 +257,18 @@ UA_Client_Service_write(UA_Client *client, const UA_WriteRequest request) {
     return response;
 }
 
-/*
+/**
+* Historical Access Service Set
+* ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ */
+static UA_INLINE UA_HistoryReadResponse
+UA_Client_Service_history_read(UA_Client *client, const UA_HistoryReadRequest request) {
+    UA_HistoryReadResponse response;
+    __UA_Client_Service(client, &request, &UA_TYPES[UA_TYPES_HISTORYREADREQUEST],
+        &response, &UA_TYPES[UA_TYPES_HISTORYREADRESPONSE]);
+    return response;
+}
+
+/**
  * Method Service Set
  * ^^^^^^^^^^^^^^^^^^ */
 #ifdef UA_ENABLE_METHODCALLS

+ 61 - 0
include/ua_client_highlevel.h

@@ -210,6 +210,67 @@ UA_Client_readUserExecutableAttribute(UA_Client *client, const UA_NodeId nodeId,
                                      &UA_TYPES[UA_TYPES_BOOLEAN]);
 }
 
+/**
+ * Historical Access
+ * ^^^^^^^^^^^^^^^^^
+ * The following functions can be used to read a single node historically.
+ * Use the regular service to read several nodes at once.
+ */
+typedef UA_Boolean
+(*UA_HistoricalIteratorCallback)(const UA_NodeId nodeId, const UA_Boolean isInverse,
+                                 const UA_Boolean moreDataAvailable,
+                                 const UA_HistoryData *data, void *handle);
+
+/* Don't call this function, use the typed versions */
+UA_HistoryReadResponse UA_EXPORT
+__UA_Client_readHistorical(UA_Client *client, const UA_NodeId nodeId,
+                           void* details, const UA_TimestampsToReturn timestampsToReturn,
+                           const UA_ByteString continuationPoint, const UA_Boolean releaseConti);
+
+/* Don't call this function, use the typed versions */
+UA_StatusCode UA_EXPORT
+__UA_Client_readHistorical_service(UA_Client *client, const UA_NodeId nodeId,
+                                   const UA_HistoricalIteratorCallback callback,
+                                   void *details, const UA_TimestampsToReturn timestampsToReturn,
+                                   UA_Boolean isInverse, UA_UInt32 maxItems, void *handle);
+
+/* Don't call this function, use the typed versions */
+UA_StatusCode UA_EXPORT
+__UA_Client_readHistorical_service_rawMod(UA_Client *client, const UA_NodeId nodeId,
+                                          const UA_HistoricalIteratorCallback callback,
+                                          const UA_DateTime startTime, const UA_DateTime endTime,
+                                          const UA_Boolean returnBounds, const UA_UInt32 maxItems,
+                                          const UA_Boolean readModified, const UA_TimestampsToReturn timestampsToReturn,
+                                          void *handle);
+
+UA_StatusCode UA_EXPORT
+UA_Client_readHistorical_events(UA_Client *client, const UA_NodeId nodeId,
+                                const UA_HistoricalIteratorCallback callback,
+                                const UA_DateTime startTime, const UA_DateTime endTime,
+                                const UA_EventFilter filter, const UA_UInt32 maxItems,
+                                const UA_TimestampsToReturn timestampsToReturn, void* handle);
+
+UA_StatusCode UA_EXPORT
+UA_Client_readHistorical_raw(UA_Client *client, const UA_NodeId nodeId,
+                             const UA_HistoricalIteratorCallback callback,
+                             const UA_DateTime startTime, const UA_DateTime endTime,
+                             const UA_Boolean returnBounds, const UA_UInt32 maxItems,
+                             const UA_TimestampsToReturn timestampsToReturn, void *handle);
+
+UA_StatusCode UA_EXPORT
+UA_Client_readHistorical_modified(UA_Client *client, const UA_NodeId nodeId,
+                                  const UA_HistoricalIteratorCallback callback,
+                                  const UA_DateTime startTime, const UA_DateTime endTime,
+                                  const UA_Boolean returnBounds, const UA_UInt32 maxItems,
+                                  const UA_TimestampsToReturn timestampsToReturn, void *handle);
+
+UA_StatusCode UA_EXPORT
+UA_Client_readHistorical_atTime(UA_Client *client, const UA_NodeId nodeId,
+                                UA_DateTime *timestamps, const size_t size,
+                                const UA_Boolean interpolate, const UA_TimestampsToReturn timestampsToReturn,
+                                UA_HistoryData *outData);
+
+
 /**
  * Write Attributes
  * ^^^^^^^^^^^^^^^^

+ 159 - 0
src/client/ua_client_highlevel.c

@@ -651,3 +651,162 @@ UA_StatusCode __UA_Client_translateBrowsePathsToNodeIds_async(UA_Client *client,
     UA_BrowsePath_deleteMembers(&browsePath);
     return retval;
 }
+
+/*********************/
+/* Historical Access */
+/*********************/
+
+// FIXME: Function for ReadProcessedDetails is missing
+UA_HistoryReadResponse
+__UA_Client_readHistorical(UA_Client *client, const UA_NodeId nodeId,
+                           void* details, const UA_TimestampsToReturn timestampsToReturn,
+                           const UA_ByteString continuationPoint, const UA_Boolean releaseConti) {
+
+    UA_HistoryReadValueId item;
+    UA_HistoryReadValueId_init(&item);
+
+    item.nodeId = nodeId;
+    item.indexRange = UA_STRING_NULL; // TODO: NumericRange (?)
+    item.continuationPoint = continuationPoint;
+    item.dataEncoding = UA_QUALIFIEDNAME(0, "Default Binary");
+
+    UA_HistoryReadRequest request;
+    UA_HistoryReadRequest_init(&request);
+
+    request.nodesToRead = &item;
+    request.nodesToReadSize = 1;
+    request.timestampsToReturn = timestampsToReturn; // Defaults to Source
+    request.releaseContinuationPoints = releaseConti; // No values are returned, if true
+
+    /* Build ReadDetails */
+    request.historyReadDetails.content.decoded.type = &UA_TYPES[UA_TYPES_READRAWMODIFIEDDETAILS];
+    request.historyReadDetails.content.decoded.data = details;
+    request.historyReadDetails.encoding = UA_EXTENSIONOBJECT_DECODED;
+
+    return UA_Client_Service_history_read(client, request);
+}
+
+UA_StatusCode
+__UA_Client_readHistorical_service(UA_Client *client, const UA_NodeId nodeId,
+                                   const UA_HistoricalIteratorCallback callback,
+                                   void *details, const UA_TimestampsToReturn timestampsToReturn,
+                                   UA_Boolean isInverse, UA_UInt32 maxItems, void *handle) {
+
+    UA_ByteString continuationPoint = UA_BYTESTRING_NULL;
+    UA_Boolean continuationAvail = UA_FALSE;
+    UA_Boolean fetchMore = UA_FALSE;
+    UA_StatusCode retval = UA_STATUSCODE_GOOD;
+
+    do {
+        /* We release the continuation point, if no more data is requested by the user */
+        UA_Boolean cleanup = !fetchMore && continuationAvail;
+        UA_HistoryReadResponse response =
+            __UA_Client_readHistorical(client, nodeId, details, timestampsToReturn, continuationPoint, cleanup);
+
+        if (cleanup) {
+            retval = response.responseHeader.serviceResult;
+cleanup:    UA_HistoryReadResponse_deleteMembers(&response);
+            return retval;
+        }
+
+        retval = response.responseHeader.serviceResult;
+        if (retval == UA_STATUSCODE_GOOD) {
+            if (response.resultsSize == 1)
+                retval = response.results[0].statusCode;
+            else
+                retval = UA_STATUSCODE_BADUNEXPECTEDERROR;
+        }
+        if (retval != UA_STATUSCODE_GOOD)
+            goto cleanup;
+
+        UA_HistoryReadResult *res = response.results;
+        UA_HistoryData *data = (UA_HistoryData*)res->historyData.content.decoded.data;
+
+        /* We should never receive more values, than requested */
+        if (maxItems && data->dataValuesSize > maxItems) {
+            retval = UA_STATUSCODE_BADUNEXPECTEDERROR;
+            goto cleanup;
+        }
+
+        /* Clear old and check / store new continuation point */
+        UA_ByteString_deleteMembers(&continuationPoint);
+        UA_ByteString_copy(&res->continuationPoint, &continuationPoint);
+        continuationAvail = !UA_ByteString_equal(&continuationPoint, &UA_BYTESTRING_NULL);
+
+        /* Client callback with posibility to request further values */
+        fetchMore = callback(nodeId, isInverse, continuationAvail, data, handle);
+
+        /* Regular cleanup */
+        UA_HistoryReadResponse_deleteMembers(&response);
+    } while (continuationAvail);
+
+    return retval;
+}
+
+UA_StatusCode
+UA_Client_readHistorical_events(UA_Client *client, const UA_NodeId nodeId,
+                                const UA_HistoricalIteratorCallback callback,
+                                const UA_DateTime startTime, const UA_DateTime endTime,
+                                const UA_EventFilter filter, const UA_UInt32 maxItems,
+                                const UA_TimestampsToReturn timestampsToReturn, void* handle) {
+
+    // TODO: ReadProcessedDetails / ReadAtTimeDetails
+    UA_ReadEventDetails details;
+    UA_ReadEventDetails_init(&details);
+    details.filter = filter;
+
+    // At least two of the following parameters must be set
+    details.numValuesPerNode = maxItems; // 0 = return all / max server is capable of
+    details.startTime = startTime;
+    details.endTime = endTime;
+
+    UA_Boolean isInverse = !startTime || (endTime && (startTime > endTime));
+    return __UA_Client_readHistorical_service(client, nodeId, callback, &details,
+                                              timestampsToReturn, isInverse, 0, handle);
+}
+
+UA_StatusCode
+__UA_Client_readHistorical_service_rawMod(UA_Client *client, const UA_NodeId nodeId,
+                                          const UA_HistoricalIteratorCallback callback,
+                                          const UA_DateTime startTime, const UA_DateTime endTime,
+                                          const UA_Boolean returnBounds, const UA_UInt32 maxItems,
+                                          const UA_Boolean readModified, const UA_TimestampsToReturn timestampsToReturn,
+                                          void *handle) {
+
+    // TODO: ReadProcessedDetails / ReadAtTimeDetails
+    UA_ReadRawModifiedDetails details;
+    UA_ReadRawModifiedDetails_init(&details);
+    details.isReadModified = readModified; // Return only modified values
+    details.returnBounds = returnBounds;   // Return values pre / post given range
+
+    // At least two of the following parameters must be set
+    details.numValuesPerNode = maxItems;   // 0 = return all / max server is capable of
+    details.startTime = startTime;
+    details.endTime = endTime;
+
+    UA_Boolean isInverse = !startTime || (endTime && (startTime > endTime));
+    return __UA_Client_readHistorical_service(client, nodeId, callback, &details,
+                                              timestampsToReturn, isInverse, maxItems, handle);
+}
+
+UA_StatusCode
+UA_Client_readHistorical_raw(UA_Client *client, const UA_NodeId nodeId,
+                             const UA_HistoricalIteratorCallback callback,
+                             const UA_DateTime startTime, const UA_DateTime endTime,
+                             const UA_Boolean returnBounds, const UA_UInt32 maxItems,
+                             const UA_TimestampsToReturn timestampsToReturn, void *handle) {
+
+    return __UA_Client_readHistorical_service_rawMod(client, nodeId, callback, startTime, endTime, returnBounds,
+                                                     maxItems, UA_FALSE, timestampsToReturn, handle);
+}
+
+UA_StatusCode
+UA_Client_readHistorical_modified(UA_Client *client, const UA_NodeId nodeId,
+                                  const UA_HistoricalIteratorCallback callback,
+                                  const UA_DateTime startTime, const UA_DateTime endTime,
+                                  const UA_Boolean returnBounds, const UA_UInt32 maxItems,
+                                  const UA_TimestampsToReturn timestampsToReturn, void *handle) {
+
+    return __UA_Client_readHistorical_service_rawMod(client, nodeId, callback, startTime, endTime, returnBounds,
+                                                     maxItems, UA_TRUE, timestampsToReturn, handle);
+}

+ 19 - 0
tools/schema/datatypes_minimal.txt

@@ -115,3 +115,22 @@ UtcTime
 LocaleId
 RedundancySupport
 ServerDiagnosticsSummaryDataType
+RedundantServerDataType
+NetworkGroupDataType
+NumericRange
+EndpointUrlListDataType
+ModelChangeStructureDataType
+SemanticChangeStructureDataType
+TimeZoneDataType
+AggregateConfiguration
+AggregateFilter
+SetTriggeringRequest
+SetTriggeringResponse
+HistoryReadValueId
+HistoryReadResult
+HistoryReadDetails
+ReadRawModifiedDetails
+ReadEventDetails
+HistoryData
+HistoryReadRequest
+HistoryReadResponse