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를 어떻게 해야 하는지 알아야겠다. 삽질해도 보람차다.ㅠㅠ