基於open62541的opc ua 伺服器開發實現(1)
關於opcua的介紹這裡就不多說了,相信大家大都有了一些瞭解,open62541
是一個開源C(C99)的opc-ua實現,開原始碼可在官網或github上下載。
話不多說,首先搭建一個opcua伺服器例項
1 #include <signal.h> 2 #include "open62541.h" 3UA_Boolean running = true; 4static void stopHandler(int sig) { 5UA_LOG_INFO(UA_Log_Stdout, UA_LOGCATEGORY_USERLAND, "received ctrl-c"); 6running = false; 7} 8 int main(void) { 9signal(SIGINT, stopHandler); 10signal(SIGTERM, stopHandler); 11UA_ServerConfig *config = UA_ServerConfig_new_default(); 12UA_Server *server = UA_Server_new(config); 13UA_StatusCode retval = UA_Server_run(server, &running); 14UA_Server_delete(server); 15UA_ServerConfig_delete(config); 16return (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 */ 7UA_VariableAttributes attr = UA_VariableAttributes_default; 8UA_Int32 myInteger = 42; 9UA_Variant_setScalar(&attr.value, &myInteger, &UA_TYPES[UA_TYPES_INT32]); 10attr.description = UA_LOCALIZEDTEXT("en-US","the answer"); 11attr.displayName = UA_LOCALIZEDTEXT("en-US","the answer"); 12attr.dataType = UA_TYPES[UA_TYPES_INT32].typeId; 13attr.accessLevel = UA_ACCESSLEVELMASK_READ | UA_ACCESSLEVELMASK_WRITE; 14/* Add the variable node to the information model */ 15UA_NodeId myIntegerNodeId = UA_NODEID_STRING(1, "the.answer"); 16UA_QualifiedName myIntegerName = UA_QUALIFIEDNAME(1, "the answer"); 17UA_NodeId parentNodeId = UA_NODEID_NUMERIC(0, UA_NS0ID_OBJECTSFOLDER); 18UA_NodeId parentReferenceNodeId = UA_NODEID_NUMERIC(0, UA_NS0ID_ORGANIZES); 19UA_Server_addVariableNode(server, myIntegerNodeId, parentNodeId, 20parentReferenceNodeId, myIntegerName, 21UA_NODEID_NUMERIC(0, UA_NS0ID_BASEDATAVARIABLETYPE),attr, NULL, NULL); 22}
現在我們用 寫服務 更改節點值。也可以通過網路由OPC UA的客戶端訪問來修改值。
1 static void 2 writeVariable(UA_Server *server) { 3UA_NodeId myIntegerNodeId = 4UA_NODEID_STRING(1,"the.answer"); 5 6/* Write a different integer value */ 7UA_Int32 myInteger = 43; 8UA_Variant myVar; 9UA_Variant_init(&myVar); 10UA_Variant_setScalar(&myVar, &myInteger, 11&UA_TYPES[UA_TYPES_INT32]); 12UA_Server_writeValue(server, myIntegerNodeId, myVar); 13 14/* Set the status code of the value to an error code. The 15function 16* UA_Server_write provides access to the raw service. The 17above 18* UA_Server_writeValue is syntactic sugar for writing a specific 19node 20* attribute with the write service. */ 21UA_WriteValue wv; 22UA_WriteValue_init(&wv); 23wv.nodeId = myIntegerNodeId; 24wv.attributeId = UA_ATTRIBUTEID_VALUE; 25wv.value.status = UA_STATUSCODE_BADNOTCONNECTED; 26wv.value.hasStatus = true; 27UA_Server_write(server, &wv); 28 29/* Reset the variable to a good statuscode with a value */ 30wv.value.hasStatus = false; 31wv.value.value = myVar; 32wv.value.hasValue = true; 33UA_Server_write(server, &wv); 34}
注意我們最初如何將變數節點的data type屬性設定為int32資料型別的nodeid。這禁止寫入非Int32的值。下面的程式碼顯示瞭如何對每次寫入執行一致性檢查。
1 static void 2 writeWrongVariable(UA_Server *server) { 3UA_NodeId myIntegerNodeId = UA_NODEID_STRING(1, "the.answer"); 4 5/* Write a string */ 6UA_String myString = UA_STRING("test"); 7UA_Variant myVar; 8UA_Variant_init(&myVar); 9UA_Variant_setScalar(&myVar, &myString, 10&UA_TYPES[UA_TYPES_STRING]); 11UA_StatusCode retval = UA_Server_writeValue(server, 12myIntegerNodeId, myVar); 13printf("Writing a string returned statuscode %s\n", 14UA_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) { 8signal(SIGINT, stopHandler); 9signal(SIGTERM, stopHandler); 10 11UA_ServerConfig *config = UA_ServerConfig_new_default(); 12UA_Server *server = UA_Server_new(config); 13 14addVariable(server); 15writeVariable(server); 16writeWrongVariable(server); 17 18UA_StatusCode retval = UA_Server_run(server, &running); 19UA_Server_delete(server); 20UA_ServerConfig_delete(config); 21return (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) { 6UA_DateTime now = UA_DateTime_now(); 7UA_Variant value; 8UA_Variant_setScalar(&value, &now, &UA_TYPES[UA_TYPES_DATETIME]); 9UA_NodeId currentNodeId = UA_NODEID_STRING(1, "current-time"); 10UA_Server_writeValue(server, currentNodeId, value); 11 } 12 static void 13 addCurrentTimeVariable(UA_Server *server) { 14UA_DateTime now = 0; 15UA_VariableAttributes attr = UA_VariableAttributes_default; 16attr.displayName = UA_LOCALIZEDTEXT("en-US", "Current time"); 17attr.accessLevel = UA_ACCESSLEVELMASK_READ | UA_ACCESSLEVELMASK_WRITE; 18UA_Variant_setScalar(&attr.value, &now, &UA_TYPES[UA_TYPES_DATETIME]); 19UA_NodeId currentNodeId = UA_NODEID_STRING(1, "current-time"); 20UA_QualifiedName currentName = UA_QUALIFIEDNAME(1, "current-time"); 21UA_NodeId parentNodeId = UA_NODEID_NUMERIC(0, UA_NS0ID_OBJECTSFOLDER); 22UA_NodeId parentReferenceNodeId = UA_NODEID_NUMERIC(0, UA_NS0ID_ORGANIZES); 23UA_NodeId variableTypeNodeId = UA_NODEID_NUMERIC(0, UA_NS0ID_BASEDATAVARIABLETYPE); 24UA_Server_addVariableNode(server, currentNodeId, parentNodeId, 25parentReferenceNodeId, currentName, 26variableTypeNodeId, attr, NULL, NULL); 27updateCurrentTime(server); 28 }
2.值回撥
當一個值不斷變化時,例如系統時間,在一個緊密的迴圈中更新該值將佔用大量的資源。值回撥允許將變數值與外部表示同步。它們將回調附加到每次讀操作之前和每次寫操作之後執行的變數。
1 static void 2 beforeReadTime(UA_Server *server, 3const UA_NodeId *sessionId, void *sessionContext, 4const UA_NodeId *nodeid, void *nodeContext, 5const UA_NumericRange *range, const UA_DataValue *data) { 6UA_DateTime now = UA_DateTime_now(); 7UA_Variant value; 8UA_Variant_setScalar(&value, &now, &UA_TYPES[UA_TYPES_DATETIME]); 9UA_NodeId currentNodeId = UA_NODEID_STRING(1, "current-time"); 10UA_Server_writeValue(server, currentNodeId, value); 11 } 12 static void 13 afterWriteTime(UA_Server *server, 14const UA_NodeId *sessionId, void *sessionContext, 15const UA_NodeId *nodeId, void *nodeContext, 16const UA_NumericRange *range, const UA_DataValue *data) { 17UA_LOG_INFO(UA_Log_Stdout, UA_LOGCATEGORY_USERLAND, 18"The variable was updated"); 19 } 20 static void 21 addValueCallbackToCurrentTimeVariable(UA_Server *server) { 22UA_NodeId currentNodeId = UA_NODEID_STRING(1, "current-time"); 23UA_ValueCallback callback; 24callback.onRead = beforeReadTime; 25callback.onWrite = afterWriteTime; 26UA_Server_setVariableNode_valueCallback(server, currentNodeId, callback); 27 }
3.變數資料來源
對於值回撥,該值仍儲存在變數節點中。所謂的資料來源更進一步。伺服器將每個讀寫請求重定向到回撥函式。在讀取時,回撥提供當前值的副本。在內部,資料來源需要實現自己的記憶體管理。
1 static UA_StatusCode 2 readCurrentTime(UA_Server *server, 3const UA_NodeId *sessionId, void *sessionContext, 4const UA_NodeId *nodeId, void *nodeContext, 5UA_Boolean sourceTimeStamp, const UA_NumericRange *range, 6UA_DataValue *dataValue) { 7UA_DateTime now = UA_DateTime_now(); 8UA_Variant_setScalarCopy(&dataValue->value, &now, 9&UA_TYPES[UA_TYPES_DATETIME]); 10dataValue->hasValue = true; 11return UA_STATUSCODE_GOOD; 12 } 13 14 static UA_StatusCode 15 writeCurrentTime(UA_Server *server, 16const UA_NodeId *sessionId, void *sessionContext, 17const UA_NodeId *nodeId, void *nodeContext, 18const UA_NumericRange *range, const UA_DataValue *data) { 19UA_LOG_INFO(UA_Log_out, UA_LOGCATEGORY_USERLAND, 20"Changing the system time is not implemented"); 21return UA_STATUSCODE_GOOD; 22 } 23 24 static void 25 addCurrentTimeDataSourceVariable(UA_Server *server) { 26UA_VariableAttributes attr = UA_VariableAttributes_default; 27attr.displayName = UA_LOCALIZEDTEXT("en-US", "Current time - data source"); 28attr.accessLevel = UA_ACCESSLEVELMASK_READ | UA_ACCESSLEVELMASK_WRITE; 29UA_NodeId currentNodeId = UA_NODEID_STRING(1, "current-time-datasource"); 30UA_QualifiedName currentName = UA_QUALIFIEDNAME(1, "current-time-datasource"); 31UA_NodeId parentNodeId = UA_NODEID_NUMERIC(0, UA_NS0ID_OBJECTSFOLDER); 32UA_NodeId parentReferenceNodeId = UA_NODEID_NUMERIC(0, UA_NS0ID_ORGANIZES); 33UA_NodeId variableTypeNodeId = UA_NODEID_NUMERIC(0, UA_NS0ID_BASEDATAVARIABLETYPE); 34UA_DataSource timeDataSource; 35timeDataSource.read = readCurrentTime; 36timeDataSource.write = writeCurrentTime; 37UA_Server_addDataSourceVariableNode(server, currentNodeId, parentNodeId, 38parentReferenceNodeId, currentName, 39variableTypeNodeId, attr, 40timeDataSource, NULL, NULL); 41 }
以下程式碼遵循主伺服器程式碼,使用上述定義。
1 UA_Boolean running = true; 2 static void stopHandler(int sign) { 3UA_LOG_INFO(UA_Log_Stdout, UA_LOGCATEGORY_SERVER, "received ctrl-c"); 4running = false; 5 } 6 int main(void) { 7signal(SIGINT, stopHandler); 8signal(SIGTERM, stopHandler); 9UA_ServerConfig *config = UA_ServerConfig_new_default(); 10UA_Server *server = UA_Server_new(config); 11addCurrentTimeVariable(server); 12addValueCallbackToCurrentTimeVariable(server); 13addCurrentTimeDataSourceVariable(server); 14UA_StatusCode retval = UA_Server_run(server, &running); 15UA_Server_delete(server); 16UA_ServerConfig_delete(config); 17return (int)retval; 18 } View Code