cron.c 7.6 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256
  1. // Cron module
  2. #include "module.h"
  3. #include "lauxlib.h"
  4. #include "lmem.h"
  5. #include "user_interface.h"
  6. #include <stdint.h>
  7. #include <stddef.h>
  8. #include <string.h>
  9. #include "ets_sys.h"
  10. #include "time.h"
  11. #include "rtc/rtctime.h"
  12. #include "mem.h"
  13. struct cronent_desc {
  14. uint64_t min; // Minutes repeat - bits 0-59
  15. uint32_t hour; // Hours repeat - bits 0-23
  16. uint32_t dom; // Day-of-month repeat - bits 0-30
  17. uint16_t mon; // Monthly repeat - bits 0-11
  18. uint8_t dow; // Day-of-week repeat - bits 0-6
  19. };
  20. typedef struct cronent_ud {
  21. struct cronent_desc desc;
  22. int cb_ref;
  23. } cronent_ud_t;
  24. static ETSTimer cron_timer;
  25. static int *cronent_list = 0;
  26. static size_t cronent_count = 0;
  27. static uint64_t lcron_parsepart(lua_State *L, char *str, char **end, uint8_t min, uint8_t max) {
  28. uint64_t res = 0;
  29. /* Gobble whitespace before potential stars; no strtol on that path */
  30. while (*str != '\0' && (*str == ' ' || *str == '\t')) {
  31. str++;
  32. }
  33. if (str[0] == '*') {
  34. uint32_t each = 1;
  35. *end = str + 1;
  36. if (str[1] == '/') {
  37. each = strtol(str + 2, end, 10);
  38. if (each == 0 || each >= max - min) {
  39. return luaL_error(L, "invalid spec (each %d)", each);
  40. }
  41. }
  42. for (int i = 0; i <= (max - min); i++) {
  43. if ((i % each) == 0) res |= (uint64_t)1 << i;
  44. }
  45. } else {
  46. uint32_t val;
  47. while (1) {
  48. val = strtol(str, end, 10);
  49. if (val < min || val > max) {
  50. return luaL_error(L, "invalid spec (val %d out of range %d..%d)", val, min, max);
  51. }
  52. res |= (uint64_t)1 << (val - min);
  53. if (**end != ',') break;
  54. str = *end + 1;
  55. }
  56. }
  57. return res;
  58. }
  59. static int lcron_parsedesc(lua_State *L, char *str, struct cronent_desc *desc) {
  60. char *s = str;
  61. desc->min = lcron_parsepart(L, s, &s, 0, 59);
  62. if (*s != ' ' && *s != '\t') return luaL_error(L, "invalid spec (separator @%d)", s - str);
  63. desc->hour = lcron_parsepart(L, s + 1, &s, 0, 23);
  64. if (*s != ' ' && *s != '\t') return luaL_error(L, "invalid spec (separator @%d)", s - str);
  65. desc->dom = lcron_parsepart(L, s + 1, &s, 1, 31);
  66. if (*s != ' ' && *s != '\t') return luaL_error(L, "invalid spec (separator @%d)", s - str);
  67. desc->mon = lcron_parsepart(L, s + 1, &s, 1, 12);
  68. if (*s != ' ' && *s != '\t') return luaL_error(L, "invalid spec (separator @%d)", s - str);
  69. desc->dow = lcron_parsepart(L, s + 1, &s, 0, 6);
  70. while (*s != '\0' && (*s == ' ' || *s == '\t')) {
  71. s++;
  72. }
  73. if (*s != 0) return luaL_error(L, "invalid spec (trailing @%d)", s - str);
  74. return 0;
  75. }
  76. static int lcron_create(lua_State *L) {
  77. // Check arguments
  78. char *strdesc = (char*)luaL_checkstring(L, 1);
  79. void *newlist;
  80. luaL_checktype(L, 2, LUA_TFUNCTION);
  81. // Parse description
  82. struct cronent_desc desc;
  83. lcron_parsedesc(L, strdesc, &desc);
  84. // Allocate userdata
  85. cronent_ud_t *ud = lua_newuserdata(L, sizeof(cronent_ud_t));
  86. // Set metatable
  87. luaL_getmetatable(L, "cron.entry");
  88. lua_setmetatable(L, -2);
  89. // Set callback
  90. lua_pushvalue(L, 2);
  91. ud->cb_ref = luaL_ref(L, LUA_REGISTRYINDEX);
  92. // Set entry
  93. ud->desc = desc;
  94. // Store entry
  95. newlist = os_realloc(cronent_list, sizeof(int) * (cronent_count + 1));
  96. if (newlist == NULL) {
  97. return luaL_error(L, "out of memory");
  98. }
  99. lua_pushvalue(L, -1);
  100. cronent_list = newlist;
  101. cronent_list[cronent_count++] = luaL_ref(L, LUA_REGISTRYINDEX);
  102. return 1;
  103. }
  104. static size_t lcron_findindex(lua_State *L, cronent_ud_t *ud) {
  105. cronent_ud_t *eud;
  106. size_t i;
  107. for (i = 0; i < cronent_count; i++) {
  108. lua_rawgeti(L, LUA_REGISTRYINDEX, cronent_list[i]);
  109. eud = lua_touserdata(L, -1);
  110. lua_pop(L, 1);
  111. if (eud == ud) break;
  112. }
  113. if (i == cronent_count) return -1;
  114. return i;
  115. }
  116. static int lcron_schedule(lua_State *L) {
  117. cronent_ud_t *ud = luaL_checkudata(L, 1, "cron.entry");
  118. char *strdesc = (char*)luaL_checkstring(L, 2);
  119. struct cronent_desc desc;
  120. lcron_parsedesc(L, strdesc, &desc);
  121. ud->desc = desc;
  122. size_t i = lcron_findindex(L, ud);
  123. if (i == -1) {
  124. void *newlist;
  125. newlist = os_realloc(cronent_list, sizeof(int) * (cronent_count + 1));
  126. if (newlist == NULL) {
  127. return luaL_error(L, "out of memory");
  128. }
  129. cronent_list = newlist;
  130. lua_pushvalue(L, 1);
  131. cronent_list[cronent_count++] = luaL_ref(L, LUA_REGISTRYINDEX);
  132. }
  133. return 0;
  134. }
  135. static int lcron_handler(lua_State *L) {
  136. cronent_ud_t *ud = luaL_checkudata(L, 1, "cron.entry");
  137. luaL_checktype(L, 2, LUA_TFUNCTION);
  138. lua_pushvalue(L, 2);
  139. luaL_unref(L, LUA_REGISTRYINDEX, ud->cb_ref);
  140. ud->cb_ref = luaL_ref(L, LUA_REGISTRYINDEX);
  141. return 0;
  142. }
  143. static int lcron_unschedule(lua_State *L) {
  144. cronent_ud_t *ud = luaL_checkudata(L, 1, "cron.entry");
  145. size_t i = lcron_findindex(L, ud);
  146. if (i == -1) return 0;
  147. luaL_unref(L, LUA_REGISTRYINDEX, cronent_list[i]);
  148. memmove(cronent_list + i, cronent_list + i + 1, sizeof(int) * (cronent_count - i - 1));
  149. cronent_count--;
  150. return 0;
  151. }
  152. static int lcron_delete(lua_State *L) {
  153. cronent_ud_t *ud = luaL_checkudata(L, 1, "cron.entry");
  154. lcron_unschedule(L);
  155. luaL_unref(L, LUA_REGISTRYINDEX, ud->cb_ref);
  156. return 0;
  157. }
  158. static int lcron_reset(lua_State *L) {
  159. for (size_t i = 0; i < cronent_count; i++) {
  160. luaL_unref(L, LUA_REGISTRYINDEX, cronent_list[i]);
  161. }
  162. cronent_count = 0;
  163. free(cronent_list);
  164. cronent_list = 0;
  165. return 0;
  166. }
  167. static void cron_handle_time(uint8_t mon, uint8_t dom, uint8_t dow, uint8_t hour, uint8_t min) {
  168. lua_State *L = lua_getstate();
  169. struct cronent_desc desc;
  170. desc.mon = (uint16_t)1 << (mon - 1);
  171. desc.dom = (uint32_t)1 << (dom - 1);
  172. desc.dow = ( uint8_t)1 << dow;
  173. desc.hour = (uint32_t)1 << hour;
  174. desc.min = (uint64_t)1 << min;
  175. for (size_t i = 0; i < cronent_count; i++) {
  176. lua_rawgeti(L, LUA_REGISTRYINDEX, cronent_list[i]);
  177. cronent_ud_t *ent = lua_touserdata(L, -1);
  178. lua_pop(L, 1);
  179. if ((ent->desc.mon & desc.mon ) == 0) continue;
  180. if ((ent->desc.dom & desc.dom ) == 0) continue;
  181. if ((ent->desc.dow & desc.dow ) == 0) continue;
  182. if ((ent->desc.hour & desc.hour) == 0) continue;
  183. if ((ent->desc.min & desc.min ) == 0) continue;
  184. lua_rawgeti(L, LUA_REGISTRYINDEX, ent->cb_ref);
  185. lua_rawgeti(L, LUA_REGISTRYINDEX, cronent_list[i]);
  186. luaL_pcallx(L, 1, 0);
  187. }
  188. }
  189. static void cron_handle_tmr() {
  190. struct rtc_timeval tv;
  191. rtctime_gettimeofday(&tv);
  192. if (tv.tv_sec == 0) { // Wait for RTC time
  193. os_timer_arm(&cron_timer, 1000, 0);
  194. return;
  195. }
  196. time_t t = tv.tv_sec;
  197. struct tm tm;
  198. gmtime_r(&t, &tm);
  199. uint32_t diff = 60000 - tm.tm_sec * 1000 - tv.tv_usec / 1000;
  200. if (tm.tm_sec == 59) {
  201. t++;
  202. diff += 60000;
  203. gmtime_r(&t, &tm);
  204. }
  205. os_timer_arm(&cron_timer, diff, 0);
  206. cron_handle_time(tm.tm_mon + 1, tm.tm_mday, tm.tm_wday, tm.tm_hour, tm.tm_min);
  207. }
  208. LROT_BEGIN(cronent, NULL, LROT_MASK_GC_INDEX)
  209. LROT_FUNCENTRY( __gc, lcron_delete )
  210. LROT_TABENTRY( __index, cronent )
  211. LROT_FUNCENTRY( schedule, lcron_schedule )
  212. LROT_FUNCENTRY( handler, lcron_handler )
  213. LROT_FUNCENTRY( unschedule, lcron_unschedule )
  214. LROT_END(cronent, NULL, LROT_MASK_GC_INDEX)
  215. LROT_BEGIN(cron, NULL, 0)
  216. LROT_FUNCENTRY( schedule, lcron_create )
  217. LROT_FUNCENTRY( reset, lcron_reset )
  218. LROT_END(cron, NULL, 0)
  219. #include "pm/swtimer.h"
  220. int luaopen_cron( lua_State *L ) {
  221. os_timer_disarm(&cron_timer);
  222. os_timer_setfn(&cron_timer, cron_handle_tmr, 0);
  223. SWTIMER_REG_CB(cron_handle_tmr, SWTIMER_RESTART);
  224. //cron_handle_tmr determines when to execute a scheduled cron job
  225. //My guess: To be sure to give the other modules required by cron enough time to get to a ready state, restart cron_timer.
  226. os_timer_arm(&cron_timer, 1000, 0);
  227. luaL_rometatable(L, "cron.entry", LROT_TABLEREF(cronent));
  228. return 0;
  229. }
  230. NODEMCU_MODULE(CRON, "cron", cron, luaopen_cron);