tutorial_server_datasource.c 7.8 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191
  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 simplicty, 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. UA_Variant_setScalar(&attr.value, &now, &UA_TYPES[UA_TYPES_DATETIME]);
  42. UA_NodeId currentNodeId = UA_NODEID_STRING(1, "current-time");
  43. UA_QualifiedName currentName = UA_QUALIFIEDNAME(1, "current-time");
  44. UA_NodeId parentNodeId = UA_NODEID_NUMERIC(0, UA_NS0ID_OBJECTSFOLDER);
  45. UA_NodeId parentReferenceNodeId = UA_NODEID_NUMERIC(0, UA_NS0ID_ORGANIZES);
  46. UA_NodeId variableTypeNodeId = UA_NODEID_NULL;
  47. UA_Server_addVariableNode(server, currentNodeId, parentNodeId,
  48. parentReferenceNodeId, currentName,
  49. variableTypeNodeId, attr, NULL, NULL);
  50. updateCurrentTime(server);
  51. }
  52. /**
  53. * Variable Value Callback
  54. * ^^^^^^^^^^^^^^^^^^^^^^^
  55. *
  56. * When a value changes continuously, such as the system time, updating the
  57. * value in a tight loop would take up a lot of resources. Value callbacks allow
  58. * to synchronize a variable value with an external representation. They attach
  59. * callbacks to the variable that are executed before every read and after every
  60. * write operation. */
  61. static void
  62. beforeReadTime(UA_Server *server,
  63. const UA_NodeId *sessionId, void *sessionContext,
  64. const UA_NodeId *nodeid, void *nodeContext,
  65. const UA_NumericRange *range, const UA_DataValue *data) {
  66. UA_DateTime now = UA_DateTime_now();
  67. UA_Variant value;
  68. UA_Variant_setScalar(&value, &now, &UA_TYPES[UA_TYPES_DATETIME]);
  69. UA_NodeId currentNodeId = UA_NODEID_STRING(1, "current-time");
  70. UA_Server_writeValue(server, currentNodeId, value);
  71. }
  72. static void
  73. afterWriteTime(UA_Server *server,
  74. const UA_NodeId *sessionId, void *sessionContext,
  75. const UA_NodeId *nodeId, void *nodeContext,
  76. const UA_NumericRange *range, const UA_DataValue *data) {
  77. UA_LOG_INFO(UA_Log_Stdout, UA_LOGCATEGORY_USERLAND,
  78. "The variable was updated");
  79. }
  80. static void
  81. addValueCallbackToCurrentTimeVariable(UA_Server *server) {
  82. UA_NodeId currentNodeId = UA_NODEID_STRING(1, "current-time");
  83. UA_ValueCallback callback ;
  84. callback.onRead = beforeReadTime;
  85. callback.onWrite = afterWriteTime;
  86. UA_Server_setVariableNode_valueCallback(server, currentNodeId, callback);
  87. }
  88. /**
  89. * Variable Data Sources
  90. * ^^^^^^^^^^^^^^^^^^^^^
  91. *
  92. * With value callbacks, the value is still stored in the variable node.
  93. * So-called data sources go one step further. The server redirects every read
  94. * and write request to a callback function. Upon reading, the callback provides
  95. * copy of the current value. Internally, the data source needs to implement its
  96. * own memory management. */
  97. static UA_StatusCode
  98. readCurrentTime(UA_Server *server,
  99. const UA_NodeId *sessionId, void *sessionContext,
  100. const UA_NodeId *nodeId, void *nodeContext,
  101. UA_Boolean sourceTimeStamp, const UA_NumericRange *range,
  102. UA_DataValue *dataValue) {
  103. UA_DateTime now = UA_DateTime_now();
  104. UA_Variant_setScalarCopy(&dataValue->value, &now,
  105. &UA_TYPES[UA_TYPES_DATETIME]);
  106. dataValue->hasValue = true;
  107. return UA_STATUSCODE_GOOD;
  108. }
  109. static UA_StatusCode
  110. writeCurrentTime(UA_Server *server,
  111. const UA_NodeId *sessionId, void *sessionContext,
  112. const UA_NodeId *nodeId, void *nodeContext,
  113. const UA_NumericRange *range, const UA_DataValue *data) {
  114. UA_LOG_INFO(UA_Log_Stdout, UA_LOGCATEGORY_USERLAND,
  115. "Changing the system time is not implemented");
  116. return UA_STATUSCODE_BADINTERNALERROR;
  117. }
  118. static void
  119. addCurrentTimeDataSourceVariable(UA_Server *server) {
  120. UA_VariableAttributes attr = UA_VariableAttributes_default;
  121. attr.displayName = UA_LOCALIZEDTEXT("en_US", "Current time - data source");
  122. UA_NodeId currentNodeId = UA_NODEID_STRING(1, "current-time-datasource");
  123. UA_QualifiedName currentName = UA_QUALIFIEDNAME(1, "current-time-datasource");
  124. UA_NodeId parentNodeId = UA_NODEID_NUMERIC(0, UA_NS0ID_OBJECTSFOLDER);
  125. UA_NodeId parentReferenceNodeId = UA_NODEID_NUMERIC(0, UA_NS0ID_ORGANIZES);
  126. UA_NodeId variableTypeNodeId = UA_NODEID_NULL;
  127. UA_DataSource timeDataSource;
  128. timeDataSource.read = readCurrentTime;
  129. timeDataSource.write = writeCurrentTime;
  130. UA_Server_addDataSourceVariableNode(server, currentNodeId, parentNodeId,
  131. parentReferenceNodeId, currentName,
  132. variableTypeNodeId, attr,
  133. timeDataSource, NULL, NULL);
  134. }
  135. /** It follows the main server code, making use of the above definitions. */
  136. UA_Boolean running = true;
  137. static void stopHandler(int sign) {
  138. UA_LOG_INFO(UA_Log_Stdout, UA_LOGCATEGORY_SERVER, "received ctrl-c");
  139. running = false;
  140. }
  141. int main(void) {
  142. signal(SIGINT, stopHandler);
  143. signal(SIGTERM, stopHandler);
  144. UA_ServerConfig *config = UA_ServerConfig_new_default();
  145. UA_Server *server = UA_Server_new(config);
  146. addCurrentTimeVariable(server);
  147. addValueCallbackToCurrentTimeVariable(server);
  148. addCurrentTimeDataSourceVariable(server);
  149. UA_StatusCode retval = UA_Server_run(server, &running);
  150. UA_Server_delete(server);
  151. UA_ServerConfig_delete(config);
  152. return (int)retval;
  153. }
  154. /**
  155. * DataChange Notifications
  156. * ^^^^^^^^^^^^^^^^^^^^^^^^
  157. * A client that is interested in the current value of a variable does not need
  158. * to regularly poll the variable. Instead, he can use the Subscription
  159. * mechanism to be notified about changes.
  160. *
  161. * Within a Subscription, the client adds so-called MonitoredItems. A DataChange
  162. * MonitoredItem defines a node attribute (usually the value attribute) that is
  163. * monitored for changes. The server internally reads the value in the defined
  164. * interval and generates the appropriate notifications. The three ways of
  165. * updating node values discussed above are all usable in combination with
  166. * notifications. That is because notifications use the standard *Read* service
  167. * to look for value changes. */