学习open62541 --- [29] 数据源的使用细节

这篇文章中简单介绍了数据源的使用,本文会进一步介绍数据源的使用细节,可以让我们更好的使用它。


一 数据源API

数据源的API有以下几个,

UA_StatusCode
UA_Server_addDataSourceVariableNode(UA_Server *server, const UA_NodeId requestedNewNodeId,
                                    const UA_NodeId parentNodeId, const UA_NodeId referenceTypeId,
                                    const UA_QualifiedName browseName, const UA_NodeId typeDefinition,
                                    const UA_VariableAttributes attr, const UA_DataSource dataSource,
                                    void *nodeContext, UA_NodeId *outNewNodeId);

UA_StatusCode
UA_Server_setVariableNode_dataSource(UA_Server *server, const UA_NodeId nodeId,
                                     const UA_DataSource dataSource); 

UA_StatusCode
UA_Server_setNodeContext(UA_Server *server, UA_NodeId nodeId,
                         void *nodeContext);

从API的名称可以看出,

  • 第一个函数可以创建变量节点,并同时添加数据源callback和nodeContext
  • 第二个函数是给已经存在的变量节点添加数据源回调
  • 第三个函数是给已经存在的变量节点添加nodeContext

第二个和第三个函数适用于变量节点已经创建的情况下。本文先介绍第一个API,再介绍后2个。


二 代码解释

首先,我们写一个函数用来给变量添加数据源,如下,

static UA_StatusCode addDataSourceForVariable(UA_Server *server, UA_NodeId varId, 
						UA_NodeId parentNodeId, UA_NodeId referenceType, const char *name) 
{
    
    
    UA_StatusCode retval;
    
    /*
    ** 设置变量属性,变量的data type是UA_INT32
    */
    UA_VariableAttributes attr = UA_VariableAttributes_default;
    attr.description = UA_LOCALIZEDTEXT((char*)"en-US", (char*)name);
    attr.displayName = UA_LOCALIZEDTEXT((char*)"en-US", (char*)name);
    attr.dataType = UA_TYPES[UA_TYPES_INT32].typeId;
    attr.accessLevel = UA_ACCESSLEVELMASK_READ | UA_ACCESSLEVELMASK_WRITE;

    /*
    ** 设置变量的qualified name
    */
    UA_QualifiedName variableName = UA_QUALIFIEDNAME(1, (char*)name);
    UA_NodeId variableTypeNodeId = UA_NODEID_NUMERIC(0, UA_NS0ID_BASEDATAVARIABLETYPE);

    /*
    ** 准备数据源
    */
    UA_DataSource varDataSource;
    varDataSource.read = readForVariable;
    varDataSource.write = writeForVariable;
    
    /*
    ** 给nodeContext分配内存空间
    */
    UA_Int32 *nodeContext = UA_malloc(sizeof(UA_Int32));
    (*nodeContext) = 0;

    /*
    ** 给变量添加数据源
    */
    retval = UA_Server_addDataSourceVariableNode(server, varId, parentNodeId,
                                        	referenceType, variableName,
                                        	variableTypeNodeId, attr,
                                        	timeDataSource, nodeContext, NULL);

    return retval;
}

函数开始是初始化变量节点的属性,准备添加一个UA_Int32变量。

然后是设置数据源,其类型是结构体UA_DataSource,该结构体里只有2个函数指针,一个用来读,一个用来写,读指针传递的是函数readForVariable,写指针传递的是writeForVariable。

接着是设置nodeContext,这里使用UA_malloc分配一个UA_Int32的内存空间,用来存放该变量的值(也可以根据需要随意分配),注意nodeContext必须是动态分配的内存空间或者一个静态变量,因为是回调,肯定是在后续执行,要保证这段内存空间在回调被调用时还存在。

最后就是调用UA_Server_addDataSourceVariableNode()来添加数据源

readForVariable()和writeForVariable()的定义如下,

static UA_StatusCode readForVariable(UA_Server *server,
                const UA_NodeId *sessionId, void *sessionContext,
                const UA_NodeId *nodeId, void *nodeContext,
                UA_Boolean sourceTimeStamp, const UA_NumericRange *range,
                UA_DataValue *dataValue) 
{
    
    
    UA_LOG_INFO(UA_Log_Stdout, UA_LOGCATEGORY_USERLAND, "read variable value");
    
    UA_Int32 * currentVal = (UA_Int32*)nodeContext;

	UA_Variant_setScalarCopy(&dataValue->value, currentVal,
                         &UA_TYPES[UA_TYPES_INT32]);

    dataValue->hasValue = true;

    (*currentVal) += 1;


    return UA_STATUSCODE_GOOD;
}

static UA_StatusCode writeForVariable(UA_Server *server,
                 const UA_NodeId *sessionId, void *sessionContext,
                 const UA_NodeId *nodeId, void *nodeContext,
                 const UA_NumericRange *range, const UA_DataValue *data) 
{
    
    
    UA_Int32 * currentVal = (UA_Int32*)nodeContext;

    if (data->value.type == &UA_TYPES[UA_TYPES_INT32])
    {
    
    
    	UA_Int32 updateVal = *(UA_Int32 *)data->value.data;
    	UA_LOG_INFO(UA_Log_Stdout, UA_LOGCATEGORY_USERLAND,
                "write variable value: %d", updateVal);


    	(*currentVal) = updateVal;
    }


    return UA_STATUSCODE_GOOD;
}

两个函数都从nodeContext参数里获取添加数据源时分配的内存空间,然后对这段内存空间进行读和写。对于读来说,会把nodeContext上存放的值传递给参数dataValue;对于写来说,写入的值是在参数dataValue里,然后把里面包含的值传递给nodeContext

注意,还有个参数nodeId,存放的是变量节点的NodeId,用来表示回调是被哪个变量调用,本例子暂时没有用到。具体可以结合实际情况来决定是否需要这个参数。

main函数如下,

int main(void) 
{
    
    
    signal(SIGINT, stopHandler);
    signal(SIGTERM, stopHandler);

    UA_StatusCode retval;

    UA_Server *server = UA_Server_new();
    

    UA_ServerConfig_setDefault(UA_Server_getConfig(server));
    
    UA_NodeId varNodeId = UA_NODEID_NUMERIC(1, 8888);
    UA_NodeId parentNodeId = UA_NODEID_NUMERIC(0, UA_NS0ID_OBJECTSFOLDER);
    UA_NodeId referenceType = UA_NODEID_NUMERIC(0, UA_NS0ID_ORGANIZES);

    retval = addDataSourceForVariable(server, varNodeId, parentNodeId, 
   	                    referenceType, "testVariable1");
    if (retval != UA_STATUSCODE_GOOD)
    {
    
    
    	return retval;
    }


    varNodeId = UA_NODEID_NUMERIC(1, 9999);

    retval = addDataSourceForVariable(server, varNodeId, parentNodeId, 
                        referenceType, "testVariable2");
    if (retval != UA_STATUSCODE_GOOD)
    {
    
    
    	return retval;
    }
    
    
    retval = UA_Server_run(server, &running);
    
    UA_Server_delete(server);
    
    return retval == UA_STATUSCODE_GOOD ? EXIT_SUCCESS : EXIT_FAILURE;
    
}

这里添加了2个变量,testVariable1和testVariable2


三 整体代码及使用

整体代码如下,



#include <signal.h>
#include <stdlib.h>

#include "open62541.h"

UA_Boolean running = true;

static void stopHandler(int sign) {
    
    
    UA_LOG_INFO(UA_Log_Stdout, UA_LOGCATEGORY_SERVER, "received ctrl-c");
    running = false;
}


static UA_StatusCode readForVariable(UA_Server *server,
                const UA_NodeId *sessionId, void *sessionContext,
                const UA_NodeId *nodeId, void *nodeContext,
                UA_Boolean sourceTimeStamp, const UA_NumericRange *range,
                UA_DataValue *dataValue) 
{
    
    
    UA_LOG_INFO(UA_Log_Stdout, UA_LOGCATEGORY_USERLAND, "read variable value");
    
    UA_Int32 * currentVal = (UA_Int32*)nodeContext;

	UA_Variant_setScalarCopy(&dataValue->value, currentVal,
                         &UA_TYPES[UA_TYPES_INT32]);

    dataValue->hasValue = true;

    (*currentVal) += 1;


    return UA_STATUSCODE_GOOD;
}

static UA_StatusCode writeForVariable(UA_Server *server,
                 const UA_NodeId *sessionId, void *sessionContext,
                 const UA_NodeId *nodeId, void *nodeContext,
                 const UA_NumericRange *range, const UA_DataValue *data) 
{
    
    
    UA_Int32 * currentVal = (UA_Int32*)nodeContext;

    if (data->value.type == &UA_TYPES[UA_TYPES_INT32])
    {
    
    
    	UA_Int32 updateVal = *(UA_Int32 *)data->value.data;
    	UA_LOG_INFO(UA_Log_Stdout, UA_LOGCATEGORY_USERLAND,
                "write variable value: %d", updateVal);


    	(*currentVal) = updateVal;
    }


    return UA_STATUSCODE_GOOD;
}




static UA_StatusCode addDataSourceForVariable(UA_Server *server, UA_NodeId varId, 
						UA_NodeId parentNodeId, UA_NodeId referenceType, const char *name) 
{
    
    
    UA_StatusCode retval;

    UA_VariableAttributes attr = UA_VariableAttributes_default;
    attr.description = UA_LOCALIZEDTEXT((char*)"en-US", (char*)name);
    attr.displayName = UA_LOCALIZEDTEXT((char*)"en-US", (char*)name);
    attr.dataType = UA_TYPES[UA_TYPES_INT32].typeId;
    attr.accessLevel = UA_ACCESSLEVELMASK_READ | UA_ACCESSLEVELMASK_WRITE;

    UA_QualifiedName variableName = UA_QUALIFIEDNAME(1, (char*)name);
    UA_NodeId variableTypeNodeId = UA_NODEID_NUMERIC(0, UA_NS0ID_BASEDATAVARIABLETYPE);

    UA_DataSource varDataSource;
    varDataSource.read = readForVariable;
    varDataSource.write = writeForVariable;

    UA_Int32 *nodeContext = UA_malloc(sizeof(UA_Int32));
    (*nodeContext) = 0;


    retval = UA_Server_addDataSourceVariableNode(server, varId, parentNodeId,
                                        	referenceType, variableName,
                                        	variableTypeNodeId, attr,
                                        	timeDataSource, nodeContext, NULL);

    return retval;
}


int main(void) 
{
    
    
    signal(SIGINT, stopHandler);
    signal(SIGTERM, stopHandler);

    UA_StatusCode retval;

    UA_Server *server = UA_Server_new();
    

    UA_ServerConfig_setDefault(UA_Server_getConfig(server));
    
    UA_NodeId varNodeId = UA_NODEID_NUMERIC(1, 8888);
    UA_NodeId parentNodeId = UA_NODEID_NUMERIC(0, UA_NS0ID_OBJECTSFOLDER);
    UA_NodeId referenceType = UA_NODEID_NUMERIC(0, UA_NS0ID_ORGANIZES);

    retval = addDataSourceForVariable(server, varNodeId, parentNodeId, referenceType, "testVariable1");
    if (retval != UA_STATUSCODE_GOOD)
    {
    
    
    	return retval;
    }


    varNodeId = UA_NODEID_NUMERIC(1, 9999);

    retval = addDataSourceForVariable(server, varNodeId, parentNodeId, referenceType, "testVariable2");
    if (retval != UA_STATUSCODE_GOOD)
    {
    
    
    	return retval;
    }
    
    
    retval = UA_Server_run(server, &running);
    
    UA_Server_delete(server);
    
    return retval == UA_STATUSCODE_GOOD ? EXIT_SUCCESS : EXIT_FAILURE;
    
}

CMakeLists.txt内容如下,

cmake_minimum_required(VERSION 3.5)

project(myDataSource)

set(EXECUTABLE_OUTPUT_PATH  ${
    
    PROJECT_SOURCE_DIR}/bin)

add_definitions(-std=c99)

include_directories(${
    
    PROJECT_SOURCE_DIR}/open62541)
include_directories(${
    
    PROJECT_SOURCE_DIR}/src)

find_library(OPEN62541_LIB libopen62541.a HINTS ${
    
    PROJECT_SOURCE_DIR}/open62541)

add_executable(server ${
    
    PROJECT_SOURCE_DIR}/src/server.c)
target_link_libraries(server ${
    
    OPEN62541_LIB})

工程结构如下,
在这里插入图片描述
cd到build目录下,执行cmake .. && make,编译OK后运行,然后使用UaExpert连接,显式如下,
在这里插入图片描述
通过不断切换点击testVariable1和testVariable2,可以在右侧的Attributes窗口中看到它们的值在不断加1,在server端也可以看到打印,
在这里插入图片描述
说明读的回调被触发了。

我们在testVariable2的右侧Attributes窗口中双击Value属性,把当前值改成400,
在这里插入图片描述
然后在server端就会出现如下打印,说明写的回调被触发。
在这里插入图片描述
因为使用了nodeContext,这2个变量节点的值不会互相影响,因为每个节点都有自己的nodeContext

还需要注意一个问题,使用这个API添加数据源后,server启动时会去主动读取这个变量,还读了2次(因为是2个变量,所以出现4次read的回调打印),
在这里插入图片描述
至于为什么,暂时不清楚,如果不想出现这种情况,可以参考下一节的,即先添加变量再添加数据源和nodeContext。


四 对已经添加变量的处理

这里使用前面提到的第二个和第三个API来处理已经添加的变量,即,

UA_StatusCode
UA_Server_setVariableNode_dataSource(UA_Server *server, const UA_NodeId nodeId,
                                     const UA_DataSource dataSource);

UA_StatusCode
UA_Server_setNodeContext(UA_Server *server, UA_NodeId nodeId,
                         void *nodeContext);

如果搞懂第一个API的使用,那么第二个和第三个就很好理解了。代码如下,



#include <signal.h>
#include <stdlib.h>

#include "open62541.h"

UA_Boolean running = true;

static void stopHandler(int sign) {
    
    
    UA_LOG_INFO(UA_Log_Stdout, UA_LOGCATEGORY_SERVER, "received ctrl-c");
    running = false;
}

/*
** 添加变量
*/
static UA_NodeId addIntegerVariable(UA_Server *server, UA_NodeId parentNodeId, 
                    UA_NodeId referenceType, const char *name) 
{
    
    
    UA_StatusCode retval;

    /* Define the attribute of the myInteger variable node */
    UA_VariableAttributes attr = UA_VariableAttributes_default;
    UA_Int32 myInteger = 0;
    UA_Variant_setScalar(&attr.value, &myInteger, &UA_TYPES[UA_TYPES_INT32]);
    attr.description = UA_LOCALIZEDTEXT((char*)"en-US", (char*)name);
    attr.displayName = UA_LOCALIZEDTEXT((char*)"en-US", (char*)name);
    attr.dataType = UA_TYPES[UA_TYPES_INT32].typeId;
    attr.accessLevel = UA_ACCESSLEVELMASK_READ | UA_ACCESSLEVELMASK_WRITE;

    /* Add the variable node to the information model */
    UA_NodeId myIntegerNodeId ;
    UA_QualifiedName myIntegerName = UA_QUALIFIEDNAME(1, (char*)name);
    retval = UA_Server_addVariableNode(server, UA_NODEID_NULL, parentNodeId,
                                referenceType, myIntegerName,
                                UA_NODEID_NUMERIC(0, UA_NS0ID_BASEDATAVARIABLETYPE), 
                                attr, NULL, &myIntegerNodeId);

    if (retval == UA_STATUSCODE_GOOD)
    {
    
    
        return myIntegerNodeId;
    }
    else
    {
    
    
        return UA_NODEID_NULL;
    }
    
}


static UA_StatusCode readForVariable(UA_Server *server,
                const UA_NodeId *sessionId, void *sessionContext,
                const UA_NodeId *nodeId, void *nodeContext,
                UA_Boolean sourceTimeStamp, const UA_NumericRange *range,
                UA_DataValue *dataValue) 
{
    
    
    UA_LOG_INFO(UA_Log_Stdout, UA_LOGCATEGORY_USERLAND, "read variable value");
    
    UA_Int32 * currentVal = (UA_Int32*)nodeContext;

    UA_Variant_setScalarCopy(&dataValue->value, currentVal,
                         &UA_TYPES[UA_TYPES_INT32]);

    dataValue->hasValue = true;

    (*currentVal) += 1;


    return UA_STATUSCODE_GOOD;
}

static UA_StatusCode writeForVariable(UA_Server *server,
                 const UA_NodeId *sessionId, void *sessionContext,
                 const UA_NodeId *nodeId, void *nodeContext,
                 const UA_NumericRange *range, const UA_DataValue *data) 
{
    
    
    UA_Int32 * currentVal = (UA_Int32*)nodeContext;

    if (data->value.type == &UA_TYPES[UA_TYPES_INT32])
    {
    
    
        UA_Int32 updateVal = *(UA_Int32 *)data->value.data;
        UA_LOG_INFO(UA_Log_Stdout, UA_LOGCATEGORY_USERLAND,
                "write variable value: %d", updateVal);


        (*currentVal) = updateVal;
    }


    return UA_STATUSCODE_GOOD;
}


int main(void) 
{
    
    
    signal(SIGINT, stopHandler);
    signal(SIGTERM, stopHandler);

    UA_StatusCode retval;

    UA_Server *server = UA_Server_new();
    

    UA_ServerConfig_setDefault(UA_Server_getConfig(server));


    UA_NodeId parentNodeId = UA_NODEID_NUMERIC(0, UA_NS0ID_OBJECTSFOLDER);
    UA_NodeId referenceType = UA_NODEID_NUMERIC(0, UA_NS0ID_ORGANIZES);

    // 添加变量
    UA_NodeId retNodeId = addIntegerVariable(server, parentNodeId, 
                                referenceType, "myTestVariable");

    if (UA_NodeId_equal(&retNodeId, &UA_NODEID_NULL))
    {
    
    
        return -1;
    }
    
    // 准备数据源
    UA_DataSource varDataSource;
    varDataSource.read = readForVariable;
    varDataSource.write = writeForVariable;
    
    // 给变量添加数据源
    UA_Server_setVariableNode_dataSource(server, retNodeId, varDataSource);
    
    // 给nodeContext分配内存空间
    UA_Int32 *nodeContext = UA_malloc(sizeof(UA_Int32));
    (*nodeContext) = 0;

    // 给变量设置nodeConetext
    UA_Server_setNodeContext(server, retNodeId, nodeContext);

    retval = UA_Server_run(server, &running);
    
    UA_Server_delete(server);
    
    return retval == UA_STATUSCODE_GOOD ? EXIT_SUCCESS : EXIT_FAILURE;
    
}

main函数首先使用addIntegerVariable()添加一个变量myTestVariable,然后使用UA_Server_setVariableNode_dataSource()给其添加数据源,最后再使用UA_Server_setNodeContext添加nodeContext

编译ok后使用UaExpert进行连接,
在这里插入图片描述
通过来回在Server和myTestVariable之间进行切换点击,可以看到myTestVariable的值在不断加1,同理,也可以修改其值。


五 总结

本质主要讲述了如何使用数据源的相关API,以及一些注意事项,还有一些细微差异。总的来说,数据源是比较灵活的,用的也比较多。

如果有写的不对的地方,希望能留言指正,谢谢阅读。

猜你喜欢

转载自blog.csdn.net/whahu1989/article/details/106298733