tutorial_server_datasource.c 7.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193
  1. /* This work is licensed under a Creative Commons CCZero 1.0 Universal License.
  2. * See http://creativecommons.org/publicdomain/zero/1.0/ for more information. */
  3. /**
  4. * Connecting a Variable with a Physical Process
  5. * ---------------------------------------------
  6. *
  7. * In OPC UA-based architectures, servers are typically situated near the source
  8. * of information. In an industrial context, this translates into servers being
  9. * near the physical process and clients consuming the data at runtime. In the
  10. * previous tutorial, we saw how to add variables to an OPC UA information
  11. * model. This tutorial shows how to connect a variable to runtime information,
  12. * for example from measurements of a physical process. For simplicity, we take
  13. * the system clock as the underlying "process".
  14. *
  15. * The following code snippets are each concerned with a different way of
  16. * updating variable values at runtime. Taken together, the code snippets define
  17. * a compilable source file.
  18. *
  19. * Updating variables manually
  20. * ^^^^^^^^^^^^^^^^^^^^^^^^^^^
  21. * As a starting point, assume that a variable for a value of type
  22. * :ref:`datetime` has been created in the server with the identifier
  23. * "ns=1,s=current-time". Assuming that our applications gets triggered when a
  24. * new value arrives from the underlying process, we can just write into the
  25. * variable. */
  26. #include <signal.h>
  27. #include "open62541.h"
  28. static void
  29. updateCurrentTime(UA_Server *server) {
  30. UA_DateTime now = UA_DateTime_now();
  31. UA_Variant value;
  32. UA_Variant_setScalar(&value, &now, &UA_TYPES[UA_TYPES_DATETIME]);
  33. UA_NodeId currentNodeId = UA_NODEID_STRING(1, "current-time");
  34. UA_Server_writeValue(server, currentNodeId, value);
  35. }
  36. static void
  37. addCurrentTimeVariable(UA_Server *server) {
  38. UA_DateTime now = 0;
  39. UA_VariableAttributes attr = UA_VariableAttributes_default;
  40. attr.displayName = UA_LOCALIZEDTEXT("en-US", "Current time");
  41. attr.accessLevel = UA_ACCESSLEVELMASK_READ | UA_ACCESSLEVELMASK_WRITE;
  42. UA_Variant_setScalar(&attr.value, &now, &UA_TYPES[UA_TYPES_DATETIME]);
  43. UA_NodeId currentNodeId = UA_NODEID_STRING(1, "current-time");
  44. UA_QualifiedName currentName = UA_QUALIFIEDNAME(1, "current-time");
  45. UA_NodeId parentNodeId = UA_NODEID_NUMERIC(0, UA_NS0ID_OBJECTSFOLDER);
  46. UA_NodeId parentReferenceNodeId = UA_NODEID_NUMERIC(0, UA_NS0ID_ORGANIZES);
  47. UA_NodeId variableTypeNodeId = UA_NODEID_NULL;
  48. UA_Server_addVariableNode(server, currentNodeId, parentNodeId,
  49. parentReferenceNodeId, currentName,
  50. variableTypeNodeId, attr, NULL, NULL);
  51. updateCurrentTime(server);
  52. }
  53. /**
  54. * Variable Value Callback
  55. * ^^^^^^^^^^^^^^^^^^^^^^^
  56. *
  57. * When a value changes continuously, such as the system time, updating the
  58. * value in a tight loop would take up a lot of resources. Value callbacks allow
  59. * to synchronize a variable value with an external representation. They attach
  60. * callbacks to the variable that are executed before every read and after every
  61. * write operation. */
  62. static void
  63. beforeReadTime(UA_Server *server,
  64. const UA_NodeId *sessionId, void *sessionContext,
  65. const UA_NodeId *nodeid, void *nodeContext,
  66. const UA_NumericRange *range, const UA_DataValue *data) {
  67. UA_DateTime now = UA_DateTime_now();
  68. UA_Variant value;
  69. UA_Variant_setScalar(&value, &now, &UA_TYPES[UA_TYPES_DATETIME]);
  70. UA_NodeId currentNodeId = UA_NODEID_STRING(1, "current-time");
  71. UA_Server_writeValue(server, currentNodeId, value);
  72. }
  73. static void
  74. afterWriteTime(UA_Server *server,
  75. const UA_NodeId *sessionId, void *sessionContext,
  76. const UA_NodeId *nodeId, void *nodeContext,
  77. const UA_NumericRange *range, const UA_DataValue *data) {
  78. UA_LOG_INFO(UA_Log_Stdout, UA_LOGCATEGORY_USERLAND,
  79. "The variable was updated");
  80. }
  81. static void
  82. addValueCallbackToCurrentTimeVariable(UA_Server *server) {
  83. UA_NodeId currentNodeId = UA_NODEID_STRING(1, "current-time");
  84. UA_ValueCallback callback ;
  85. callback.onRead = beforeReadTime;
  86. callback.onWrite = afterWriteTime;
  87. UA_Server_setVariableNode_valueCallback(server, currentNodeId, callback);
  88. }
  89. /**
  90. * Variable Data Sources
  91. * ^^^^^^^^^^^^^^^^^^^^^
  92. *
  93. * With value callbacks, the value is still stored in the variable node.
  94. * So-called data sources go one step further. The server redirects every read
  95. * and write request to a callback function. Upon reading, the callback provides
  96. * copy of the current value. Internally, the data source needs to implement its
  97. * own memory management. */
  98. static UA_StatusCode
  99. readCurrentTime(UA_Server *server,
  100. const UA_NodeId *sessionId, void *sessionContext,
  101. const UA_NodeId *nodeId, void *nodeContext,
  102. UA_Boolean sourceTimeStamp, const UA_NumericRange *range,
  103. UA_DataValue *dataValue) {
  104. UA_DateTime now = UA_DateTime_now();
  105. UA_Variant_setScalarCopy(&dataValue->value, &now,
  106. &UA_TYPES[UA_TYPES_DATETIME]);
  107. dataValue->hasValue = true;
  108. return UA_STATUSCODE_GOOD;
  109. }
  110. static UA_StatusCode
  111. writeCurrentTime(UA_Server *server,
  112. const UA_NodeId *sessionId, void *sessionContext,
  113. const UA_NodeId *nodeId, void *nodeContext,
  114. const UA_NumericRange *range, const UA_DataValue *data) {
  115. UA_LOG_INFO(UA_Log_Stdout, UA_LOGCATEGORY_USERLAND,
  116. "Changing the system time is not implemented");
  117. return UA_STATUSCODE_BADINTERNALERROR;
  118. }
  119. static void
  120. addCurrentTimeDataSourceVariable(UA_Server *server) {
  121. UA_VariableAttributes attr = UA_VariableAttributes_default;
  122. attr.displayName = UA_LOCALIZEDTEXT("en-US", "Current time - data source");
  123. attr.accessLevel = UA_ACCESSLEVELMASK_READ | UA_ACCESSLEVELMASK_WRITE;
  124. UA_NodeId currentNodeId = UA_NODEID_STRING(1, "current-time-datasource");
  125. UA_QualifiedName currentName = UA_QUALIFIEDNAME(1, "current-time-datasource");
  126. UA_NodeId parentNodeId = UA_NODEID_NUMERIC(0, UA_NS0ID_OBJECTSFOLDER);
  127. UA_NodeId parentReferenceNodeId = UA_NODEID_NUMERIC(0, UA_NS0ID_ORGANIZES);
  128. UA_NodeId variableTypeNodeId = UA_NODEID_NULL;
  129. UA_DataSource timeDataSource;
  130. timeDataSource.read = readCurrentTime;
  131. timeDataSource.write = writeCurrentTime;
  132. UA_Server_addDataSourceVariableNode(server, currentNodeId, parentNodeId,
  133. parentReferenceNodeId, currentName,
  134. variableTypeNodeId, attr,
  135. timeDataSource, NULL, NULL);
  136. }
  137. /** It follows the main server code, making use of the above definitions. */
  138. UA_Boolean running = true;
  139. static void stopHandler(int sign) {
  140. UA_LOG_INFO(UA_Log_Stdout, UA_LOGCATEGORY_SERVER, "received ctrl-c");
  141. running = false;
  142. }
  143. int main(void) {
  144. signal(SIGINT, stopHandler);
  145. signal(SIGTERM, stopHandler);
  146. UA_ServerConfig *config = UA_ServerConfig_new_default();
  147. UA_Server *server = UA_Server_new(config);
  148. addCurrentTimeVariable(server);
  149. addValueCallbackToCurrentTimeVariable(server);
  150. addCurrentTimeDataSourceVariable(server);
  151. UA_StatusCode retval = UA_Server_run(server, &running);
  152. UA_Server_delete(server);
  153. UA_ServerConfig_delete(config);
  154. return (int)retval;
  155. }
  156. /**
  157. * DataChange Notifications
  158. * ^^^^^^^^^^^^^^^^^^^^^^^^
  159. * A client that is interested in the current value of a variable does not need
  160. * to regularly poll the variable. Instead, he can use the Subscription
  161. * mechanism to be notified about changes.
  162. *
  163. * Within a Subscription, the client adds so-called MonitoredItems. A DataChange
  164. * MonitoredItem defines a node attribute (usually the value attribute) that is
  165. * monitored for changes. The server internally reads the value in the defined
  166. * interval and generates the appropriate notifications. The three ways of
  167. * updating node values discussed above are all usable in combination with
  168. * notifications. That is because notifications use the standard *Read* service
  169. * to look for value changes. */