app_waitforcond.c 7.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239
  1. /*
  2. * Asterisk -- An open source telephony toolkit.
  3. *
  4. * Copyright (C) 2021, Naveen Albert
  5. *
  6. * Naveen Albert <asterisk@phreaknet.org>
  7. *
  8. * See http://www.asterisk.org for more information about
  9. * the Asterisk project. Please do not directly contact
  10. * any of the maintainers of this project for assistance;
  11. * the project provides a web site, mailing lists and IRC
  12. * channels for your use.
  13. *
  14. * This program is free software, distributed under the terms of
  15. * the GNU General Public License Version 2. See the LICENSE file
  16. * at the top of the source tree.
  17. */
  18. /*! \file
  19. *
  20. * \brief Sleep until a condition is true
  21. *
  22. * \author Naveen Albert <asterisk@phreaknet.org>
  23. *
  24. * \ingroup applications
  25. */
  26. /*** MODULEINFO
  27. <support_level>extended</support_level>
  28. ***/
  29. #include "asterisk.h"
  30. #include "asterisk/logger.h"
  31. #include "asterisk/channel.h"
  32. #include "asterisk/pbx.h"
  33. #include "asterisk/module.h"
  34. #include "asterisk/app.h"
  35. /*** DOCUMENTATION
  36. <application name="WaitForCondition" language="en_US">
  37. <since>
  38. <version>16.20.0</version>
  39. <version>18.6.0</version>
  40. <version>19.0.0</version>
  41. </since>
  42. <synopsis>
  43. Wait (sleep) until the given condition is true.
  44. </synopsis>
  45. <syntax>
  46. <parameter name="replacementchar" required="true">
  47. <para>Specifies the character in the expression used to replace the <literal>$</literal>
  48. character. This character should not be used anywhere in the expression itself.</para>
  49. </parameter>
  50. <parameter name="expression" required="true">
  51. <para>A modified logical expression with the <literal>$</literal> characters replaced by
  52. <replaceable>replacementchar</replaceable>. This is necessary to pass the expression itself
  53. into the application, rather than its initial evaluation.</para>
  54. </parameter>
  55. <parameter name="timeout">
  56. <para>The maximum amount of time, in seconds, this application should wait for a condition
  57. to become true before dialplan execution continues automatically to the next priority.
  58. By default, there is no timeout.</para>
  59. </parameter>
  60. <parameter name="interval">
  61. <para>The frequency, in seconds, of polling the condition, which can be adjusted depending
  62. on how time-sensitive execution needs to be. By default, this is 0.05.</para>
  63. </parameter>
  64. </syntax>
  65. <description>
  66. <para>Waits until <replaceable>expression</replaceable> evaluates to true, checking every
  67. <replaceable>interval</replaceable> seconds for up to <replaceable>timeout</replaceable>. Default
  68. is evaluate <replaceable>expression</replaceable> every 50 milliseconds with no timeout.</para>
  69. <example title="Wait for condition dialplan variable/function to become 1 for up to 40 seconds, checking every 500ms">
  70. same => n,WaitForCondition(#,#["#{condition}"="1"],40,0.5)
  71. </example>
  72. <para>Sets <variable>WAITFORCONDITIONSTATUS</variable> to one of the following values:</para>
  73. <variablelist>
  74. <variable name="WAITFORCONDITIONSTATUS">
  75. <value name="TRUE">
  76. Condition evaluated to true before timeout expired.
  77. </value>
  78. <value name="FAILURE">
  79. Invalid argument.
  80. </value>
  81. <value name="TIMEOUT">
  82. Timeout elapsed without condition evaluating to true.
  83. </value>
  84. <value name="HANGUP">
  85. Channel hung up before condition became true.
  86. </value>
  87. </variable>
  88. </variablelist>
  89. </description>
  90. </application>
  91. ***/
  92. static char *app = "WaitForCondition";
  93. static int waitforcond_exec(struct ast_channel *chan, const char *data)
  94. {
  95. int ms, i;
  96. double timeout = 0, poll = 0;
  97. int timeout_ms = 0;
  98. int poll_ms = 50; /* default is evaluate the condition every 50ms */
  99. struct timeval start = ast_tvnow();
  100. char dollarsignrep;
  101. int brackets = 0;
  102. char *pos, *open_bracket, *expression, *optargs = NULL;
  103. char condition[512];
  104. AST_DECLARE_APP_ARGS(args,
  105. AST_APP_ARG(timeout);
  106. AST_APP_ARG(interval);
  107. );
  108. pos = ast_strdupa(data);
  109. if (ast_strlen_zero(pos)) {
  110. ast_log(LOG_ERROR, "WaitForCondition requires a condition\n");
  111. pbx_builtin_setvar_helper(chan, "WAITFORCONDITIONSTATUS", "FAILURE");
  112. return 0;
  113. }
  114. /* is there at least a [ followed by a ] somewhere ? */
  115. if (!(open_bracket = strchr(pos, '[')) || !strchr(open_bracket, ']')) {
  116. ast_log(LOG_ERROR, "No expression detected. Did you forget to replace the $ signs?\n");
  117. pbx_builtin_setvar_helper(chan, "WAITFORCONDITIONSTATUS", "FAILURE");
  118. return 0;
  119. }
  120. dollarsignrep = pos[0];
  121. if (dollarsignrep == '$' || dollarsignrep == '[' || dollarsignrep == ']'
  122. || dollarsignrep == '{' || dollarsignrep == '}') {
  123. ast_log(LOG_ERROR, "Dollar sign replacement cannot be %c.\n", dollarsignrep);
  124. pbx_builtin_setvar_helper(chan, "WAITFORCONDITIONSTATUS", "FAILURE");
  125. return 0;
  126. }
  127. ++pos;
  128. if (pos[0] != ',') {
  129. ast_log(LOG_ERROR, "Invalid separator: %c\n", pos[0]);
  130. pbx_builtin_setvar_helper(chan, "WAITFORCONDITIONSTATUS", "FAILURE");
  131. return 0;
  132. }
  133. ++pos;
  134. if (pos[0] != dollarsignrep) {
  135. ast_log(LOG_ERROR, "Expression start does not match provided replacement: %c\n", pos[0]);
  136. pbx_builtin_setvar_helper(chan, "WAITFORCONDITIONSTATUS", "FAILURE");
  137. return 0;
  138. }
  139. expression = pos; /* we're at the start of the expression */
  140. /* commas may appear within the expression, so go until we've encountered as many closing brackets as opening */
  141. while (++pos) {
  142. if (pos[0] == '\0') {
  143. ast_log(LOG_ERROR, "Could not parse end of expression.\n");
  144. pbx_builtin_setvar_helper(chan, "WAITFORCONDITIONSTATUS", "FAILURE");
  145. return 0;
  146. }
  147. if (pos[0] == '[') {
  148. brackets++;
  149. } else if (pos[0] == ']') {
  150. brackets--;
  151. }
  152. if (brackets == 0) { /* reached end of expression */
  153. break;
  154. }
  155. }
  156. ++pos;
  157. if (pos[0] != '\0') {
  158. ++pos; /* eat comma separator */
  159. if (pos[0] != '\0') {
  160. optargs = ast_strdupa(pos);
  161. AST_STANDARD_APP_ARGS(args, optargs);
  162. if (!ast_strlen_zero(args.timeout)) {
  163. if (sscanf(args.timeout, "%30lg", &timeout) != 1) {
  164. ast_log(LOG_WARNING, "Invalid timeout provided: %s. No timeout set.\n", args.timeout);
  165. return -1;
  166. }
  167. timeout_ms = timeout * 1000.0;
  168. }
  169. if (!ast_strlen_zero(args.interval)) {
  170. if (sscanf(args.interval, "%30lg", &poll) != 1) {
  171. ast_log(LOG_WARNING, "Invalid polling interval provided: %s. Default unchanged.\n", args.interval);
  172. return -1;
  173. }
  174. if (poll < 0.001) {
  175. ast_log(LOG_WARNING, "Polling interval cannot be less than 1ms. Default unchanged.\n");
  176. return -1;
  177. }
  178. poll_ms = poll * 1000.0;
  179. }
  180. }
  181. }
  182. for (i = 0; expression[i] != '\0'; i++) {
  183. if (expression[i] == dollarsignrep) {
  184. expression[i] = '$'; /* replace $s back into expression for variable parsing */
  185. }
  186. }
  187. if (timeout_ms > 0) {
  188. ast_debug(1, "Waiting for condition for %f seconds: %s (checking every %d ms)", timeout, expression, poll_ms);
  189. } else {
  190. ast_debug(1, "Waiting for condition, forever: %s (checking every %d ms)", expression, poll_ms);
  191. }
  192. while (1) {
  193. /* Substitute variables now */
  194. pbx_substitute_variables_helper(chan, expression, condition, sizeof(condition) - 1);
  195. if (pbx_checkcondition(condition)) {
  196. pbx_builtin_setvar_helper(chan, "WAITFORCONDITIONSTATUS", "TRUE");
  197. return 0;
  198. }
  199. /* If a timeout was specified, check that it hasn't expired */
  200. if ((timeout_ms > 0) && !(ms = ast_remaining_ms(start, timeout_ms))) {
  201. pbx_builtin_setvar_helper(chan, "WAITFORCONDITIONSTATUS", "TIMEOUT");
  202. return 0;
  203. }
  204. if (ast_safe_sleep(chan, poll_ms)) { /* don't waste CPU, we don't need a super tight loop */
  205. pbx_builtin_setvar_helper(chan, "WAITFORCONDITIONSTATUS", "HANGUP");
  206. return -1; /* channel hung up */
  207. }
  208. }
  209. }
  210. static int unload_module(void)
  211. {
  212. return ast_unregister_application(app);
  213. }
  214. static int load_module(void)
  215. {
  216. return ast_register_application_xml(app, waitforcond_exec);
  217. }
  218. AST_MODULE_INFO_STANDARD_EXTENDED(ASTERISK_GPL_KEY, "Wait until condition is true");