server_register.c 8.2 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197
  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. * A simple server instance which registers with the discovery server (see server_discovery.c).
  5. * Before shutdown it has to unregister itself.
  6. */
  7. #include <signal.h>
  8. #include <stdio.h>
  9. #include <stdlib.h>
  10. #ifdef _MSC_VER
  11. # include <io.h> //access
  12. #else
  13. # include <unistd.h> //access
  14. #endif
  15. #ifdef UA_NO_AMALGAMATION
  16. # include "ua_types.h"
  17. # include "ua_server.h"
  18. # include "ua_config_standard.h"
  19. # include "ua_network_tcp.h"
  20. # include "ua_log_stdout.h"
  21. #else
  22. # include "open62541.h"
  23. #endif
  24. UA_Boolean running = true;
  25. UA_Logger logger = UA_Log_Stdout;
  26. static void stopHandler(int sign) {
  27. UA_LOG_INFO(logger, UA_LOGCATEGORY_SERVER, "received ctrl-c");
  28. running = false;
  29. }
  30. static UA_StatusCode
  31. readInteger(void *handle, const UA_NodeId nodeid, UA_Boolean sourceTimeStamp,
  32. const UA_NumericRange *range, UA_DataValue *dataValue) {
  33. dataValue->hasValue = true;
  34. UA_Variant_setScalarCopy(&dataValue->value, (UA_UInt32*)handle, &UA_TYPES[UA_TYPES_INT32]);
  35. // we know the nodeid is a string
  36. UA_LOG_INFO(logger, UA_LOGCATEGORY_USERLAND, "Node read %s",
  37. nodeid.identifier.string.length, nodeid.identifier.string.data);
  38. UA_LOG_INFO(logger, UA_LOGCATEGORY_USERLAND, "read value %i", *(UA_UInt32*)handle);
  39. return UA_STATUSCODE_GOOD;
  40. }
  41. static UA_StatusCode
  42. writeInteger(void *handle, const UA_NodeId nodeid,
  43. const UA_Variant *data, const UA_NumericRange *range) {
  44. if(UA_Variant_isScalar(data) && data->type == &UA_TYPES[UA_TYPES_INT32] && data->data){
  45. *(UA_UInt32*)handle = *(UA_UInt32*)data->data;
  46. }
  47. // we know the nodeid is a string
  48. UA_LOG_INFO(logger, UA_LOGCATEGORY_USERLAND, "Node written %.*s",
  49. nodeid.identifier.string.length, nodeid.identifier.string.data);
  50. UA_LOG_INFO(logger, UA_LOGCATEGORY_USERLAND, "written value %i", *(UA_UInt32*)handle);
  51. return UA_STATUSCODE_GOOD;
  52. }
  53. struct PeriodicServerRegisterJob {
  54. UA_Guid job_id;
  55. UA_Job *job;
  56. UA_UInt32 this_interval;
  57. };
  58. /**
  59. * Called by the UA_Server job.
  60. * The OPC UA specification says:
  61. *
  62. * > If an error occurs during registration (e.g. the Discovery Server is not running) then the Server
  63. * > must periodically re-attempt registration. The frequency of these attempts should start at 1 second
  64. * > but gradually increase until the registration frequency is the same as what it would be if not
  65. * > errors occurred. The recommended approach would double the period each attempt until reaching the maximum.
  66. *
  67. * We will do so by using the additional data parameter. If it is NULL, it is the first attempt
  68. * (or the default periodic register of 10 Minutes).
  69. * Otherwise it indicates the wait time in seconds for the next try.
  70. */
  71. static void periodicServerRegister(UA_Server *server, void *data) {
  72. struct PeriodicServerRegisterJob *retryJob = NULL;
  73. // retry registration by doubling the interval. If it is again 10 Minutes, don't retry.
  74. UA_UInt32 nextInterval = 0;
  75. if (data) {
  76. // if data!=NULL this method call was a retry not within the default 10 minutes.
  77. retryJob = (struct PeriodicServerRegisterJob *)data;
  78. // remove the retry job because we don't want to fire it again. If it still fails,
  79. // we double the interval and create a new job
  80. UA_Server_removeRepeatedJob(server, retryJob->job_id);
  81. nextInterval = retryJob->this_interval * 2;
  82. free(retryJob->job);
  83. free(retryJob);
  84. }
  85. UA_StatusCode retval = UA_Server_register_discovery(server, "opc.tcp://localhost:4840", NULL);
  86. // You can also use a semaphore file. That file must exist. When the file is deleted, the server is automatically unregistered.
  87. // The semaphore file has to be accessible by the discovery server
  88. // UA_StatusCode retval = UA_Server_register_discovery(server, "opc.tcp://localhost:4840", "/path/to/some/file");
  89. if (retval != UA_STATUSCODE_GOOD) {
  90. UA_LOG_ERROR(logger, UA_LOGCATEGORY_SERVER, "Could not register server with discovery server. Is the discovery server started? StatusCode 0x%08x", retval);
  91. // first retry in 1 second
  92. if (nextInterval == 0)
  93. nextInterval = 1;
  94. // as long as next retry is smaller than 10 minutes, retry
  95. if (nextInterval < 10*60) {
  96. UA_LOG_INFO(logger, UA_LOGCATEGORY_SERVER, "Retrying registration in %d seconds", nextInterval);
  97. struct PeriodicServerRegisterJob *newRetryJob = malloc(sizeof(struct PeriodicServerRegisterJob));
  98. newRetryJob->job = malloc(sizeof(UA_Job));
  99. newRetryJob->this_interval = nextInterval;
  100. newRetryJob->job->type = UA_JOBTYPE_METHODCALL;
  101. newRetryJob->job->job.methodCall.method = periodicServerRegister;
  102. newRetryJob->job->job.methodCall.data = newRetryJob;
  103. UA_Server_addRepeatedJob(server, *newRetryJob->job, nextInterval*1000, &newRetryJob->job_id);
  104. }
  105. } else {
  106. UA_LOG_INFO(logger, UA_LOGCATEGORY_SERVER, "Server successfully registered. Next periodical register will be in 10 Minutes");
  107. }
  108. }
  109. int main(int argc, char** argv) {
  110. signal(SIGINT, stopHandler); /* catches ctrl-c */
  111. UA_ServerConfig config = UA_ServerConfig_standard;
  112. config.applicationDescription.applicationUri=UA_String_fromChars("open62541.example.server_register");
  113. UA_ServerNetworkLayer nl = UA_ServerNetworkLayerTCP(UA_ConnectionConfig_standard, 16664);
  114. config.networkLayers = &nl;
  115. config.networkLayersSize = 1;
  116. UA_Server *server = UA_Server_new(config);
  117. /* add a variable node to the address space */
  118. UA_Int32 myInteger = 42;
  119. UA_NodeId myIntegerNodeId = UA_NODEID_STRING(1, "the.answer");
  120. UA_QualifiedName myIntegerName = UA_QUALIFIEDNAME(1, "the answer");
  121. UA_DataSource dateDataSource = (UA_DataSource) {
  122. .handle = &myInteger, .read = readInteger, .write = writeInteger};
  123. UA_VariableAttributes attr;
  124. UA_VariableAttributes_init(&attr);
  125. attr.description = UA_LOCALIZEDTEXT("en_US","the answer");
  126. attr.displayName = UA_LOCALIZEDTEXT("en_US","the answer");
  127. UA_Server_addDataSourceVariableNode(server, myIntegerNodeId,
  128. UA_NODEID_NUMERIC(0, UA_NS0ID_OBJECTSFOLDER),
  129. UA_NODEID_NUMERIC(0, UA_NS0ID_ORGANIZES),
  130. myIntegerName, UA_NODEID_NULL, attr, dateDataSource, NULL);
  131. // registering the server should be done periodically. Approx. every 10 minutes. The first call will be in 10 Minutes.
  132. UA_Job job = {.type = UA_JOBTYPE_METHODCALL,
  133. .job.methodCall = {.method = periodicServerRegister, .data = NULL} };
  134. UA_Server_addRepeatedJob(server, job, 10*60*1000, NULL);
  135. // Register the server with the discovery server.
  136. // Delay this first registration until the server is fully initialized
  137. // will be freed in the callback
  138. struct PeriodicServerRegisterJob *newRetryJob = malloc(sizeof(struct PeriodicServerRegisterJob));
  139. newRetryJob->job = malloc(sizeof(UA_Job));
  140. newRetryJob->this_interval = 0;
  141. newRetryJob->job->type = UA_JOBTYPE_METHODCALL;
  142. newRetryJob->job->job.methodCall.method = periodicServerRegister;
  143. newRetryJob->job->job.methodCall.data = newRetryJob;
  144. UA_Server_addRepeatedJob(server, *newRetryJob->job, 1000, &newRetryJob->job_id);
  145. UA_StatusCode retval = UA_Server_run(server, &running);
  146. if (retval != UA_STATUSCODE_GOOD) {
  147. UA_LOG_ERROR(logger, UA_LOGCATEGORY_SERVER, "Could not start discovery server. StatusCode 0x%08x", retval);
  148. UA_Server_delete(server);
  149. nl.deleteMembers(&nl);
  150. return (int)retval;
  151. }
  152. // UNregister the server from the discovery server.
  153. retval = UA_Server_unregister_discovery(server, "opc.tcp://localhost:4840" );
  154. if (retval != UA_STATUSCODE_GOOD) {
  155. UA_LOG_ERROR(logger, UA_LOGCATEGORY_SERVER, "Could not unregister server from discovery server. StatusCode 0x%08x", retval);
  156. UA_Server_delete(server);
  157. nl.deleteMembers(&nl);
  158. return (int)retval;
  159. }
  160. UA_String_deleteMembers(&config.applicationDescription.applicationUri);
  161. UA_Server_delete(server);
  162. nl.deleteMembers(&nl);
  163. return (int)retval;
  164. }