[태그:] raspberry

  • open62541 method call

    튜토리얼에 method call이 있어 따라 해봤다. method를 서버에 설정하고, client에서 반응을 보고 싶었는데, 잘 안됬다. 두 시간 삽질만에 UA_VARINT에서 UA_String을 출력했다. 처음에는 몰랐는데 gdb로 구조를 보면 대략 알 수 있다. UA_String도 구조체라 data와 length로 구성된다. UA_Variant로 data를 출력하려면 UA_String으로 캐스팅하여 data의 data를 찾아야 한다.–;

    (gdb) 
    45		/* Call a remote method */
    46		UA_Variant input;
    47		UA_String argString = UA_STRING("Hello Server");
    48		UA_Variant_init(&input);
    49		//UA_Variant_setScalarCopy(&input, &argString, &UA_TYPES[UA_TYPES_STRING]);
    50		UA_Variant_setScalarCopy(&input, &argString, &UA_TYPES[UA_TYPES_STRING]);
    51		UA_LOG_INFO(UA_Log_Stdout, UA_LOGCATEGORY_USERLAND, "input data is %s\n",(UA_String*)input.data);
    52	
    53		size_t outputSize;
    54		UA_Variant *output;
    (gdb) display input
    1: input = {type = 0xbefff4c8, storageType = (unknown: 3070066356), arrayLength = 0, data = 0xb6ffd13c, arrayDimensionsSize = 4, 
      arrayDimensions = 0xb6ffd14c}
    (gdb) n
    48		UA_Variant_init(&input);
    1: input = {type = 0xbefff4c8, storageType = (unknown: 3070066356), arrayLength = 0, data = 0xb6ffd13c, arrayDimensionsSize = 4, 
      arrayDimensions = 0xb6ffd14c}
    (gdb) print argString
    $1 = {length = 12, data = 0x10f0c "Hello Server"}
    (gdb) print argString->data
    $2 = (UA_Byte *) 0x10f0c "Hello Server"
    (gdb) n
    50		UA_Variant_setScalarCopy(&input, &argString, &UA_TYPES[UA_TYPES_STRING]);
    1: input = {type = 0x0, storageType = UA_VARIANT_DATA, arrayLength = 0, data = 0x0, arrayDimensionsSize = 0, arrayDimensions = 0x0}
    (gdb) n
    51		UA_LOG_INFO(UA_Log_Stdout, UA_LOGCATEGORY_USERLAND, "input data is %s\n",(UA_String*)input.data);
    1: input = {type = 0x22250 <UA_TYPES+704>, storageType = UA_VARIANT_DATA, arrayLength = 0, data = 0x282d8, arrayDimensionsSize = 0, arrayDimensions = 0x0}
    (gdb) print input->data
    $3 = (void *) 0x282d8
    (gdb) print input->data->data
    Attempt to dereference a generic pointer.
    (gdb) print (UA_String*)(input->data)->data
    Attempt to dereference a generic pointer.
    (gdb) print (UA_String*)(input.data)->data
    Attempt to dereference a generic pointer.
    (gdb) display argString
    2: argString = {length = 12, data = 0x10f0c "Hello Server"}
    (gdb) display
    1: input = {type = 0x22250 <UA_TYPES+704>, storageType = UA_VARIANT_DATA, arrayLength = 0, data = 0x282d8, arrayDimensionsSize = 0, arrayDimensions = 0x0}
    2: argString = {length = 12, data = 0x10f0c "Hello Server"}
    (gdb) display &argString
    3: &argString = (UA_String *) 0xbefff368
    [2020-09-07 04:29:15.352 (UTC+0900)] info/userland	input data is Hello Server��
    Method call was successful, and 1 returned values available.
    [2020-09-07 04:29:15.352 (UTC+0900)] info/userland	output data is Hello Hello Server
    [2020-09-07 04:29:15.353 (UTC+0900)] info/client	Client Status: ChannelState: Closed, SessionState: Closed, ConnectStatus: Good
    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 attribute */
        UA_Int32 value2 = 0;
        printf("\nReading the value of node (1, \"Test Var:Me\"):\n");
        UA_Variant *val = UA_Variant_new();
        retval = UA_Client_readValueAttribute(client, UA_NODEID_STRING(1, "Test Var:Me"), val);
        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);
    
    	}
    
    	//method call
    
    #ifdef UA_ENABLE_METHODCALLS
    	/* Call a remote method */
    	UA_Variant input;
    	UA_String argString = UA_STRING("Hello Server");
    	UA_Variant_init(&input);
    	//UA_Variant_setScalarCopy(&input, &argString, &UA_TYPES[UA_TYPES_STRING]);
    	UA_Variant_setScalarCopy(&input, &argString, &UA_TYPES[UA_TYPES_STRING]);
    	UA_LOG_INFO(UA_Log_Stdout, UA_LOGCATEGORY_USERLAND, "input data is %s\n",((UA_String*)input.data)->data);
    	size_t outputSize;
    	UA_Variant *output;
    	retval = UA_Client_call(client, UA_NODEID_NUMERIC(0, UA_NS0ID_OBJECTSFOLDER),
    			UA_NODEID_NUMERIC(1, 62541), 1, &input, &outputSize, &output);
    
    	if(retval == UA_STATUSCODE_GOOD) {
    		printf("Method call was successful, and %lu returned values available.\n",
    				(unsigned long)outputSize);
    
    		UA_LOG_INFO(UA_Log_Stdout, UA_LOGCATEGORY_USERLAND, "output data is %s\n",((UA_String*)output->data)->data);
    
    		UA_Array_delete(output, outputSize, &UA_TYPES[UA_TYPES_VARIANT]);
    	} else {
    		printf("Method call was unsuccessful, and %x returned values available.\n", retval);
    	}
    	UA_Variant_clear(&input);
    #endif
    
    	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;
    }
    
    

  • open62541 server – client variable 전달

    tutorial을 열심히 따라했지만 변수를 추가하다 막혀 못했다. 내가 OPC-UA 프로토콜을 제대로 이해하지 못한 탓이다. 아래를 참조하여 변수를 서버에 간단히 추가했다.

    https://open62541.org/doc/current/tutorial_server_variabletype.html

    클라이언트가 서버 변수를 읽으려 했으나, 쉽게 하지 못했다. tutorial이 현재 시각을 쉽게 보여줘서 너무 일찍 시작하였다. callback 함수까지 설정했어야 했는데, 설명하지 않았다. 너무 불친절하다. 다행히도 좋은 동영상을 보고 이해했다. 다음 장에 나온 Connecting a Variable with a Physical Process 항목까지 읽고 따라 했어야 했다.

    https://open62541.org/doc/current/tutorial_server_datasource.html
    #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, 데이터 타입 등 까다롭게 맞춰줘야 한다. 틀리면 못 읽는다.

  • raspberry pi + opc-ua + open62541 삽질기

    하. 드디어 여기까지 왔다. opc ua를 설치하면 자동화 기기에서 정보를 쉽게 읽을 수 있다. 여기에서 여러 오픈소스 프로그램을 찾았다. 소개한 홈페이지와 마찬가지로 가볍게 C로 작성한 open62541를 설치하기로 했다.

    3달 전만해도 PLC가 당연히 OPC를 지원할 줄 알았으나, 지멘스 S7-1500 최신 제품, MELSEC 정도나 OPCUA를 지원한다. 나름 최신 기술인가보다. OPC 솔루션을 가진 회사에 연락하면 쉽게 지원받을 수 있는데 가격대가 높다. 7만원 짜리 라즈베리 하드웨어에 몇 천만원 소프트웨어를 올리면 미친 짓이다. 쓸 사람도 없고. 이런 기능을 PLC 메이커가 좀 쉽게 해줘야 하는데, 아쉽다.

    설치는 정말 그대로 따라하면 된다. 컴파일 후 open62541.c와 open62541.h 파일을 현재 폴더에 넣고 #include “open62541.c”로 하던가, gcc 표준 라이브러리 경로를 찾아 디렉토리 구조째 넣어주고 <>으로 감싸도 된다. tutorial은 뭔가 잘못되었는지 잘 실행되지 않는다. 참조한 사이트 작성자가 했듯이 주석처리하면 된다. 실행하면 서버가 열리고 접속할 수 있다. 웹 브라우저가 아닌 OPC 클라이언트가 필요하다.

    pi@raspberrypi:~/OPCUA/sample $ ./myServer 
    [2020-09-04 07:01:39.438 (UTC+0900)] warn/server	AccessControl: Unconfigured AccessControl. Users have all permissions.
    [2020-09-04 07:01:39.438 (UTC+0900)] info/server	AccessControl: Anonymous login is enabled
    [2020-09-04 07:01:39.438 (UTC+0900)] warn/server	Username/Password configured, but no encrypting SecurityPolicy. This can leak credentials on the network.
    [2020-09-04 07:01:39.438 (UTC+0900)] warn/userland	AcceptAll Certificate Verification. Any remote certificate will be accepted.
    [2020-09-04 07:01:39.439 (UTC+0900)] info/network	TCP network layer listening on opc.tcp://raspberrypi:4840/

    PLC가 opcua를 지원하면 서버, 클라이언트 양쪽 다 지원할 듯 하다. 여기를 보고 다음단계로 가보자.

    동영상 다 보고, 코드 몇 개 실행하면 자가격리 끝나 있겠다. 아 허리야.

    이어서…

    위 방법대로 sample 코드를 컴파일하면 에러가 무지하게 뜬다. 아마도 중복으로 선언되어 문제인 듯 하다. 여기에 있는 raspberry용 파일을 받아 압축을 푼 뒤, include에 해당하는 c, h 파일을 gcc 경로에 넣어주면 된다. 혹시나 해서 build한 버전의 c, h 파일을 모두 찾아 되는지 해보았지만 실패했다. 다음 3개 경로에서 찾을 수 있는데, raspberry용 파일과 linux 파일이 조금 다르다. raspberry는 헤더를 포함하는 부분이 좀 간략하게 되어 있다. server.h를 예를 들면.

    pi@raspberrypi:~/OPCUA/open62541 $ find ./ -type d -iname open62541
    ./include/open62541
    ./plugins/include/open62541
    ./build/src_generated/open62541
    ./build/CMakeFiles/open62541-object.dir/src_generated/open62541
    ./build/CMakeFiles/Export/lib/cmake/open62541

    결국 라즈베리용 파일을 해당 경로에 넣어준다. library는 build 아래 bin에 디렉토리에 libopen62541.so을 만든다. 라즈베리용 라이브러리보다 좀 작다. 이 linux용 build 라이브러리를 사용하면 오류난다. 라즈베리용 라이브러리를 사용해야 한다. raspberry에서 컴파일 할 수도 있어 보이지만, readme에 표시되지 않았다. 결국 되니 패스하겠다.

    정리하면.

    1. open62541 컴파일하여 open62541.c, open62541.h를 얻음.
    2. 라즈베리용 파일을 구하여 압축 해제.(build 후 header를 중복으로 선언한 부분을 수정한 듯 함. 조금씩 다름)
    3. include에 포함된 c, h를 해당 경로로 이동.
    4. lib에 포함된 .so를 각 환경에 맞는 경로로 이동.
    5. gcc -std=c99 -lopen62541 sample.c -o target
    6. 이런 형식으로 컴파일.

    라면 끝인 줄 알았으나, 아래 두 방법 중 하나를 선택하면 된다.

    • 컴파일 한 open62541.c, open62541.h 사용.
      1. ccmake 중 UA_ENABLE_AMALGAMATION=on 을 선택하여 open62541.c open62541.h를 만들면 끝나도록 한다.
      2. 라이브러리를 만들면 해당 디렉토리에 복사.

    In CMake, select freertosLWIP using the variable UA_ARCHITECTURE, enable amalgamation using the UA_ENABLE_AMALGAMATION variable and just select the native compilers. Then try to compile as always. The compilation will fail, but the open62541.h and open62541.c will be generated.

    • 라즈베리용 헤더, 라이브러리 사용.
      1. 컴파일 필요 없음.
      2. 적당히 include, lib 파일을 찾아 복사해 줌.
      3. tutorial 코드를 그대로 사용할 수 있음.
    #include <open62541/plugin/log_stdout.h>
    #include <open62541/server.h>
    #include <open62541/server_config_default.h>
    //#include <open62541/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;
    }
    
    int main(void) {
        signal(SIGINT, stopHandler);
        signal(SIGTERM, stopHandler);
    
        UA_Server *server = UA_Server_new();
        UA_ServerConfig_setDefault(UA_Server_getConfig(server));
    
        UA_StatusCode retval = UA_Server_run(server, &running);
    
        UA_Server_delete(server);
        return retval == UA_STATUSCODE_GOOD ? EXIT_SUCCESS : EXIT_FAILURE;
    }

    위 코드에서 open62541/open62541.h를 주석 처리했다. open62541.h 파일을 현재 디렉토리로 옮긴 후 #include “open62541.h”으로 쓰고, 위 앞 3줄을 주석처리해도 컴파일 된다. 끝.

  • Learning OpenCV 4 Computer Vision with Python 3, Chapter2, Modue2

    삽질로 video로 들어오는 입력을 필터링 했다. cv2.waitKey(delay)가 없으면 프레임을 업데이트 하지 않는다. windowmanager에서 callback 함수를 만들어, 웹캠 캡쳐 -> 파일로 변환 -> 프레임으로 표시 -> 대기 -> 입력을 받음 …반복 이런 식으로 잘~~(노가다 대박) 했다.

    2장부터 각 예제를 따라할 수 있다. 알고리즘을 이해한다기 보다는 이런식으로 사용한다는 느낌이다.

  • Learning OpenCV 4 Computer Vision with Python 3, chapter1

    Learning OpenCV 4 Computer Vision with Python 3, chapter1

    isbn: 978-1789531619

    opencv를 배우려 인터넷을 헤메기 전 적당한 책을 보기로 했다. 역시 찾아보면 인터넷에 있다.

    책에 실린 예제를 실행했다. 객체지향 방식으로 프로그램 하라는데, 기본 개념이 없는 난 잘 못하겠다. 내가 복잡한 프로그램을 작성하지도 않을 듯 하다. callback을 활용하여 window manager를 사용했는데, 찾아보면 알겠지만 일단 그냥 넘어갔다. 이 책이 객체지향을 설명하지 않으니까.

    python은 call by refrence로 값을 전달하는데, 형식이 맞지 않으면 무시되나 보다. 입력으로 보낸 dimension이 100, 100, 3인데 내부에서 계산한 값 dimension이 100, 100, 1이면 값을 써 주질 못한다. 이를 모르고 한참 해맸다. 이미지를 비디오 캡쳐 장치나 파일로 color를 입혀 읽으면 width, height, color = 3 형식을 갖는다. 이 이미지를 edge detection 함수를 통과시키면 width, height, color = 1로 변경된다. filter를 적용하기 전 강제로 틀을 맞춰줬다. 다른 능력자가 쓴 코드를 쓰려니 힘들다.

    docker로 xhost로 실행했는데, cv2.imshow에 좀 문제가 있는 듯 하다. matplot으로 그래프를 바꿀려고 했지만, 나중에 결국 숫자로만 인식할 것 같아 시간을 아끼려 그냥 두었다.

    https://dejavuqa.tistory.com/120