#include "module.h" #include "lauxlib.h" #include #include "task/task.h" #include "freertos/FreeRTOS.h" #include "freertos/timers.h" #define TIMER_MODE_OFF 3 #define TIMER_MODE_SINGLE 0 #define TIMER_MODE_SEMI 2 #define TIMER_MODE_AUTO 1 #define TIMER_IDLE_FLAG (1<<7) #define STRINGIFY_VAL(x) #x #define STRINGIFY(x) STRINGIFY_VAL(x) // assuming system_timer_reinit() has *not* been called #define MAX_TIMEOUT_DEF 6870947 //SDK 1.5.3 limit (0x68D7A3) static const uint32_t MAX_TIMEOUT=MAX_TIMEOUT_DEF; static const char* MAX_TIMEOUT_ERR_STR = "Range: 1-"STRINGIFY(MAX_TIMEOUT_DEF); typedef struct{ TimerHandle_t timer; int32_t cb_ref, self_ref; uint32_t interval; uint8_t mode; } tmr_struct_t; typedef tmr_struct_t *tmr_t; static task_handle_t alarm_task_id; static void timer_callback(TimerHandle_t xTimer) { tmr_t tmr = (tmr_t)pvTimerGetTimerID(xTimer); task_post_high(alarm_task_id, (task_param_t)tmr); } static void alarm_timer_task(task_param_t param, task_prio_t prio) { tmr_t tmr = (tmr_t)param; lua_State* L = lua_getstate(); if (tmr->cb_ref == LUA_NOREF || tmr->self_ref == LUA_NOREF) return; lua_rawgeti(L, LUA_REGISTRYINDEX, tmr->cb_ref); lua_rawgeti(L, LUA_REGISTRYINDEX, tmr->self_ref); // if the timer was set to single run we clean up after it if (tmr->mode == TIMER_MODE_SINGLE) { luaL_unref(L, LUA_REGISTRYINDEX, tmr->cb_ref); tmr->cb_ref = LUA_NOREF; tmr->mode = TIMER_MODE_OFF; } else if (tmr->mode == TIMER_MODE_SEMI) { tmr->mode |= TIMER_IDLE_FLAG; } if (tmr->mode != TIMER_MODE_AUTO && tmr->self_ref != LUA_REFNIL) { luaL_unref(L, LUA_REGISTRYINDEX, tmr->self_ref); tmr->self_ref = LUA_NOREF; } luaL_pcallx(L, 1, 0); } static tmr_t tmr_get( lua_State *L, int stack ) { return (tmr_t)luaL_checkudata(L, stack, "tmr.timer"); } // Lua: tmr.register( self, interval, mode, function ) static int tmr_register(lua_State* L) { int stack = 0; tmr_t tmr = tmr_get(L, ++stack); uint32_t interval = luaL_checkinteger(L, ++stack); luaL_argcheck(L, interval > 0 && interval <= MAX_TIMEOUT, stack, MAX_TIMEOUT_ERR_STR); uint8_t mode = luaL_checkinteger(L, ++stack); luaL_argcheck(L, mode == TIMER_MODE_SINGLE || mode == TIMER_MODE_SEMI || mode == TIMER_MODE_AUTO, stack, "Invalid mode"); ++stack; luaL_checkfunction(L, stack); if (tmr->timer) { // delete previous timer since mode change could be requested here xTimerDelete(tmr->timer, portMAX_DELAY); } //get the lua function reference luaL_unref(L, LUA_REGISTRYINDEX, tmr->cb_ref); lua_pushvalue(L, stack); tmr->cb_ref = luaL_ref(L, LUA_REGISTRYINDEX); tmr->mode = mode | TIMER_IDLE_FLAG; tmr->interval = interval; tmr->timer = xTimerCreate("tmr", pdMS_TO_TICKS(interval), mode == TIMER_MODE_AUTO ? pdTRUE : pdFALSE, (void *)tmr, timer_callback); if (!tmr->timer) { luaL_error(L, "FreeRTOS timer creation failed"); } return 0; } // Lua: tmr.start( self ) static int tmr_start(lua_State* L) { tmr_t tmr = tmr_get(L, 1); if (tmr->self_ref == LUA_NOREF) { lua_pushvalue(L, 1); tmr->self_ref = luaL_ref(L, LUA_REGISTRYINDEX); } if (!tmr->timer || tmr->cb_ref == LUA_NOREF) { luaL_error(L, "timer not registered"); } // we return false if the timer is not idle if(!(tmr->mode & TIMER_IDLE_FLAG)) { lua_pushboolean(L, 0); } else { tmr->mode &= ~TIMER_IDLE_FLAG; if (xTimerStart(tmr->timer, portMAX_DELAY) != pdPASS) { luaL_error(L, "timer start failed"); } lua_pushboolean(L, 1); } return 1; } // Lua: tmr.alarm( self, interval, repeat, function ) static int tmr_alarm(lua_State* L){ tmr_register(L); return tmr_start(L); } // Lua: tmr.stop( id / ref ) static int tmr_stop(lua_State* L) { tmr_t tmr = tmr_get(L, 1); luaL_unref(L, LUA_REGISTRYINDEX, tmr->self_ref); tmr->self_ref = LUA_NOREF; //we return false if the timer is idle (of not registered) if(!(tmr->mode & TIMER_IDLE_FLAG) && tmr->mode != TIMER_MODE_OFF){ tmr->mode |= TIMER_IDLE_FLAG; xTimerStop(tmr->timer, portMAX_DELAY); lua_pushboolean(L, 1); } else { lua_pushboolean(L, 0); } return 1; } // Lua: tmr.unregister( self ) static int tmr_unregister(lua_State* L){ tmr_t tmr = tmr_get(L, 1); if (tmr->self_ref != LUA_REFNIL) { luaL_unref(L, LUA_REGISTRYINDEX, tmr->self_ref); tmr->self_ref = LUA_NOREF; } if(!(tmr->mode & TIMER_IDLE_FLAG) && tmr->mode != TIMER_MODE_OFF) { if (tmr->timer) { xTimerStop(tmr->timer, portMAX_DELAY); } } if (tmr->timer) { xTimerDelete(tmr->timer, portMAX_DELAY); } tmr->timer = NULL; luaL_unref(L, LUA_REGISTRYINDEX, tmr->cb_ref); tmr->cb_ref = LUA_NOREF; tmr->mode = TIMER_MODE_OFF; return 0; } // Lua: tmr.interval( self, interval ) static int tmr_interval(lua_State* L) { tmr_t tmr = tmr_get(L, 1); uint32_t interval = luaL_checkinteger(L, 2); luaL_argcheck(L, interval > 0 && interval <= MAX_TIMEOUT, 2, MAX_TIMEOUT_ERR_STR); if (tmr->mode != TIMER_MODE_OFF) { tmr->interval = interval; if (xTimerChangePeriod(tmr->timer, pdMS_TO_TICKS(tmr->interval), portMAX_DELAY) != pdPASS) { luaL_error(L, "cannot change period"); } if (tmr->mode & TIMER_IDLE_FLAG) { // xTimerChangePeriod will start a dormant timer, thus force stop if it was dormant xTimerStop(tmr->timer, portMAX_DELAY); } } return 0; } // Lua: tmr.state( self, state ) static int tmr_state(lua_State* L) { tmr_t tmr = tmr_get(L, 1); if (tmr->mode == TIMER_MODE_OFF) { lua_pushnil(L); return 1; } lua_pushboolean(L, (tmr->mode & TIMER_IDLE_FLAG) == 0); lua_pushinteger(L, tmr->mode & (~TIMER_IDLE_FLAG)); return 2; } // Lua: tmr.create() static int tmr_create( lua_State *L ) { tmr_t ud = (tmr_t)lua_newuserdata(L, sizeof(tmr_struct_t)); if (!ud) return luaL_error(L, "not enough memory"); luaL_getmetatable(L, "tmr.timer"); lua_setmetatable(L, -2); ud->cb_ref = LUA_NOREF; ud->self_ref = LUA_NOREF; ud->mode = TIMER_MODE_OFF; ud->timer = NULL; return 1; } // Lua: tmr.wdclr() static int tmr_wdclr( lua_State *L ) { // Suspend ourselves momentarily to let the IDLE task do its thing vTaskDelay(1); return 0; } // Module function map LROT_BEGIN(tmr_dyn, NULL, LROT_MASK_GC_INDEX) LROT_FUNCENTRY( __gc, tmr_unregister ) LROT_TABENTRY ( __index, tmr_dyn ) LROT_FUNCENTRY( register, tmr_register ) LROT_FUNCENTRY( alarm, tmr_alarm ) LROT_FUNCENTRY( start, tmr_start ) LROT_FUNCENTRY( stop, tmr_stop ) LROT_FUNCENTRY( unregister, tmr_unregister ) LROT_FUNCENTRY( interval, tmr_interval) LROT_FUNCENTRY( state, tmr_state ) LROT_END(tmr_dyn, NULL, LROT_MASK_GC_INDEX) LROT_BEGIN(tmr, NULL, 0) LROT_FUNCENTRY( create, tmr_create ) LROT_FUNCENTRY( wdclr, tmr_wdclr ) LROT_NUMENTRY ( ALARM_SINGLE, TIMER_MODE_SINGLE ) LROT_NUMENTRY ( ALARM_SEMI, TIMER_MODE_SEMI ) LROT_NUMENTRY ( ALARM_AUTO, TIMER_MODE_AUTO ) LROT_END(tmr, NULL, 0) static int luaopen_tmr( lua_State *L ){ luaL_rometatable(L, "tmr.timer", LROT_TABLEREF(tmr_dyn)); alarm_task_id = task_get_id(alarm_timer_task); return 0; } NODEMCU_MODULE(TMR, "tmr", tmr, luaopen_tmr);