tutorial을 따라하다 보면 내가 무엇을 모르는지 잘 모른다. 아래 같은 그림을 그리고 싶었는데 잘 안된다. 내가 서버를 건드릴 일은 없겠지만, 클라이언트를 테스트하려면 서버가 필요하다.

#include "open62541.h"
#include <signal.h>
#include <stdlib.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}};
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
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 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);
}
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);
defineObjectTypes(server);
defineCylTypes(server);
addPumpObjectInstance(server, "pump2");
addPumpObjectInstance(server, "pump3");
addPumpTypeConstructor(server);
addPumpObjectInstance(server, "pump4");
addPumpObjectInstance(server, "pump5");
addCylObjectInstance(server, "pin1");
addCylObjectInstance(server, "pin2");
//method
addHellWorldMethod(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;
}
object를 define할 때 어느 변수를 인스턴스에 포함할지 결정할 수 있다. defineCylTypes 함수 중 UA_Server_addReference의 변수로 전에 선언한 id를 넘겨주면 붙는다.
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);
꼬리의 꼬리를 무는 OPC UA.