tutorial을 열심히 따라했지만 변수를 추가하다 막혀 못했다. 내가 OPC-UA 프로토콜을 제대로 이해하지 못한 탓이다. 아래를 참조하여 변수를 서버에 간단히 추가했다.
클라이언트가 서버 변수를 읽으려 했으나, 쉽게 하지 못했다. tutorial이 현재 시각을 쉽게 보여줘서 너무 일찍 시작하였다. callback 함수까지 설정했어야 했는데, 설명하지 않았다. 너무 불친절하다. 다행히도 좋은 동영상을 보고 이해했다. 다음 장에 나온 Connecting a Variable with a Physical Process 항목까지 읽고 따라 했어야 했다.
#include "open62541.h" #include <signal.h> #include <stdlib.h> static volatile UA_Boolean running = true; static void stopHandler(int sig) { UA_LOG_INFO(UA_Log_Stdout, UA_LOGCATEGORY_USERLAND, "received ctrl-c"); running = false; } static void addVariable(UA_Server *server) { /* Define the attribute of the myInteger variable node */ UA_VariableAttributes attr = UA_VariableAttributes_default; UA_Int32 myInteger = 42; UA_Variant_setScalar(&attr.value, &myInteger, &UA_TYPES[UA_TYPES_INT32]); attr.description = UA_LOCALIZEDTEXT("en-US","the answer"); attr.displayName = UA_LOCALIZEDTEXT("en-US","the answer"); 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_NODEID_STRING(1, "the.answer"); UA_QualifiedName myIntegerName = UA_QUALIFIEDNAME(1, "the answer"); UA_NodeId parentNodeId = UA_NODEID_NUMERIC(0, UA_NS0ID_OBJECTSFOLDER); UA_NodeId parentReferenceNodeId = UA_NODEID_NUMERIC(0, UA_NS0ID_ORGANIZES); UA_Server_addVariableNode(server, myIntegerNodeId, parentNodeId, parentReferenceNodeId, myIntegerName, UA_NODEID_NUMERIC(0, UA_NS0ID_BASEDATAVARIABLETYPE), attr, NULL, NULL); } static void writeVariable(UA_Server *server) { UA_NodeId myIntegerNodeId = UA_NODEID_STRING(1, "the.answer"); /* Write a different integer value */ UA_Int32 myInteger = 43; UA_Variant myVar; UA_Variant_init(&myVar); UA_Variant_setScalar(&myVar, &myInteger, &UA_TYPES[UA_TYPES_INT32]); UA_Server_writeValue(server, myIntegerNodeId, myVar); /* Set the status code of the value to an error code. The function * UA_Server_write provides access to the raw service. The above * UA_Server_writeValue is syntactic sugar for writing a specific node * attribute with the write service. */ UA_WriteValue wv; UA_WriteValue_init(&wv); wv.nodeId = myIntegerNodeId; wv.attributeId = UA_ATTRIBUTEID_VALUE; wv.value.status = UA_STATUSCODE_BADNOTCONNECTED; wv.value.hasStatus = true; UA_Server_write(server, &wv); /* Reset the variable to a good statuscode with a value */ wv.value.hasStatus = false; wv.value.value = myVar; wv.value.hasValue = true; UA_Server_write(server, &wv); } static void writeWrongVariable(UA_Server *server) { UA_NodeId myIntegerNodeId = UA_NODEID_STRING(1, "the.answer"); /* Write a string */ UA_String myString = UA_STRING("test"); UA_Variant myVar; UA_Variant_init(&myVar); UA_Variant_setScalar(&myVar, &myString, &UA_TYPES[UA_TYPES_STRING]); UA_StatusCode retval = UA_Server_writeValue(server, myIntegerNodeId, myVar); printf("Writing a string returned statuscode %s\n", UA_StatusCode_name(retval)); } static void beforeReadVal(UA_Server *server, const UA_NodeId *sessionId, void *sessionContext, const UA_NodeId *nodeid, void *nodeContext, const UA_NumericRange *range, const UA_DataValue *data) { //updateCurrentTime(server); int tmpVal = rand()%1000; UA_Variant value; UA_Variant_setScalar(&value, &tmpVal, &UA_TYPES[UA_TYPES_INT32]); UA_NodeId currentNodeId = UA_NODEID_STRING(1, "Test Var:Me"); UA_Server_writeValue(server, currentNodeId, value); } int main(void) { signal(SIGINT, stopHandler); signal(SIGTERM, stopHandler); UA_Server *server = UA_Server_new(); UA_ServerConfig_setDefault(UA_Server_getConfig(server)); //UA_ServerConfig* config = UA_Server_getConfig(server); //config->verifyRequestTimestamp = UA_RULEHANDLING_ACCEPT; //addVariable(server); //writeVariable(server); //writeWrongVariable(server); //vendor, serial, variable 순으로 추가. //공통으로 사용할 부분 설정. UA_NodeId parentNodeId = UA_NODEID_NUMERIC(0, UA_NS0ID_OBJECTSFOLDER); UA_NodeId parentReferenceNodeId = UA_NODEID_NUMERIC(0, UA_NS0ID_HASCOMPONENT); // // //vendor name 추가. // UA_VariableAttributes vnAttr = UA_VariableAttributes_default; // UA_String vendorName = UA_STRING("test vendor"); // UA_Variant_setScalar(&vnAttr.value, &vendorName, &UA_TYPES[UA_TYPES_STRING]); // UA_NodeId vendorNodeId = UA_NODEID_STRING(1, "Test VendorName:Me"); // UA_QualifiedName myVendorName = UA_QUALIFIEDNAME(1, "the answer"); // UA_Server_addVariableNode(server, vendorNodeId, parentNodeId, // parentReferenceNodeId, myVendorName, // UA_NODEID_NUMERIC(0, UA_NS0ID_BASEDATAVARIABLETYPE), vnAttr, NULL, NULL); // // // //serial no 추가. // UA_VariableAttributes serialAttr = UA_VariableAttributes_default; // UA_Int32 serialName = 123456; // UA_Variant_setScalar(&serialAttr.value, &serialName, &UA_TYPES[UA_TYPES_INT32]); // UA_NodeId serialNodeId = UA_NODEID_STRING(1, "Test serial:Me"); // UA_QualifiedName mySerialName = UA_QUALIFIEDNAME(1, "serial No"); // UA_Server_addVariableNode(server, serialNodeId, parentNodeId, // parentReferenceNodeId, mySerialName, // UA_NODEID_NUMERIC(0, UA_NS0ID_BASEDATAVARIABLETYPE), serialAttr, NULL, NULL); // //variable 추가. UA_VariableAttributes varAttr = UA_VariableAttributes_default; UA_Int32 varName = 10; UA_Variant_setScalar(&varAttr.value, &varName, &UA_TYPES[UA_TYPES_INT32]); UA_NodeId varNodeId = UA_NODEID_STRING(1, "Test Var:Me"); UA_QualifiedName myVarName = UA_QUALIFIEDNAME(1, "variable"); UA_Server_addVariableNode(server, varNodeId, parentNodeId, parentReferenceNodeId, myVarName, UA_NODEID_NUMERIC(0, UA_NS0ID_BASEDATAVARIABLETYPE), varAttr, NULL, NULL); //add callback UA_ValueCallback callback ; callback.onRead = beforeReadVal; callback.onWrite = NULL; UA_NodeId currentNodeId = UA_NODEID_STRING(1, "Test Var:Me"); UA_Server_setVariableNode_valueCallback(server, currentNodeId, callback); //서버 구동. UA_LOG_INFO(UA_Log_Stdout, UA_LOGCATEGORY_USERLAND, "Starting server..."); UA_StatusCode retval = UA_Server_run(server, &running); UA_LOG_INFO(UA_Log_Stdout, UA_LOGCATEGORY_USERLAND, "Shutdown server..."); UA_Server_delete(server); return retval == UA_STATUSCODE_GOOD ? EXIT_SUCCESS : EXIT_FAILURE; }
#include "open62541.h" #include <stdlib.h> int main(void) { UA_Client *client = UA_Client_new(); UA_ClientConfig_setDefault(UA_Client_getConfig(client)); UA_StatusCode retval = UA_Client_connect(client, "opc.tcp://localhost:4840"); printf("retval is %d\n",retval); if(retval != UA_STATUSCODE_GOOD) { UA_Client_delete(client); return (int)retval; } /* Read the value attribute of the node. UA_Client_readValueAttribute is a * wrapper for the raw read service available as UA_Client_Service_read. */ // UA_Variant value; /* Variants can hold scalar values and arrays of any type */ // UA_Variant_init(&value); // // /* NodeId of the variable holding the current time */ // const UA_NodeId nodeId = UA_NODEID_NUMERIC(0, UA_NS0ID_SERVER_SERVERSTATUS_CURRENTTIME); // retval = UA_Client_readValueAttribute(client, nodeId, &value); // printf("test\n"); // if(retval == UA_STATUSCODE_GOOD && // UA_Variant_hasScalarType(&value, &UA_TYPES[UA_TYPES_DATETIME])) { // UA_DateTime raw_date = *(UA_DateTime *) value.data; // UA_DateTimeStruct dts = UA_DateTime_toStruct(raw_date); // UA_LOG_INFO(UA_Log_Stdout, UA_LOGCATEGORY_USERLAND, "date is: %u-%u-%u %u:%u:%u.%03u\n", // dts.day, dts.month, dts.year, dts.hour, dts.min, dts.sec, dts.milliSec); // // } // //const UA_NodeId nodeId2 = UA_NODEID_NUMERIC(0, UA_NS0ID_SERVER_SERVERSTATUS_CURRENTTIME); // const UA_NodeId nodeId2 = UA_NODEID_STRING(1, "the.answer"); // UA_Variant value2; // UA_Variant_init(&value2); // UA_Int32 node_value=0; // // retval = UA_Client_readValueAttribute(client, nodeId2, &value2); // if(retval == UA_STATUSCODE_GOOD && // //UA_Variant_hasScalarType(&value2, &UA_TYPES[UA_TYPES_INT32])) { // UA_Variant_isScalar(&value2)) { // UA_LOG_INFO(UA_Log_Stdout, UA_LOGCATEGORY_USERLAND,"myData is "); // // } // /* Read attribute */ UA_Int32 value2 = 0; printf("\nReading the value of node (1, \"Test Var:Me\"):\n"); UA_Variant *val = UA_Variant_new(); printf("retval is %d\n",retval); retval = UA_Client_readValueAttribute(client, UA_NODEID_STRING(1, "Test Var:Me"), val); printf("retval is %d\n",retval); if(retval == UA_STATUSCODE_GOOD && UA_Variant_isScalar(val) && val->type == &UA_TYPES[UA_TYPES_INT32]) { value2 = *(UA_Int32*)val->data; //printf("the value is: %i\n", value2); UA_LOG_INFO(UA_Log_Stdout, UA_LOGCATEGORY_USERLAND,"myData is %d",value2); } UA_Variant_delete(val); /* Clean up */ // UA_Variant_clear(&value); UA_Variant_clear(&value2); UA_Client_delete(client); /* Disconnects the client internally */ return EXIT_SUCCESS; }
노드 ID, 데이터 타입 등 까다롭게 맞춰줘야 한다. 틀리면 못 읽는다.