[카테고리:] 생활코딩

  • blas gemm launch failed tensorflow 2.0

    전에 어떻게 해결했는지 모르겠지만, gtx 1060에 model prediction 을 실행하면 BLAS GEMM 어쩌구 나온다. 아래와 같이 cuda 버전 문제이다.

    apt-get purge libcublas10 libcublas-dev
    apt-get install libcublas10=10.2.1.243-1 libcublas-dev=10.2.1.243-1 cuda-libraries-10-1 cuda-libraries-dev-10-1
    https://github.com/tensorflow/tensorflow/issues/37233

  • 강화학습으로 훈련한 스토리지(experience replay)

    전 방법으로 학습되지 않아, expirence replay를 끼워 넣었다. 한 state, action 세트를 학습하면 다른 케이스를 잘 학습하지 못했다. 대차가 뒤쪽에 들어간 경우 문제를 풀지 못해, 매 reset 시점 typeA 대차를 뒤쪽에 있을 확률을 키워 학습했지만 잘 안되었다. batch로 한 번에 여러 케이스를 학습해야 잘 되었다.

    역시 누군가 뚫은 길을 가는게 쉽다. experience repaly로 한번에 1,000개씩 학습 시키면 q-table을 네트웍으로 잘 근사한다. update를 할 때, 해당 target만 부분적으로 업데이트 해야 한다. “파이썬과 케라스로 배우는 강화학습” 234페이지에 잘 나와있다.

    각 케이스를 보면

    episode 71을 보면 typeA 대차가 구석에 처박혀 있다. 약간 삽질 후 4턴만에 빼내었다. 만족스럽다.

    episode 72는 바로 해결했다. 쉬우니까.

    episode 87을 풀지 못했다. 전의 경우와 같이 getX3만 반복하다 끝냈다.

    episode 814는 typeA가 구석에 박혀있다. 몇 번 삽질 후 빼내는데 성공했다.

    episode 625는 typeA가 X1/Y1 구석에 박혀있다. 역시 몇 번 삽질 후 빼냈다.

    episode 18은 앞쪽에 대차를 빼면 뒤에 있는 대차가 앞으로 오기 때문에, 문제를 해결할 수 없다. 얘는 가까운 A를 call하고 공간을 확보한 뒤, put으로 넣어주려 했다. 괜찮은 전략이다. 종료 조건을 추가해 줘야한다.

    episode 31은 X3에서 먼저 대차를 call했는데, 앞이 막혀있어 X1에서 대차를 call 했다. X1/Y2가 비어 있음을 확인하고, X3/Y3 대차를 밀어 넣었다. 바보가 아니었네.

    experience replay를 적용하기 전에 꼬박 3일 돌렸는데 잘 안되었다. 이번에는 1시간만에(100,000회) 문제를 해결했다. 와우!

  • 강화학습으로 훈련한 스토리지

    스토리지에 강화 학습을 적용했다. 스토리지 상황은 2개 열 * 3개 행 = 6개 셀을 가지고 있다. 처음 셀(X1/Y2, X2/Y2, X3/Y2)에만 대차를 넣고 뺄 수 있다.

    스토리지7에서 스토리지3, 4, 5으로 대차를 이동할 때 putX라 하고, 스토리지 3, 4, 5에서 스토리지 6으로 대차를 이동할 때 getX라 했다. 스토리지 7에서 스토리지 6으로 바로 가는 조건을 getX3Y3로 했다.

    종료 조건은 스토리지 6에 대차 종류가 A이고 스토리 7에 대차가 없어야(종류 V) 한다. 이럴 경우 보상으로 100점을 얻고 종료한다. 비정상 종료는 모든 셀에 대차가 없는 경우 벌점 -100점을 받고, 매 스탭마다 80점씩 까이도록 했다. 셀에 대차가 있는데 put을 해버리면 벌점 100을 받고 종료 되도록 했다.

    비교적 간단한 네트웍을 설정한 후 사골국(200,000회 정도? 학습)을 우려냈다. 한 에피소드가 끝나면 각 셀에 랜덤으로 대차 종류를 A, B, V(vacant)로 초기화 했다. 탐험 확률은 0.8에서 0.03까지 까이도록 했고, 0.03에 도달하면 더 안까이도록 유지되도록 했다. Q-table에 기반한 학습을 하고, 층이 3개정도네트웍으로 Approximation 했다.

    여기 저리 많이 참조했다.

    http://www.yes24.com/Product/Goods/44136413
    https://github.com/haje01/gym-tictactoe

    각 경우를 보면..

    X2, Y2 셀 typeA를 call하고 X3, Y3셀 대차를 puy으로 밀어 넣어야 하는데, 공간이 없다. agent는 X3 열 셀을 모두 비워 A 대차를 X1, Y3로 옮긴 후 X3, Y3 대차를 3행으로 밀어 넣었다. 일단 agent가 바보 아닌 걸로…

    episode 65920을 보면 X3, Y3 대차를 바로 call 하면 되는데, 셀에 있는 typeA 대차를 사용했다. 그리고 X3, Y3 대차를 put으로 비워진 셀에 넣었다.

    에피소드 65924를 보면 X2, Y2번 셀 대차 typeA를 먼저 빼워 공간을 비운 뒤, X3, Y3 대차를 put했다. 내가 했어도 이 방법대로 했다.

    episode 65944는 셀에 있는 대차 typeA를 사용하지 않고, X3, Y3 대차 typeA를 바로 돌려 사용하여 100점을 받았다. 정답.

    안쪽에 typeA 대차가 쳐박혀 있다면, getX3 삽질만 하다 성공하지 못한다. 이 상태에 빠지면 헤어나올 수 없다. 구석에 쳐 박혀 있다면 이 케이스를 고정하여 학습을 시키면 몇 만회 정도 걸려 앞에 걸리는 대차를 치워 typeA를 빼 내도록 한다. 랜덤으로 초기화 시키면 대차가 안쪽에 들어가 있는 경우가 너무 드물어 학습을 잘 할 수 없다. 틀리는 문제만 집중적으로 풀도록 환경을 설정 해줘야 할 듯 하다.

    업데이트.

    한 번 학습을 잘못하면 구렁텅이로 빠져 들어 매 10,000회마다 탐험확율 epsilon을 0.8로 초기화 시켰다. typeA 대차가 앞에 있다면 한번의 동작으로 쉽게 점수를 얻을 수 있으나,typeA 대차가 뒤쪽에 있다면 성공할 확율이 낮아져 어느 방법이 좋은지 agent가 알 수 없다. 어려운 문제를 더 많이 풀도록 reset을 조정했다.

    에피소드 38897을 보면 typeA가 구석에 있다. typeA를 빼기 위해서는 앞에 있는 typeV를 빼내야 하는데, X3/Y3셀에 typeV 가 있어 에피소드가 종료된다. 더 좋은 방법은 X1/Y2 셀 typeV를 바로 빼내어 벌점을 적게 받고 에피소드를 종료시키는 방법이 있다. 왠지 모르겠는데, 학습되지 않았다.

    에피소드 38898을 보면 typeA 대차가 안쪽을 들어가 있다. X1/Y1 또는 X3/Y1 중 하나를 빼내야 하는데, agent는 X1/Y1을 선택했다. 전에 탐험 확율을 낮췄을 경우에는 항상 getX3을 실행했는데, 이번에는 getX1을 선택했다. X1 행을 모두 비운 뒤, X3/Y3 대차를 putX1으로 처리했다. 내가 했어도 비슷하게 했다.

  • open62541 server+client 공정 동작 시간 측정

    처음 설정한 목표를 드디어 수행했다. 서버를 대략 구현했으니 다음으로 클라이언트를 만들었다. 내가 쪼랩이라 tutorial 문서 그대로 사용했다. cilent는 UA_Client_Service_browse로 server가 어떤 데이터를 가지고 있는지 볼 수 있다. 그러나 역시 한 단계밖에 볼 수 없다. 하부 구조를 보려면 nodeId를 기억하여 다시 browse를 해야 한다. 더 좋은 방법이 있겠지만, 이것도 되니까 문제 안된다.

    서버가 method를 가지고 있어 client에서 오는 콜을 받아 정리해서 보내 줄 수도 있지만, 시간을 많이 쓸 듯하여 쉽고 간단하게 갔다.

    서버가 각 공정 동작 상황을 OPC UA nodeId에 기록한다. 5초마다 업데이트 했는데, 실재 PLC로 구현한다면 매 초마다 update하는 방식으로 해야 할 듯하다. 그리 많은 부하가 걸리지는 않을 듯 하다.

    클라이언트는 매 3초마다 서버로 request하여 정보를 받아오는 방식으로 작성했다. timestamp는 server쪽 시각을 쓰지 않고, 클라이언트가 임의로 만든 시각을 기록했다.

    데이터를 데이터 베이스로 바로 업데이트 한다던가, 파일로 기록할 수도 있다. 그러나 시간을 많이 쓰므로 표준 출력으로 나온 메세지를 file로 redirect하면 쉽게 할 수 있다. 중복 데이터는 sort로 쉽게 지울 수 있다. bash 만세!

    #include "open62541.h"
    
    #include <signal.h>
    #include <stdlib.h>
    #include <time.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
    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, "SEQ");
    	//UA_Server_writeValue(server, currentNodeId, value);
    }
    
    /* predefined identifier for later use */
    UA_NodeId pumpTypeId = {1, UA_NODEIDTYPE_NUMERIC, {1001}};
    UA_NodeId cylTypeId = {1, UA_NODEIDTYPE_NUMERIC, {2001}};
    UA_NodeId robotTypeId = {1, UA_NODEIDTYPE_NUMERIC, {3001}};
    
    
    static void
    defineObjectTypes(UA_Server *server) {
    	/* Define the object type for "Device" */
    	UA_NodeId deviceTypeId; /* get the nodeid assigned by the server */
    	UA_ObjectTypeAttributes dtAttr = UA_ObjectTypeAttributes_default;
    	dtAttr.displayName = UA_LOCALIZEDTEXT("en-US", "DeviceType");
    	UA_Server_addObjectTypeNode(server, UA_NODEID_NULL,
    			UA_NODEID_NUMERIC(0, UA_NS0ID_BASEOBJECTTYPE),
    			UA_NODEID_NUMERIC(0, UA_NS0ID_HASSUBTYPE),
    			UA_QUALIFIEDNAME(1, "DeviceType"), dtAttr,
    			NULL, &deviceTypeId);
    
    	UA_VariableAttributes mnAttr = UA_VariableAttributes_default;
    	mnAttr.displayName = UA_LOCALIZEDTEXT("en-US", "ManufacturerName");
    	UA_NodeId manufacturerNameId;
    	UA_Server_addVariableNode(server, UA_NODEID_NULL, deviceTypeId,
    			UA_NODEID_NUMERIC(0, UA_NS0ID_HASCOMPONENT),
    			UA_QUALIFIEDNAME(1, "ManufacturerName"),
    			UA_NODEID_NUMERIC(0, UA_NS0ID_BASEDATAVARIABLETYPE), mnAttr, NULL, &manufacturerNameId);
    	/* Make the manufacturer name mandatory */
    	UA_Server_addReference(server, manufacturerNameId,
    			UA_NODEID_NUMERIC(0, UA_NS0ID_HASMODELLINGRULE),
    			UA_EXPANDEDNODEID_NUMERIC(0, UA_NS0ID_MODELLINGRULE_MANDATORY), true);
    
    
    	UA_VariableAttributes modelAttr = UA_VariableAttributes_default;
    	modelAttr.displayName = UA_LOCALIZEDTEXT("en-US", "ModelName");
    	UA_Server_addVariableNode(server, UA_NODEID_NULL, deviceTypeId,
    			UA_NODEID_NUMERIC(0, UA_NS0ID_HASCOMPONENT),
    			UA_QUALIFIEDNAME(1, "ModelName"),
    			UA_NODEID_NUMERIC(0, UA_NS0ID_BASEDATAVARIABLETYPE), modelAttr, NULL, NULL);
    
    	/* Define the object type for "Pump" */
    	UA_ObjectTypeAttributes ptAttr = UA_ObjectTypeAttributes_default;
    	ptAttr.displayName = UA_LOCALIZEDTEXT("en-US", "PumpType");
    	UA_Server_addObjectTypeNode(server, pumpTypeId,
    			deviceTypeId, UA_NODEID_NUMERIC(0, UA_NS0ID_HASSUBTYPE),
    			UA_QUALIFIEDNAME(1, "PumpType"), ptAttr,
    			NULL, NULL);
    
    	UA_VariableAttributes statusAttr = UA_VariableAttributes_default;
    	statusAttr.displayName = UA_LOCALIZEDTEXT("en-US", "Status");
    	statusAttr.valueRank = UA_VALUERANK_SCALAR;
    	UA_NodeId statusId;
    	UA_Server_addVariableNode(server, UA_NODEID_NULL, pumpTypeId,
    			UA_NODEID_NUMERIC(0, UA_NS0ID_HASCOMPONENT),
    			UA_QUALIFIEDNAME(1, "Status"),
    			UA_NODEID_NUMERIC(0, UA_NS0ID_BASEDATAVARIABLETYPE), statusAttr, NULL, &statusId);
    	/* Make the status variable mandatory */
    	UA_Server_addReference(server, statusId,
    			UA_NODEID_NUMERIC(0, UA_NS0ID_HASMODELLINGRULE),
    			UA_EXPANDEDNODEID_NUMERIC(0, UA_NS0ID_MODELLINGRULE_MANDATORY), true);
    
    	UA_VariableAttributes rpmAttr = UA_VariableAttributes_default;
    	rpmAttr.displayName = UA_LOCALIZEDTEXT("en-US", "MotorRPM");
    	rpmAttr.valueRank = UA_VALUERANK_SCALAR;
    	UA_Server_addVariableNode(server, UA_NODEID_NULL, pumpTypeId,
    			UA_NODEID_NUMERIC(0, UA_NS0ID_HASCOMPONENT),
    			UA_QUALIFIEDNAME(1, "MotorRPMs"),
    			UA_NODEID_NUMERIC(0, UA_NS0ID_BASEDATAVARIABLETYPE), rpmAttr, NULL, NULL);
    }
    
    static void
    defineCylTypes(UA_Server *server) {
    	/* Define the object type for "Device" */
    	UA_NodeId deviceTypeId; /* get the nodeid assigned by the server */
    	UA_ObjectTypeAttributes dtAttr = UA_ObjectTypeAttributes_default;
    	dtAttr.displayName = UA_LOCALIZEDTEXT("en-US", "Cylinder Type");
    	UA_Server_addObjectTypeNode(server, UA_NODEID_NULL,
    			UA_NODEID_NUMERIC(0, UA_NS0ID_BASEOBJECTTYPE),
    			UA_NODEID_NUMERIC(0, UA_NS0ID_HASSUBTYPE),
    			UA_QUALIFIEDNAME(1, "Cylinder Type"), dtAttr,
    			NULL, &deviceTypeId);
    
    	UA_VariableAttributes mnAttr = UA_VariableAttributes_default;
    	mnAttr.displayName = UA_LOCALIZEDTEXT("en-US", "Manufacturer Name");
    	UA_NodeId manufacturerNameId;
    	UA_Server_addVariableNode(server, UA_NODEID_NULL, deviceTypeId,
    			UA_NODEID_NUMERIC(0, UA_NS0ID_HASCOMPONENT),
    			UA_QUALIFIEDNAME(1, "Cylinder"),
    			UA_NODEID_NUMERIC(0, UA_NS0ID_BASEDATAVARIABLETYPE), mnAttr, NULL, &manufacturerNameId);
    
    	UA_VariableAttributes modelAttr = UA_VariableAttributes_default;
    	UA_NodeId modelNameId;
    	modelAttr.displayName = UA_LOCALIZEDTEXT("en-US", "Cyl Model Name");
    	UA_Server_addVariableNode(server, UA_NODEID_NULL, deviceTypeId,
    			UA_NODEID_NUMERIC(0, UA_NS0ID_HASCOMPONENT),
    			UA_QUALIFIEDNAME(1, "Cyl Model Name"),
    			UA_NODEID_NUMERIC(0, UA_NS0ID_BASEDATAVARIABLETYPE), modelAttr, NULL, &modelNameId);
    
    	UA_Server_addReference(server,modelNameId,
    			UA_NODEID_NUMERIC(0, UA_NS0ID_HASMODELLINGRULE),
    			UA_EXPANDEDNODEID_NUMERIC(0, UA_NS0ID_MODELLINGRULE_MANDATORY), true);
    
    	/* Define the object type for "Pump" */
    	UA_ObjectTypeAttributes ptAttr = UA_ObjectTypeAttributes_default;
    	ptAttr.displayName = UA_LOCALIZEDTEXT("en-US", "Running State");
    	UA_Server_addObjectTypeNode(server, cylTypeId,
    			deviceTypeId, UA_NODEID_NUMERIC(0, UA_NS0ID_HASSUBTYPE),
    			UA_QUALIFIEDNAME(1, "Running State"), ptAttr,
    			NULL, NULL);
    
    
    	UA_VariableAttributes statusAttr = UA_VariableAttributes_default;
    	statusAttr.displayName = UA_LOCALIZEDTEXT("en-US", "Status");
    	statusAttr.valueRank = UA_VALUERANK_SCALAR;
    	UA_NodeId statusId;
    	UA_Server_addVariableNode(server, UA_NODEID_NULL, cylTypeId,
    			UA_NODEID_NUMERIC(0, UA_NS0ID_HASCOMPONENT),
    			UA_QUALIFIEDNAME(1, "Status"),
    			UA_NODEID_NUMERIC(0, UA_NS0ID_BASEDATAVARIABLETYPE), statusAttr, NULL, &statusId);
    	/* Make the manufacturer name mandatory */
    	// UA_Server_addReference()를 어떻게 하냐에 따라  statusId
    	// instance 만들경우 표시
    	UA_Server_addReference(server, statusId,
    			UA_NODEID_NUMERIC(0, UA_NS0ID_HASMODELLINGRULE),
    			UA_EXPANDEDNODEID_NUMERIC(0, UA_NS0ID_MODELLINGRULE_MANDATORY), true);
    }
    
    
    
    static void
    defineRobotTypes(UA_Server *server) {
    	/* Define the object type for "Device" */
    	UA_NodeId deviceTypeId; /* get the nodeid assigned by the server */
    	UA_ObjectTypeAttributes dtAttr = UA_ObjectTypeAttributes_default;
    	dtAttr.displayName = UA_LOCALIZEDTEXT("en-US", "Robot Type");
    	UA_Server_addObjectTypeNode(server, UA_NODEID_NULL,
    			UA_NODEID_NUMERIC(0, UA_NS0ID_BASEOBJECTTYPE),
    			UA_NODEID_NUMERIC(0, UA_NS0ID_HASSUBTYPE),
    			UA_QUALIFIEDNAME(1, "Robot Type"), dtAttr,
    			NULL, &deviceTypeId);
    
    	UA_VariableAttributes mnAttr = UA_VariableAttributes_default;
    	mnAttr.displayName = UA_LOCALIZEDTEXT("en-US", "Manufacturer Name");
    	UA_NodeId manufacturerNameId;
    	UA_Server_addVariableNode(server, UA_NODEID_NULL, deviceTypeId,
    			UA_NODEID_NUMERIC(0, UA_NS0ID_HASCOMPONENT),
    			UA_QUALIFIEDNAME(1, "Robot"),
    			UA_NODEID_NUMERIC(0, UA_NS0ID_BASEDATAVARIABLETYPE), mnAttr, NULL, &manufacturerNameId);
    
    	UA_VariableAttributes modelAttr = UA_VariableAttributes_default;
    	UA_NodeId modelNameId;
    	modelAttr.displayName = UA_LOCALIZEDTEXT("en-US", "Robot Model Name");
    	UA_Server_addVariableNode(server, UA_NODEID_NULL, deviceTypeId,
    			UA_NODEID_NUMERIC(0, UA_NS0ID_HASCOMPONENT),
    			UA_QUALIFIEDNAME(1, "Robot Model Name"),
    			UA_NODEID_NUMERIC(0, UA_NS0ID_BASEDATAVARIABLETYPE), modelAttr, NULL, &modelNameId);
    
    	UA_Server_addReference(server,modelNameId,
    			UA_NODEID_NUMERIC(0, UA_NS0ID_HASMODELLINGRULE),
    			UA_EXPANDEDNODEID_NUMERIC(0, UA_NS0ID_MODELLINGRULE_MANDATORY), true);
    
    	/* Define the object type for "Pump" */
    	UA_ObjectTypeAttributes ptAttr = UA_ObjectTypeAttributes_default;
    	ptAttr.displayName = UA_LOCALIZEDTEXT("en-US", "Interlock");
    	UA_Server_addObjectTypeNode(server, robotTypeId,
    			deviceTypeId, UA_NODEID_NUMERIC(0, UA_NS0ID_HASSUBTYPE),
    			UA_QUALIFIEDNAME(1, "Interlock"), ptAttr,
    			NULL, NULL);
    
    
    	UA_VariableAttributes statusAttr = UA_VariableAttributes_default;
    	statusAttr.displayName = UA_LOCALIZEDTEXT("en-US", "Status");
    	statusAttr.valueRank = UA_VALUERANK_SCALAR;
    	UA_NodeId statusId;
    	UA_Server_addVariableNode(server, UA_NODEID_NULL, robotTypeId,
    			UA_NODEID_NUMERIC(0, UA_NS0ID_HASCOMPONENT),
    			UA_QUALIFIEDNAME(1, "Status"),
    			UA_NODEID_NUMERIC(0, UA_NS0ID_BASEDATAVARIABLETYPE), statusAttr, NULL, &statusId);
    	/* Make the manufacturer name mandatory */
    	// UA_Server_addReference()를 어떻게 하냐에 따라  statusId
    	// instance 만들경우 표시
    	UA_Server_addReference(server, statusId,
    			UA_NODEID_NUMERIC(0, UA_NS0ID_HASMODELLINGRULE),
    			UA_EXPANDEDNODEID_NUMERIC(0, UA_NS0ID_MODELLINGRULE_MANDATORY), true);
    }
    
    
    
    static void
    addPumpObjectInstance(UA_Server *server, char *name) {
    	UA_ObjectAttributes oAttr = UA_ObjectAttributes_default;
    	oAttr.displayName = UA_LOCALIZEDTEXT("en-US", name);
    	UA_Server_addObjectNode(server, UA_NODEID_NULL,
    			UA_NODEID_NUMERIC(0, UA_NS0ID_OBJECTSFOLDER),
    			UA_NODEID_NUMERIC(0, UA_NS0ID_ORGANIZES),
    			UA_QUALIFIEDNAME(1, name),
    			pumpTypeId, /* this refers to the object type
    						   identifier */
    			oAttr, NULL, NULL);
    }
    
    static void
    addCylObjectInstance(UA_Server *server, char *name) {
    	UA_ObjectAttributes oAttr = UA_ObjectAttributes_default;
    	oAttr.displayName = UA_LOCALIZEDTEXT("en-US", name);
    	/*
    	   UA_Server_addObjectNode(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_ObjectAttributes attr,
    	   void *nodeContext, UA_NodeId *outNewNodeId) {
    	 */
    
    	UA_Server_addObjectNode(server, UA_NODEID_NULL,
    			UA_NODEID_NUMERIC(0, UA_NS0ID_OBJECTSFOLDER),
    			UA_NODEID_NUMERIC(0, UA_NS0ID_ORGANIZES),
    			UA_QUALIFIEDNAME(1, name),
    			cylTypeId, /* this refers to the object type
    						  identifier */
    			oAttr, NULL, NULL);
    }
    
    
    
    static void
    addRbtObjectInstance(UA_Server *server, char *name) {
    	UA_ObjectAttributes oAttr = UA_ObjectAttributes_default;
    	oAttr.displayName = UA_LOCALIZEDTEXT("en-US", name);
    	/*
    	   UA_Server_addObjectNode(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_ObjectAttributes attr,
    	   void *nodeContext, UA_NodeId *outNewNodeId) {
    	 */
    
    	UA_Server_addObjectNode(server, UA_NODEID_NULL,
    			UA_NODEID_NUMERIC(0, UA_NS0ID_OBJECTSFOLDER),
    			UA_NODEID_NUMERIC(0, UA_NS0ID_ORGANIZES),
    			UA_QUALIFIEDNAME(1, name),
    			robotTypeId, /* this refers to the object type
    							identifier */
    			oAttr, NULL, NULL);
    }
    
    
    static UA_StatusCode
    pumpTypeConstructor(UA_Server *server,
    		const UA_NodeId *sessionId, void *sessionContext,
    		const UA_NodeId *typeId, void *typeContext,
    		const UA_NodeId *nodeId, void **nodeContext) {
    	UA_LOG_INFO(UA_Log_Stdout, UA_LOGCATEGORY_USERLAND, "New pump created");
    
    	/* Find the NodeId of the status child variable */
    	UA_RelativePathElement rpe;
    	UA_RelativePathElement_init(&rpe);
    	rpe.referenceTypeId = UA_NODEID_NUMERIC(0, UA_NS0ID_HASCOMPONENT);
    	rpe.isInverse = false;
    	rpe.includeSubtypes = false;
    	rpe.targetName = UA_QUALIFIEDNAME(1, "Status");
    
    	UA_BrowsePath bp;
    	UA_BrowsePath_init(&bp);
    	bp.startingNode = *nodeId;
    	bp.relativePath.elementsSize = 1;
    	bp.relativePath.elements = &rpe;
    
    	UA_BrowsePathResult bpr =
    		UA_Server_translateBrowsePathToNodeIds(server, &bp);
    	if(bpr.statusCode != UA_STATUSCODE_GOOD ||
    			bpr.targetsSize < 1)
    		return bpr.statusCode;
    
    	/* Set the status value */
    	UA_Boolean status = true;
    	UA_Variant value;
    	UA_Variant_setScalar(&value, &status, &UA_TYPES[UA_TYPES_BOOLEAN]);
    	UA_Server_writeValue(server, bpr.targets[0].targetId.nodeId, value);
    	UA_BrowsePathResult_clear(&bpr);
    
    	/* At this point we could replace the node context .. */
    
    	return UA_STATUSCODE_GOOD;
    }
    
    static void
    addPumpTypeConstructor(UA_Server *server) {
    	UA_NodeTypeLifecycle lifecycle;
    	lifecycle.constructor = pumpTypeConstructor;
    	lifecycle.destructor = NULL;
    	UA_Server_setNodeTypeLifecycle(server, pumpTypeId, lifecycle);
    }
    
    
    
    
    //method 추가
    static UA_StatusCode
    helloWorldMethodCallback(UA_Server *server,
    		const UA_NodeId *sessionId, void *sessionHandle,
    		const UA_NodeId *methodId, void *methodContext,
    		const UA_NodeId *objectId, void *objectContext,
    		size_t inputSize, const UA_Variant *input,
    		size_t outputSize, UA_Variant *output) {
    	UA_String *inputStr = (UA_String*)input->data;
    	UA_String tmp = UA_STRING_ALLOC("Hello ");
    	if(inputStr->length > 0) {
    		tmp.data = (UA_Byte *)UA_realloc(tmp.data, tmp.length + inputStr->length);
    		memcpy(&tmp.data[tmp.length], inputStr->data, inputStr->length);
    		tmp.length += inputStr->length;
    	}
    	UA_Variant_setScalarCopy(output, &tmp, &UA_TYPES[UA_TYPES_STRING]);
    	UA_String_clear(&tmp);
    	UA_LOG_INFO(UA_Log_Stdout, UA_LOGCATEGORY_SERVER, "Hello World was called");
    	return UA_STATUSCODE_GOOD;
    }
    
    static void
    addHellWorldMethod(UA_Server *server) {
    	UA_Argument inputArgument;
    	UA_Argument_init(&inputArgument);
    	inputArgument.description = UA_LOCALIZEDTEXT("en-US", "A String");
    	inputArgument.name = UA_STRING("MyInput");
    	inputArgument.dataType = UA_TYPES[UA_TYPES_STRING].typeId;
    	inputArgument.valueRank = UA_VALUERANK_SCALAR;
    
    	UA_Argument outputArgument;
    	UA_Argument_init(&outputArgument);
    	outputArgument.description = UA_LOCALIZEDTEXT("en-US", "A String");
    	outputArgument.name = UA_STRING("MyOutput");
    	outputArgument.dataType = UA_TYPES[UA_TYPES_STRING].typeId;
    	outputArgument.valueRank = UA_VALUERANK_SCALAR;
    
    	UA_MethodAttributes helloAttr = UA_MethodAttributes_default;
    	helloAttr.description = UA_LOCALIZEDTEXT("en-US","Say `Hello World`");
    	helloAttr.displayName = UA_LOCALIZEDTEXT("en-US","Hello World");
    	helloAttr.executable = true;
    	helloAttr.userExecutable = true;
    	UA_Server_addMethodNode(server, UA_NODEID_NUMERIC(1,62541),
    			UA_NODEID_NUMERIC(0, UA_NS0ID_OBJECTSFOLDER),
    			UA_NODEID_NUMERIC(0, UA_NS0ID_HASCOMPONENT),
    			UA_QUALIFIEDNAME(1, "hello world"),
    			helloAttr, &helloWorldMethodCallback,
    			1, &inputArgument, 1, &outputArgument, NULL, NULL);
    }
    
    
    //이름으로 nodeId를 찾는 함수.
    UA_StatusCode
    findNodeIdWithName(UA_Server *server, UA_NodeId *startNodeId, char *name, UA_NodeId **retNodeId){
    
    	/* Find the NodeId of the status child variable */
    	UA_RelativePathElement rpe;
    	UA_RelativePathElement_init(&rpe);
    	rpe.referenceTypeId = UA_NODEID_NUMERIC(0, UA_NS0ID_HASCOMPONENT);
    	rpe.isInverse = false;
    	rpe.includeSubtypes = false;
    	rpe.targetName = UA_QUALIFIEDNAME(1,name); 
    
    	UA_BrowsePath bp;
    	UA_BrowsePath_init(&bp);
    
    
    	bp.startingNode = *startNodeId;
    	bp.relativePath.elementsSize = 1;
    	bp.relativePath.elements = &rpe;
    
    	UA_BrowsePathResult bpr =
    		UA_Server_translateBrowsePathToNodeIds(server, &bp);
    	if(bpr.statusCode != UA_STATUSCODE_GOOD ||
    			bpr.targetsSize < 1)
    		return bpr.statusCode;
    	UA_LOG_INFO(UA_Log_Stdout, UA_LOGCATEGORY_USERLAND, "found target is %d", bpr.targets->targetId.nodeId.identifier.numeric);
    	**retNodeId = bpr.targets->targetId.nodeId;
    
    }
    
    
    
    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);
    	//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, "SEQ");
    	UA_QualifiedName myVarName = UA_QUALIFIEDNAME(1, "SEQ");
    	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, "SEQ");
    	UA_Server_setVariableNode_valueCallback(server, currentNodeId, callback);
    
    	//설비 정의
    
    	defineCylTypes(server);
    	addCylObjectInstance(server, "pin");
    	addCylObjectInstance(server, "latch");
    	addCylObjectInstance(server, "clamp");
    	defineRobotTypes(server);
    	addRbtObjectInstance(server, "Loading Robot");
    
    	/*
    	   defineObjectTypes(server);
    	   addPumpTypeConstructor(server);
    	   addPumpObjectInstance(server, "pump4");
    	 */
    
    
    	//method
    	addHellWorldMethod(server);
    
    
    	//ladder를 시뮬래이션하는 부분.
    	//thread로 latch fwd -> clamp fwd -> pin fwd -> robot in -> pin bwd: 공정 작업시간
    	//         robot in off -> clamp bwd -> latch bwd 순으로 실행
    
    #undef TESe
    #ifdef TEST
    
    	//테스트
    	//각 설비값을 설정하는 부분
    	UA_Variant tmp_value;
    	bool bitoff = 0;
    	UA_Variant_setScalar(&tmp_value, &bitoff, &UA_TYPES[UA_TYPES_BOOLEAN]);
    	UA_NodeId tmpNodeId = UA_NODEID_STRING(1, "Status");
    	UA_Server_writeValue(server, tmpNodeId, tmp_value);
    
    	/* Find the NodeId of the status child variable */
    	UA_RelativePathElement rpe;
    	UA_RelativePathElement_init(&rpe);
    	rpe.referenceTypeId = UA_NODEID_NUMERIC(0, UA_NS0ID_HASCOMPONENT);
    	rpe.isInverse = false;
    	rpe.includeSubtypes = true;
    	//rpe.targetName = UA_QUALIFIEDNAME(1, "Status");
    	rpe.targetName = UA_QUALIFIEDNAME(1, "latch");
    	//rpe.targetName = UA_QUALIFIEDNAME(1, "variable");
    
    
    	UA_BrowsePath bp;
    	UA_BrowsePath_init(&bp);
    	UA_NodeId *nodeId  = UA_NodeId_new();
    	*nodeId = UA_NODEID_NUMERIC(0, UA_NS0ID_OBJECTSFOLDER);
    	//UA_NodeId tmp = {0, UA_NODEIDTYPE_NUMERIC, {50238}};
    	//printf("\nNode id is %d\n", tmp);
    	//UA_NodeId *nodeId = &tmp;
    	bp.startingNode = *nodeId;
    	bp.relativePath.elementsSize = 1;
    	bp.relativePath.elements = &rpe;
    
    	UA_BrowsePathResult bpr =
    		UA_Server_translateBrowsePathToNodeIds(server, &bp);
    	if(bpr.statusCode != UA_STATUSCODE_GOOD ||
    			bpr.targetsSize < 1)
    		return bpr.statusCode;
    
    	UA_LOG_INFO(UA_Log_Stdout, UA_LOGCATEGORY_USERLAND, "found target is %d", bpr.targets->targetId.nodeId.identifier.numeric);
    	//print로 structure를 출력할 수 없음.
    	//printf("\nnode id is %d\n", (UA_NodeId)bpr.targets->targetId.nodeId);
    	//* Set the status value */
    	UA_Boolean status = true;
    	UA_Variant value;
    	UA_Variant_setScalar(&value, &status, &UA_TYPES[UA_TYPES_BOOLEAN]);
    	UA_Server_writeValue(server, bpr.targets->targetId.nodeId, value);
    	//UA_Server_writeValue(server, tmp, value);
    
    	//다시 아래로 내림.
    	bp.startingNode = bpr.targets->targetId.nodeId;
    	rpe.targetName = UA_QUALIFIEDNAME(1, "Status");
    	UA_BrowsePathResult bpr2 =
    		UA_Server_translateBrowsePathToNodeIds(server, &bp);
    	if(bpr2.statusCode != UA_STATUSCODE_GOOD ||
    			bpr2.targetsSize < 1)
    		return bpr.statusCode;
    
    	UA_LOG_INFO(UA_Log_Stdout, UA_LOGCATEGORY_USERLAND, "found target is %d", bpr2.targets->targetId.nodeId.identifier.numeric);
    	UA_Server_writeValue(server, bpr2.targets->targetId.nodeId, value);
    	UA_BrowsePathResult_clear(&bpr);
    #endif
    
    	//NodeId를 저장할 structure 생성
    	struct eqiupNodeId{
    		UA_NodeId latchNodeId;
    		UA_NodeId clampNodeId;
    		UA_NodeId pinNodeId;
    		UA_NodeId robotNodeId;
    	};
    
    	struct eqiupNodeId myEquipNodeId;
    
    	UA_NodeId **nodeIdtmp;
    	UA_NodeId *nodeIdtmp2 = UA_NodeId_new();
    	nodeIdtmp = &nodeIdtmp2;
    	UA_NodeId *nodeId  = UA_NodeId_new();
    	*nodeId = UA_NODEID_NUMERIC(0, UA_NS0ID_OBJECTSFOLDER);
    
    	if(	findNodeIdWithName(server, nodeId, "latch", nodeIdtmp) == UA_STATUSCODE_GOOD){
    		nodeId = *nodeIdtmp;
    		if( findNodeIdWithName(server, nodeId, "Status", nodeIdtmp) == UA_STATUSCODE_GOOD){
    			nodeId = *nodeIdtmp;
    			myEquipNodeId.latchNodeId = *nodeId;
    			//printf("Variable 변경\n");
    			//* Set the status value */
    			//			UA_Boolean status = false;
    			//			UA_Variant value;
    			//			UA_Variant_setScalar(&value, &status, &UA_TYPES[UA_TYPES_BOOLEAN]);
    			//			UA_Server_writeValue(server, *nodeId, value);
    			//			UA_LOG_INFO(UA_Log_Stdout, UA_LOGCATEGORY_USERLAND, "node id %d value was written", nodeId->identifier.numeric);
    		}
    	}
    	*nodeId = UA_NODEID_NUMERIC(0, UA_NS0ID_OBJECTSFOLDER);
    	if(	findNodeIdWithName(server, nodeId, "clamp", nodeIdtmp) == UA_STATUSCODE_GOOD){
    		nodeId = *nodeIdtmp;
    		if( findNodeIdWithName(server, nodeId, "Status", nodeIdtmp) == UA_STATUSCODE_GOOD){
    			nodeId = *nodeIdtmp;
    			myEquipNodeId.clampNodeId = *nodeId;
    
    			//printf("Variable 변경\n");
    			//* Set the status value */
    			//			UA_Boolean status = false;
    			//			UA_Variant value;
    			//			UA_Variant_setScalar(&value, &status, &UA_TYPES[UA_TYPES_BOOLEAN]);
    			//			UA_Server_writeValue(server, *nodeId, value);
    			//			UA_LOG_INFO(UA_Log_Stdout, UA_LOGCATEGORY_USERLAND, "node id %d value was written", nodeId->identifier.numeric);
    		}
    	}
    
    	*nodeId = UA_NODEID_NUMERIC(0, UA_NS0ID_OBJECTSFOLDER);
    	if(	findNodeIdWithName(server, nodeId, "pin", nodeIdtmp) == UA_STATUSCODE_GOOD){
    		nodeId = *nodeIdtmp;
    		if( findNodeIdWithName(server, nodeId, "Status", nodeIdtmp) == UA_STATUSCODE_GOOD){
    			nodeId = *nodeIdtmp;
    			myEquipNodeId.pinNodeId = *nodeId;
    			//printf("Variable 변경\n");
    			//* Set the status value */
    			//			UA_Boolean status = false;
    			//			UA_Variant value;
    			//			UA_Variant_setScalar(&value, &status, &UA_TYPES[UA_TYPES_BOOLEAN]);
    			//			UA_Server_writeValue(server, *nodeId, value);
    			//			UA_LOG_INFO(UA_Log_Stdout, UA_LOGCATEGORY_USERLAND, "node id %d value was written", nodeId->identifier.numeric);
    		}
    	}
    
    	*nodeId = UA_NODEID_NUMERIC(0, UA_NS0ID_OBJECTSFOLDER);
    	if(	findNodeIdWithName(server, nodeId, "Loading Robot", nodeIdtmp) == UA_STATUSCODE_GOOD){
    		nodeId = *nodeIdtmp;
    		if( findNodeIdWithName(server, nodeId, "Status", nodeIdtmp) == UA_STATUSCODE_GOOD){
    			nodeId = *nodeIdtmp;
    			myEquipNodeId.robotNodeId = *nodeId;
    			//printf("Variable 변경\n");
    			//* Set the status value */
    			//			UA_Boolean status = false;
    			//			UA_Variant value;
    			//			UA_Variant_setScalar(&value, &status, &UA_TYPES[UA_TYPES_BOOLEAN]);
    			//			UA_Server_writeValue(server, *nodeId, value);
    			//			UA_LOG_INFO(UA_Log_Stdout, UA_LOGCATEGORY_USERLAND, "node id %d value was written", nodeId->identifier.numeric);
    		}
    	}
    
    
    	//구조체 출력.
    	//printf("현재 노드는 %d \n", myEquipNodeId.latchNodeId.identifier.numeric);
    	//printf("현재 노드는 %d \n", myEquipNodeId.clampNodeId.identifier.numeric);
    	//printf("현재 노드는 %d \n", myEquipNodeId.pinNodeId.identifier.numeric);
    	//printf("현재 노드는 %d \n", myEquipNodeId.robotNodeId.identifier.numeric);
    
    	//한번에 업데이트
    	UA_Boolean statusTrue = true;
    	UA_Boolean statusFalse = false;
    	UA_Variant value;
    	UA_Variant_setScalar(&value, &statusFalse, &UA_TYPES[UA_TYPES_BOOLEAN]);
    
    	//초기상태
    	UA_Server_writeValue(server, myEquipNodeId.latchNodeId, value);
    	UA_Server_writeValue(server, myEquipNodeId.clampNodeId, value);
    	UA_Server_writeValue(server, myEquipNodeId.pinNodeId, value);
    	UA_Server_writeValue(server, myEquipNodeId.robotNodeId, value);
    	UA_LOG_INFO(UA_Log_Stdout, UA_LOGCATEGORY_USERLAND, "모든 노드가 초기화(모든 Value가 0) 됨.");
    
    
    
    	//서버 구동.
    	//주기적으로 업데이트로 수정
    	//https://youtu.be/abDnBv5u6bU//
    	UA_LOG_INFO(UA_Log_Stdout, UA_LOGCATEGORY_USERLAND, "Starting server...every 5 secs");
    	UA_StatusCode retval = UA_Server_run_startup(server);
    
    	if(retval != UA_STATUSCODE_GOOD){
    		UA_Server_delete(server);
    		return retval;
    	}
    
    
    	int timestamp = time(0)+5;
    	int equipStates = 0;
    	int seqInt = 0;
    	bool seqFlag=false;		//false: 현재 작업 중, true: 작업완료 다음 작업 시작
    
    	while(running == true){
    		// Handle Server
    		UA_Server_run_iterate(server, true);
    
    
    		//새로운 seq을 할당 받음
    		if(seqFlag == true)
    			seqInt = rand()%1000;
    
    
    		//Update Status Variable
    		if(time(0) > timestamp)
    		{
    			timestamp = time(0) + 5;
    
    			switch(equipStates)
    			{
    				case 0: //초기상태
    					equipStates = 1;
    					//seq 기록.
    					UA_Variant_setScalar(&value, &seqInt, &UA_TYPES[UA_TYPES_INT32]);
    					UA_NodeId currentNodeId = UA_NODEID_STRING(1, "SEQ");
    					UA_Server_writeValue(server, currentNodeId, value);
    					//flag 비활성화
    					seqFlag = false;
    
    					UA_Variant_setScalar(&value, &statusFalse, &UA_TYPES[UA_TYPES_BOOLEAN]);
    
    					UA_Server_writeValue(server, myEquipNodeId.latchNodeId, value);
    					UA_Server_writeValue(server, myEquipNodeId.clampNodeId, value);
    					UA_Server_writeValue(server, myEquipNodeId.pinNodeId, value);
    					UA_Server_writeValue(server, myEquipNodeId.robotNodeId, value);
    					UA_LOG_INFO(UA_Log_Stdout, UA_LOGCATEGORY_USERLAND, "모든 노드가 초기화(모든 Value가 0) 됨.");
    					break;
    
    				case 1: //1단계, latch fwd
    					equipStates = 2;
    					//flag 비활성화
    					seqFlag = false;
    
    					UA_Variant_setScalar(&value, &statusFalse, &UA_TYPES[UA_TYPES_BOOLEAN]);
    
    					UA_Server_writeValue(server, myEquipNodeId.clampNodeId, value);
    					UA_Server_writeValue(server, myEquipNodeId.pinNodeId, value);
    					UA_Server_writeValue(server, myEquipNodeId.robotNodeId, value);
    
    					UA_Variant_setScalar(&value, &statusTrue, &UA_TYPES[UA_TYPES_BOOLEAN]);
    					UA_Server_writeValue(server, myEquipNodeId.latchNodeId, value);
    					UA_LOG_INFO(UA_Log_Stdout, UA_LOGCATEGORY_USERLAND, "1단계 동작 완료..latch 전진");
    					break;
    
    				case 2: //2단계, clamp fwd
    					equipStates = 3;
    					//flag 비활성화
    					seqFlag = false;
    
    					UA_Variant_setScalar(&value, &statusFalse, &UA_TYPES[UA_TYPES_BOOLEAN]);
    					UA_Server_writeValue(server, myEquipNodeId.pinNodeId, value);
    					UA_Server_writeValue(server, myEquipNodeId.robotNodeId, value);
    
    					UA_Variant_setScalar(&value, &statusTrue, &UA_TYPES[UA_TYPES_BOOLEAN]);
    					UA_Server_writeValue(server, myEquipNodeId.latchNodeId, value);
    					UA_Server_writeValue(server, myEquipNodeId.clampNodeId, value);
    					UA_LOG_INFO(UA_Log_Stdout, UA_LOGCATEGORY_USERLAND, "2단계 동작 완료..calmp 전진(latch, clamp 전진상태)");
    					break;
    
    				case 3: //3단계, pin fwd
    					equipStates = 4;
    					//flag 비활성화
    					seqFlag = false;
    
    					UA_Variant_setScalar(&value, &statusFalse, &UA_TYPES[UA_TYPES_BOOLEAN]);
    					UA_Server_writeValue(server, myEquipNodeId.robotNodeId, value);
    
    					UA_Variant_setScalar(&value, &statusTrue, &UA_TYPES[UA_TYPES_BOOLEAN]);
    					UA_Server_writeValue(server, myEquipNodeId.latchNodeId, value);
    					UA_Server_writeValue(server, myEquipNodeId.clampNodeId, value);
    					UA_Server_writeValue(server, myEquipNodeId.pinNodeId, value);
    					UA_LOG_INFO(UA_Log_Stdout, UA_LOGCATEGORY_USERLAND, "3단계 동작 완료..pin 전진(latch, clamp, pin 전진상태)");
    					break;
    
    				case 4: //4단계, robot in
    					equipStates = 5;
    					//flag 비활성화
    					seqFlag = false;
    
    					UA_Variant_setScalar(&value, &statusTrue, &UA_TYPES[UA_TYPES_BOOLEAN]);
    
    					UA_Server_writeValue(server, myEquipNodeId.robotNodeId, value);
    					UA_Server_writeValue(server, myEquipNodeId.latchNodeId, value);
    					UA_Server_writeValue(server, myEquipNodeId.clampNodeId, value);
    					UA_Server_writeValue(server, myEquipNodeId.pinNodeId, value);
    					UA_LOG_INFO(UA_Log_Stdout, UA_LOGCATEGORY_USERLAND, "4단계 동작 완료..robot in (latch, clamp, pin 전진상태, robot 진입)");
    					break;
    
    				case 5: //5단계, robot in
    					//다시 초기상태로 이동
    					equipStates = 0;
    					//flag 활성화
    					seqFlag = true;
    
    					UA_Variant_setScalar(&value, &statusTrue, &UA_TYPES[UA_TYPES_BOOLEAN]);
    
    					UA_Server_writeValue(server, myEquipNodeId.robotNodeId, value);
    					UA_Server_writeValue(server, myEquipNodeId.latchNodeId, value);
    					UA_Server_writeValue(server, myEquipNodeId.clampNodeId, value);
    
    					UA_Variant_setScalar(&value, &statusFalse, &UA_TYPES[UA_TYPES_BOOLEAN]);
    					UA_Server_writeValue(server, myEquipNodeId.pinNodeId, value);
    
    					UA_LOG_INFO(UA_Log_Stdout, UA_LOGCATEGORY_USERLAND, "5단계 동작 완료..pin bwd(latch, clamp, 전진상태, robot 진입, pin 후진)");
    					break;
    
    				default:
    					equipStates = 0;
    					//flag 비활성화
    					seqFlag = false;
    					seqInt = 0;
    
    					break;
    
    			} //switch
    
    
    		}	//if
    
    
    	} //while
    
    	retval = UA_Server_run_shutdown(server);
    	//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;
    } // main
    #include "open62541.h"
    #include <time.h>
    #include <stdlib.h>
    
    
    //#ifdef UA_ENABLE_METHODCALLS
    //UA_StatusCode
    //UA_Client_call(UA_Client *client, const UA_NodeId objectId,
    //               const UA_NodeId methodId, size_t inputSize, const UA_Variant *input,
    //               size_t *outputSize, UA_Variant **output);
    //#endif
    
    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;
        }
    
    
    	//NodeId로 조회한 값을 저장할 structure 생성
    	struct eqiupVal {
    		UA_Int32 seq;
    		UA_NodeId latchNodeId;
    		UA_NodeId clampNodeId;
    		UA_NodeId pinNodeId;
    		UA_NodeId robotNodeId;
    		UA_Boolean latchVal;
    		UA_Boolean clampVal;
    		UA_Boolean pinVal;
    		UA_Boolean robotVal;
    	};
    	struct eqiupVal myEquipVal;
    	time_t now;
    	struct tm *ts;
    
    
    
        /* 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, "SEQ"), 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,"current seq is %d",value2);
    
    	}
    //#ifdef UA_ENABLE_METHODCALLS
    //UA_StatusCode
    //UA_Client_call(UA_Client *client, const UA_NodeId objectId,
    //               const UA_NodeId methodId, size_t inputSize, const UA_Variant *input,
    //               size_t *outputSize, UA_Variant **output);
    //#endif
    
    
    	//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);
    
    //		if(UA_Variant_hasScalarType(output, &UA_TYPES[UA_TYPES_STRING])) { // if you are paranoid you may also want to check the dimensions
    //			UA_String value4 = *(UA_String *)output->data;
    //			printf("value4 is %s\n",value4);
    //		}
    		UA_LOG_INFO(UA_Log_Stdout, UA_LOGCATEGORY_USERLAND, "output data is %s\n",((UA_String*)output->data)->data);
    		//UA_LOG_INFO(UA_Log_Stdout, UA_LOGCATEGORY_USERLAND, (UA_String*)output->data);
    
    		//UA_LOG_INFO(UA_Log_Stdout, UA_LOGCATEGORY_USERLAND, "output is %s", value4);
    		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
    
        /* Browse some objects */
        printf("Browsing nodes in objects folder:\n");
        UA_BrowseRequest bReq;
        UA_BrowseRequest_init(&bReq);
        bReq.requestedMaxReferencesPerNode = 0;
        bReq.nodesToBrowse = UA_BrowseDescription_new();
        bReq.nodesToBrowseSize = 1;
        bReq.nodesToBrowse[0].nodeId = UA_NODEID_NUMERIC(0, UA_NS0ID_OBJECTSFOLDER); /* browse objects folder */
        bReq.nodesToBrowse[0].resultMask = UA_BROWSERESULTMASK_ALL; /* return everything */
        UA_BrowseResponse bResp = UA_Client_Service_browse(client, bReq);
        printf("%-9s %-16s %-16s %-16s\n", "NAMESPACE", "NODEID", "BROWSE NAME", "DISPLAY NAME");
        for(size_t i = 0; i < bResp.resultsSize; ++i) {
            for(size_t j = 0; j < bResp.results[i].referencesSize; ++j) {
                UA_ReferenceDescription *ref = &(bResp.results[i].references[j]);
                if(ref->nodeId.nodeId.identifierType == UA_NODEIDTYPE_NUMERIC) {
                    printf("%-9d %-16d %-16.*s %-16.*s\n", ref->nodeId.nodeId.namespaceIndex,
                           ref->nodeId.nodeId.identifier.numeric, (int)ref->browseName.name.length,
                           ref->browseName.name.data, (int)ref->displayName.text.length,
                           ref->displayName.text.data);
                } else if(ref->nodeId.nodeId.identifierType == UA_NODEIDTYPE_STRING) {
                    printf("%-9d %-16.*s %-16.*s %-16.*s\n", ref->nodeId.nodeId.namespaceIndex,
                           (int)ref->nodeId.nodeId.identifier.string.length,
                           ref->nodeId.nodeId.identifier.string.data,
                           (int)ref->browseName.name.length, ref->browseName.name.data,
                           (int)ref->displayName.text.length, ref->displayName.text.data);
                }
                /* TODO: distinguish further types */
    			//아래 문장으로 client 모든 brwose를 접근할 수 있음.
    			//한 단계 아래만 보임.
    			//UA_BrowseResponse bResp = UA_Client_Service_browse(client, bReq);
    
    
    			// object class 만 찾아 node로 기록.
    			if(ref->nodeClass == UA_NODECLASS_OBJECT &&
    					!strcmp(ref->browseName.name.data, "Loading Robot")){
    				printf("\nNODE CLASS OBJECT\n");
    				//임시로 node ID를 저장
    				myEquipVal.robotNodeId = (UA_NodeId)ref->nodeId.nodeId;
    			}
    			// object class 만 찾아 node로 기록.
    			if(ref->nodeClass == UA_NODECLASS_OBJECT &&
    					!strcmp(ref->browseName.name.data, "pin")){
    				printf("\nNODE CLASS OBJECT\n");
    				//임시로 node ID를 저장
    				myEquipVal.pinNodeId = (UA_NodeId)ref->nodeId.nodeId;
    			}
    
    			if(ref->nodeClass == UA_NODECLASS_OBJECT &&
    					!strcmp(ref->browseName.name.data, "latch")){
    				printf("\nNODE CLASS OBJECT\n");
    				//임시로 node ID를 저장
    				myEquipVal.latchNodeId = (UA_NodeId)ref->nodeId.nodeId;
    			}
    			if(ref->nodeClass == UA_NODECLASS_OBJECT &&
    					!strcmp(ref->browseName.name.data, "clamp")){
    				printf("\nNODE CLASS OBJECT\n");
    				//임시로 node ID를 저장
    				myEquipVal.clampNodeId = (UA_NodeId)ref->nodeId.nodeId;
    			}
    
            }
        }
        UA_BrowseRequest_clear(&bReq);
    	UA_BrowseResponse_clear(&bResp);
    
    	//얻은 데이터로 value값을 찾기 위한 nodeId 확인
    	//BaseDataType을 얻어내야 함
    	UA_BrowseRequest_init(&bReq);
    	bReq.requestedMaxReferencesPerNode = 0;
    	bReq.requestedMaxReferencesPerNode = 0;
        bReq.nodesToBrowse = UA_BrowseDescription_new();
    	bReq.nodesToBrowseSize = 1;
    	bReq.nodesToBrowse[0].nodeId = myEquipVal.robotNodeId; /* browse objects folder */
        bReq.nodesToBrowse[0].resultMask = UA_BROWSERESULTMASK_ALL; /* return everything */
    	//bResp로 다시 query
    	bResp = UA_Client_Service_browse(client, bReq);
    	for(size_t i = 0; i < bResp.resultsSize; ++i) {
    		for(size_t j = 0; j < bResp.results[i].referencesSize; ++j) {
    			UA_ReferenceDescription *ref = &(bResp.results[i].references[j]);
    			//robot
    			if(ref->nodeClass == UA_NODECLASS_VARIABLE &&
    					!strcmp(ref->browseName.name.data, "Status")){
    				printf("found\n");
    				myEquipVal.robotNodeId = (UA_NodeId)ref->nodeId.nodeId;
    			}
    			
    		}
    	}
    
    	bReq.nodesToBrowse[0].nodeId = myEquipVal.latchNodeId; /* browse objects folder */
        bReq.nodesToBrowse[0].resultMask = UA_BROWSERESULTMASK_ALL; /* return everything */
    	//bResp로 다시 query
    	bResp = UA_Client_Service_browse(client, bReq);
    	for(size_t i = 0; i < bResp.resultsSize; ++i) {
    		for(size_t j = 0; j < bResp.results[i].referencesSize; ++j) {
    			UA_ReferenceDescription *ref = &(bResp.results[i].references[j]);
    			//robot
    			if(ref->nodeClass == UA_NODECLASS_VARIABLE &&
    					!strcmp(ref->browseName.name.data, "Status")){
    				printf("found\n");
    				myEquipVal.latchNodeId = (UA_NodeId)ref->nodeId.nodeId;
    			}
    			
    		}
    	}
    	bReq.nodesToBrowse[0].nodeId = myEquipVal.pinNodeId; /* browse objects folder */
        bReq.nodesToBrowse[0].resultMask = UA_BROWSERESULTMASK_ALL; /* return everything */
    	//bResp로 다시 query
    	bResp = UA_Client_Service_browse(client, bReq);
    	for(size_t i = 0; i < bResp.resultsSize; ++i) {
    		for(size_t j = 0; j < bResp.results[i].referencesSize; ++j) {
    			UA_ReferenceDescription *ref = &(bResp.results[i].references[j]);
    			//robot
    			if(ref->nodeClass == UA_NODECLASS_VARIABLE &&
    					!strcmp(ref->browseName.name.data, "Status")){
    				printf("found\n");
    				myEquipVal.pinNodeId = (UA_NodeId)ref->nodeId.nodeId;
    			}
    			
    		}
    	}
    
    	bReq.nodesToBrowse[0].nodeId = myEquipVal.clampNodeId; /* browse objects folder */
        bReq.nodesToBrowse[0].resultMask = UA_BROWSERESULTMASK_ALL; /* return everything */
    	//bResp로 다시 query
    	bResp = UA_Client_Service_browse(client, bReq);
    	for(size_t i = 0; i < bResp.resultsSize; ++i) {
    		for(size_t j = 0; j < bResp.results[i].referencesSize; ++j) {
    			UA_ReferenceDescription *ref = &(bResp.results[i].references[j]);
    			//robot
    			if(ref->nodeClass == UA_NODECLASS_VARIABLE &&
    					!strcmp(ref->browseName.name.data, "Status")){
    				printf("found\n");
    				myEquipVal.clampNodeId = (UA_NodeId)ref->nodeId.nodeId;
    			}
    			
    		}
    	}
        UA_BrowseRequest_clear(&bReq);
    	UA_BrowseResponse_clear(&bResp);
    
    
    	for(int i=0;i<100;i++){
    
        /* Read attribute */
        UA_Int32 value2 = 0;
        //printf("\nReading the value of node (1, \"Test Var:Me\"):\n");
        retval = UA_Client_readValueAttribute(client, UA_NODEID_STRING(1, "SEQ"), 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,"current seq is %d",value2);
    		myEquipVal.seq = *(UA_Int32*)val->data;
    
    	}
    
    		//nodeId로 Value를 얻음. 
    		retval = UA_Client_readValueAttribute(client, myEquipVal.pinNodeId , val);
    		if(retval == UA_STATUSCODE_GOOD && UA_Variant_isScalar(val) &&
    				val->type == &UA_TYPES[UA_TYPES_BOOLEAN]) {
    			myEquipVal.pinVal = *(UA_Boolean*)val->data;
    			//UA_LOG_INFO(UA_Log_Stdout, UA_LOGCATEGORY_USERLAND,"node id 50232(pin) is %d", *(UA_Boolean*)val->data);
    		}
    
    		//nodeId로 Value를 얻음. 
    		retval = UA_Client_readValueAttribute(client, myEquipVal.robotNodeId, val);
    		if(retval == UA_STATUSCODE_GOOD && UA_Variant_isScalar(val) &&
    				val->type == &UA_TYPES[UA_TYPES_BOOLEAN]) {
    			myEquipVal.robotVal = *(UA_Boolean*)val->data;
    			//UA_LOG_INFO(UA_Log_Stdout, UA_LOGCATEGORY_USERLAND,"node id 50246(robot) is %d", *(UA_Boolean*)val->data);
    		}
    
    		//nodeId로 Value를 얻음. 
    		retval = UA_Client_readValueAttribute(client, myEquipVal.clampNodeId, val);
    		if(retval == UA_STATUSCODE_GOOD && UA_Variant_isScalar(val) &&
    				val->type == &UA_TYPES[UA_TYPES_BOOLEAN]) {
    			myEquipVal.clampVal = *(UA_Boolean*)val->data;
    			//UA_LOG_INFO(UA_Log_Stdout, UA_LOGCATEGORY_USERLAND,"node id 50238(clamp) is %d", *(UA_Boolean*)val->data);
    		}
    
    		//nodeId로 Value를 얻음. 
    		retval = UA_Client_readValueAttribute(client, myEquipVal.latchNodeId, val);
    		if(retval == UA_STATUSCODE_GOOD && UA_Variant_isScalar(val) &&
    				val->type == &UA_TYPES[UA_TYPES_BOOLEAN]) {
    			myEquipVal.latchVal = *(UA_Boolean*)val->data;
    			//UA_LOG_INFO(UA_Log_Stdout, UA_LOGCATEGORY_USERLAND,"node id 50235(latch) is %d", *(UA_Boolean*)val->data);
    		}
    
    
    		///printf("myEquipVal is\n");
    		///printf("pin is %d\n", myEquipVal.pinVal);
    		///printf("robot is %d\n", myEquipVal.robotVal);
    		///printf("latch is %d\n", myEquipVal.latchVal);
    		///printf("clamp is %d\n", myEquipVal.clampVal);
    		now = time(NULL);
    		ts = localtime(&now);
    		UA_LOG_INFO(UA_Log_Stdout, UA_LOGCATEGORY_USERLAND,"%d cylcle read 완료",i);
    		UA_LOG_INFO(UA_Log_Stdout, UA_LOGCATEGORY_USERLAND,"%d-%d-%d, %d:%d:%d, seq,%d,latch,%d, clamp,%d, pin,%d,robot,%d.",
    				//ts 3개 +3개 숫자.년월일 + 시각:분:초
    				ts->tm_year+1900, ts->tm_mon+1, ts->tm_mday,
    				ts->tm_hour, ts->tm_min, ts->tm_sec,
    				myEquipVal.seq,
    				myEquipVal.latchVal,myEquipVal.clampVal,
    				myEquipVal.pinVal, myEquipVal.robotVal);
    
    		sleep(3);
    	}
    
    
    	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;
    }

  • open62531 server plc 시뮬레이션

    임의 설비에 latch, clamp, pin, robot을 설치했다. 대부분 래더로 동작하고, PLC 메이커가 cpu에 opc ua 서버 기능을 구현하여 주기적으로 plc 메모리 데이터를 server로 업데이트 한다.(고 생각하자.) 집에 PLC가 없고, 아직 이를 제대로 구현할지 모르겠어 대충 짜집기로 만들었다.

    object로 각 설비를 등록하고 Value 변수로 staus를 bool 타입으로 등록했다. OPC UA 문서를 읽지 못해 이렇게 하는게 맞는지 모르겠다. 암튼 4개(pin, latch, clamp, robot) 를 등록하고 순서대로 동작하도록 했다.

    처음에는 thread로 돌려 UA_Server_writeValue(server, myEquipNodeId.pinNodeId, value);로 써 주려했으나, 유투브에 좋은 동영상을 찾아 그대로 따라했다. 서버 중간에 sleep을 넣어 버리면 client 쪽 연결도 응답하지 못한다. timestamp를 찍어 일정 조건을 넘어가면 update 하도록 (따라)했다. 항상 cycle time이 똑같을 수 없으니, random 값으로 조정 해야겠다.

    #include "open62541.h"
    
    #include <signal.h>
    #include <stdlib.h>
    #include <time.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
    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);
    
    
    }
    
    /* predefined identifier for later use */
    UA_NodeId pumpTypeId = {1, UA_NODEIDTYPE_NUMERIC, {1001}};
    UA_NodeId cylTypeId = {1, UA_NODEIDTYPE_NUMERIC, {2001}};
    UA_NodeId robotTypeId = {1, UA_NODEIDTYPE_NUMERIC, {3001}};
    
    
    static void
    defineObjectTypes(UA_Server *server) {
    	/* Define the object type for "Device" */
    	UA_NodeId deviceTypeId; /* get the nodeid assigned by the server */
    	UA_ObjectTypeAttributes dtAttr = UA_ObjectTypeAttributes_default;
    	dtAttr.displayName = UA_LOCALIZEDTEXT("en-US", "DeviceType");
    	UA_Server_addObjectTypeNode(server, UA_NODEID_NULL,
    			UA_NODEID_NUMERIC(0, UA_NS0ID_BASEOBJECTTYPE),
    			UA_NODEID_NUMERIC(0, UA_NS0ID_HASSUBTYPE),
    			UA_QUALIFIEDNAME(1, "DeviceType"), dtAttr,
    			NULL, &deviceTypeId);
    
    	UA_VariableAttributes mnAttr = UA_VariableAttributes_default;
    	mnAttr.displayName = UA_LOCALIZEDTEXT("en-US", "ManufacturerName");
    	UA_NodeId manufacturerNameId;
    	UA_Server_addVariableNode(server, UA_NODEID_NULL, deviceTypeId,
    			UA_NODEID_NUMERIC(0, UA_NS0ID_HASCOMPONENT),
    			UA_QUALIFIEDNAME(1, "ManufacturerName"),
    			UA_NODEID_NUMERIC(0, UA_NS0ID_BASEDATAVARIABLETYPE), mnAttr, NULL, &manufacturerNameId);
    	/* Make the manufacturer name mandatory */
    	UA_Server_addReference(server, manufacturerNameId,
    			UA_NODEID_NUMERIC(0, UA_NS0ID_HASMODELLINGRULE),
    			UA_EXPANDEDNODEID_NUMERIC(0, UA_NS0ID_MODELLINGRULE_MANDATORY), true);
    
    
    	UA_VariableAttributes modelAttr = UA_VariableAttributes_default;
    	modelAttr.displayName = UA_LOCALIZEDTEXT("en-US", "ModelName");
    	UA_Server_addVariableNode(server, UA_NODEID_NULL, deviceTypeId,
    			UA_NODEID_NUMERIC(0, UA_NS0ID_HASCOMPONENT),
    			UA_QUALIFIEDNAME(1, "ModelName"),
    			UA_NODEID_NUMERIC(0, UA_NS0ID_BASEDATAVARIABLETYPE), modelAttr, NULL, NULL);
    
    	/* Define the object type for "Pump" */
    	UA_ObjectTypeAttributes ptAttr = UA_ObjectTypeAttributes_default;
    	ptAttr.displayName = UA_LOCALIZEDTEXT("en-US", "PumpType");
    	UA_Server_addObjectTypeNode(server, pumpTypeId,
    			deviceTypeId, UA_NODEID_NUMERIC(0, UA_NS0ID_HASSUBTYPE),
    			UA_QUALIFIEDNAME(1, "PumpType"), ptAttr,
    			NULL, NULL);
    
    	UA_VariableAttributes statusAttr = UA_VariableAttributes_default;
    	statusAttr.displayName = UA_LOCALIZEDTEXT("en-US", "Status");
    	statusAttr.valueRank = UA_VALUERANK_SCALAR;
    	UA_NodeId statusId;
    	UA_Server_addVariableNode(server, UA_NODEID_NULL, pumpTypeId,
    			UA_NODEID_NUMERIC(0, UA_NS0ID_HASCOMPONENT),
    			UA_QUALIFIEDNAME(1, "Status"),
    			UA_NODEID_NUMERIC(0, UA_NS0ID_BASEDATAVARIABLETYPE), statusAttr, NULL, &statusId);
    	/* Make the status variable mandatory */
    	UA_Server_addReference(server, statusId,
    			UA_NODEID_NUMERIC(0, UA_NS0ID_HASMODELLINGRULE),
    			UA_EXPANDEDNODEID_NUMERIC(0, UA_NS0ID_MODELLINGRULE_MANDATORY), true);
    
    	UA_VariableAttributes rpmAttr = UA_VariableAttributes_default;
    	rpmAttr.displayName = UA_LOCALIZEDTEXT("en-US", "MotorRPM");
    	rpmAttr.valueRank = UA_VALUERANK_SCALAR;
    	UA_Server_addVariableNode(server, UA_NODEID_NULL, pumpTypeId,
    			UA_NODEID_NUMERIC(0, UA_NS0ID_HASCOMPONENT),
    			UA_QUALIFIEDNAME(1, "MotorRPMs"),
    			UA_NODEID_NUMERIC(0, UA_NS0ID_BASEDATAVARIABLETYPE), rpmAttr, NULL, NULL);
    }
    
    static void
    defineCylTypes(UA_Server *server) {
    	/* Define the object type for "Device" */
    	UA_NodeId deviceTypeId; /* get the nodeid assigned by the server */
    	UA_ObjectTypeAttributes dtAttr = UA_ObjectTypeAttributes_default;
    	dtAttr.displayName = UA_LOCALIZEDTEXT("en-US", "Cylinder Type");
    	UA_Server_addObjectTypeNode(server, UA_NODEID_NULL,
    			UA_NODEID_NUMERIC(0, UA_NS0ID_BASEOBJECTTYPE),
    			UA_NODEID_NUMERIC(0, UA_NS0ID_HASSUBTYPE),
    			UA_QUALIFIEDNAME(1, "Cylinder Type"), dtAttr,
    			NULL, &deviceTypeId);
    
    	UA_VariableAttributes mnAttr = UA_VariableAttributes_default;
    	mnAttr.displayName = UA_LOCALIZEDTEXT("en-US", "Manufacturer Name");
    	UA_NodeId manufacturerNameId;
    	UA_Server_addVariableNode(server, UA_NODEID_NULL, deviceTypeId,
    			UA_NODEID_NUMERIC(0, UA_NS0ID_HASCOMPONENT),
    			UA_QUALIFIEDNAME(1, "Cylinder"),
    			UA_NODEID_NUMERIC(0, UA_NS0ID_BASEDATAVARIABLETYPE), mnAttr, NULL, &manufacturerNameId);
    
    	UA_VariableAttributes modelAttr = UA_VariableAttributes_default;
    	UA_NodeId modelNameId;
    	modelAttr.displayName = UA_LOCALIZEDTEXT("en-US", "Cyl Model Name");
    	UA_Server_addVariableNode(server, UA_NODEID_NULL, deviceTypeId,
    			UA_NODEID_NUMERIC(0, UA_NS0ID_HASCOMPONENT),
    			UA_QUALIFIEDNAME(1, "Cyl Model Name"),
    			UA_NODEID_NUMERIC(0, UA_NS0ID_BASEDATAVARIABLETYPE), modelAttr, NULL, &modelNameId);
    
    	UA_Server_addReference(server,modelNameId,
    			UA_NODEID_NUMERIC(0, UA_NS0ID_HASMODELLINGRULE),
    			UA_EXPANDEDNODEID_NUMERIC(0, UA_NS0ID_MODELLINGRULE_MANDATORY), true);
    
    	/* Define the object type for "Pump" */
    	UA_ObjectTypeAttributes ptAttr = UA_ObjectTypeAttributes_default;
    	ptAttr.displayName = UA_LOCALIZEDTEXT("en-US", "Running State");
    	UA_Server_addObjectTypeNode(server, cylTypeId,
    			deviceTypeId, UA_NODEID_NUMERIC(0, UA_NS0ID_HASSUBTYPE),
    			UA_QUALIFIEDNAME(1, "Running State"), ptAttr,
    			NULL, NULL);
    
    
    	UA_VariableAttributes statusAttr = UA_VariableAttributes_default;
    	statusAttr.displayName = UA_LOCALIZEDTEXT("en-US", "Status");
    	statusAttr.valueRank = UA_VALUERANK_SCALAR;
    	UA_NodeId statusId;
    	UA_Server_addVariableNode(server, UA_NODEID_NULL, cylTypeId,
    			UA_NODEID_NUMERIC(0, UA_NS0ID_HASCOMPONENT),
    			UA_QUALIFIEDNAME(1, "Status"),
    			UA_NODEID_NUMERIC(0, UA_NS0ID_BASEDATAVARIABLETYPE), statusAttr, NULL, &statusId);
    	/* Make the manufacturer name mandatory */
    	// UA_Server_addReference()를 어떻게 하냐에 따라  statusId
    	// instance 만들경우 표시
    	UA_Server_addReference(server, statusId,
    			UA_NODEID_NUMERIC(0, UA_NS0ID_HASMODELLINGRULE),
    			UA_EXPANDEDNODEID_NUMERIC(0, UA_NS0ID_MODELLINGRULE_MANDATORY), true);
    }
    
    
    
    static void
    defineRobotTypes(UA_Server *server) {
    	/* Define the object type for "Device" */
    	UA_NodeId deviceTypeId; /* get the nodeid assigned by the server */
    	UA_ObjectTypeAttributes dtAttr = UA_ObjectTypeAttributes_default;
    	dtAttr.displayName = UA_LOCALIZEDTEXT("en-US", "Robot Type");
    	UA_Server_addObjectTypeNode(server, UA_NODEID_NULL,
    			UA_NODEID_NUMERIC(0, UA_NS0ID_BASEOBJECTTYPE),
    			UA_NODEID_NUMERIC(0, UA_NS0ID_HASSUBTYPE),
    			UA_QUALIFIEDNAME(1, "Robot Type"), dtAttr,
    			NULL, &deviceTypeId);
    
    	UA_VariableAttributes mnAttr = UA_VariableAttributes_default;
    	mnAttr.displayName = UA_LOCALIZEDTEXT("en-US", "Manufacturer Name");
    	UA_NodeId manufacturerNameId;
    	UA_Server_addVariableNode(server, UA_NODEID_NULL, deviceTypeId,
    			UA_NODEID_NUMERIC(0, UA_NS0ID_HASCOMPONENT),
    			UA_QUALIFIEDNAME(1, "Robot"),
    			UA_NODEID_NUMERIC(0, UA_NS0ID_BASEDATAVARIABLETYPE), mnAttr, NULL, &manufacturerNameId);
    
    	UA_VariableAttributes modelAttr = UA_VariableAttributes_default;
    	UA_NodeId modelNameId;
    	modelAttr.displayName = UA_LOCALIZEDTEXT("en-US", "Robot Model Name");
    	UA_Server_addVariableNode(server, UA_NODEID_NULL, deviceTypeId,
    			UA_NODEID_NUMERIC(0, UA_NS0ID_HASCOMPONENT),
    			UA_QUALIFIEDNAME(1, "Robot Model Name"),
    			UA_NODEID_NUMERIC(0, UA_NS0ID_BASEDATAVARIABLETYPE), modelAttr, NULL, &modelNameId);
    
    	UA_Server_addReference(server,modelNameId,
    			UA_NODEID_NUMERIC(0, UA_NS0ID_HASMODELLINGRULE),
    			UA_EXPANDEDNODEID_NUMERIC(0, UA_NS0ID_MODELLINGRULE_MANDATORY), true);
    
    	/* Define the object type for "Pump" */
    	UA_ObjectTypeAttributes ptAttr = UA_ObjectTypeAttributes_default;
    	ptAttr.displayName = UA_LOCALIZEDTEXT("en-US", "Interlock");
    	UA_Server_addObjectTypeNode(server, robotTypeId,
    			deviceTypeId, UA_NODEID_NUMERIC(0, UA_NS0ID_HASSUBTYPE),
    			UA_QUALIFIEDNAME(1, "Interlock"), ptAttr,
    			NULL, NULL);
    
    
    	UA_VariableAttributes statusAttr = UA_VariableAttributes_default;
    	statusAttr.displayName = UA_LOCALIZEDTEXT("en-US", "Status");
    	statusAttr.valueRank = UA_VALUERANK_SCALAR;
    	UA_NodeId statusId;
    	UA_Server_addVariableNode(server, UA_NODEID_NULL, robotTypeId,
    			UA_NODEID_NUMERIC(0, UA_NS0ID_HASCOMPONENT),
    			UA_QUALIFIEDNAME(1, "Status"),
    			UA_NODEID_NUMERIC(0, UA_NS0ID_BASEDATAVARIABLETYPE), statusAttr, NULL, &statusId);
    	/* Make the manufacturer name mandatory */
    	// UA_Server_addReference()를 어떻게 하냐에 따라  statusId
    	// instance 만들경우 표시
    	UA_Server_addReference(server, statusId,
    			UA_NODEID_NUMERIC(0, UA_NS0ID_HASMODELLINGRULE),
    			UA_EXPANDEDNODEID_NUMERIC(0, UA_NS0ID_MODELLINGRULE_MANDATORY), true);
    }
    
    
    
    static void
    addPumpObjectInstance(UA_Server *server, char *name) {
    	UA_ObjectAttributes oAttr = UA_ObjectAttributes_default;
    	oAttr.displayName = UA_LOCALIZEDTEXT("en-US", name);
    	UA_Server_addObjectNode(server, UA_NODEID_NULL,
    			UA_NODEID_NUMERIC(0, UA_NS0ID_OBJECTSFOLDER),
    			UA_NODEID_NUMERIC(0, UA_NS0ID_ORGANIZES),
    			UA_QUALIFIEDNAME(1, name),
    			pumpTypeId, /* this refers to the object type
    						   identifier */
    			oAttr, NULL, NULL);
    }
    
    static void
    addCylObjectInstance(UA_Server *server, char *name) {
    	UA_ObjectAttributes oAttr = UA_ObjectAttributes_default;
    	oAttr.displayName = UA_LOCALIZEDTEXT("en-US", name);
    	/*
    	   UA_Server_addObjectNode(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_ObjectAttributes attr,
    	   void *nodeContext, UA_NodeId *outNewNodeId) {
    	 */
    
    	UA_Server_addObjectNode(server, UA_NODEID_NULL,
    			UA_NODEID_NUMERIC(0, UA_NS0ID_OBJECTSFOLDER),
    			UA_NODEID_NUMERIC(0, UA_NS0ID_ORGANIZES),
    			UA_QUALIFIEDNAME(1, name),
    			cylTypeId, /* this refers to the object type
    						  identifier */
    			oAttr, NULL, NULL);
    }
    
    
    
    static void
    addRbtObjectInstance(UA_Server *server, char *name) {
    	UA_ObjectAttributes oAttr = UA_ObjectAttributes_default;
    	oAttr.displayName = UA_LOCALIZEDTEXT("en-US", name);
    	/*
    	   UA_Server_addObjectNode(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_ObjectAttributes attr,
    	   void *nodeContext, UA_NodeId *outNewNodeId) {
    	 */
    
    	UA_Server_addObjectNode(server, UA_NODEID_NULL,
    			UA_NODEID_NUMERIC(0, UA_NS0ID_OBJECTSFOLDER),
    			UA_NODEID_NUMERIC(0, UA_NS0ID_ORGANIZES),
    			UA_QUALIFIEDNAME(1, name),
    			robotTypeId, /* this refers to the object type
    							identifier */
    			oAttr, NULL, NULL);
    }
    
    
    static UA_StatusCode
    pumpTypeConstructor(UA_Server *server,
    		const UA_NodeId *sessionId, void *sessionContext,
    		const UA_NodeId *typeId, void *typeContext,
    		const UA_NodeId *nodeId, void **nodeContext) {
    	UA_LOG_INFO(UA_Log_Stdout, UA_LOGCATEGORY_USERLAND, "New pump created");
    
    	/* Find the NodeId of the status child variable */
    	UA_RelativePathElement rpe;
    	UA_RelativePathElement_init(&rpe);
    	rpe.referenceTypeId = UA_NODEID_NUMERIC(0, UA_NS0ID_HASCOMPONENT);
    	rpe.isInverse = false;
    	rpe.includeSubtypes = false;
    	rpe.targetName = UA_QUALIFIEDNAME(1, "Status");
    
    	UA_BrowsePath bp;
    	UA_BrowsePath_init(&bp);
    	bp.startingNode = *nodeId;
    	bp.relativePath.elementsSize = 1;
    	bp.relativePath.elements = &rpe;
    
    	UA_BrowsePathResult bpr =
    		UA_Server_translateBrowsePathToNodeIds(server, &bp);
    	if(bpr.statusCode != UA_STATUSCODE_GOOD ||
    			bpr.targetsSize < 1)
    		return bpr.statusCode;
    
    	/* Set the status value */
    	UA_Boolean status = true;
    	UA_Variant value;
    	UA_Variant_setScalar(&value, &status, &UA_TYPES[UA_TYPES_BOOLEAN]);
    	UA_Server_writeValue(server, bpr.targets[0].targetId.nodeId, value);
    	UA_BrowsePathResult_clear(&bpr);
    
    	/* At this point we could replace the node context .. */
    
    	return UA_STATUSCODE_GOOD;
    }
    
    static void
    addPumpTypeConstructor(UA_Server *server) {
    	UA_NodeTypeLifecycle lifecycle;
    	lifecycle.constructor = pumpTypeConstructor;
    	lifecycle.destructor = NULL;
    	UA_Server_setNodeTypeLifecycle(server, pumpTypeId, lifecycle);
    }
    
    
    
    
    //method 추가
    static UA_StatusCode
    helloWorldMethodCallback(UA_Server *server,
    		const UA_NodeId *sessionId, void *sessionHandle,
    		const UA_NodeId *methodId, void *methodContext,
    		const UA_NodeId *objectId, void *objectContext,
    		size_t inputSize, const UA_Variant *input,
    		size_t outputSize, UA_Variant *output) {
    	UA_String *inputStr = (UA_String*)input->data;
    	UA_String tmp = UA_STRING_ALLOC("Hello ");
    	if(inputStr->length > 0) {
    		tmp.data = (UA_Byte *)UA_realloc(tmp.data, tmp.length + inputStr->length);
    		memcpy(&tmp.data[tmp.length], inputStr->data, inputStr->length);
    		tmp.length += inputStr->length;
    	}
    	UA_Variant_setScalarCopy(output, &tmp, &UA_TYPES[UA_TYPES_STRING]);
    	UA_String_clear(&tmp);
    	UA_LOG_INFO(UA_Log_Stdout, UA_LOGCATEGORY_SERVER, "Hello World was called");
    	return UA_STATUSCODE_GOOD;
    }
    
    static void
    addHellWorldMethod(UA_Server *server) {
    	UA_Argument inputArgument;
    	UA_Argument_init(&inputArgument);
    	inputArgument.description = UA_LOCALIZEDTEXT("en-US", "A String");
    	inputArgument.name = UA_STRING("MyInput");
    	inputArgument.dataType = UA_TYPES[UA_TYPES_STRING].typeId;
    	inputArgument.valueRank = UA_VALUERANK_SCALAR;
    
    	UA_Argument outputArgument;
    	UA_Argument_init(&outputArgument);
    	outputArgument.description = UA_LOCALIZEDTEXT("en-US", "A String");
    	outputArgument.name = UA_STRING("MyOutput");
    	outputArgument.dataType = UA_TYPES[UA_TYPES_STRING].typeId;
    	outputArgument.valueRank = UA_VALUERANK_SCALAR;
    
    	UA_MethodAttributes helloAttr = UA_MethodAttributes_default;
    	helloAttr.description = UA_LOCALIZEDTEXT("en-US","Say `Hello World`");
    	helloAttr.displayName = UA_LOCALIZEDTEXT("en-US","Hello World");
    	helloAttr.executable = true;
    	helloAttr.userExecutable = true;
    	UA_Server_addMethodNode(server, UA_NODEID_NUMERIC(1,62541),
    			UA_NODEID_NUMERIC(0, UA_NS0ID_OBJECTSFOLDER),
    			UA_NODEID_NUMERIC(0, UA_NS0ID_HASCOMPONENT),
    			UA_QUALIFIEDNAME(1, "hello world"),
    			helloAttr, &helloWorldMethodCallback,
    			1, &inputArgument, 1, &outputArgument, NULL, NULL);
    }
    
    
    //이름으로 nodeId를 찾는 함수.
    UA_StatusCode
    findNodeIdWithName(UA_Server *server, UA_NodeId *startNodeId, char *name, UA_NodeId **retNodeId){
    
    	/* Find the NodeId of the status child variable */
    	UA_RelativePathElement rpe;
    	UA_RelativePathElement_init(&rpe);
    	rpe.referenceTypeId = UA_NODEID_NUMERIC(0, UA_NS0ID_HASCOMPONENT);
    	rpe.isInverse = false;
    	rpe.includeSubtypes = false;
    	rpe.targetName = UA_QUALIFIEDNAME(1,name); 
    
    	UA_BrowsePath bp;
    	UA_BrowsePath_init(&bp);
    
    
    	bp.startingNode = *startNodeId;
    	bp.relativePath.elementsSize = 1;
    	bp.relativePath.elements = &rpe;
    
    	UA_BrowsePathResult bpr =
    		UA_Server_translateBrowsePathToNodeIds(server, &bp);
    	if(bpr.statusCode != UA_STATUSCODE_GOOD ||
    			bpr.targetsSize < 1)
    		return bpr.statusCode;
    	UA_LOG_INFO(UA_Log_Stdout, UA_LOGCATEGORY_USERLAND, "found target is %d", bpr.targets->targetId.nodeId.identifier.numeric);
    	**retNodeId = bpr.targets->targetId.nodeId;
    
    }
    
    
    
    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);
    	//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);
    
    	//설비 정의
    
    	defineCylTypes(server);
    	addCylObjectInstance(server, "pin");
    	addCylObjectInstance(server, "latch");
    	addCylObjectInstance(server, "clamp");
    	defineRobotTypes(server);
    	addRbtObjectInstance(server, "Loading Robot");
    
    	/*
    	   defineObjectTypes(server);
    	   addPumpTypeConstructor(server);
    	   addPumpObjectInstance(server, "pump4");
    	 */
    
    
    	//method
    	addHellWorldMethod(server);
    
    
    	//ladder를 시뮬래이션하는 부분.
    	//thread로 latch fwd -> clamp fwd -> pin fwd -> robot in -> pin bwd: 공정 작업시간
    	//         robot in off -> clamp bwd -> latch bwd 순으로 실행
    
    #undef TESe
    #ifdef TEST
    
    	//테스트
    	//각 설비값을 설정하는 부분
    	UA_Variant tmp_value;
    	bool bitoff = 0;
    	UA_Variant_setScalar(&tmp_value, &bitoff, &UA_TYPES[UA_TYPES_BOOLEAN]);
    	UA_NodeId tmpNodeId = UA_NODEID_STRING(1, "Status");
    	UA_Server_writeValue(server, tmpNodeId, tmp_value);
    
    	/* Find the NodeId of the status child variable */
    	UA_RelativePathElement rpe;
    	UA_RelativePathElement_init(&rpe);
    	rpe.referenceTypeId = UA_NODEID_NUMERIC(0, UA_NS0ID_HASCOMPONENT);
    	rpe.isInverse = false;
    	rpe.includeSubtypes = true;
    	//rpe.targetName = UA_QUALIFIEDNAME(1, "Status");
    	rpe.targetName = UA_QUALIFIEDNAME(1, "latch");
    	//rpe.targetName = UA_QUALIFIEDNAME(1, "variable");
    
    
    	UA_BrowsePath bp;
    	UA_BrowsePath_init(&bp);
    	UA_NodeId *nodeId  = UA_NodeId_new();
    	*nodeId = UA_NODEID_NUMERIC(0, UA_NS0ID_OBJECTSFOLDER);
    	//UA_NodeId tmp = {0, UA_NODEIDTYPE_NUMERIC, {50238}};
    	//printf("\nNode id is %d\n", tmp);
    	//UA_NodeId *nodeId = &tmp;
    	bp.startingNode = *nodeId;
    	bp.relativePath.elementsSize = 1;
    	bp.relativePath.elements = &rpe;
    
    	UA_BrowsePathResult bpr =
    		UA_Server_translateBrowsePathToNodeIds(server, &bp);
    	if(bpr.statusCode != UA_STATUSCODE_GOOD ||
    			bpr.targetsSize < 1)
    		return bpr.statusCode;
    
    	UA_LOG_INFO(UA_Log_Stdout, UA_LOGCATEGORY_USERLAND, "found target is %d", bpr.targets->targetId.nodeId.identifier.numeric);
    	//print로 structure를 출력할 수 없음.
    	//printf("\nnode id is %d\n", (UA_NodeId)bpr.targets->targetId.nodeId);
    	//* Set the status value */
    	UA_Boolean status = true;
    	UA_Variant value;
    	UA_Variant_setScalar(&value, &status, &UA_TYPES[UA_TYPES_BOOLEAN]);
    	UA_Server_writeValue(server, bpr.targets->targetId.nodeId, value);
    	//UA_Server_writeValue(server, tmp, value);
    
    	//다시 아래로 내림.
    	bp.startingNode = bpr.targets->targetId.nodeId;
    	rpe.targetName = UA_QUALIFIEDNAME(1, "Status");
    	UA_BrowsePathResult bpr2 =
    		UA_Server_translateBrowsePathToNodeIds(server, &bp);
    	if(bpr2.statusCode != UA_STATUSCODE_GOOD ||
    			bpr2.targetsSize < 1)
    		return bpr.statusCode;
    
    	UA_LOG_INFO(UA_Log_Stdout, UA_LOGCATEGORY_USERLAND, "found target is %d", bpr2.targets->targetId.nodeId.identifier.numeric);
    	UA_Server_writeValue(server, bpr2.targets->targetId.nodeId, value);
    	UA_BrowsePathResult_clear(&bpr);
    #endif
    
    	//NodeId를 저장할 structure 생성
    	struct eqiupNodeId{
    		UA_NodeId latchNodeId;
    		UA_NodeId clampNodeId;
    		UA_NodeId pinNodeId;
    		UA_NodeId robotNodeId;
    	};
    
    	struct eqiupNodeId myEquipNodeId;
    
    	UA_NodeId **nodeIdtmp;
    	UA_NodeId *nodeIdtmp2 = UA_NodeId_new();
    	nodeIdtmp = &nodeIdtmp2;
    	UA_NodeId *nodeId  = UA_NodeId_new();
    	*nodeId = UA_NODEID_NUMERIC(0, UA_NS0ID_OBJECTSFOLDER);
    
    	if(	findNodeIdWithName(server, nodeId, "latch", nodeIdtmp) == UA_STATUSCODE_GOOD){
    		nodeId = *nodeIdtmp;
    		if( findNodeIdWithName(server, nodeId, "Status", nodeIdtmp) == UA_STATUSCODE_GOOD){
    			nodeId = *nodeIdtmp;
    			myEquipNodeId.latchNodeId = *nodeId;
    			//printf("Variable 변경\n");
    			//* Set the status value */
    			//			UA_Boolean status = false;
    			//			UA_Variant value;
    			//			UA_Variant_setScalar(&value, &status, &UA_TYPES[UA_TYPES_BOOLEAN]);
    			//			UA_Server_writeValue(server, *nodeId, value);
    			//			UA_LOG_INFO(UA_Log_Stdout, UA_LOGCATEGORY_USERLAND, "node id %d value was written", nodeId->identifier.numeric);
    		}
    	}
    	*nodeId = UA_NODEID_NUMERIC(0, UA_NS0ID_OBJECTSFOLDER);
    	if(	findNodeIdWithName(server, nodeId, "clamp", nodeIdtmp) == UA_STATUSCODE_GOOD){
    		nodeId = *nodeIdtmp;
    		if( findNodeIdWithName(server, nodeId, "Status", nodeIdtmp) == UA_STATUSCODE_GOOD){
    			nodeId = *nodeIdtmp;
    			myEquipNodeId.clampNodeId = *nodeId;
    
    			//printf("Variable 변경\n");
    			//* Set the status value */
    			//			UA_Boolean status = false;
    			//			UA_Variant value;
    			//			UA_Variant_setScalar(&value, &status, &UA_TYPES[UA_TYPES_BOOLEAN]);
    			//			UA_Server_writeValue(server, *nodeId, value);
    			//			UA_LOG_INFO(UA_Log_Stdout, UA_LOGCATEGORY_USERLAND, "node id %d value was written", nodeId->identifier.numeric);
    		}
    	}
    
    	*nodeId = UA_NODEID_NUMERIC(0, UA_NS0ID_OBJECTSFOLDER);
    	if(	findNodeIdWithName(server, nodeId, "pin", nodeIdtmp) == UA_STATUSCODE_GOOD){
    		nodeId = *nodeIdtmp;
    		if( findNodeIdWithName(server, nodeId, "Status", nodeIdtmp) == UA_STATUSCODE_GOOD){
    			nodeId = *nodeIdtmp;
    			myEquipNodeId.pinNodeId = *nodeId;
    			//printf("Variable 변경\n");
    			//* Set the status value */
    			//			UA_Boolean status = false;
    			//			UA_Variant value;
    			//			UA_Variant_setScalar(&value, &status, &UA_TYPES[UA_TYPES_BOOLEAN]);
    			//			UA_Server_writeValue(server, *nodeId, value);
    			//			UA_LOG_INFO(UA_Log_Stdout, UA_LOGCATEGORY_USERLAND, "node id %d value was written", nodeId->identifier.numeric);
    		}
    	}
    
    	*nodeId = UA_NODEID_NUMERIC(0, UA_NS0ID_OBJECTSFOLDER);
    	if(	findNodeIdWithName(server, nodeId, "Loading Robot", nodeIdtmp) == UA_STATUSCODE_GOOD){
    		nodeId = *nodeIdtmp;
    		if( findNodeIdWithName(server, nodeId, "Status", nodeIdtmp) == UA_STATUSCODE_GOOD){
    			nodeId = *nodeIdtmp;
    			myEquipNodeId.robotNodeId = *nodeId;
    			//printf("Variable 변경\n");
    			//* Set the status value */
    			//			UA_Boolean status = false;
    			//			UA_Variant value;
    			//			UA_Variant_setScalar(&value, &status, &UA_TYPES[UA_TYPES_BOOLEAN]);
    			//			UA_Server_writeValue(server, *nodeId, value);
    			//			UA_LOG_INFO(UA_Log_Stdout, UA_LOGCATEGORY_USERLAND, "node id %d value was written", nodeId->identifier.numeric);
    		}
    	}
    
    
    	//구조체 출력.
    	//printf("현재 노드는 %d \n", myEquipNodeId.latchNodeId.identifier.numeric);
    	//printf("현재 노드는 %d \n", myEquipNodeId.clampNodeId.identifier.numeric);
    	//printf("현재 노드는 %d \n", myEquipNodeId.pinNodeId.identifier.numeric);
    	//printf("현재 노드는 %d \n", myEquipNodeId.robotNodeId.identifier.numeric);
    
    	//한번에 업데이트
    	UA_Boolean statusTrue = true;
    	UA_Boolean statusFalse = false;
    	UA_Variant value;
    	UA_Variant_setScalar(&value, &statusFalse, &UA_TYPES[UA_TYPES_BOOLEAN]);
    
    	//초기상태
    	UA_Server_writeValue(server, myEquipNodeId.latchNodeId, value);
    	UA_Server_writeValue(server, myEquipNodeId.clampNodeId, value);
    	UA_Server_writeValue(server, myEquipNodeId.pinNodeId, value);
    	UA_Server_writeValue(server, myEquipNodeId.robotNodeId, value);
    	UA_LOG_INFO(UA_Log_Stdout, UA_LOGCATEGORY_USERLAND, "모든 노드가 초기화(모든 Value가 0) 됨.");
    
    
    
    	//서버 구동.
    	//주기적으로 업데이트로 수정
    	//https://youtu.be/abDnBv5u6bU//
    	UA_LOG_INFO(UA_Log_Stdout, UA_LOGCATEGORY_USERLAND, "Starting server...every 5 secs");
    	UA_StatusCode retval = UA_Server_run_startup(server);
    
    	if(retval != UA_STATUSCODE_GOOD){
    		UA_Server_delete(server);
    		return retval;
    	}
    
    
    	int timestamp = time(0)+5;
    	int equipStates = 0;
    
    	while(running == true){
    		// Handle Server
    		UA_Server_run_iterate(server, true);
    
    		//Update Status Variable
    		if(time(0) > timestamp)
    		{
    			timestamp = time(0) + 5;
    			switch(equipStates)
    			{
    				case 0: //초기상태
    					equipStates = 1;
    
    					UA_Variant_setScalar(&value, &statusFalse, &UA_TYPES[UA_TYPES_BOOLEAN]);
    
    					UA_Server_writeValue(server, myEquipNodeId.latchNodeId, value);
    					UA_Server_writeValue(server, myEquipNodeId.clampNodeId, value);
    					UA_Server_writeValue(server, myEquipNodeId.pinNodeId, value);
    					UA_Server_writeValue(server, myEquipNodeId.robotNodeId, value);
    					UA_LOG_INFO(UA_Log_Stdout, UA_LOGCATEGORY_USERLAND, "모든 노드가 초기화(모든 Value가 0) 됨.");
    					break;
    
    				case 1: //1단계, latch fwd
    					equipStates = 2;
    					UA_Variant_setScalar(&value, &statusFalse, &UA_TYPES[UA_TYPES_BOOLEAN]);
    
    					UA_Server_writeValue(server, myEquipNodeId.clampNodeId, value);
    					UA_Server_writeValue(server, myEquipNodeId.pinNodeId, value);
    					UA_Server_writeValue(server, myEquipNodeId.robotNodeId, value);
    
    					UA_Variant_setScalar(&value, &statusTrue, &UA_TYPES[UA_TYPES_BOOLEAN]);
    					UA_Server_writeValue(server, myEquipNodeId.latchNodeId, value);
    					UA_LOG_INFO(UA_Log_Stdout, UA_LOGCATEGORY_USERLAND, "1단계 동작 완료..latch 전진");
    					break;
    
    				case 2: //2단계, clamp fwd
    					equipStates = 3;
    					UA_Variant_setScalar(&value, &statusFalse, &UA_TYPES[UA_TYPES_BOOLEAN]);
    					UA_Server_writeValue(server, myEquipNodeId.pinNodeId, value);
    					UA_Server_writeValue(server, myEquipNodeId.robotNodeId, value);
    
    					UA_Variant_setScalar(&value, &statusTrue, &UA_TYPES[UA_TYPES_BOOLEAN]);
    					UA_Server_writeValue(server, myEquipNodeId.latchNodeId, value);
    					UA_Server_writeValue(server, myEquipNodeId.clampNodeId, value);
    					UA_LOG_INFO(UA_Log_Stdout, UA_LOGCATEGORY_USERLAND, "2단계 동작 완료..calmp 전진(latch, clamp 전진상태)");
    					break;
    
    				case 3: //3단계, pin fwd
    					equipStates = 4;
    					UA_Variant_setScalar(&value, &statusFalse, &UA_TYPES[UA_TYPES_BOOLEAN]);
    					UA_Server_writeValue(server, myEquipNodeId.robotNodeId, value);
    
    					UA_Variant_setScalar(&value, &statusTrue, &UA_TYPES[UA_TYPES_BOOLEAN]);
    					UA_Server_writeValue(server, myEquipNodeId.latchNodeId, value);
    					UA_Server_writeValue(server, myEquipNodeId.clampNodeId, value);
    					UA_Server_writeValue(server, myEquipNodeId.pinNodeId, value);
    					UA_LOG_INFO(UA_Log_Stdout, UA_LOGCATEGORY_USERLAND, "3단계 동작 완료..pin 전진(latch, clamp, pin 전진상태)");
    					break;
    
    				case 4: //4단계, robot in
    					equipStates = 5;
    					UA_Variant_setScalar(&value, &statusTrue, &UA_TYPES[UA_TYPES_BOOLEAN]);
    
    					UA_Server_writeValue(server, myEquipNodeId.robotNodeId, value);
    					UA_Server_writeValue(server, myEquipNodeId.latchNodeId, value);
    					UA_Server_writeValue(server, myEquipNodeId.clampNodeId, value);
    					UA_Server_writeValue(server, myEquipNodeId.pinNodeId, value);
    					UA_LOG_INFO(UA_Log_Stdout, UA_LOGCATEGORY_USERLAND, "4단계 동작 완료..robot in (latch, clamp, pin 전진상태, robot 진입)");
    					break;
    
    				case 5: //5단계, robot in
    					//다시 초기상태로 이동
    					equipStates = 0;
    					UA_Variant_setScalar(&value, &statusTrue, &UA_TYPES[UA_TYPES_BOOLEAN]);
    
    					UA_Server_writeValue(server, myEquipNodeId.robotNodeId, value);
    					UA_Server_writeValue(server, myEquipNodeId.latchNodeId, value);
    					UA_Server_writeValue(server, myEquipNodeId.clampNodeId, value);
    
    					UA_Variant_setScalar(&value, &statusFalse, &UA_TYPES[UA_TYPES_BOOLEAN]);
    					UA_Server_writeValue(server, myEquipNodeId.pinNodeId, value);
    
    					UA_LOG_INFO(UA_Log_Stdout, UA_LOGCATEGORY_USERLAND, "5단계 동작 완료..pin bwd(latch, clamp, 전진상태, robot 진입, pin 후진)");
    					break;
    
    				default:
    					equipStates = 0;
    					break;
    
    			} //switch
    
    
    		}	//if
    
    
    	} //while
    
    	retval = UA_Server_run_shutdown(server);
    	//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;
    } // main

    이렇게 하는게 맞는지 모르겠다. 나중에 client에서 접속하면 안되는건 아니겠지. 다음 단계는 client에서 node를 아니 받아서 mysql에 업로드하면 된다. 이제 라즈베리 파이로 입력 8, 출력 4점 PLC + OPC UA 서버를 만들 수 있다. 신뢰성은 0이어서 의미 없겠지만.