Cron module (#1674)
This commit is contained in:
parent
c57af8972f
commit
2b897e56c7
|
@ -26,6 +26,7 @@
|
|||
//#define LUA_USE_MODULES_BME280
|
||||
//#define LUA_USE_MODULES_CJSON
|
||||
//#define LUA_USE_MODULES_COAP
|
||||
//#define LUA_USE_MODULES_CRON
|
||||
//#define LUA_USE_MODULES_CRYPTO
|
||||
#define LUA_USE_MODULES_DHT
|
||||
//#define LUA_USE_MODULES_ENCODER
|
||||
|
|
|
@ -0,0 +1,233 @@
|
|||
// Cron module
|
||||
|
||||
#include "module.h"
|
||||
#include "lauxlib.h"
|
||||
#include "lmem.h"
|
||||
#include "user_interface.h"
|
||||
#include "c_types.h"
|
||||
#include "c_string.h"
|
||||
#include "ets_sys.h"
|
||||
#include "time.h"
|
||||
#include "rtc/rtctime_internal.h"
|
||||
#include "rtc/rtctime.h"
|
||||
#include "stdlib.h"
|
||||
#include "mem.h"
|
||||
|
||||
struct cronent_desc {
|
||||
uint64_t min; // Minutes repeat - bits 0-59
|
||||
uint32_t hour; // Hours repeat - bits 0-23
|
||||
uint32_t dom; // Day-of-month repeat - bits 0-30
|
||||
uint16_t mon; // Monthly repeat - bits 0-11
|
||||
uint8_t dow; // Day-of-week repeat - bits 0-6
|
||||
};
|
||||
|
||||
typedef struct cronent_ud {
|
||||
struct cronent_desc desc;
|
||||
int cb_ref;
|
||||
} cronent_ud_t;
|
||||
|
||||
static ETSTimer cron_timer;
|
||||
|
||||
static int *cronent_list = 0;
|
||||
static size_t cronent_count = 0;
|
||||
|
||||
static uint64_t lcron_parsepart(lua_State *L, char *str, char **end, uint8_t min, uint8_t max) {
|
||||
uint64_t res = 0;
|
||||
if (str[0] == '*') {
|
||||
uint32_t each = 1;
|
||||
*end = str + 1;
|
||||
if (str[1] == '/') {
|
||||
each = strtol(str + 2, end, 10);
|
||||
if (end != 0)
|
||||
if (each == 0 || each >= max - min) {
|
||||
return luaL_error(L, "invalid spec (each %d)", each);
|
||||
}
|
||||
}
|
||||
for (int i = 0; i <= (max - min); i++) {
|
||||
if (((min + i) % each) == 0) res |= (uint64_t)1 << i;
|
||||
}
|
||||
} else {
|
||||
uint32_t val;
|
||||
while (1) {
|
||||
val = strtol(str, end, 10);
|
||||
if (val < min || val > max) {
|
||||
return luaL_error(L, "invalid spec (val %d out of range %d..%d)", val, min, max);
|
||||
}
|
||||
res |= (1 << (val - min));
|
||||
if (**end != ',') break;
|
||||
str = *end + 1;
|
||||
}
|
||||
}
|
||||
return res;
|
||||
}
|
||||
|
||||
static int lcron_parsedesc(lua_State *L, char *str, struct cronent_desc *desc) {
|
||||
char *s = str;
|
||||
desc->min = lcron_parsepart(L, s, &s, 0, 59);
|
||||
if (*s != ' ') return luaL_error(L, "invalid spec (separator @%d)", s - str);
|
||||
desc->hour = lcron_parsepart(L, s + 1, &s, 0, 23);
|
||||
if (*s != ' ') return luaL_error(L, "invalid spec (separator @%d)", s - str);
|
||||
desc->dow = lcron_parsepart(L, s + 1, &s, 0, 6);
|
||||
if (*s != ' ') return luaL_error(L, "invalid spec (separator @%d)", s - str);
|
||||
desc->dom = lcron_parsepart(L, s + 1, &s, 1, 31);
|
||||
if (*s != ' ') return luaL_error(L, "invalid spec (separator @%d)", s - str);
|
||||
desc->mon = lcron_parsepart(L, s + 1, &s, 1, 12);
|
||||
if (*s != 0) return luaL_error(L, "invalid spec (trailing @%d)", s - str);
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int lcron_create(lua_State *L) {
|
||||
// Check arguments
|
||||
char *strdesc = (char*)luaL_checkstring(L, 1);
|
||||
luaL_checkanyfunction(L, 2);
|
||||
// Parse description
|
||||
struct cronent_desc desc;
|
||||
lcron_parsedesc(L, strdesc, &desc);
|
||||
// Allocate userdata
|
||||
cronent_ud_t *ud = lua_newuserdata(L, sizeof(cronent_ud_t));
|
||||
// Set metatable
|
||||
luaL_getmetatable(L, "cron.entry");
|
||||
lua_setmetatable(L, -2);
|
||||
// Set callback
|
||||
lua_pushvalue(L, 2);
|
||||
ud->cb_ref = luaL_ref(L, LUA_REGISTRYINDEX);
|
||||
// Set entry
|
||||
ud->desc = desc;
|
||||
// Store entry
|
||||
lua_pushvalue(L, -1);
|
||||
cronent_list = os_realloc(cronent_list, sizeof(int) * (cronent_count + 1));
|
||||
cronent_list[cronent_count++] = luaL_ref(L, LUA_REGISTRYINDEX);
|
||||
return 1;
|
||||
}
|
||||
|
||||
static size_t lcron_findindex(lua_State *L, cronent_ud_t *ud) {
|
||||
cronent_ud_t *eud;
|
||||
size_t i;
|
||||
for (i = 0; i < cronent_count; i++) {
|
||||
lua_rawgeti(L, LUA_REGISTRYINDEX, cronent_list[i]);
|
||||
eud = lua_touserdata(L, 1);
|
||||
lua_pop(L, 1);
|
||||
if (eud == ud) break;
|
||||
}
|
||||
if (i == cronent_count) return -1;
|
||||
return i;
|
||||
}
|
||||
|
||||
static int lcron_schedule(lua_State *L) {
|
||||
cronent_ud_t *ud = luaL_checkudata(L, 1, "cron.entry");
|
||||
char *strdesc = (char*)luaL_checkstring(L, 2);
|
||||
struct cronent_desc desc;
|
||||
lcron_parsedesc(L, strdesc, &desc);
|
||||
ud->desc = desc;
|
||||
size_t i = lcron_findindex(L, ud);
|
||||
if (i == -1) {
|
||||
lua_pushvalue(L, 1);
|
||||
cronent_list = os_realloc(cronent_list, sizeof(int) * (cronent_count + 1));
|
||||
cronent_list[cronent_count++] = lua_ref(L, LUA_REGISTRYINDEX);
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int lcron_handler(lua_State *L) {
|
||||
cronent_ud_t *ud = luaL_checkudata(L, 1, "cron.entry");
|
||||
luaL_checkanyfunction(L, 2);
|
||||
lua_pushvalue(L, 2);
|
||||
luaL_unref(L, LUA_REGISTRYINDEX, ud->cb_ref);
|
||||
ud->cb_ref = luaL_ref(L, LUA_REGISTRYINDEX);
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int lcron_unschedule(lua_State *L) {
|
||||
cronent_ud_t *ud = luaL_checkudata(L, 1, "cron.entry");
|
||||
size_t i = lcron_findindex(L, ud);
|
||||
if (i == -1) return 0;
|
||||
luaL_unref(L, LUA_REGISTRYINDEX, cronent_list[i]);
|
||||
memmove(cronent_list + i, cronent_list + i + 1, sizeof(int) * cronent_count - i - 1);
|
||||
cronent_count--;
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int lcron_delete(lua_State *L) {
|
||||
cronent_ud_t *ud = luaL_checkudata(L, 1, "cron.entry");
|
||||
lcron_unschedule(L);
|
||||
luaL_unref(L, LUA_REGISTRYINDEX, ud->cb_ref);
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int lcron_reset(lua_State *L) {
|
||||
for (size_t i = 0; i < cronent_count; i++) {
|
||||
luaL_unref(L, LUA_REGISTRYINDEX, cronent_list[i]);
|
||||
}
|
||||
cronent_count = 0;
|
||||
os_free(cronent_list);
|
||||
cronent_list = 0;
|
||||
return 0;
|
||||
}
|
||||
|
||||
static void cron_handle_time(uint8_t mon, uint8_t dom, uint8_t dow, uint8_t hour, uint8_t min) {
|
||||
lua_State *L = lua_getstate();
|
||||
struct cronent_desc desc;
|
||||
desc.mon = (uint16_t)1 << (mon - 1);
|
||||
desc.dom = (uint32_t)1 << (dom - 1);
|
||||
desc.dow = ( uint8_t)1 << dow;
|
||||
desc.hour = (uint32_t)1 << hour;
|
||||
desc.min = (uint64_t)1 << min;
|
||||
for (size_t i = 0; i < cronent_count; i++) {
|
||||
lua_rawgeti(L, LUA_REGISTRYINDEX, cronent_list[i]);
|
||||
cronent_ud_t *ent = lua_touserdata(L, 1);
|
||||
lua_pop(L, 1);
|
||||
if ((ent->desc.mon & desc.mon ) == 0) continue;
|
||||
if ((ent->desc.dom & desc.dom ) == 0) continue;
|
||||
if ((ent->desc.dow & desc.dow ) == 0) continue;
|
||||
if ((ent->desc.hour & desc.hour) == 0) continue;
|
||||
if ((ent->desc.min & desc.min ) == 0) continue;
|
||||
lua_rawgeti(L, LUA_REGISTRYINDEX, ent->cb_ref);
|
||||
lua_rawgeti(L, LUA_REGISTRYINDEX, cronent_list[i]);
|
||||
lua_call(L, 1, 0);
|
||||
}
|
||||
}
|
||||
|
||||
static void cron_handle_tmr() {
|
||||
struct rtc_timeval tv;
|
||||
rtc_time_gettimeofday(&tv);
|
||||
if (tv.tv_sec == 0) { // Wait for RTC time
|
||||
ets_timer_arm_new(&cron_timer, 1000, 0, 1);
|
||||
return;
|
||||
}
|
||||
time_t t = tv.tv_sec;
|
||||
struct tm tm;
|
||||
gmtime_r(&t, &tm);
|
||||
uint32_t diff = 60000 - tm.tm_sec * 1000 - tv.tv_usec / 1000;
|
||||
if (tm.tm_sec == 59) {
|
||||
t++;
|
||||
diff += 60000;
|
||||
gmtime_r(&t, &tm);
|
||||
}
|
||||
ets_timer_arm_new(&cron_timer, diff, 0, 1);
|
||||
cron_handle_time(tm.tm_mon + 1, tm.tm_mday, tm.tm_wday, tm.tm_hour, tm.tm_min);
|
||||
}
|
||||
|
||||
static const LUA_REG_TYPE cronent_map[] = {
|
||||
{ LSTRKEY( "schedule" ), LFUNCVAL( lcron_schedule ) },
|
||||
{ LSTRKEY( "handler" ), LFUNCVAL( lcron_handler ) },
|
||||
{ LSTRKEY( "unschedule" ), LFUNCVAL( lcron_unschedule ) },
|
||||
{ LSTRKEY( "__gc" ), LFUNCVAL( lcron_delete ) },
|
||||
{ LSTRKEY( "__index" ), LROVAL( cronent_map ) },
|
||||
{ LNILKEY, LNILVAL }
|
||||
};
|
||||
|
||||
static const LUA_REG_TYPE cron_map[] = {
|
||||
{ LSTRKEY( "schedule" ), LFUNCVAL( lcron_create ) },
|
||||
{ LSTRKEY( "reset" ), LFUNCVAL( lcron_reset ) },
|
||||
{ LNILKEY, LNILVAL }
|
||||
};
|
||||
|
||||
int luaopen_cron( lua_State *L ) {
|
||||
ets_timer_disarm(&cron_timer);
|
||||
ets_timer_setfn(&cron_timer, cron_handle_tmr, 0);
|
||||
ets_timer_arm_new(&cron_timer, 1000, 0, 1);
|
||||
luaL_rometatable(L, "cron.entry", (void *)cronent_map);
|
||||
return 0;
|
||||
}
|
||||
|
||||
NODEMCU_MODULE(CRON, "cron", cron_map, luaopen_cron);
|
|
@ -0,0 +1,129 @@
|
|||
# Cron Module
|
||||
| Since | Origin / Contributor | Maintainer | Source |
|
||||
| :----- | :-------------------- | :---------- | :------ |
|
||||
| 2016-12-18 | [PhoeniX](https://github.com/djphoenix) | [PhoeniX](https://github.com/djphoenix) | [cron.c](../../../app/modules/cron.c)|
|
||||
|
||||
[Cron](https://en.wikipedia.org/wiki/Cron)-like scheduler module.
|
||||
|
||||
!!! important
|
||||
This module needs RTC time to operate correctly. Do not forget to include the [`rtctime`](rtctime.md) module.
|
||||
|
||||
## cron.schedule()
|
||||
|
||||
Creates a new schedule entry.
|
||||
|
||||
#### Syntax
|
||||
`cron.schedule(mask, callback)`
|
||||
|
||||
#### Parameters
|
||||
- `mask` - [crontab](https://en.wikipedia.org/wiki/Cron#Overview)-like string mask for schedule
|
||||
- `callback` - callback `function(entry)` that is executed at the scheduled time
|
||||
|
||||
#### Returns
|
||||
`cron.entry` sub module
|
||||
|
||||
#### Example
|
||||
|
||||
```lua
|
||||
cron.schedule("* * * * *", function(e)
|
||||
print("Every minute")
|
||||
end)
|
||||
|
||||
cron.schedule("*/5 * * * *", function(e)
|
||||
print("Every 5 minutes")
|
||||
end)
|
||||
|
||||
cron.schedule("0 */2 * * *", function(e)
|
||||
print("Every 2 hours")
|
||||
end)
|
||||
```
|
||||
|
||||
## cron.reset()
|
||||
|
||||
Removes all scheduled entries.
|
||||
|
||||
#### Syntax
|
||||
`cron.reset()`
|
||||
|
||||
#### Parameters
|
||||
none
|
||||
|
||||
#### Returns
|
||||
nil
|
||||
|
||||
# cron.entry Module
|
||||
|
||||
## cron.entry:handler()
|
||||
|
||||
Sets a new handler for entry.
|
||||
|
||||
#### Syntax
|
||||
`handler(callback)`
|
||||
|
||||
#### Parameters
|
||||
- `callback` - callback `function(entry)` that is executed at the scheduled time
|
||||
|
||||
#### Returns
|
||||
nil
|
||||
|
||||
#### Example
|
||||
|
||||
```lua
|
||||
ent = cron.schedule("* * * * *", function(e)
|
||||
print("Every minute")
|
||||
end)
|
||||
|
||||
ent:handler(function(e)
|
||||
print("New handler: Every minute")
|
||||
end)
|
||||
```
|
||||
|
||||
## cron.entry:schedule()
|
||||
|
||||
Sets a new schedule mask.
|
||||
|
||||
#### Syntax
|
||||
`schedule(mask)`
|
||||
|
||||
#### Parameters
|
||||
- `mask` - [crontab](https://en.wikipedia.org/wiki/Cron#Overview)-like string mask for schedule
|
||||
|
||||
#### Returns
|
||||
none
|
||||
|
||||
#### Example
|
||||
|
||||
```lua
|
||||
ent = cron.schedule("* * * * *", function(e)
|
||||
print("Tick")
|
||||
end)
|
||||
|
||||
-- Every 5 minutes is really better!
|
||||
ent:schedule("*/5 * * * *")
|
||||
```
|
||||
|
||||
## cron.entry:unschedule()
|
||||
|
||||
Disables schedule.
|
||||
|
||||
Disabled schedules may be enabled again by calling [`:schedule(mask)`](cron.md#cronentryschedule).
|
||||
|
||||
#### Syntax
|
||||
`unschedule()`
|
||||
|
||||
#### Parameters
|
||||
none
|
||||
|
||||
#### Returns
|
||||
nil
|
||||
|
||||
#### Example
|
||||
|
||||
```lua
|
||||
ent = cron.schedule("* * * * *", function(e)
|
||||
print("Tick")
|
||||
end)
|
||||
|
||||
-- We don't need this anymore
|
||||
ent:unschedule()
|
||||
```
|
|
@ -44,6 +44,7 @@ pages:
|
|||
- 'bmp085': 'en/modules/bmp085.md'
|
||||
- 'cjson': 'en/modules/cjson.md'
|
||||
- 'coap': 'en/modules/coap.md'
|
||||
- 'cron': 'en/modules/cron.md'
|
||||
- 'crypto': 'en/modules/crypto.md'
|
||||
- 'dht': 'en/modules/dht.md'
|
||||
- 'encoder': 'en/modules/encoder.md'
|
||||
|
|
Loading…
Reference in New Issue