cron.c 7.0 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233
  1. // Cron module
  2. #include "module.h"
  3. #include "lauxlib.h"
  4. #include "lmem.h"
  5. #include "user_interface.h"
  6. #include "c_types.h"
  7. #include "c_string.h"
  8. #include "ets_sys.h"
  9. #include "time.h"
  10. #include "rtc/rtctime_internal.h"
  11. #include "rtc/rtctime.h"
  12. #include "stdlib.h"
  13. #include "mem.h"
  14. struct cronent_desc {
  15. uint64_t min; // Minutes repeat - bits 0-59
  16. uint32_t hour; // Hours repeat - bits 0-23
  17. uint32_t dom; // Day-of-month repeat - bits 0-30
  18. uint16_t mon; // Monthly repeat - bits 0-11
  19. uint8_t dow; // Day-of-week repeat - bits 0-6
  20. };
  21. typedef struct cronent_ud {
  22. struct cronent_desc desc;
  23. int cb_ref;
  24. } cronent_ud_t;
  25. static ETSTimer cron_timer;
  26. static int *cronent_list = 0;
  27. static size_t cronent_count = 0;
  28. static uint64_t lcron_parsepart(lua_State *L, char *str, char **end, uint8_t min, uint8_t max) {
  29. uint64_t res = 0;
  30. if (str[0] == '*') {
  31. uint32_t each = 1;
  32. *end = str + 1;
  33. if (str[1] == '/') {
  34. each = strtol(str + 2, end, 10);
  35. if (end != 0)
  36. if (each == 0 || each >= max - min) {
  37. return luaL_error(L, "invalid spec (each %d)", each);
  38. }
  39. }
  40. for (int i = 0; i <= (max - min); i++) {
  41. if (((min + i) % each) == 0) res |= (uint64_t)1 << i;
  42. }
  43. } else {
  44. uint32_t val;
  45. while (1) {
  46. val = strtol(str, end, 10);
  47. if (val < min || val > max) {
  48. return luaL_error(L, "invalid spec (val %d out of range %d..%d)", val, min, max);
  49. }
  50. res |= (uint64_t)1 << (val - min);
  51. if (**end != ',') break;
  52. str = *end + 1;
  53. }
  54. }
  55. return res;
  56. }
  57. static int lcron_parsedesc(lua_State *L, char *str, struct cronent_desc *desc) {
  58. char *s = str;
  59. desc->min = lcron_parsepart(L, s, &s, 0, 59);
  60. if (*s != ' ') return luaL_error(L, "invalid spec (separator @%d)", s - str);
  61. desc->hour = lcron_parsepart(L, s + 1, &s, 0, 23);
  62. if (*s != ' ') return luaL_error(L, "invalid spec (separator @%d)", s - str);
  63. desc->dom = lcron_parsepart(L, s + 1, &s, 1, 31);
  64. if (*s != ' ') return luaL_error(L, "invalid spec (separator @%d)", s - str);
  65. desc->mon = lcron_parsepart(L, s + 1, &s, 1, 12);
  66. if (*s != ' ') return luaL_error(L, "invalid spec (separator @%d)", s - str);
  67. desc->dow = lcron_parsepart(L, s + 1, &s, 0, 6);
  68. if (*s != 0) return luaL_error(L, "invalid spec (trailing @%d)", s - str);
  69. return 0;
  70. }
  71. static int lcron_create(lua_State *L) {
  72. // Check arguments
  73. char *strdesc = (char*)luaL_checkstring(L, 1);
  74. luaL_checkanyfunction(L, 2);
  75. // Parse description
  76. struct cronent_desc desc;
  77. lcron_parsedesc(L, strdesc, &desc);
  78. // Allocate userdata
  79. cronent_ud_t *ud = lua_newuserdata(L, sizeof(cronent_ud_t));
  80. // Set metatable
  81. luaL_getmetatable(L, "cron.entry");
  82. lua_setmetatable(L, -2);
  83. // Set callback
  84. lua_pushvalue(L, 2);
  85. ud->cb_ref = luaL_ref(L, LUA_REGISTRYINDEX);
  86. // Set entry
  87. ud->desc = desc;
  88. // Store entry
  89. lua_pushvalue(L, -1);
  90. cronent_list = os_realloc(cronent_list, sizeof(int) * (cronent_count + 1));
  91. cronent_list[cronent_count++] = luaL_ref(L, LUA_REGISTRYINDEX);
  92. return 1;
  93. }
  94. static size_t lcron_findindex(lua_State *L, cronent_ud_t *ud) {
  95. cronent_ud_t *eud;
  96. size_t i;
  97. for (i = 0; i < cronent_count; i++) {
  98. lua_rawgeti(L, LUA_REGISTRYINDEX, cronent_list[i]);
  99. eud = lua_touserdata(L, 1);
  100. lua_pop(L, 1);
  101. if (eud == ud) break;
  102. }
  103. if (i == cronent_count) return -1;
  104. return i;
  105. }
  106. static int lcron_schedule(lua_State *L) {
  107. cronent_ud_t *ud = luaL_checkudata(L, 1, "cron.entry");
  108. char *strdesc = (char*)luaL_checkstring(L, 2);
  109. struct cronent_desc desc;
  110. lcron_parsedesc(L, strdesc, &desc);
  111. ud->desc = desc;
  112. size_t i = lcron_findindex(L, ud);
  113. if (i == -1) {
  114. lua_pushvalue(L, 1);
  115. cronent_list = os_realloc(cronent_list, sizeof(int) * (cronent_count + 1));
  116. cronent_list[cronent_count++] = lua_ref(L, LUA_REGISTRYINDEX);
  117. }
  118. return 0;
  119. }
  120. static int lcron_handler(lua_State *L) {
  121. cronent_ud_t *ud = luaL_checkudata(L, 1, "cron.entry");
  122. luaL_checkanyfunction(L, 2);
  123. lua_pushvalue(L, 2);
  124. luaL_unref(L, LUA_REGISTRYINDEX, ud->cb_ref);
  125. ud->cb_ref = luaL_ref(L, LUA_REGISTRYINDEX);
  126. return 0;
  127. }
  128. static int lcron_unschedule(lua_State *L) {
  129. cronent_ud_t *ud = luaL_checkudata(L, 1, "cron.entry");
  130. size_t i = lcron_findindex(L, ud);
  131. if (i == -1) return 0;
  132. luaL_unref(L, LUA_REGISTRYINDEX, cronent_list[i]);
  133. memmove(cronent_list + i, cronent_list + i + 1, sizeof(int) * cronent_count - i - 1);
  134. cronent_count--;
  135. return 0;
  136. }
  137. static int lcron_delete(lua_State *L) {
  138. cronent_ud_t *ud = luaL_checkudata(L, 1, "cron.entry");
  139. lcron_unschedule(L);
  140. luaL_unref(L, LUA_REGISTRYINDEX, ud->cb_ref);
  141. return 0;
  142. }
  143. static int lcron_reset(lua_State *L) {
  144. for (size_t i = 0; i < cronent_count; i++) {
  145. luaL_unref(L, LUA_REGISTRYINDEX, cronent_list[i]);
  146. }
  147. cronent_count = 0;
  148. os_free(cronent_list);
  149. cronent_list = 0;
  150. return 0;
  151. }
  152. static void cron_handle_time(uint8_t mon, uint8_t dom, uint8_t dow, uint8_t hour, uint8_t min) {
  153. lua_State *L = lua_getstate();
  154. struct cronent_desc desc;
  155. desc.mon = (uint16_t)1 << (mon - 1);
  156. desc.dom = (uint32_t)1 << (dom - 1);
  157. desc.dow = ( uint8_t)1 << dow;
  158. desc.hour = (uint32_t)1 << hour;
  159. desc.min = (uint64_t)1 << min;
  160. for (size_t i = 0; i < cronent_count; i++) {
  161. lua_rawgeti(L, LUA_REGISTRYINDEX, cronent_list[i]);
  162. cronent_ud_t *ent = lua_touserdata(L, 1);
  163. lua_pop(L, 1);
  164. if ((ent->desc.mon & desc.mon ) == 0) continue;
  165. if ((ent->desc.dom & desc.dom ) == 0) continue;
  166. if ((ent->desc.dow & desc.dow ) == 0) continue;
  167. if ((ent->desc.hour & desc.hour) == 0) continue;
  168. if ((ent->desc.min & desc.min ) == 0) continue;
  169. lua_rawgeti(L, LUA_REGISTRYINDEX, ent->cb_ref);
  170. lua_rawgeti(L, LUA_REGISTRYINDEX, cronent_list[i]);
  171. lua_call(L, 1, 0);
  172. }
  173. }
  174. static void cron_handle_tmr() {
  175. struct rtc_timeval tv;
  176. rtc_time_gettimeofday(&tv);
  177. if (tv.tv_sec == 0) { // Wait for RTC time
  178. ets_timer_arm_new(&cron_timer, 1000, 0, 1);
  179. return;
  180. }
  181. time_t t = tv.tv_sec;
  182. struct tm tm;
  183. gmtime_r(&t, &tm);
  184. uint32_t diff = 60000 - tm.tm_sec * 1000 - tv.tv_usec / 1000;
  185. if (tm.tm_sec == 59) {
  186. t++;
  187. diff += 60000;
  188. gmtime_r(&t, &tm);
  189. }
  190. ets_timer_arm_new(&cron_timer, diff, 0, 1);
  191. cron_handle_time(tm.tm_mon + 1, tm.tm_mday, tm.tm_wday, tm.tm_hour, tm.tm_min);
  192. }
  193. static const LUA_REG_TYPE cronent_map[] = {
  194. { LSTRKEY( "schedule" ), LFUNCVAL( lcron_schedule ) },
  195. { LSTRKEY( "handler" ), LFUNCVAL( lcron_handler ) },
  196. { LSTRKEY( "unschedule" ), LFUNCVAL( lcron_unschedule ) },
  197. { LSTRKEY( "__gc" ), LFUNCVAL( lcron_delete ) },
  198. { LSTRKEY( "__index" ), LROVAL( cronent_map ) },
  199. { LNILKEY, LNILVAL }
  200. };
  201. static const LUA_REG_TYPE cron_map[] = {
  202. { LSTRKEY( "schedule" ), LFUNCVAL( lcron_create ) },
  203. { LSTRKEY( "reset" ), LFUNCVAL( lcron_reset ) },
  204. { LNILKEY, LNILVAL }
  205. };
  206. int luaopen_cron( lua_State *L ) {
  207. ets_timer_disarm(&cron_timer);
  208. ets_timer_setfn(&cron_timer, cron_handle_tmr, 0);
  209. ets_timer_arm_new(&cron_timer, 1000, 0, 1);
  210. luaL_rometatable(L, "cron.entry", (void *)cronent_map);
  211. return 0;
  212. }
  213. NODEMCU_MODULE(CRON, "cron", cron_map, luaopen_cron);