瀏覽代碼

History read interface

Peter Rustler 5 年之前
父節點
當前提交
8d6d980538

+ 17 - 0
CMakeLists.txt

@@ -255,6 +255,16 @@ if(UA_BUILD_FUZZING)
     add_definitions(-DFUZZING_BUILD_MODE_UNSAFE_FOR_PRODUCTION)
 endif()
 
+if(UA_ENABLE_HISTORIZING)
+    set(historizing_exported_headers
+        ${PROJECT_SOURCE_DIR}/include/ua_plugin_history_data_service.h
+        )
+    set(historizing_default_plugin_headers
+        )
+    set(historizing_default_plugin_sources
+        )
+endif()
+
 option(UA_BUILD_FUZZING_CORPUS "Build the fuzzing corpus" OFF)
 mark_as_advanced(UA_BUILD_FUZZING_CORPUS)
 if(UA_BUILD_FUZZING_CORPUS)
@@ -426,6 +436,7 @@ set(exported_headers ${exported_headers}
                      ${PROJECT_BINARY_DIR}/src_generated/ua_types_generated_handling.h
                      ${PROJECT_SOURCE_DIR}/include/ua_util.h
                      ${PROJECT_SOURCE_DIR}/include/ua_server.h
+                     ${historizing_exported_headers}
                      ${PROJECT_SOURCE_DIR}/include/ua_plugin_log.h
                      ${PROJECT_SOURCE_DIR}/include/ua_plugin_network.h
                      ${PROJECT_SOURCE_DIR}/include/ua_plugin_access_control.h
@@ -520,6 +531,7 @@ set(lib_sources ${PROJECT_SOURCE_DIR}/src/ua_types.c
                 ${PROJECT_SOURCE_DIR}/deps/base64.c)
 
 set(default_plugin_headers ${PROJECT_SOURCE_DIR}/plugins/ua_accesscontrol_default.h
+                           ${historizing_default_plugin_headers}
                            ${PROJECT_SOURCE_DIR}/plugins/ua_pki_certificate.h
                            ${PROJECT_SOURCE_DIR}/plugins/ua_log_stdout.h
                            ${PROJECT_SOURCE_DIR}/plugins/ua_nodestore_default.h
@@ -529,6 +541,7 @@ set(default_plugin_headers ${PROJECT_SOURCE_DIR}/plugins/ua_accesscontrol_defaul
 
 set(default_plugin_sources ${PROJECT_SOURCE_DIR}/plugins/ua_log_stdout.c
                            ${PROJECT_SOURCE_DIR}/plugins/ua_accesscontrol_default.c
+                           ${historizing_default_plugin_sources}
                            ${PROJECT_SOURCE_DIR}/plugins/ua_pki_certificate.c
                            ${PROJECT_SOURCE_DIR}/plugins/ua_nodestore_default.c
                            ${PROJECT_SOURCE_DIR}/plugins/ua_config_default.c
@@ -602,6 +615,10 @@ else()
         list(APPEND UA_FILE_DATATYPES ${PROJECT_SOURCE_DIR}/tools/schema/datatypes_subscriptions.txt)
     endif()
 
+    if(UA_ENABLE_HISTORIZING)
+        list(APPEND UA_FILE_DATATYPES ${PROJECT_SOURCE_DIR}/tools/schema/datatypes_historizing.txt)
+    endif()
+
     if(UA_ENABLE_DISCOVERY)
         list(APPEND UA_FILE_DATATYPES ${PROJECT_SOURCE_DIR}/tools/schema/datatypes_discovery.txt)
     endif()

+ 89 - 0
include/ua_plugin_history_data_service.h

@@ -0,0 +1,89 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
+ *
+ *    Copyright 2018 (c) basysKom GmbH <opensource@basyskom.com> (Author: Peter Rustler)
+ */
+
+#ifndef UA_PLUGIN_HISTORY_DATA_SERVICE_H_
+#define UA_PLUGIN_HISTORY_DATA_SERVICE_H_
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+#include "ua_types.h"
+#include "ua_server.h"
+
+struct UA_HistoryDataService;
+typedef struct UA_HistoryDataService UA_HistoryDataService;
+
+struct UA_HistoryDataService {
+    void *context;
+
+    void
+    (*deleteMembers)(UA_HistoryDataService *service);
+
+    /* This function will be called when a nodes value is set.
+     * Use this to insert data into your database(s) if polling is not suitable
+     * and you need to get all data changes.
+     * Set it to NULL if you do not need it.
+     *
+     * server is the server this node lives in.
+     * hdsContext is the context of the UA_HistoryDataService. UA_HistoryDataService.context
+     * sessionId and sessionContext identify the session which set this value.
+     * nodeId is the node id for which data was set.
+     * historizing is the nodes boolean flag for historizing
+     * value is the new value.
+     */
+    void
+    (*setValue)(UA_Server *server,
+                void *hdsContext,
+                const UA_NodeId *sessionId,
+                void *sessionContext,
+                const UA_NodeId *nodeId,
+                UA_Boolean historizing,
+                const UA_DataValue *value);
+
+    /* This function is called if a history read is requested
+     * with isRawReadModified set to false.
+     * Setting it to NULL will result in a response with statuscode UA_STATUSCODE_BADHISTORYOPERATIONUNSUPPORTED.
+     *
+     * server is the server this node lives in.
+     * hdsContext is the context of the UA_HistoryDataService. UA_HistoryDataService.context
+     * sessionId and sessionContext identify the session which set this value.
+     * requestHeader, historyReadDetails, timestampsToReturn, releaseContinuationPoints
+     * nodesToReadSize and nodesToRead is the requested data from the client. It is from the request object.
+     * response the response to fill for the client. If the request is ok, there is no need to use it.
+     *          Use this to set status codes other than "Good" or other data.
+     *          You find an already allocated UA_HistoryReadResult array with an UA_HistoryData object
+     *          in the extension object in the size of nodesToReadSize. If you are not willing to return data,
+     *          you have to delete the results array, set it to NULL and set the resultsSize to 0.
+     *          Do not access historyData after that.
+     * historyData is a proper typed pointer array pointing in the UA_HistoryReadResult extension object.
+     *             use this to provide result data to the client.
+     *             Index in the array is the same as in nodesToRead and the UA_HistoryReadResult array.
+     */
+    void
+    (*readRaw)(UA_Server *server,
+               void *hdsContext,
+               const UA_NodeId *sessionId,
+               void *sessionContext,
+               const UA_RequestHeader *requestHeader,
+               const UA_ReadRawModifiedDetails *historyReadDetails,
+               UA_TimestampsToReturn timestampsToReturn,
+               UA_Boolean releaseContinuationPoints,
+               size_t nodesToReadSize,
+               const UA_HistoryReadValueId *nodesToRead,
+               UA_HistoryReadResponse *response,
+               UA_HistoryData * const * const historyData);
+
+    // Add more function pointer here.
+    // For example for read_event, read_modified, read_processed, read_at_time
+};
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif /* UA_PLUGIN_HISTORY_DATA_SERVICE_H_ */

+ 1 - 1
include/ua_plugin_nodestore.h

@@ -188,7 +188,7 @@ typedef struct {
     UA_NODE_VARIABLEATTRIBUTES
     UA_Byte accessLevel;
     UA_Double minimumSamplingInterval;
-    UA_Boolean historizing; /* currently unsupported */
+    UA_Boolean historizing;
 } UA_VariableNode;
 
 /**

+ 10 - 2
include/ua_server.h

@@ -291,8 +291,7 @@ UA_Server_readExecutable(UA_Server *server, const UA_NodeId nodeId,
  * - UserWriteMask
  * - UserAccessLevel
  * - UserExecutable
- *
- * Historizing is currently unsupported */
+ */
 
 /* Overwrite an attribute of a node. The specialized functions below provide a
  * more concise syntax.
@@ -407,6 +406,15 @@ UA_Server_writeMinimumSamplingInterval(UA_Server *server, const UA_NodeId nodeId
                              &miniumSamplingInterval);
 }
 
+static UA_INLINE UA_StatusCode
+UA_Server_writeHistorizing(UA_Server *server, const UA_NodeId nodeId,
+                          const UA_Boolean historizing) {
+    return __UA_Server_write(server, &nodeId,
+                             UA_ATTRIBUTEID_HISTORIZING,
+                             &UA_TYPES[UA_TYPES_BOOLEAN],
+                             &historizing);
+}
+
 static UA_INLINE UA_StatusCode
 UA_Server_writeExecutable(UA_Server *server, const UA_NodeId nodeId,
                           const UA_Boolean executable) {

+ 6 - 0
include/ua_server_config.h

@@ -27,6 +27,10 @@ extern "C" {
 #include "ua_plugin_pubsub.h"
 #endif
 
+#ifdef UA_ENABLE_HISTORIZING
+#include "ua_plugin_history_data_service.h"
+#endif
+
 /**
  * .. _server-configuration:
  *
@@ -169,6 +173,8 @@ struct UA_ServerConfig {
 
     /* Historical Access */
 #ifdef UA_ENABLE_HISTORIZING
+    UA_HistoryDataService historyDataService;
+    
     UA_Boolean accessHistoryDataCapability;
     UA_UInt32  maxReturnDataValues; /* 0 -> unlimited size */
     

+ 6 - 0
plugins/ua_config_default.c

@@ -653,6 +653,12 @@ UA_ServerConfig_delete(UA_ServerConfig *config) {
     /* Access Control */
     config->accessControl.deleteMembers(&config->accessControl);
 
+    /* Historical data */
+#ifdef UA_ENABLE_HISTORIZING
+    if (config->historyDataService.deleteMembers)
+        config->historyDataService.deleteMembers(&config->historyDataService);
+#endif
+
     UA_free(config);
 }
 

+ 8 - 0
src/server/ua_server_binary.c

@@ -221,6 +221,14 @@ getServicePointers(UA_UInt32 requestTypeId, const UA_DataType **requestType,
         *responseType = &UA_TYPES[UA_TYPES_SETMONITORINGMODERESPONSE];
         break;
 #endif
+#ifdef UA_ENABLE_HISTORIZING
+        /* For History read */
+    case UA_NS0ID_HISTORYREADREQUEST_ENCODING_DEFAULTBINARY:
+        *service = (UA_Service)Service_HistoryRead;
+        *requestType = &UA_TYPES[UA_TYPES_HISTORYREADREQUEST];
+        *responseType = &UA_TYPES[UA_TYPES_HISTORYREADRESPONSE];
+        break;
+#endif
 
 #ifdef UA_ENABLE_METHODCALLS
     case UA_NS0ID_CALLREQUEST_ENCODING_DEFAULTBINARY:

+ 6 - 0
src/server/ua_services.h

@@ -408,6 +408,12 @@ void Service_SetMonitoringMode(UA_Server *server, UA_Session *session,
                                const UA_SetMonitoringModeRequest *request,
                                UA_SetMonitoringModeResponse *response);
 
+#ifdef UA_ENABLE_HISTORIZING
+void Service_HistoryRead(UA_Server *server, UA_Session *session,
+                         const UA_HistoryReadRequest *request,
+                         UA_HistoryReadResponse *response);
+#endif
+
 /**
  * SetTriggering Service
  * ^^^^^^^^^^^^^^^^^^^^^

+ 67 - 0
src/server/ua_services_attribute.c

@@ -1100,6 +1100,16 @@ writeValueAttribute(UA_Server *server, UA_Session *session,
         else
             retval = writeValueAttributeWithRange(node, &adjustedValue, rangeptr);
 
+#ifdef UA_ENABLE_HISTORIZING
+        if(retval == UA_STATUSCODE_GOOD && server->config.historyDataService.setValue)
+            server->config.historyDataService.setValue(server,
+                                                       server->config.historyDataService.context,
+                                                       &session->sessionId,
+                                                       session->sessionHandle,
+                                                       &node->nodeId,
+                                                       node->historizing,
+                                                       &adjustedValue);
+#endif
         /* Callback after writing */
         if(retval == UA_STATUSCODE_GOOD && node->value.data.callback.onWrite)
             node->value.data.callback.onWrite(server, &session->sessionId,
@@ -1405,6 +1415,63 @@ __UA_Server_write(UA_Server *server, const UA_NodeId *nodeId,
     return UA_Server_write(server, &wvalue);
 }
 
+#ifdef UA_ENABLE_HISTORIZING
+
+#include "ua_plugin_history_data_service.h"
+
+void
+Service_HistoryRead(UA_Server *server,
+                    UA_Session *session,
+                    const UA_HistoryReadRequest *request,
+                    UA_HistoryReadResponse *response) {
+    if (request->historyReadDetails.encoding != UA_EXTENSIONOBJECT_DECODED) {
+        response->responseHeader.serviceResult = UA_STATUSCODE_BADNOTSUPPORTED;
+        return;
+    }
+    if (request->historyReadDetails.content.decoded.type == &UA_TYPES[UA_TYPES_READRAWMODIFIEDDETAILS]) {
+        UA_ReadRawModifiedDetails * details = (UA_ReadRawModifiedDetails*)request->historyReadDetails.content.decoded.data;
+        if (details->isReadModified) {
+            // TODO add server->config.historyReadService.read_modified
+            response->responseHeader.serviceResult = UA_STATUSCODE_BADHISTORYOPERATIONUNSUPPORTED;
+            return;
+        } else {
+            if (server->config.historyDataService.readRaw) {
+                response->resultsSize = request->nodesToReadSize;
+
+                response->results = (UA_HistoryReadResult*)UA_Array_new(response->resultsSize, &UA_TYPES[UA_TYPES_HISTORYREADRESULT]);
+                UA_HistoryData ** historyData = (UA_HistoryData **)UA_calloc(response->resultsSize, sizeof(UA_HistoryData*));
+                for (size_t i = 0; i < response->resultsSize; ++i) {
+                    UA_HistoryData * data = UA_HistoryData_new();
+                    response->results[i].historyData.encoding = UA_EXTENSIONOBJECT_DECODED;
+                    response->results[i].historyData.content.decoded.type = &UA_TYPES[UA_TYPES_HISTORYDATA];
+                    response->results[i].historyData.content.decoded.data = data;
+                    historyData[i] = data;
+                }
+                server->config.historyDataService.readRaw(server,
+                                                          server->config.historyDataService.context,
+                                                          &session->sessionId,
+                                                          session->sessionHandle,
+                                                          &request->requestHeader,
+                                                          details,
+                                                          request->timestampsToReturn,
+                                                          request->releaseContinuationPoints,
+                                                          request->nodesToReadSize,
+                                                          request->nodesToRead,
+                                                          response,
+                                                          historyData);
+                UA_free(historyData);
+                return;
+            }
+            response->responseHeader.serviceResult = UA_STATUSCODE_BADHISTORYOPERATIONUNSUPPORTED;
+            return;
+        }
+    }
+    // TODO handle more request->historyReadDetails.content.decoded.type types
+    response->responseHeader.serviceResult = UA_STATUSCODE_BADHISTORYOPERATIONUNSUPPORTED;
+    return;
+}
+#endif
+
 UA_StatusCode UA_EXPORT
 UA_Server_writeObjectProperty(UA_Server *server, const UA_NodeId objectId,
                               const UA_QualifiedName propertyName,

+ 14 - 0
tools/schema/datatypes_historizing.txt

@@ -0,0 +1,14 @@
+HistoryReadRequest
+HistoryReadResponse
+HistoryReadResult
+HistoryReadValueId
+ReadEventDetails
+ReadRawModifiedDetails
+ReadProcessedDetails
+ReadAtTimeDetails
+HistoryData
+HistoryModifiedData
+HistoryUpdateType
+ModificationInfo
+HistoryEvent
+HistoryEventFieldList