임의 설비에 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이어서 의미 없겠지만.