基于open62541的opc ua 服务器开发实现
Posted 月下叉猹
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了基于open62541的opc ua 服务器开发实现相关的知识,希望对你有一定的参考价值。
关于opcua的介绍这里就不多说了,相信大家大都有了一些了解,open62541是一个开源C(C99)的opc-ua实现,开源代码可在官网或github上下载。
话不多说,首先搭建一个opcua服务器实例
1 #include <signal.h> 2 #include "open62541.h" 3 UA_Boolean running = true; 4 static void stopHandler(int sig)
{ 5 UA_LOG_INFO(UA_Log_Stdout, UA_LOGCATEGORY_USERLAND, "received ctrl-c"); 6 running = false; 7 } 8 int main(void) { 9 signal(SIGINT, stopHandler); 10 signal(SIGTERM, stopHandler); 11 UA_ServerConfig *config = UA_ServerConfig_new_default(); 12 UA_Server *server = UA_Server_new(config); 13 UA_StatusCode retval = UA_Server_run(server, &running); 14 UA_Server_delete(server); 15 UA_ServerConfig_delete(config); 16 return (int)retval; 17 }
以下代码演示如何使用数据类型以及如何向服务器添加变量节点。首先,我们向服务器添加一个新变量。查看UA变量属性结构的定义,以查看为变量节点定义的所有属性的列表。请注意,默认设置将变量值的访问级别设置为只读。有关使变量可写的信息,请参见下文。
1 #include <signal.h> 2 #include <stdio.h> 3 #include "open62541.h" 4 static void 5 addVariable(UA_Server *server) { 6 /* Define the attribute of the myInteger variable node */ 7 UA_VariableAttributes attr = UA_VariableAttributes_default; 8 UA_Int32 myInteger = 42; 9 UA_Variant_setScalar(&attr.value, &myInteger, &UA_TYPES[UA_TYPES_INT32]); 10 attr.description = UA_LOCALIZEDTEXT("en-US","the answer"); 11 attr.displayName = UA_LOCALIZEDTEXT("en-US","the answer"); 12 attr.dataType = UA_TYPES[UA_TYPES_INT32].typeId; 13 attr.accessLevel = UA_ACCESSLEVELMASK_READ | UA_ACCESSLEVELMASK_WRITE; 14 /* Add the variable node to the information model */ 15 UA_NodeId myIntegerNodeId = UA_NODEID_STRING(1, "the.answer"); 16 UA_QualifiedName myIntegerName = UA_QUALIFIEDNAME(1, "the answer"); 17 UA_NodeId parentNodeId = UA_NODEID_NUMERIC(0, UA_NS0ID_OBJECTSFOLDER); 18 UA_NodeId parentReferenceNodeId = UA_NODEID_NUMERIC(0, UA_NS0ID_ORGANIZES); 19 UA_Server_addVariableNode(server, myIntegerNodeId, parentNodeId, 20 parentReferenceNodeId, myIntegerName, 21 UA_NODEID_NUMERIC(0, UA_NS0ID_BASEDATAVARIABLETYPE),attr, NULL, NULL); 22 }
现在我们用 写服务 更改节点值。也可以通过网络由OPC UA的客户端访问来修改值 。
1 static void 2 writeVariable(UA_Server *server) { 3 UA_NodeId myIntegerNodeId = 4 UA_NODEID_STRING(1,"the.answer"); 5 6 /* Write a different integer value */ 7 UA_Int32 myInteger = 43; 8 UA_Variant myVar; 9 UA_Variant_init(&myVar); 10 UA_Variant_setScalar(&myVar, &myInteger, 11 &UA_TYPES[UA_TYPES_INT32]); 12 UA_Server_writeValue(server, myIntegerNodeId, myVar); 13 14 /* Set the status code of the value to an error code. The 15 function 16 * UA_Server_write provides access to the raw service. The 17 above 18 * UA_Server_writeValue is syntactic sugar for writing a specific 19 node 20 * attribute with the write service. */ 21 UA_WriteValue wv; 22 UA_WriteValue_init(&wv); 23 wv.nodeId = myIntegerNodeId; 24 wv.attributeId = UA_ATTRIBUTEID_VALUE; 25 wv.value.status = UA_STATUSCODE_BADNOTCONNECTED; 26 wv.value.hasStatus = true; 27 UA_Server_write(server, &wv); 28 29 /* Reset the variable to a good statuscode with a value */ 30 wv.value.hasStatus = false; 31 wv.value.value = myVar; 32 wv.value.hasValue = true; 33 UA_Server_write(server, &wv); 34 }
注意我们最初如何将变量节点的data type属性设置为int32数据类型的nodeid。这禁止写入非Int32的值。下面的代码显示了如何对每次写入执行一致性检查。
1 static void 2 writeWrongVariable(UA_Server *server) { 3 UA_NodeId myIntegerNodeId = UA_NODEID_STRING(1, "the.answer"); 4 5 /* Write a string */ 6 UA_String myString = UA_STRING("test"); 7 UA_Variant myVar; 8 UA_Variant_init(&myVar); 9 UA_Variant_setScalar(&myVar, &myString, 10 &UA_TYPES[UA_TYPES_STRING]); 11 UA_StatusCode retval = UA_Server_writeValue(server, 12 myIntegerNodeId, myVar); 13 printf("Writing a string returned statuscode %s ", 14 UA_StatusCode_name(retval)); 15 }
它遵循主服务器代码,使用上述定义。
1 UA_Boolean running = true; 2 static void stopHandler(int sign) { 3 UA_LOG_INFO(UA_Log_Stdout, UA_LOGCATEGORY_SERVER, "received ctrl-c"); 4 running = false; 5 } 6 7 int main(void) { 8 signal(SIGINT, stopHandler); 9 signal(SIGTERM, stopHandler); 10 11 UA_ServerConfig *config = UA_ServerConfig_new_default(); 12 UA_Server *server = UA_Server_new(config); 13 14 addVariable(server); 15 writeVariable(server); 16 writeWrongVariable(server); 17 18 UA_StatusCode retval = UA_Server_run(server, &running); 19 UA_Server_delete(server); 20 UA_ServerConfig_delete(config); 21 return (int)retval; 22 }
在基于OPC UA的体系结构中,服务器通常位于信息源附近。在工业环境中,这意味着服务器接近物理过程,客户机在运行时使用数据。在前面的教程中,我们看到了如何向OPC UA信息模型添加变量。本实例演示如何将变量连接到运行时信息,例如从物理过程的测量中连接。为简单起见,我们以系统时钟为基础的“过程”。以下代码段分别与运行时更新变量值的不同方式有关。总之,代码片段定义了一个可编译的源文件。
作为起点,假设已在服务器中为datetime类型的值创建了一个标识符为“ns=1,s=current time”的变量。假设当一个新的值从底层进程到达时,我们的应用程序被触发,我们可以直接写入变量。
1 #include <signal.h> 2 #include "open62541.h" 3 #pragma comment(lib, "WS2_32") 4 static void 5 updateCurrentTime(UA_Server *server) { 6 UA_DateTime now = UA_DateTime_now(); 7 UA_Variant value; 8 UA_Variant_setScalar(&value, &now, &UA_TYPES[UA_TYPES_DATETIME]); 9 UA_NodeId currentNodeId = UA_NODEID_STRING(1, "current-time"); 10 UA_Server_writeValue(server, currentNodeId, value); 11 } 12 static void 13 addCurrentTimeVariable(UA_Server *server) { 14 UA_DateTime now = 0; 15 UA_VariableAttributes attr = UA_VariableAttributes_default; 16 attr.displayName = UA_LOCALIZEDTEXT("en-US", "Current time"); 17 attr.accessLevel = UA_ACCESSLEVELMASK_READ | UA_ACCESSLEVELMASK_WRITE; 18 UA_Variant_setScalar(&attr.value, &now, &UA_TYPES[UA_TYPES_DATETIME]); 19 UA_NodeId currentNodeId = UA_NODEID_STRING(1, "current-time"); 20 UA_QualifiedName currentName = UA_QUALIFIEDNAME(1, "current-time"); 21 UA_NodeId parentNodeId = UA_NODEID_NUMERIC(0, UA_NS0ID_OBJECTSFOLDER); 22 UA_NodeId parentReferenceNodeId = UA_NODEID_NUMERIC(0, UA_NS0ID_ORGANIZES); 23 UA_NodeId variableTypeNodeId = UA_NODEID_NUMERIC(0, UA_NS0ID_BASEDATAVARIABLETYPE); 24 UA_Server_addVariableNode(server, currentNodeId, parentNodeId, 25 parentReferenceNodeId, currentName, 26 variableTypeNodeId, attr, NULL, NULL); 27 updateCurrentTime(server); 28 }
2.值回调
当一个值不断变化时,例如系统时间,在一个紧密的循环中更新该值将占用大量的资源。值回调允许将变量值与外部表示同步。它们将回调附加到每次读操作之前和每次写操作之后执行的变量。
1 static void 2 beforeReadTime(UA_Server *server, 3 const UA_NodeId *sessionId, void *sessionContext, 4 const UA_NodeId *nodeid, void *nodeContext, 5 const UA_NumericRange *range, const UA_DataValue *data) { 6 UA_DateTime now = UA_DateTime_now(); 7 UA_Variant value; 8 UA_Variant_setScalar(&value, &now, &UA_TYPES[UA_TYPES_DATETIME]); 9 UA_NodeId currentNodeId = UA_NODEID_STRING(1, "current-time"); 10 UA_Server_writeValue(server, currentNodeId, value); 11 } 12 static void 13 afterWriteTime(UA_Server *server, 14 const UA_NodeId *sessionId, void *sessionContext, 15 const UA_NodeId *nodeId, void *nodeContext, 16 const UA_NumericRange *range, const UA_DataValue *data) { 17 UA_LOG_INFO(UA_Log_Stdout, UA_LOGCATEGORY_USERLAND, 18 "The variable was updated"); 19 } 20 static void 21 addValueCallbackToCurrentTimeVariable(UA_Server *server) { 22 UA_NodeId currentNodeId = UA_NODEID_STRING(1, "current-time"); 23 UA_ValueCallback callback; 24 callback.onRead = beforeReadTime; 25 callback.onWrite = afterWriteTime; 26 UA_Server_setVariableNode_valueCallback(server, currentNodeId, callback); 27 }
3.变量数据源
对于值回调,该值仍存储在变量节点中。所谓的数据源更进一步。服务器将每个读写请求重定向到回调函数。在读取时,回调提供当前值的副本。在内部,数据源需要实现自己的内存管理。
1 static UA_StatusCode 2 readCurrentTime(UA_Server *server, 3 const UA_NodeId *sessionId, void *sessionContext, 4 const UA_NodeId *nodeId, void *nodeContext, 5 UA_Boolean sourceTimeStamp, const UA_NumericRange *range, 6 UA_DataValue *dataValue) { 7 UA_DateTime now = UA_DateTime_now(); 8 UA_Variant_setScalarCopy(&dataValue->value, &now, 9 &UA_TYPES[UA_TYPES_DATETIME]); 10 dataValue->hasValue = true; 11 return UA_STATUSCODE_GOOD; 12 } 13 14 static UA_StatusCode 15 writeCurrentTime(UA_Server *server, 16 const UA_NodeId *sessionId, void *sessionContext, 17 const UA_NodeId *nodeId, void *nodeContext, 18 const UA_NumericRange *range, const UA_DataValue *data) { 19 UA_LOG_INFO(UA_Log_out, UA_LOGCATEGORY_USERLAND, 20 "Changing the system time is not implemented"); 21 return UA_STATUSCODE_GOOD; 22 } 23 24 static void 25 addCurrentTimeDataSourceVariable(UA_Server *server) { 26 UA_VariableAttributes attr = UA_VariableAttributes_default; 27 attr.displayName = UA_LOCALIZEDTEXT("en-US", "Current time - data source"); 28 attr.accessLevel = UA_ACCESSLEVELMASK_READ | UA_ACCESSLEVELMASK_WRITE; 29 UA_NodeId currentNodeId = UA_NODEID_STRING(1, "current-time-datasource"); 30 UA_QualifiedName currentName = UA_QUALIFIEDNAME(1, "current-time-datasource"); 31 UA_NodeId parentNodeId = UA_NODEID_NUMERIC(0, UA_NS0ID_OBJECTSFOLDER); 32 UA_NodeId parentReferenceNodeId = UA_NODEID_NUMERIC(0, UA_NS0ID_ORGANIZES); 33 UA_NodeId variableTypeNodeId = UA_NODEID_NUMERIC(0, UA_NS0ID_BASEDATAVARIABLETYPE); 34 UA_DataSource timeDataSource; 35 timeDataSource.read = readCurrentTime; 36 timeDataSource.write = writeCurrentTime; 37 UA_Server_addDataSourceVariableNode(server, currentNodeId, parentNodeId, 38 parentReferenceNodeId, currentName, 39 variableTypeNodeId, attr, 40 timeDataSource, NULL, NULL); 41 }
以下代码遵循主服务器代码,使用上述定义。
1 UA_Boolean running = true; 2 static void stopHandler(int sign) { 3 UA_LOG_INFO(UA_Log_Stdout, UA_LOGCATEGORY_SERVER, "received ctrl-c"); 4 running = false; 5 } 6 int main(void) { 7 signal(SIGINT, stopHandler); 8 signal(SIGTERM, stopHandler); 9 UA_ServerConfig *config = UA_ServerConfig_new_default(); 10 UA_Server *server = UA_Server_new(config); 11 addCurrentTimeVariable(server); 12 addValueCallbackToCurrentTimeVariable(server); 13 addCurrentTimeDataSourceVariable(server); 14 UA_StatusCode retval = UA_Server_run(server, &running); 15 UA_Server_delete(server); 16 UA_ServerConfig_delete(config); 17 return (int)retval; 18 }
以上是关于基于open62541的opc ua 服务器开发实现的主要内容,如果未能解决你的问题,请参考以下文章
客户端和服务器之间的双工通信 open62541(OPC-UA)
open62541 客户端断开连接时的 OPC UA 堆栈服务器端回调