open62541이 OPC UA 규격에 맞춰 browse 기능을 지원한다. OPC UA 문서에 어떻게 사용하는지 알 수 있는데, 멤버만 볼 수 있다. 일단 대충 필요한 실린더를 구성했다 하자. 상용 PLC 메이커가 OPC UA 기능을 구현한다면 내부에 server 기능으로 넣을 듯 하다. 지금 구할 수 없으니, 대충 비슷하게 만들고 싶다. 여기서 문제를 알았다. 한번 Variable을 할당하면 어떻게 바꾸지? callback으로 바꿀 수 있는데, nodeId를 알아야 한다. nodeId를 null로 설정하면 server가 임의로 할당한다.
아래 그림 NodeID i=50235, Status Value를 True로 만들고 싶다. 어떻게??
이럴때 browse를 사용한다.(아마도..) sample로 구현되어 있는데, example 디렉토리 tutorial_server_object.c에 있다. 가장 간단한 방법이 아래와 같다.
#define TEST #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); UA_BrowsePathResult_clear(&bpr); #endif
여기를 gdb로 보면 시작 위치를 설정, 알고 있는 정보를 입력, 두 구조체를 비교하여 찾는다. 찾으면 0을 반환한다.
450 UA_RelativePathElement_init(&rpe); (gdb) 451 rpe.referenceTypeId = UA_NODEID_NUMERIC(0, UA_NS0ID_HASCOMPONENT); (gdb) 452 rpe.isInverse = false; (gdb) 453 rpe.includeSubtypes = true; (gdb) 455 rpe.targetName = UA_QUALIFIEDNAME(1, "latch"); (gdb) 460 UA_BrowsePath_init(&bp); (gdb) 461 UA_NodeId *nodeId = UA_NodeId_new(); (gdb) 462 *nodeId = UA_NODEID_NUMERIC(0, UA_NS0ID_OBJECTSFOLDER); (gdb) 466 bp.startingNode = *nodeId; (gdb) 467 bp.relativePath.elementsSize = 1; (gdb) 468 bp.relativePath.elements = &rpe; (gdb) 471 UA_Server_translateBrowsePathToNodeIds(server, &bp); (gdb) 472 if(bpr.statusCode != UA_STATUSCODE_GOOD || (gdb) display rpe 1: rpe = {referenceTypeId = {namespaceIndex = 0, identifierType = UA_NODEIDTYPE_NUMERIC, identifier = {numeric = 47, string = {length = 47, data = 0x136b4 <main+692> "b?K\342\003"}, guid = {data1 = 47, data2 = 14004, data3 = 1, data4 = "\257\362\377\276l\362\377\276"}, byteString = { length = 47, data = 0x136b4 <main+692> "b?K\342\003"}}}, isInverse = false, includeSubtypes = true, targetName = {namespaceIndex = 1, name = { length = 5, data = 0x13bbc "latch"}}} (gdb) display bp 2: bp = {startingNode = {namespaceIndex = 0, identifierType = UA_NODEIDTYPE_NUMERIC, identifier = {numeric = 85, string = {length = 85, data = 0x52a48 ""}, guid = {data1 = 85, data2 = 10824, data3 = 5, data4 = "\024\361\377\276\234\016\001"}, byteString = {length = 85, data = 0x52a48 ""}}}, relativePath = {elementsSize = 1, elements = 0xbefff26c}} (gdb) display bpr 3: bpr = {statusCode = 0, targetsSize = 1, targets = 0x2c9c8} (gdb) display *bpr.targets 4: *bpr.targets = {targetId = {nodeId = {namespaceIndex = 0, identifierType = UA_NODEIDTYPE_NUMERIC, identifier = {numeric = 50234, string = { length = 50234, data = 0x0}, guid = {data1 = 50234, data2 = 0, data3 = 0, data4 = "\000\000\000\000\000\000\000"}, byteString = {length = 50234, data = 0x0}}}, namespaceUri = {length = 0, data = 0x0}, serverIndex = 0}, remainingPathIndex = 4294967295} (gdb) n 473 bpr.targetsSize < 1) 1: rpe = {referenceTypeId = {namespaceIndex = 0, identifierType = UA_NODEIDTYPE_NUMERIC, identifier = {numeric = 47, string = {length = 47, data = 0x136b4 <main+692> "b?K\342\003"}, guid = {data1 = 47, data2 = 14004, data3 = 1, data4 = "\257\362\377\276l\362\377\276"}, byteString = { length = 47, data = 0x136b4 <main+692> "b?K\342\003"}}}, isInverse = false, includeSubtypes = true, targetName = {namespaceIndex = 1, name = { length = 5, data = 0x13bbc "latch"}}} 2: bp = {startingNode = {namespaceIndex = 0, identifierType = UA_NODEIDTYPE_NUMERIC, identifier = {numeric = 85, string = {length = 85, data = 0x52a48 ""}, guid = {data1 = 85, data2 = 10824, data3 = 5, data4 = "\024\361\377\276\234\016\001"}, byteString = {length = 85, data = 0x52a48 ""}}}, relativePath = {elementsSize = 1, elements = 0xbefff26c}} 3: bpr = {statusCode = 0, targetsSize = 1, targets = 0x2c9c8} 4: *bpr.targets = {targetId = {nodeId = {namespaceIndex = 0, identifierType = UA_NODEIDTYPE_NUMERIC, identifier = {numeric = 50234, string = { length = 50234, data = 0x0}, guid = {data1 = 50234, data2 = 0, data3 = 0, data4 = "\000\000\000\000\000\000\000"}, byteString = {length = 50234, data = 0x0}}}, namespaceUri = {length = 0, data = 0x0}, serverIndex = 0}, remainingPathIndex = 4294967295}
어디에서 시작할 지 정해줘야 한다. 가장 상위인 Object에서 시작했다. NodeId i=84다. 이 번호도 정했을 것 같다. 문제는 이 설정이 어떻게 되어 있는지 Objects 바로 아래 항목만 찾는다. 위 방식으로 NodeId i=50234까지는 찾았다. 그러나 Varible이 없어 write를 하더라도 효과 없다. 하위 항목인 1:Status, NodeId i=50235를 찾아야 했다. 편법으로 시작점을 50243으로 넣어 줬다.
#define TEST #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
Variable을 제대로 수정한다. 이제 rucursive browse를 어떻게 해야 하는지 알아야겠다. 삽질해도 보람차다.ㅠㅠ