1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176
|
/* 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 application gets triggered when a
* new value arrives from the underlying process, we can just write into the
* variable. */
#include <open62541/plugin/log_stdout.h>
#include <open62541/server.h>
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
* a 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);
}
static UA_DataValue *externalValue;
static void
addCurrentTimeExternalDataSource(UA_Server *server) {
UA_NodeId currentNodeId = UA_NODEID_STRING(1, "current-time-external-source");
UA_ValueBackend valueBackend;
valueBackend.backendType = UA_VALUEBACKENDTYPE_EXTERNAL;
valueBackend.backend.external.value = &externalValue;
UA_Server_setVariableNode_valueBackend(server, currentNodeId, valueBackend);
}
/** It follows the main server code, making use of the above definitions. */
int main(void) {
UA_Server *server = UA_Server_new();
addCurrentTimeVariable(server);
addValueCallbackToCurrentTimeVariable(server);
addCurrentTimeDataSourceVariable(server);
addCurrentTimeExternalDataSource(server);
UA_Server_runUntilInterrupt(server);
UA_Server_delete(server);
return 0;
}
|