처음 설정한 목표를 드디어 수행했다. 서버를 대략 구현했으니 다음으로 클라이언트를 만들었다. 내가 쪼랩이라 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; }