浏览代码

Add support for dynamic timers, deprecating the static ones (#1424)

Yury Popov 7 年之前
父节点
当前提交
f1afe7b6ab
共有 2 个文件被更改,包括 155 次插入50 次删除
  1. 98 35
      app/modules/tmr.c
  2. 57 15
      docs/en/modules/tmr.md

+ 98 - 35
app/modules/tmr.c

@@ -71,7 +71,7 @@ static const char* MAX_TIMEOUT_ERR_STR = "Range: 1-"STRINGIFY(MAX_TIMEOUT_DEF);
 
 typedef struct{
 	os_timer_t os;
-	sint32_t lua_ref;
+	sint32_t lua_ref, self_ref;
 	uint32_t interval;
 	uint8_t mode;
 }timer_struct_t;
@@ -95,11 +95,17 @@ static timer_struct_t alarm_timers[NUM_TMR];
 static os_timer_t rtc_timer;
 
 static void alarm_timer_common(void* arg){
-	timer_t tmr = &alarm_timers[(uint32_t)arg];
+	timer_t tmr = (timer_t)arg;
 	lua_State* L = lua_getstate();
 	if(tmr->lua_ref == LUA_NOREF)
 		return;
 	lua_rawgeti(L, LUA_REGISTRYINDEX, tmr->lua_ref);
+	if (tmr->self_ref == LUA_REFNIL) {
+		uint32_t id = tmr - alarm_timers;
+		lua_pushinteger(L, id);
+	} else {
+		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->lua_ref);
@@ -108,7 +114,11 @@ static void alarm_timer_common(void* arg){
 	}else if(tmr->mode == TIMER_MODE_SEMI){
 		tmr->mode |= TIMER_IDLE_FLAG;
 	}
-	lua_call(L, 0, 0);
+	if (tmr->mode != TIMER_MODE_AUTO && tmr->self_ref != LUA_REFNIL) {
+		luaL_unref(L, LUA_REGISTRYINDEX, tmr->self_ref);
+		tmr->self_ref = LUA_NOREF;
+	}
+	lua_call(L, 1, 0);
 }
 
 // Lua: tmr.delay( us )
@@ -135,20 +145,32 @@ static int tmr_now(lua_State* L){
 	return 1; 
 }
 
-// Lua: tmr.register( id, interval, mode, function )
+static timer_t tmr_get( lua_State *L, int stack ) {
+	// Deprecated: static 0-6 timers control by index.
+	luaL_argcheck(L, (lua_isuserdata(L, stack) || lua_isnumber(L, stack)), 1, "timer object or numerical ID expected");
+	if (lua_isuserdata(L, stack)) {
+		return (timer_t)luaL_checkudata(L, stack, "tmr.timer");
+	} else {
+		uint32_t id = luaL_checkinteger(L, 1);
+		luaL_argcheck(L, platform_tmr_exists(id), 1, "invalid timer index");
+		return &alarm_timers[id];
+	}
+	return 0;
+}
+
+// Lua: tmr.register( id / ref, interval, mode, function )
 static int tmr_register(lua_State* L){
-	uint32_t id = luaL_checkinteger(L, 1);
+	timer_t tmr = tmr_get(L, 1);
+
 	uint32_t interval = luaL_checkinteger(L, 2);
 	uint8_t mode = luaL_checkinteger(L, 3);
-	//Check if provided parameters are valid
-	MOD_CHECK_ID(tmr, id);
+
 	luaL_argcheck(L, (interval > 0 && interval <= MAX_TIMEOUT), 2, MAX_TIMEOUT_ERR_STR);
-  luaL_argcheck(L, (mode == TIMER_MODE_SINGLE || mode == TIMER_MODE_SEMI || mode == TIMER_MODE_AUTO), 3, "Invalid mode");
-  luaL_argcheck(L, (lua_type(L, 4) == LUA_TFUNCTION || lua_type(L, 4) == LUA_TLIGHTFUNCTION), 4, "Must be function");
+	luaL_argcheck(L, (mode == TIMER_MODE_SINGLE || mode == TIMER_MODE_SEMI || mode == TIMER_MODE_AUTO), 3, "Invalid mode");
+	luaL_argcheck(L, (lua_type(L, 4) == LUA_TFUNCTION || lua_type(L, 4) == LUA_TLIGHTFUNCTION), 4, "Must be function");
 	//get the lua function reference
 	lua_pushvalue(L, 4);
 	sint32_t ref = luaL_ref(L, LUA_REGISTRYINDEX);
-	timer_t tmr = &alarm_timers[id];
 	if(!(tmr->mode & TIMER_IDLE_FLAG) && tmr->mode != TIMER_MODE_OFF)
 		ets_timer_disarm(&tmr->os);
 	//there was a bug in this part, the second part of the following condition was missing
@@ -157,15 +179,19 @@ static int tmr_register(lua_State* L){
 	tmr->lua_ref = ref;
 	tmr->mode = mode|TIMER_IDLE_FLAG;
 	tmr->interval = interval;
-	ets_timer_setfn(&tmr->os, alarm_timer_common, (void*)id);
+	ets_timer_setfn(&tmr->os, alarm_timer_common, tmr);
 	return 0;  
 }
 
-// Lua: tmr.start( id )
+// Lua: tmr.start( id / ref )
 static int tmr_start(lua_State* L){
-	uint8_t id = luaL_checkinteger(L, 1);
-	MOD_CHECK_ID(tmr,id);
-	timer_t tmr = &alarm_timers[id];
+	timer_t tmr = tmr_get(L, 1);
+
+	if (tmr->self_ref == LUA_NOREF) {
+		lua_pushvalue(L, 1);
+		tmr->self_ref = luaL_ref(L, LUA_REGISTRYINDEX);
+	}
+
 	//we return false if the timer is not idle
 	if(!(tmr->mode&TIMER_IDLE_FLAG)){
 		lua_pushboolean(L, 0);
@@ -177,17 +203,21 @@ static int tmr_start(lua_State* L){
 	return 1;
 }
 
-// Lua: tmr.alarm( id, interval, repeat, function )
+// Lua: tmr.alarm( id / ref, interval, repeat, function )
 static int tmr_alarm(lua_State* L){
 	tmr_register(L);
 	return tmr_start(L);
 }
 
-// Lua: tmr.stop( id )
+// Lua: tmr.stop( id / ref )
 static int tmr_stop(lua_State* L){
-	uint8_t id = luaL_checkinteger(L, 1);
-	MOD_CHECK_ID(tmr,id);
-	timer_t tmr = &alarm_timers[id];
+	timer_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;
+	}
+
 	//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;
@@ -199,11 +229,15 @@ static int tmr_stop(lua_State* L){
 	return 1;  
 }
 
-// Lua: tmr.unregister( id )
+// Lua: tmr.unregister( id / ref )
 static int tmr_unregister(lua_State* L){
-	uint8_t id = luaL_checkinteger(L, 1);
-	MOD_CHECK_ID(tmr,id);
-	timer_t tmr = &alarm_timers[id];
+	timer_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)
 		ets_timer_disarm(&tmr->os);
 	if(tmr->lua_ref != LUA_NOREF)
@@ -213,13 +247,12 @@ static int tmr_unregister(lua_State* L){
 	return 0;
 }
 
-// Lua: tmr.interval( id, interval )
+// Lua: tmr.interval( id / ref, interval )
 static int tmr_interval(lua_State* L){
-	uint8_t id = luaL_checkinteger(L, 1);
-	MOD_CHECK_ID(tmr,id);
-	timer_t tmr = &alarm_timers[id];
+	timer_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);
+	luaL_argcheck(L, (interval > 0 && interval <= MAX_TIMEOUT), 2, MAX_TIMEOUT_ERR_STR);
 	if(tmr->mode != TIMER_MODE_OFF){	
 		tmr->interval = interval;
 		if(!(tmr->mode&TIMER_IDLE_FLAG)){
@@ -230,11 +263,10 @@ static int tmr_interval(lua_State* L){
 	return 0;
 }
 
-// Lua: tmr.state( id )
+// Lua: tmr.state( id / ref )
 static int tmr_state(lua_State* L){
-	uint8_t id = luaL_checkinteger(L, 1);
-	MOD_CHECK_ID(tmr,id);
-	timer_t tmr = &alarm_timers[id];
+	timer_t tmr = tmr_get(L, 1);
+
 	if(tmr->mode == TIMER_MODE_OFF){
 		lua_pushnil(L);
 		return 1;
@@ -307,8 +339,34 @@ static int tmr_softwd( lua_State* L ){
 	return 0; 
 }
 
+// Lua: tmr.create()
+static int tmr_create( lua_State *L ) {
+	timer_t ud = (timer_t)lua_newuserdata(L, sizeof(timer_struct_t));
+	if (!ud) return luaL_error(L, "not enough memory");
+	luaL_getmetatable(L, "tmr.timer");
+	lua_setmetatable(L, -2);
+	ud->lua_ref = LUA_NOREF;
+	ud->self_ref = LUA_NOREF;
+	ud->mode = TIMER_MODE_OFF;
+	ets_timer_disarm(&ud->os);
+	return 1;
+}
+
 // Module function map
 
+static const LUA_REG_TYPE tmr_dyn_map[] = {
+	{ LSTRKEY( "register" ),    LFUNCVAL( tmr_register ) },
+	{ LSTRKEY( "alarm" ),       LFUNCVAL( tmr_alarm ) },
+	{ LSTRKEY( "start" ),       LFUNCVAL( tmr_start ) },
+	{ LSTRKEY( "stop" ),        LFUNCVAL( tmr_stop ) },
+	{ LSTRKEY( "unregister" ),  LFUNCVAL( tmr_unregister ) },
+	{ LSTRKEY( "state" ),       LFUNCVAL( tmr_state ) },
+	{ LSTRKEY( "interval" ),    LFUNCVAL( tmr_interval) },
+	{ LSTRKEY( "__gc" ),        LFUNCVAL( tmr_unregister ) },
+	{ LSTRKEY( "__index" ),     LROVAL( tmr_dyn_map ) },
+	{ LNILKEY, LNILVAL }
+};
+
 static const LUA_REG_TYPE tmr_map[] = {
 	{ LSTRKEY( "delay" ),        LFUNCVAL( tmr_delay ) },
 	{ LSTRKEY( "now" ),          LFUNCVAL( tmr_now ) },
@@ -321,7 +379,8 @@ static const LUA_REG_TYPE tmr_map[] = {
 	{ LSTRKEY( "stop" ),         LFUNCVAL( tmr_stop ) },
 	{ LSTRKEY( "unregister" ),   LFUNCVAL( tmr_unregister ) },
 	{ LSTRKEY( "state" ),        LFUNCVAL( tmr_state ) },
-	{ LSTRKEY( "interval" ),     LFUNCVAL( tmr_interval) }, 
+	{ LSTRKEY( "interval" ),     LFUNCVAL( tmr_interval ) },
+	{ LSTRKEY( "create" ),       LFUNCVAL( tmr_create ) },
 	{ LSTRKEY( "ALARM_SINGLE" ), LNUMVAL( TIMER_MODE_SINGLE ) },
 	{ LSTRKEY( "ALARM_SEMI" ),   LNUMVAL( TIMER_MODE_SEMI ) },
 	{ LSTRKEY( "ALARM_AUTO" ),   LNUMVAL( TIMER_MODE_AUTO ) },
@@ -330,8 +389,12 @@ static const LUA_REG_TYPE tmr_map[] = {
 
 int luaopen_tmr( lua_State *L ){
 	int i;	
+
+	luaL_rometatable(L, "tmr.timer", (void *)tmr_dyn_map);
+
 	for(i=0; i<NUM_TMR; i++){
 		alarm_timers[i].lua_ref = LUA_NOREF;
+		alarm_timers[i].self_ref = LUA_REFNIL;
 		alarm_timers[i].mode = TIMER_MODE_OFF;
 		ets_timer_disarm(&alarm_timers[i].os);
 	}
@@ -341,7 +404,7 @@ int luaopen_tmr( lua_State *L ){
 	ets_timer_disarm(&rtc_timer);
 	ets_timer_setfn(&rtc_timer, rtc_callback, NULL);
 	ets_timer_arm_new(&rtc_timer, 1000, 1, 1);
-  return 0;
+	return 0;
 }
 
 NODEMCU_MODULE(TMR, "tmr", tmr_map, luaopen_tmr);

+ 57 - 15
docs/en/modules/tmr.md

@@ -9,7 +9,11 @@ It is aimed at setting up regularly occurring tasks, timing out operations, and
 
 What the tmr module is *not* however, is a time keeping module. While most timeouts are expressed in milliseconds or even microseconds, the accuracy is limited and compounding errors would lead to rather inaccurate time keeping. Consider using the [rtctime](rtctime.md) module for "wall clock" time.
 
-NodeMCU provides 7 timers, numbered 0-6. It is currently up to the user to keep track of which timers are used for what.
+NodeMCU provides 7 static timers, numbered 0-6, and dynamic timer creation function [`tmr.create()`](#tmrcreate).
+
+!!! attention
+
+    Static timers are deprecated and will be removed later.
 
 ## tmr.alarm()
 
@@ -18,7 +22,7 @@ This is a convenience function combining [`tmr.register()`](#tmrregister) and [`
 To free up the resources with this timer when done using it, call [`tmr.unregister()`](#tmrunregister) on it. For one-shot timers this is not necessary, unless they were stopped before they expired.
 
 #### Parameters
-- `id` timer id (0-6)
+- `id`/`ref` timer id (0-6) or object
 - `interval_ms` timer interval in milliseconds. Maximum value is 6870947 (1:54:30.947).
 - `mode` timer mode:
 	- `tmr.ALARM_SINGLE` a one-shot alarm (and no need to call [`tmr.unregister()`](#tmrunregister))
@@ -33,10 +37,46 @@ To free up the resources with this timer when done using it, call [`tmr.unregist
 if not tmr.alarm(0, 5000, tmr.ALARM_SINGLE, function() print("hey there") end) then print("whoopsie") end
 ```
 #### See also
+- [`tmr.create()`](#tmrcreate)
 - [`tmr.register()`](#tmrregister)
 - [`tmr.start()`](#tmrstart)
 - [`tmr.unregister()`](#tmrunregister)
 
+## tmr.create()
+
+Creates a dynamic timer object.
+
+Dynamic timer can be used instead of numeric ID in control functions. Also can be controlled in object-oriented way.
+
+Functions supported in timer object:
+
+- [`t:alarm()`](#tmralarm)
+- [`t:register()`](#tmrregister)
+- [`t:start()`](#tmrstart)
+- [`t:stop()`](#tmrstop)
+- [`t:unregister()`](#tmrunregister)
+- [`t:state()`](#tmrstate)
+- [`t:interval()`](#tmrinterval)
+
+#### Parameters
+none
+
+#### Returns
+`timer` object
+
+#### Example
+```lua
+local mytimer = tmr.create()
+
+-- oo calling
+mytimer:register(5000, tmr.ALARM_SINGLE, function (t) print("expired"); t:unregister() end)
+mytimer:start()
+
+-- with self parameter
+tmr.register(mytimer, 5000, tmr.ALARM_SINGLE, function (t) print("expired"); tmr.unregister(t) end)
+tmr.start(mytimer)
+```
+
 ## tmr.delay()
 
 Busyloops the processor for a specified number of microseconds.
@@ -64,10 +104,10 @@ tmr.delay(100)
 Changes a registered timer's expiry interval.
 
 #### Syntax
-`tmr.interval(id, interval_ms)`
+`tmr.interval(id/ref, interval_ms)`
 
 #### Parameters
-- `id` timer id (0-6)
+- `id`/`ref` timer id (0-6) or object
 - `interval_ms` new timer interval in milliseconds. Maximum value is 6870947 (1:54:30.947).
 
 #### Returns
@@ -105,10 +145,10 @@ Configures a timer and registers the callback function to call on expiry.
 To free up the resources with this timer when done using it, call [`tmr.unregister()`](#tmrunregister) on it. For one-shot timers this is not necessary, unless they were stopped before they expired.
 
 #### Syntax
-`tmr.register(id, interval_ms, mode, func)`
+`tmr.register(id/ref, interval_ms, mode, func)`
 
 #### Parameters
-- `id` timer id (0-6)
+- `id`/`ref` timer id (0-6) or object
 - `interval_ms` timer interval in milliseconds. Maximum value is 6870947 (1:54:30.947).
 - `mode` timer mode:
 	- `tmr.ALARM_SINGLE` a one-shot alarm (and no need to call [`tmr.unregister()`](#tmrunregister))
@@ -126,7 +166,8 @@ tmr.register(0, 5000, tmr.ALARM_SINGLE, function() print("hey there") end)
 tmr.start(0)
 ```
 #### See also
-[`tmr.alarm()`](#tmralarm)
+- [`tmr.create()`](#tmrcreate)
+- [`tmr.alarm()`](#tmralarm)
 
 ## tmr.softwd()
 
@@ -158,10 +199,10 @@ complex_stuff_which_might_never_call_the_callback(on_success_callback)
 Starts or restarts a previously configured timer.
 
 #### Syntax
-`tmr.start(id)`
+`tmr.start(id/ref)`
 
 #### Parameters
-`id` timer id (0-6)
+`id`/`ref` timer id (0-6) or object
 
 #### Returns
 `true` if the timer was started, `false` on error
@@ -172,6 +213,7 @@ tmr.register(0, 5000, tmr.ALARM_SINGLE, function() print("hey there") end)
 if not tmr.start(0) then print("uh oh") end
 ```
 #### See also
+- [`tmr.create()`](#tmrcreate)
 - [`tmr.register()`](#tmrregister)
 - [`tmr.stop()`](#tmrstop)
 - [`tmr.unregister()`](#tmrunregister)
@@ -181,10 +223,10 @@ if not tmr.start(0) then print("uh oh") end
 Checks the state of a timer.
 
 #### Syntax
-`tmr.state(id)`
+`tmr.state(id/ref)`
 
 #### Parameters
-`id` timer id (0-6)
+`id`/`ref` timer id (0-6) or object
 
 #### Returns
 (bool, int) or `nil`
@@ -201,10 +243,10 @@ running, mode = tmr.state(0)
 Stops a running timer, but does *not* unregister it. A stopped timer can be restarted with [`tmr.start()`](#tmrstart).
 
 #### Syntax
-`tmr.stop(id)`
+`tmr.stop(id/ref)`
 
 #### Parameters
-`id` timer id (0-6)
+`id`/`ref` timer id (0-6) or object
 
 #### Returns
 `true` if the timer was stopped, `false` on error
@@ -243,10 +285,10 @@ Stops the timer (if running) and unregisters the associated callback.
 This isn't necessary for one-shot timers (`tmr.ALARM_SINGLE`), as those automatically unregister themselves when fired.
 
 #### Syntax
-`tmr.unregister(id)`
+`tmr.unregister(id/ref)`
 
 #### Parameters
-`id` timer id (0-6)
+`id`/`ref` timer id (0-6) or object
 
 #### Returns
 `nil`