/* This work is licensed under a Creative Commons CCZero 1.0 Universal License. * See http://creativecommons.org/publicdomain/zero/1.0/ for more information. */ /** * Connecting a Variable with a Physical Process * --------------------------------------------- * * In OPC UA-based architectures, servers are typically situated near the source * of information. In an industrial context, this translates into servers being * near the physical process and clients consuming the data at runtime. In the * previous tutorial, we saw how to add variables to an OPC UA information * model. This tutorial shows how to connect a variable to runtime information, * for example from measurements of a physical process. For simplicity, we take * the system clock as the underlying "process". * * The following code snippets are each concerned with a different way of * updating variable values at runtime. Taken together, the code snippets define * a compilable source file. * * Updating variables manually * ^^^^^^^^^^^^^^^^^^^^^^^^^^^ * As a starting point, assume that a variable for a value of type * :ref:`datetime` has been created in the server with the identifier * "ns=1,s=current-time". Assuming that our applications gets triggered when a * new value arrives from the underlying process, we can just write into the * variable. */ #include #include #include #include #include static void updateCurrentTime(UA_Server *server) { UA_DateTime now = UA_DateTime_now(); UA_Variant value; UA_Variant_setScalar(&value, &now, &UA_TYPES[UA_TYPES_DATETIME]); UA_NodeId currentNodeId = UA_NODEID_STRING(1, "current-time-value-callback"); UA_Server_writeValue(server, currentNodeId, value); } static void addCurrentTimeVariable(UA_Server *server) { UA_DateTime now = 0; UA_VariableAttributes attr = UA_VariableAttributes_default; attr.displayName = UA_LOCALIZEDTEXT("en-US", "Current time - value callback"); attr.accessLevel = UA_ACCESSLEVELMASK_READ | UA_ACCESSLEVELMASK_WRITE; UA_Variant_setScalar(&attr.value, &now, &UA_TYPES[UA_TYPES_DATETIME]); UA_NodeId currentNodeId = UA_NODEID_STRING(1, "current-time-value-callback"); UA_QualifiedName currentName = UA_QUALIFIEDNAME(1, "current-time-value-callback"); UA_NodeId parentNodeId = UA_NODEID_NUMERIC(0, UA_NS0ID_OBJECTSFOLDER); UA_NodeId parentReferenceNodeId = UA_NODEID_NUMERIC(0, UA_NS0ID_ORGANIZES); UA_NodeId variableTypeNodeId = UA_NODEID_NUMERIC(0, UA_NS0ID_BASEDATAVARIABLETYPE); UA_Server_addVariableNode(server, currentNodeId, parentNodeId, parentReferenceNodeId, currentName, variableTypeNodeId, attr, NULL, NULL); updateCurrentTime(server); } /** * Variable Value Callback * ^^^^^^^^^^^^^^^^^^^^^^^ * * When a value changes continuously, such as the system time, updating the * value in a tight loop would take up a lot of resources. Value callbacks allow * to synchronize a variable value with an external representation. They attach * callbacks to the variable that are executed before every read and after every * write operation. */ static void beforeReadTime(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); } static void afterWriteTime(UA_Server *server, const UA_NodeId *sessionId, void *sessionContext, const UA_NodeId *nodeId, void *nodeContext, const UA_NumericRange *range, const UA_DataValue *data) { UA_LOG_INFO(UA_Log_Stdout, UA_LOGCATEGORY_USERLAND, "The variable was updated"); } static void addValueCallbackToCurrentTimeVariable(UA_Server *server) { UA_NodeId currentNodeId = UA_NODEID_STRING(1, "current-time-value-callback"); UA_ValueCallback callback ; callback.onRead = beforeReadTime; callback.onWrite = afterWriteTime; UA_Server_setVariableNode_valueCallback(server, currentNodeId, callback); } /** * Variable Data Sources * ^^^^^^^^^^^^^^^^^^^^^ * * With value callbacks, the value is still stored in the variable node. * So-called data sources go one step further. The server redirects every read * and write request to a callback function. Upon reading, the callback provides * copy of the current value. Internally, the data source needs to implement its * own memory management. */ static UA_StatusCode readCurrentTime(UA_Server *server, const UA_NodeId *sessionId, void *sessionContext, const UA_NodeId *nodeId, void *nodeContext, UA_Boolean sourceTimeStamp, const UA_NumericRange *range, UA_DataValue *dataValue) { UA_DateTime now = UA_DateTime_now(); UA_Variant_setScalarCopy(&dataValue->value, &now, &UA_TYPES[UA_TYPES_DATETIME]); dataValue->hasValue = true; return UA_STATUSCODE_GOOD; } static UA_StatusCode writeCurrentTime(UA_Server *server, const UA_NodeId *sessionId, void *sessionContext, const UA_NodeId *nodeId, void *nodeContext, const UA_NumericRange *range, const UA_DataValue *data) { UA_LOG_INFO(UA_Log_Stdout, UA_LOGCATEGORY_USERLAND, "Changing the system time is not implemented"); return UA_STATUSCODE_BADINTERNALERROR; } static void addCurrentTimeDataSourceVariable(UA_Server *server) { UA_VariableAttributes attr = UA_VariableAttributes_default; attr.displayName = UA_LOCALIZEDTEXT("en-US", "Current time - data source"); attr.accessLevel = UA_ACCESSLEVELMASK_READ | UA_ACCESSLEVELMASK_WRITE; UA_NodeId currentNodeId = UA_NODEID_STRING(1, "current-time-datasource"); UA_QualifiedName currentName = UA_QUALIFIEDNAME(1, "current-time-datasource"); UA_NodeId parentNodeId = UA_NODEID_NUMERIC(0, UA_NS0ID_OBJECTSFOLDER); UA_NodeId parentReferenceNodeId = UA_NODEID_NUMERIC(0, UA_NS0ID_ORGANIZES); UA_NodeId variableTypeNodeId = UA_NODEID_NUMERIC(0, UA_NS0ID_BASEDATAVARIABLETYPE); UA_DataSource timeDataSource; timeDataSource.read = readCurrentTime; timeDataSource.write = writeCurrentTime; UA_Server_addDataSourceVariableNode(server, currentNodeId, parentNodeId, parentReferenceNodeId, currentName, variableTypeNodeId, attr, timeDataSource, NULL, NULL); } /** It follows the main server code, making use of the above definitions. */ static volatile UA_Boolean running = true; static void stopHandler(int sign) { UA_LOG_INFO(UA_Log_Stdout, UA_LOGCATEGORY_SERVER, "received ctrl-c"); running = false; } int main(void) { signal(SIGINT, stopHandler); signal(SIGTERM, stopHandler); UA_Server *server = UA_Server_new(); UA_ServerConfig_setDefault(UA_Server_getConfig(server)); addCurrentTimeVariable(server); addValueCallbackToCurrentTimeVariable(server); addCurrentTimeDataSourceVariable(server); UA_StatusCode retval = UA_Server_run(server, &running); UA_Server_delete(server); return retval == UA_STATUSCODE_GOOD ? EXIT_SUCCESS : EXIT_FAILURE; }