Browse Source

Add wifi.suspend() and node.sleep() (#1231)

* Exposed forced sleep API and more
Added timer suspend functionality
* wifi.suspend
* wifi.resume
* node.sleep 
* tmr.suspend
* tmr.suspend_all
* tmr.resume
* tmr.resume_all
* Implement timer suspend functionality
* Fix for uart TX glitch
* Made some modifications to the error reporting
dnc40085 7 years ago
parent
commit
41a5736d78

+ 9 - 1
app/Makefile

@@ -45,7 +45,11 @@ SUBDIRS= 					\
 	http					\
 	http					\
 	fatfs					\
 	fatfs					\
 	esp-gdbstub				\
 	esp-gdbstub				\
-	websocket
+	websocket				\
+	swTimer					\
+	misc					\
+	pm						\
+	
 
 
 endif # } PDIR
 endif # } PDIR
 
 
@@ -90,11 +94,15 @@ COMPONENTS_eagle.app.v6 = 			\
 	dhtlib/libdhtlib.a 			\
 	dhtlib/libdhtlib.a 			\
 	tsl2561/tsl2561lib.a			\
 	tsl2561/tsl2561lib.a			\
 	http/libhttp.a				\
 	http/libhttp.a				\
+	pm/libpm.a					\
 	websocket/libwebsocket.a		\
 	websocket/libwebsocket.a		\
 	esp-gdbstub/libgdbstub.a		\
 	esp-gdbstub/libgdbstub.a		\
 	net/libnodemcu_net.a			\
 	net/libnodemcu_net.a			\
 	mbedtls/libmbedtls.a      \
 	mbedtls/libmbedtls.a      \
 	modules/libmodules.a			\
 	modules/libmodules.a			\
+	swTimer/libswtimer.a			\
+	misc/libmisc.a					\
+	
 
 
 # Inspect the modules library and work out which modules need to be linked.
 # Inspect the modules library and work out which modules need to be linked.
 # For each enabled module, a symbol name of the form XYZ_module_selected is
 # For each enabled module, a symbol name of the form XYZ_module_selected is

+ 40 - 0
app/include/misc/dynarr.h

@@ -0,0 +1,40 @@
+#ifndef __DYNARR_H__
+#define __DYNARR_H__
+#include "user_interface.h"
+#include "c_stdio.h"
+#include "c_stdlib.h"
+
+//#define DYNARR_DEBUG
+//#define DYNARR_ERROR
+
+
+typedef struct _dynarr{
+  void* data_ptr;
+  size_t used;
+  size_t array_size;
+  size_t data_size;
+} dynarr_t;
+
+bool dynarr_init(dynarr_t* array_ptr, size_t array_size, size_t data_size);
+bool dynarr_resize(dynarr_t* array_ptr, size_t elements_to_add);
+bool dynarr_remove(dynarr_t* array_ptr, void* element_ptr);
+bool dynarr_add(dynarr_t* array_ptr, void* data_ptr, size_t data_size);
+bool dynarr_boundaryCheck(dynarr_t* array_ptr, void* element_ptr);
+bool dynarr_free(dynarr_t* array_ptr);
+
+
+#if 0 || defined(DYNARR_DEBUG) || defined(NODE_DEBUG)
+#define DYNARR_DBG(fmt, ...) c_printf("\n DYNARR_DBG(%s): "fmt"\n", __FUNCTION__, ##__VA_ARGS__)
+#else
+#define DYNARR_DBG(...)
+
+#endif
+
+#if 0 || defined(DYNARR_ERROR) || defined(NODE_ERROR)
+#define DYNARR_ERR(fmt, ...) c_printf("\n DYNARR: "fmt"\n", ##__VA_ARGS__)
+#else
+#define DYNARR_ERR(...)
+
+#endif
+
+#endif // __DYNARR_H__

+ 53 - 0
app/include/swTimer/swTimer.h

@@ -0,0 +1,53 @@
+#ifndef __SW_TIMER_H__
+#define __SW_TIMER_H__
+#include "user_interface.h"
+//#define SWTMR_DEBUG
+#define USE_SWTMR_ERROR_STRINGS
+
+#if defined(DEVELOP_VERSION)
+#define SWTMR_DEBUG
+#endif
+
+#if defined(SWTMR_DEBUG)
+  #define SWTMR_DBG(fmt, ...) dbg_printf("\tSWTIMER(%s):"fmt"\n", __FUNCTION__, ##__VA_ARGS__)
+#else
+  #define SWTMR_DBG(...)
+#endif
+
+#if defined(NODE_ERROR)
+  #define SWTMR_ERR(fmt, ...) NODE_ERR("%s"fmt"\n", "SWTIMER:", ##__VA_ARGS__)
+#else
+  #define SWTMR_ERR(...)
+#endif
+
+enum SWTMR_STATUS{
+  SWTMR_FAIL = 0,
+  SWTMR_OK = 1,
+
+  SWTMR_MALLOC_FAIL = 10,
+  SWTMR_TIMER_NOT_ARMED,
+//  SWTMR_NULL_PTR,
+
+  SWTMR_REGISTRY_NO_REGISTERED_TIMERS,
+
+//  SWTMR_SUSPEND_ARRAY_INITIALIZATION_FAILED,
+//  SWTMR_SUSPEND_ARRAY_ADD_FAILED,
+//  SWTMR_SUSPEND_ARRAY_REMOVE_FAILED,
+  SWTMR_SUSPEND_TIMER_ALREADY_SUSPENDED,
+  SWTMR_SUSPEND_TIMER_ALREADY_REARMED,
+  SWTMR_SUSPEND_NO_SUSPENDED_TIMERS,
+  SWTMR_SUSPEND_TIMER_NOT_SUSPENDED,
+
+};
+
+/*      Global Function Declarations      */
+void swtmr_register(void* timer_ptr);
+void swtmr_unregister(void* timer_ptr);
+int swtmr_suspend(os_timer_t* timer_ptr);
+int swtmr_resume(os_timer_t* timer_ptr);
+void swtmr_print_registry(void);
+void swtmr_print_suspended(void);
+void swtmr_print_timer_list(void);
+const char* swtmr_errorcode2str(int error_value);
+bool swtmr_suspended_test(os_timer_t* timer_ptr);
+#endif // __SW_TIMER_H__

+ 4 - 0
app/include/user_config.h

@@ -116,6 +116,10 @@ extern void luaL_assertfail(const char *file, int line, const char *message);
 #define WIFI_SDK_EVENT_MONITOR_ENABLE
 #define WIFI_SDK_EVENT_MONITOR_ENABLE
 #define WIFI_EVENT_MONITOR_DISCONNECT_REASON_LIST_ENABLE
 #define WIFI_EVENT_MONITOR_DISCONNECT_REASON_LIST_ENABLE
 
 
+#define ENABLE_TIMER_SUSPEND
+#define PMSLEEP_ENABLE
+
+
 #define STRBUF_DEFAULT_INCREMENT 32
 #define STRBUF_DEFAULT_INCREMENT 32
 
 
 #endif	/* __USER_CONFIG_H__ */
 #endif	/* __USER_CONFIG_H__ */

+ 52 - 0
app/misc/Makefile

@@ -0,0 +1,52 @@
+
+#############################################################
+# Required variables for each makefile
+# Discard this section from all parent makefiles
+# Expected variables (with automatic defaults):
+#   CSRCS (all "C" files in the dir)
+#   SUBDIRS (all subdirs with a Makefile)
+#   GEN_LIBS - list of libs to be generated ()
+#   GEN_IMAGES - list of images to be generated ()
+#   COMPONENTS_xxx - a list of libs/objs in the form
+#     subdir/lib to be extracted and rolled up into
+#     a generated lib/image xxx.a ()
+#
+ifndef PDIR
+GEN_LIBS = libmisc.a
+endif
+
+STD_CFLAGS=-std=gnu11 -Wimplicit
+
+#############################################################
+# Configuration i.e. compile options etc.
+# Target specific stuff (defines etc.) goes in here!
+# Generally values applying to a tree are captured in the
+#   makefile at its root level - these are then overridden
+#   for a subtree within the makefile rooted therein
+#
+#DEFINES += 
+
+#############################################################
+# Recursion Magic - Don't touch this!!
+#
+# Each subtree potentially has an include directory
+#   corresponding to the common APIs applicable to modules
+#   rooted at that subtree. Accordingly, the INCLUDE PATH
+#   of a module can only contain the include directories up
+#   its parent path, and not its siblings
+#
+# Required for each makefile to inherit from the parent
+#
+
+INCLUDES := $(INCLUDES) -I $(PDIR)include
+INCLUDES += -I ./
+INCLUDES += -I ./include
+INCLUDES += -I ../include
+INCLUDES += -I ../../include
+INCLUDES += -I ../lua
+INCLUDES += -I ../platform
+INCLUDES += -I ../libc
+
+PDIR := ../$(PDIR)
+sinclude $(PDIR)Makefile
+

+ 131 - 0
app/misc/dyn_arr.c

@@ -0,0 +1,131 @@
+#include "misc/dynarr.h"
+
+#define ARRAY_PTR_CHECK if(array_ptr == NULL || array_ptr->data_ptr == NULL){\
+    /**/DYNARR_DBG("array not initialized");\
+    return false; \
+  }
+
+bool dynarr_init(dynarr_t* array_ptr, size_t array_size, size_t data_size){
+  if(array_ptr == NULL || data_size == 0 || array_size == 0){
+    /**/DYNARR_DBG("Invalid parameter: array_ptr(%p) data_size(%u) array_size(%u)", array_ptr, data_size, array_size);
+    return false;
+  }
+  if(array_ptr->data_ptr != NULL ){
+    /**/DYNARR_DBG("Array already initialized: array_ptr->data_ptr=%p", array_ptr->data_ptr);
+    return false;
+  }
+  /**/DYNARR_DBG("Array parameters:\n\t\t\tarray_size(%u)\n\t\t\tdata_size(%u)\n\t\t\ttotal size(bytes):%u", array_size, data_size, (array_size * data_size));
+
+  void* temp_array = c_zalloc(array_size * data_size);
+  if(temp_array == NULL){
+    /**/DYNARR_ERR("malloc FAIL! req:%u free:%u", (array_size * data_size), system_get_free_heap_size());
+    return false;
+  }
+
+  array_ptr->data_ptr = temp_array;
+  array_ptr->array_size = array_size;
+  array_ptr->data_size = data_size;
+  array_ptr->used = 0;
+
+  return true;
+}
+
+bool dynarr_resize(dynarr_t* array_ptr, size_t elements_to_add){
+  ARRAY_PTR_CHECK;
+
+  if(elements_to_add <= 0){
+    /**/DYNARR_DBG("Invalid qty: elements_to_add=%u", elements_to_add);
+    return false;
+  }
+
+  size_t new_array_size = array_ptr->array_size + elements_to_add;
+
+  /**/DYNARR_DBG("old size=%u\tnew size=%u\tmem used=%u",
+      array_ptr->array_size, new_array_size, (new_array_size * array_ptr->data_size));
+
+  void* temp_array_p = c_realloc(array_ptr->data_ptr, new_array_size * array_ptr->data_size);
+  if(temp_array_p == NULL){
+    /**/DYNARR_ERR("malloc FAIL! req:%u free:%u", (new_array_size * array_ptr->data_size), system_get_free_heap_size());
+    return false;
+  }
+
+  array_ptr->data_ptr = temp_array_p;
+
+  size_t prev_size = array_ptr->array_size;
+
+  array_ptr->array_size = new_array_size;
+
+  //set memory to 0 for newly added array elements
+  memset((uint8*) array_ptr->data_ptr + (prev_size * array_ptr->data_size), 0, (elements_to_add * array_ptr->data_size));
+
+  /**/DYNARR_DBG("Array successfully resized");
+  return true;
+}
+
+bool dynarr_remove(dynarr_t* array_ptr, void* element_to_remove){
+  ARRAY_PTR_CHECK;
+
+  uint8* element_ptr = element_to_remove;
+  uint8* data_ptr = array_ptr->data_ptr;
+
+  if(dynarr_boundaryCheck(array_ptr, element_to_remove) == FALSE){
+    return false;
+  }
+
+  //overwrite element to be removed by shifting all elements to the left
+  memmove(element_ptr, element_ptr + array_ptr->data_size,  (array_ptr->array_size - 1) * array_ptr->data_size - (element_ptr - data_ptr));
+
+  //clear newly freed element
+  memset(data_ptr + ((array_ptr->array_size-1) * array_ptr->data_size), 0, array_ptr->data_size);
+
+  //decrement array used since we removed an element
+  array_ptr->used--;
+  /**/DYNARR_DBG("element(%p) removed from array", element_ptr);
+  return true;
+}
+
+bool dynarr_add(dynarr_t* array_ptr, void* data_ptr, size_t data_size){
+  ARRAY_PTR_CHECK;
+
+  if(data_size != array_ptr->data_size){
+    /**/DYNARR_DBG("Invalid data size: data_size(%u) != arr->data_size(%u)", data_size, array_ptr->data_size);
+    return false;
+  }
+
+  if(array_ptr->array_size == array_ptr->used){
+    if(!dynarr_resize(array_ptr, (array_ptr->array_size/2))){
+      return false;
+    }
+  }
+  memcpy(((uint8*)array_ptr->data_ptr + (array_ptr->used * array_ptr->data_size)), data_ptr, array_ptr->data_size);
+
+  array_ptr->used++;
+  return true;
+}
+
+bool dynarr_boundaryCheck(dynarr_t* array_ptr, void* element_to_check){
+  ARRAY_PTR_CHECK;
+
+  uint8* data_ptr = array_ptr->data_ptr;
+  uint8* element_ptr = element_to_check;
+
+  if(element_ptr < data_ptr || ((element_ptr - data_ptr) / array_ptr->data_size) > array_ptr->array_size - 1){
+    /**/DYNARR_DBG("element_ptr(%p) out of bounds: first element ptr:%p last element ptr:%p",
+        element_ptr, data_ptr, data_ptr + ((array_ptr->array_size - 1) * array_ptr->data_size));
+    return false;
+  }
+  return true;
+}
+
+bool dynarr_free(dynarr_t* array_ptr){
+  ARRAY_PTR_CHECK;
+
+  c_free(array_ptr->data_ptr);
+
+  array_ptr->data_ptr=NULL;
+  array_ptr->array_size = array_ptr->used = 0;
+
+  /**/DYNARR_DBG("array freed");
+  return true;
+}
+

+ 1 - 0
app/modules/Makefile

@@ -55,6 +55,7 @@ INCLUDES += -I ../fatfs
 INCLUDES += -I ../http
 INCLUDES += -I ../http
 INCLUDES += -I ../sjson
 INCLUDES += -I ../sjson
 INCLUDES += -I ../websocket
 INCLUDES += -I ../websocket
+INCLUDES += -I ../pm
 PDIR := ../$(PDIR)
 PDIR := ../$(PDIR)
 sinclude $(PDIR)Makefile
 sinclude $(PDIR)Makefile
 
 

+ 34 - 13
app/modules/node.c

@@ -74,19 +74,36 @@ static int node_deepsleep( lua_State* L )
   return 0;
   return 0;
 }
 }
 
 
-// Lua: dsleep_set_options
-// Combined to dsleep( us, option )
-// static int node_deepsleep_setoption( lua_State* L )
-// {
-//   s32 option;
-//   option = luaL_checkinteger( L, 1 );
-//   if ( option < 0 || option > 4)
-//     return luaL_error( L, "wrong arg range" );
-//   else
-//    deep_sleep_set_option( option );
-//   return 0;
-// }
-// Lua: info()
+
+#ifdef PMSLEEP_ENABLE
+#include "pmSleep.h"
+
+int node_sleep_resume_cb_ref= LUA_NOREF;
+void node_sleep_resume_cb(void)
+{
+  PMSLEEP_DBG("START");
+  pmSleep_execute_lua_cb(&node_sleep_resume_cb_ref);
+  PMSLEEP_DBG("END");
+}
+
+// Lua: node.sleep(table)
+static int node_sleep( lua_State* L )
+{
+  pmSleep_INIT_CFG(cfg);
+  cfg.sleep_mode=LIGHT_SLEEP_T;
+
+  if(lua_istable(L, 1)){
+    pmSleep_parse_table_lua(L, 1, &cfg, NULL, &node_sleep_resume_cb_ref);
+  }
+  else{
+    return luaL_argerror(L, 1, "must be table");
+  }
+
+  cfg.resume_cb_ptr = &node_sleep_resume_cb;
+  pmSleep_suspend(&cfg);
+  return 0;
+}
+#endif //PMSLEEP_ENABLE
 
 
 static int node_info( lua_State* L )
 static int node_info( lua_State* L )
 {
 {
@@ -570,6 +587,10 @@ static const LUA_REG_TYPE node_map[] =
 {
 {
   { LSTRKEY( "restart" ), LFUNCVAL( node_restart ) },
   { LSTRKEY( "restart" ), LFUNCVAL( node_restart ) },
   { LSTRKEY( "dsleep" ), LFUNCVAL( node_deepsleep ) },
   { LSTRKEY( "dsleep" ), LFUNCVAL( node_deepsleep ) },
+#ifdef PMSLEEP_ENABLE
+  { LSTRKEY( "sleep" ), LFUNCVAL( node_sleep ) },
+  PMSLEEP_INT_MAP,
+#endif
   { LSTRKEY( "info" ), LFUNCVAL( node_info ) },
   { LSTRKEY( "info" ), LFUNCVAL( node_info ) },
   { LSTRKEY( "chipid" ), LFUNCVAL( node_chipid ) },
   { LSTRKEY( "chipid" ), LFUNCVAL( node_chipid ) },
   { LSTRKEY( "flashid" ), LFUNCVAL( node_flashid ) },
   { LSTRKEY( "flashid" ), LFUNCVAL( node_flashid ) },

+ 131 - 15
app/modules/tmr.c

@@ -53,12 +53,14 @@ tmr.softwd(int)
 #include "platform.h"
 #include "platform.h"
 #include "c_types.h"
 #include "c_types.h"
 #include "user_interface.h"
 #include "user_interface.h"
+#include "swTimer/swTimer.h"
 
 
 #define TIMER_MODE_OFF 3
 #define TIMER_MODE_OFF 3
 #define TIMER_MODE_SINGLE 0
 #define TIMER_MODE_SINGLE 0
 #define TIMER_MODE_SEMI 2
 #define TIMER_MODE_SEMI 2
 #define TIMER_MODE_AUTO 1
 #define TIMER_MODE_AUTO 1
-#define TIMER_IDLE_FLAG (1<<7) 
+#define TIMER_IDLE_FLAG (1<<7)
+
 
 
 #define STRINGIFY_VAL(x) #x
 #define STRINGIFY_VAL(x) #x
 #define STRINGIFY(x) STRINGIFY_VAL(x)
 #define STRINGIFY(x) STRINGIFY_VAL(x)
@@ -172,14 +174,14 @@ static int tmr_register(lua_State* L){
 	lua_pushvalue(L, 4);
 	lua_pushvalue(L, 4);
 	sint32_t ref = luaL_ref(L, LUA_REGISTRYINDEX);
 	sint32_t ref = luaL_ref(L, LUA_REGISTRYINDEX);
 	if(!(tmr->mode & TIMER_IDLE_FLAG) && tmr->mode != TIMER_MODE_OFF)
 	if(!(tmr->mode & TIMER_IDLE_FLAG) && tmr->mode != TIMER_MODE_OFF)
-		ets_timer_disarm(&tmr->os);
+		os_timer_disarm(&tmr->os);
 	//there was a bug in this part, the second part of the following condition was missing
 	//there was a bug in this part, the second part of the following condition was missing
 	if(tmr->lua_ref != LUA_NOREF && tmr->lua_ref != ref)
 	if(tmr->lua_ref != LUA_NOREF && tmr->lua_ref != ref)
 		luaL_unref(L, LUA_REGISTRYINDEX, tmr->lua_ref);
 		luaL_unref(L, LUA_REGISTRYINDEX, tmr->lua_ref);
 	tmr->lua_ref = ref;
 	tmr->lua_ref = ref;
 	tmr->mode = mode|TIMER_IDLE_FLAG;
 	tmr->mode = mode|TIMER_IDLE_FLAG;
 	tmr->interval = interval;
 	tmr->interval = interval;
-	ets_timer_setfn(&tmr->os, alarm_timer_common, tmr);
+	os_timer_setfn(&tmr->os, alarm_timer_common, tmr);
 	return 0;  
 	return 0;  
 }
 }
 
 
@@ -197,7 +199,7 @@ static int tmr_start(lua_State* L){
 		lua_pushboolean(L, 0);
 		lua_pushboolean(L, 0);
 	}else{
 	}else{
 		tmr->mode &= ~TIMER_IDLE_FLAG;
 		tmr->mode &= ~TIMER_IDLE_FLAG;
-		ets_timer_arm_new(&tmr->os, tmr->interval, tmr->mode==TIMER_MODE_AUTO, 1);
+		os_timer_arm(&tmr->os, tmr->interval, tmr->mode==TIMER_MODE_AUTO);
 		lua_pushboolean(L, 1);
 		lua_pushboolean(L, 1);
 	}
 	}
 	return 1;
 	return 1;
@@ -221,7 +223,7 @@ static int tmr_stop(lua_State* L){
 	//we return false if the timer is idle (of not registered)
 	//we return false if the timer is idle (of not registered)
 	if(!(tmr->mode & TIMER_IDLE_FLAG) && tmr->mode != TIMER_MODE_OFF){
 	if(!(tmr->mode & TIMER_IDLE_FLAG) && tmr->mode != TIMER_MODE_OFF){
 		tmr->mode |= TIMER_IDLE_FLAG;
 		tmr->mode |= TIMER_IDLE_FLAG;
-		ets_timer_disarm(&tmr->os);
+		os_timer_disarm(&tmr->os);
 		lua_pushboolean(L, 1);
 		lua_pushboolean(L, 1);
 	}else{
 	}else{
 		lua_pushboolean(L, 0);
 		lua_pushboolean(L, 0);
@@ -229,6 +231,73 @@ static int tmr_stop(lua_State* L){
 	return 1;  
 	return 1;  
 }
 }
 
 
+#ifdef ENABLE_TIMER_SUSPEND
+
+static int tmr_suspend(lua_State* L){
+  timer_t tmr = tmr_get(L, 1);
+
+  if((tmr->mode & TIMER_IDLE_FLAG) == 1){
+    return luaL_error(L, "timer not armed");
+  }
+
+  int retval = swtmr_suspend(&tmr->os);
+
+  if(retval != SWTMR_OK){
+    return luaL_error(L, swtmr_errorcode2str(retval));
+  }
+  else{
+    lua_pushboolean(L, true);
+  }
+
+  return 1;
+}
+
+static int tmr_resume(lua_State* L){
+  timer_t tmr = tmr_get(L, 1);
+
+  if(swtmr_suspended_test(&tmr->os) == FALSE){
+    return luaL_error(L, "timer not suspended");
+  }
+
+  int retval = swtmr_resume(&tmr->os);
+
+  if(retval != SWTMR_OK){
+    return luaL_error(L, swtmr_errorcode2str(retval));
+  }
+  else{
+  lua_pushboolean(L, true);
+  }
+  return 1;
+}
+
+static int tmr_suspend_all (lua_State *L)
+{
+  sint32 retval = swtmr_suspend(NULL);
+  //  lua_pushnumber(L, swtmr_suspend(NULL));
+  if(retval!=SWTMR_OK){
+    return luaL_error(L, swtmr_errorcode2str(retval));
+  }
+  else{
+    lua_pushboolean(L, true);
+  }
+  return 1;
+}
+
+static int tmr_resume_all (lua_State *L)
+{
+  sint32 retval = swtmr_resume(NULL);
+  if(retval!=SWTMR_OK){
+    return luaL_error(L, swtmr_errorcode2str(retval));
+  }
+  else{
+    lua_pushboolean(L, true);
+  }
+  return 1;
+}
+
+
+#endif
+
 // Lua: tmr.unregister( id / ref )
 // Lua: tmr.unregister( id / ref )
 static int tmr_unregister(lua_State* L){
 static int tmr_unregister(lua_State* L){
 	timer_t tmr = tmr_get(L, 1);
 	timer_t tmr = tmr_get(L, 1);
@@ -239,7 +308,7 @@ static int tmr_unregister(lua_State* L){
 	}
 	}
 
 
 	if(!(tmr->mode & TIMER_IDLE_FLAG) && tmr->mode != TIMER_MODE_OFF)
 	if(!(tmr->mode & TIMER_IDLE_FLAG) && tmr->mode != TIMER_MODE_OFF)
-		ets_timer_disarm(&tmr->os);
+		os_timer_disarm(&tmr->os);
 	if(tmr->lua_ref != LUA_NOREF)
 	if(tmr->lua_ref != LUA_NOREF)
 		luaL_unref(L, LUA_REGISTRYINDEX, tmr->lua_ref);
 		luaL_unref(L, LUA_REGISTRYINDEX, tmr->lua_ref);
 	tmr->lua_ref = LUA_NOREF;
 	tmr->lua_ref = LUA_NOREF;
@@ -256,8 +325,8 @@ static int tmr_interval(lua_State* L){
 	if(tmr->mode != TIMER_MODE_OFF){	
 	if(tmr->mode != TIMER_MODE_OFF){	
 		tmr->interval = interval;
 		tmr->interval = interval;
 		if(!(tmr->mode&TIMER_IDLE_FLAG)){
 		if(!(tmr->mode&TIMER_IDLE_FLAG)){
-			ets_timer_disarm(&tmr->os);
-			ets_timer_arm_new(&tmr->os, tmr->interval, tmr->mode==TIMER_MODE_AUTO, 1);
+			os_timer_disarm(&tmr->os);
+			os_timer_arm(&tmr->os, tmr->interval, tmr->mode==TIMER_MODE_AUTO);
 		}
 		}
 	}
 	}
 	return 0;
 	return 0;
@@ -271,9 +340,15 @@ static int tmr_state(lua_State* L){
 		lua_pushnil(L);
 		lua_pushnil(L);
 		return 1;
 		return 1;
 	}
 	}
-	lua_pushboolean(L, (tmr->mode&TIMER_IDLE_FLAG)==0);
-	lua_pushinteger(L, tmr->mode&(~TIMER_IDLE_FLAG));
-	return 2;
+
+  lua_pushboolean(L, (tmr->mode & TIMER_IDLE_FLAG) == 0);
+  lua_pushinteger(L, tmr->mode & (~TIMER_IDLE_FLAG));
+#ifdef ENABLE_TIMER_SUSPEND
+  lua_pushboolean(L, swtmr_suspended_test(&tmr->os));
+#else
+  lua_pushnil(L);
+#endif
+	return 3;
 }
 }
 
 
 /*I left the led comments 'couse I don't know
 /*I left the led comments 'couse I don't know
@@ -348,10 +423,27 @@ static int tmr_create( lua_State *L ) {
 	ud->lua_ref = LUA_NOREF;
 	ud->lua_ref = LUA_NOREF;
 	ud->self_ref = LUA_NOREF;
 	ud->self_ref = LUA_NOREF;
 	ud->mode = TIMER_MODE_OFF;
 	ud->mode = TIMER_MODE_OFF;
-	ets_timer_disarm(&ud->os);
+	os_timer_disarm(&ud->os);
 	return 1;
 	return 1;
 }
 }
 
 
+
+#if defined(SWTMR_DEBUG)
+static void tmr_printRegistry(lua_State* L){
+  swtmr_print_registry();
+}
+
+static void tmr_printSuspended(lua_State* L){
+  swtmr_print_suspended();
+}
+
+static void tmr_printTimerlist(lua_State* L){
+  swtmr_print_timer_list();
+}
+
+
+#endif
+
 // Module function map
 // Module function map
 
 
 static const LUA_REG_TYPE tmr_dyn_map[] = {
 static const LUA_REG_TYPE tmr_dyn_map[] = {
@@ -362,11 +454,24 @@ static const LUA_REG_TYPE tmr_dyn_map[] = {
 	{ LSTRKEY( "unregister" ),  LFUNCVAL( tmr_unregister ) },
 	{ LSTRKEY( "unregister" ),  LFUNCVAL( tmr_unregister ) },
 	{ LSTRKEY( "state" ),       LFUNCVAL( tmr_state ) },
 	{ LSTRKEY( "state" ),       LFUNCVAL( tmr_state ) },
 	{ LSTRKEY( "interval" ),    LFUNCVAL( tmr_interval) },
 	{ LSTRKEY( "interval" ),    LFUNCVAL( tmr_interval) },
+#ifdef ENABLE_TIMER_SUSPEND
+	{ LSTRKEY( "suspend" ),      LFUNCVAL( tmr_suspend ) },
+  { LSTRKEY( "resume" ),       LFUNCVAL( tmr_resume ) },
+#endif
 	{ LSTRKEY( "__gc" ),        LFUNCVAL( tmr_unregister ) },
 	{ LSTRKEY( "__gc" ),        LFUNCVAL( tmr_unregister ) },
 	{ LSTRKEY( "__index" ),     LROVAL( tmr_dyn_map ) },
 	{ LSTRKEY( "__index" ),     LROVAL( tmr_dyn_map ) },
 	{ LNILKEY, LNILVAL }
 	{ LNILKEY, LNILVAL }
 };
 };
 
 
+#if defined(SWTMR_DEBUG)
+static const LUA_REG_TYPE tmr_dbg_map[] = {
+    { LSTRKEY( "printRegistry" ),        LFUNCVAL( tmr_printRegistry ) },
+    { LSTRKEY( "printSuspended" ),        LFUNCVAL( tmr_printSuspended ) },
+    { LSTRKEY( "printTimerlist" ),        LFUNCVAL( tmr_printTimerlist ) },
+  { LNILKEY, LNILVAL }
+};
+#endif
+
 static const LUA_REG_TYPE tmr_map[] = {
 static const LUA_REG_TYPE tmr_map[] = {
 	{ LSTRKEY( "delay" ),        LFUNCVAL( tmr_delay ) },
 	{ LSTRKEY( "delay" ),        LFUNCVAL( tmr_delay ) },
 	{ LSTRKEY( "now" ),          LFUNCVAL( tmr_now ) },
 	{ LSTRKEY( "now" ),          LFUNCVAL( tmr_now ) },
@@ -376,11 +481,20 @@ static const LUA_REG_TYPE tmr_map[] = {
 	{ LSTRKEY( "register" ),     LFUNCVAL( tmr_register ) },
 	{ LSTRKEY( "register" ),     LFUNCVAL( tmr_register ) },
 	{ LSTRKEY( "alarm" ),        LFUNCVAL( tmr_alarm ) },
 	{ LSTRKEY( "alarm" ),        LFUNCVAL( tmr_alarm ) },
 	{ LSTRKEY( "start" ),        LFUNCVAL( tmr_start ) },
 	{ LSTRKEY( "start" ),        LFUNCVAL( tmr_start ) },
-	{ LSTRKEY( "stop" ),         LFUNCVAL( tmr_stop ) },
+  { LSTRKEY( "stop" ),         LFUNCVAL( tmr_stop ) },
+#ifdef ENABLE_TIMER_SUSPEND
+  { LSTRKEY( "suspend" ),      LFUNCVAL( tmr_suspend ) },
+  { LSTRKEY( "suspend_all" ),  LFUNCVAL( tmr_suspend_all ) },
+  { LSTRKEY( "resume" ),       LFUNCVAL( tmr_resume ) },
+  { LSTRKEY( "resume_all" ),   LFUNCVAL( tmr_resume_all ) },
+#endif
 	{ LSTRKEY( "unregister" ),   LFUNCVAL( tmr_unregister ) },
 	{ LSTRKEY( "unregister" ),   LFUNCVAL( tmr_unregister ) },
 	{ LSTRKEY( "state" ),        LFUNCVAL( tmr_state ) },
 	{ LSTRKEY( "state" ),        LFUNCVAL( tmr_state ) },
 	{ LSTRKEY( "interval" ),     LFUNCVAL( tmr_interval ) },
 	{ LSTRKEY( "interval" ),     LFUNCVAL( tmr_interval ) },
 	{ LSTRKEY( "create" ),       LFUNCVAL( tmr_create ) },
 	{ LSTRKEY( "create" ),       LFUNCVAL( tmr_create ) },
+#if defined(SWTMR_DEBUG)
+  { LSTRKEY( "debug" ),       LROVAL( tmr_dbg_map ) },
+#endif
 	{ LSTRKEY( "ALARM_SINGLE" ), LNUMVAL( TIMER_MODE_SINGLE ) },
 	{ LSTRKEY( "ALARM_SINGLE" ), LNUMVAL( TIMER_MODE_SINGLE ) },
 	{ LSTRKEY( "ALARM_SEMI" ),   LNUMVAL( TIMER_MODE_SEMI ) },
 	{ LSTRKEY( "ALARM_SEMI" ),   LNUMVAL( TIMER_MODE_SEMI ) },
 	{ LSTRKEY( "ALARM_AUTO" ),   LNUMVAL( TIMER_MODE_AUTO ) },
 	{ LSTRKEY( "ALARM_AUTO" ),   LNUMVAL( TIMER_MODE_AUTO ) },
@@ -396,14 +510,16 @@ int luaopen_tmr( lua_State *L ){
 		alarm_timers[i].lua_ref = LUA_NOREF;
 		alarm_timers[i].lua_ref = LUA_NOREF;
 		alarm_timers[i].self_ref = LUA_REFNIL;
 		alarm_timers[i].self_ref = LUA_REFNIL;
 		alarm_timers[i].mode = TIMER_MODE_OFF;
 		alarm_timers[i].mode = TIMER_MODE_OFF;
+		//improve boot speed by using ets_timer_disarm instead of os_timer_disarm to avoid timer registry maintenance call.
 		ets_timer_disarm(&alarm_timers[i].os);
 		ets_timer_disarm(&alarm_timers[i].os);
 	}
 	}
 	last_rtc_time=system_get_rtc_time(); // Right now is time 0
 	last_rtc_time=system_get_rtc_time(); // Right now is time 0
 	last_rtc_time_us=0;
 	last_rtc_time_us=0;
 
 
+  //improve boot speed by using ets_timer_disarm instead of os_timer_disarm to avoid timer registry maintenance call.
 	ets_timer_disarm(&rtc_timer);
 	ets_timer_disarm(&rtc_timer);
-	ets_timer_setfn(&rtc_timer, rtc_callback, NULL);
-	ets_timer_arm_new(&rtc_timer, 1000, 1, 1);
+	os_timer_setfn(&rtc_timer, rtc_callback, NULL);
+	os_timer_arm(&rtc_timer, 1000, 1);
 	return 0;
 	return 0;
 }
 }
 
 

+ 75 - 50
app/modules/wifi.c

@@ -26,10 +26,6 @@ static uint8 getap_output_format=0;
 
 
 #define INVALID_MAC_STR "MAC:FF:FF:FF:FF:FF:FF"
 #define INVALID_MAC_STR "MAC:FF:FF:FF:FF:FF:FF"
 
 
-//wifi.sleep variables
-#define FPM_SLEEP_MAX_TIME 0xFFFFFFF
-static bool FLAG_wifi_force_sleep_enabled=0;
-
 #ifdef WIFI_SMART_ENABLE
 #ifdef WIFI_SMART_ENABLE
 static void wifi_smart_succeed_cb(sc_status status, void *pdata){
 static void wifi_smart_succeed_cb(sc_status status, void *pdata){
   NODE_DBG("wifi_smart_succeed_cb is called.\n");
   NODE_DBG("wifi_smart_succeed_cb is called.\n");
@@ -302,66 +298,92 @@ static int wifi_getphymode( lua_State* L )
   return 1;
   return 1;
 }
 }
 
 
-// Lua: wifi.sleep()
-static int wifi_sleep(lua_State* L)
+#ifdef PMSLEEP_ENABLE
+/* Begin WiFi suspend functions*/
+#include "pmSleep.h"
+
+static int wifi_resume_cb_ref = LUA_NOREF; // Holds resume callback reference
+static int wifi_suspend_cb_ref = LUA_NOREF; // Holds suspend callback reference
+
+void wifi_pmSleep_suspend_CB(void)
 {
 {
-  uint8 desired_sleep_state = 2;
-  sint8 wifi_fpm_do_sleep_return_value = 1;
-  if(lua_isnumber(L, 1))
+  PMSLEEP_DBG("\n\tDBG: %s start\n", __func__);
+  if (wifi_suspend_cb_ref != LUA_NOREF)
   {
   {
-    if(luaL_checknumber(L, 1) == 0)
-    {
-      desired_sleep_state = 0;
-    }
-    else if(luaL_checknumber(L, 1) == 1)
-    {
-      desired_sleep_state = 1;
-    }
+    lua_State* L = lua_getstate(); // Get main Lua thread pointer
+    lua_rawgeti(L, LUA_REGISTRYINDEX, wifi_suspend_cb_ref); // Push suspend callback onto stack
+    lua_unref(L, wifi_suspend_cb_ref); // remove suspend callback from LUA_REGISTRY
+    wifi_suspend_cb_ref = LUA_NOREF; // Update variable since reference is no longer valid
+    lua_call(L, 0, 0); // Execute suspend callback
   }
   }
-  if (!FLAG_wifi_force_sleep_enabled && desired_sleep_state == 1)
+  else
   {
   {
-    uint8 wifi_current_opmode = wifi_get_opmode();
-    if (wifi_current_opmode == 1 || wifi_current_opmode == 3)
-    {
-      wifi_station_disconnect();
-    }
-    // set WiFi mode to null mode
-    wifi_set_opmode(NULL_MODE);
-    // set force sleep type
-    wifi_fpm_set_sleep_type(MODEM_SLEEP_T);
-    wifi_fpm_open();
-    wifi_fpm_do_sleep_return_value = wifi_fpm_do_sleep(FPM_SLEEP_MAX_TIME);
-    if (wifi_fpm_do_sleep_return_value == 0)
-    {
-      FLAG_wifi_force_sleep_enabled = TRUE;
-    }
-    else
-    {
-      wifi_fpm_close();
-      FLAG_wifi_force_sleep_enabled = FALSE;
-    }
+    PMSLEEP_DBG("\n\tDBG: lua cb unavailable\n");
   }
   }
-  else if(FLAG_wifi_force_sleep_enabled && desired_sleep_state == 0)
+  PMSLEEP_DBG("\n\tDBG: %s end\n", __func__);
+  return;
+}
+
+void wifi_pmSleep_resume_CB(void)
+{
+  PMSLEEP_DBG("\n\tDBG: %s start\n", __func__);
+  // If resume callback was defined
+  pmSleep_execute_lua_cb(&wifi_resume_cb_ref);
+  PMSLEEP_DBG("\n\tDBG: %s end\n", __func__);
+  return;
+}
+
+// Lua: wifi.suspend({duration, suspend_cb, resume_cb, preserve_mode})
+static int wifi_suspend(lua_State* L)
+{
+  // If no parameters were provided
+  if (lua_isnone(L, 1))
   {
   {
-    FLAG_wifi_force_sleep_enabled = FALSE;
-    // wake up to use WiFi again
-    wifi_fpm_do_wakeup();
-    wifi_fpm_close();
+    // Return current WiFi suspension state
+    lua_pushnumber(L, pmSleep_get_state());
+    return 1; // Return WiFi suspension state
   }
   }
 
 
-  if (desired_sleep_state == 1 && FLAG_wifi_force_sleep_enabled == FALSE)
+  pmSleep_INIT_CFG(cfg);
+  cfg.sleep_mode = MODEM_SLEEP_T;
+  if(lua_istable(L, 1))
   {
   {
-    lua_pushnil(L);
-    lua_pushnumber(L, wifi_fpm_do_sleep_return_value);
+    pmSleep_parse_table_lua(L, 1, &cfg, &wifi_suspend_cb_ref, &wifi_resume_cb_ref);
   }
   }
   else
   else
+    return luaL_argerror(L, 1, "must be table");
+  cfg.resume_cb_ptr  = &wifi_pmSleep_resume_CB;
+  cfg.suspend_cb_ptr = &wifi_pmSleep_suspend_CB;
+  pmSleep_suspend(&cfg);
+  return 0;
+}
+// Lua: wifi.resume([Resume_CB])
+static int wifi_resume(lua_State* L)
+{
+  PMSLEEP_DBG("\n\tDBG: %s start\n", __func__);
+  uint8 fpm_state = pmSleep_get_state();
+// If forced sleep api is not enabled, return error
+  if (fpm_state == 0)
   {
   {
-    lua_pushnumber(L, FLAG_wifi_force_sleep_enabled);
-    lua_pushnil(L);
+      return luaL_error(L, "WIFi not suspended");
   }
   }
-  return 2;
+
+  // If a resume callback was provided
+  if (lua_isfunction(L, 1))
+  {
+    // If there is already a resume callback reference
+    lua_pushvalue(L, 1); //Push resume callback to the top of the stack
+    register_lua_cb(L, &wifi_resume_cb_ref);
+    PMSLEEP_DBG("\n\tDBG: Resume CB registered\n");
+  }
+  pmSleep_resume(NULL);
+  PMSLEEP_DBG("\n\tDBG: %s end\n", __func__);
+  return 0;
 }
 }
 
 
+/* End WiFi suspend functions*/
+#endif
+
 // Lua: wifi.nullmodesleep()
 // Lua: wifi.nullmodesleep()
 static int wifi_null_mode_auto_sleep(lua_State* L)
 static int wifi_null_mode_auto_sleep(lua_State* L)
 {
 {
@@ -1659,7 +1681,10 @@ static const LUA_REG_TYPE wifi_map[] =  {
   { LSTRKEY( "getchannel" ),     LFUNCVAL( wifi_getchannel ) },
   { LSTRKEY( "getchannel" ),     LFUNCVAL( wifi_getchannel ) },
   { LSTRKEY( "setphymode" ),     LFUNCVAL( wifi_setphymode ) },
   { LSTRKEY( "setphymode" ),     LFUNCVAL( wifi_setphymode ) },
   { LSTRKEY( "getphymode" ),     LFUNCVAL( wifi_getphymode ) },
   { LSTRKEY( "getphymode" ),     LFUNCVAL( wifi_getphymode ) },
-  { LSTRKEY( "sleep" ),          LFUNCVAL( wifi_sleep ) },
+#ifdef PMSLEEP_ENABLE
+  { LSTRKEY( "suspend" ),        LFUNCVAL( wifi_suspend ) },
+  { LSTRKEY( "resume" ),         LFUNCVAL( wifi_resume ) },
+#endif
   { LSTRKEY( "nullmodesleep" ),  LFUNCVAL( wifi_null_mode_auto_sleep ) },
   { LSTRKEY( "nullmodesleep" ),  LFUNCVAL( wifi_null_mode_auto_sleep ) },
 #ifdef WIFI_SMART_ENABLE 
 #ifdef WIFI_SMART_ENABLE 
   { LSTRKEY( "startsmart" ),     LFUNCVAL( wifi_start_smart ) },
   { LSTRKEY( "startsmart" ),     LFUNCVAL( wifi_start_smart ) },

+ 9 - 0
app/modules/wifi_common.h

@@ -52,6 +52,15 @@ void wifi_change_default_host_name(void);
 #define EVENT_DBG(...) //c_printf(__VA_ARGS__)
 #define EVENT_DBG(...) //c_printf(__VA_ARGS__)
 #endif
 #endif
 
 
+enum wifi_suspension_state
+{
+  WIFI_AWAKE = 0,
+  WIFI_SUSPENSION_PENDING = 1,
+  WIFI_SUSPENDED = 2
+};
+
+
+
 #ifdef WIFI_SDK_EVENT_MONITOR_ENABLE
 #ifdef WIFI_SDK_EVENT_MONITOR_ENABLE
   extern const LUA_REG_TYPE wifi_event_monitor_map[];
   extern const LUA_REG_TYPE wifi_event_monitor_map[];
   void wifi_eventmon_init();
   void wifi_eventmon_init();

+ 52 - 0
app/pm/Makefile

@@ -0,0 +1,52 @@
+
+#############################################################
+# Required variables for each makefile
+# Discard this section from all parent makefiles
+# Expected variables (with automatic defaults):
+#   CSRCS (all "C" files in the dir)
+#   SUBDIRS (all subdirs with a Makefile)
+#   GEN_LIBS - list of libs to be generated ()
+#   GEN_IMAGES - list of images to be generated ()
+#   COMPONENTS_xxx - a list of libs/objs in the form
+#     subdir/lib to be extracted and rolled up into
+#     a generated lib/image xxx.a ()
+#
+ifndef PDIR
+GEN_LIBS = libpm.a
+endif
+
+STD_CFLAGS=-std=gnu11 -Wimplicit
+
+#############################################################
+# Configuration i.e. compile options etc.
+# Target specific stuff (defines etc.) goes in here!
+# Generally values applying to a tree are captured in the
+#   makefile at its root level - these are then overridden
+#   for a subtree within the makefile rooted therein
+#
+#DEFINES += 
+
+#############################################################
+# Recursion Magic - Don't touch this!!
+#
+# Each subtree potentially has an include directory
+#   corresponding to the common APIs applicable to modules
+#   rooted at that subtree. Accordingly, the INCLUDE PATH
+#   of a module can only contain the include directories up
+#   its parent path, and not its siblings
+#
+# Required for each makefile to inherit from the parent
+#
+
+INCLUDES := $(INCLUDES) -I $(PDIR)include
+INCLUDES += -I ./
+INCLUDES += -I ./include
+INCLUDES += -I ../include
+INCLUDES += -I ../../include
+INCLUDES += -I ../lua
+INCLUDES += -I ../platform
+INCLUDES += -I ../libc
+
+PDIR := ../$(PDIR)
+sinclude $(PDIR)Makefile
+

+ 381 - 0
app/pm/pmSleep.c

@@ -0,0 +1,381 @@
+#include "pmSleep.h"
+#ifdef  PMSLEEP_ENABLE
+#define STRINGIFY_VAL(x) #x
+#define STRINGIFY(x) STRINGIFY_VAL(x)
+
+//holds duration error string
+//uint32 PMSLEEP_SLEEP_MAX_TIME=FPM_SLEEP_MAX_TIME-1;
+const char *PMSLEEP_DURATION_ERR_STR="duration: 0 or "STRINGIFY(PMSLEEP_SLEEP_MIN_TIME)"-"STRINGIFY(PMSLEEP_SLEEP_MAX_TIME)" us";
+
+
+/*  INTERNAL VARIABLES  */
+static void (*user_suspend_cb)(void) = NULL;
+static void (*user_resume_cb)(void) = NULL;
+static uint8 resume_opmode = 0;
+static os_timer_t wifi_suspended_test_timer;
+static uint8 autosleep_setting_temp = 0;
+static os_timer_t null_mode_check_timer;
+static pmSleep_param_t current_config;
+
+
+/*  INTERNAL FUNCTION DECLARATIONS  */
+static void suspend_all_timers(void);
+static void null_mode_check_timer_cb(void* arg);
+static void resume_all_timers(void);
+static inline void register_lua_cb(lua_State* L,int* cb_ref);
+static void resume_cb(void);
+static void wifi_suspended_timer_cb(int arg);
+
+/*  INTERNAL FUNCTIONS  */
+
+#include "swTimer/swTimer.h"
+static void suspend_all_timers(void){
+#ifdef ENABLE_TIMER_SUSPEND
+  swtmr_suspend(NULL);
+#endif
+  return;
+}
+
+static void resume_all_timers(void){
+#ifdef ENABLE_TIMER_SUSPEND
+  swtmr_resume(NULL);
+#endif
+  return;
+}
+
+static void null_mode_check_timer_cb(void* arg){
+  if (wifi_get_opmode() == NULL_MODE){
+    //check if uart 0 tx buffer is empty and uart 1 tx buffer is empty
+    if(current_config.sleep_mode == LIGHT_SLEEP_T){
+      if((READ_PERI_REG(UART_STATUS(0)) & (UART_TXFIFO_CNT<<UART_TXFIFO_CNT_S)) == 0 &&
+         (READ_PERI_REG(UART_STATUS(1)) & (UART_TXFIFO_CNT<<UART_TXFIFO_CNT_S)) == 0){
+        ets_timer_disarm(&null_mode_check_timer);
+        suspend_all_timers();
+        //Ensure UART 0/1 TX FIFO is clear
+        SET_PERI_REG_MASK(UART_CONF0(0), UART_TXFIFO_RST);//RESET FIFO
+        CLEAR_PERI_REG_MASK(UART_CONF0(0), UART_TXFIFO_RST);
+        SET_PERI_REG_MASK(UART_CONF0(1), UART_TXFIFO_RST);//RESET FIFO
+        CLEAR_PERI_REG_MASK(UART_CONF0(1), UART_TXFIFO_RST);
+        wifi_fpm_do_sleep(current_config.sleep_duration);
+        return;
+      }
+      else{
+        return;
+      }
+    }
+    else{  //MODEM_SLEEP_T
+      sint8 retval_wifi_fpm_do_sleep = wifi_fpm_do_sleep(current_config.sleep_duration); // Request WiFi suspension and store return value
+
+      // If wifi_fpm_do_sleep success
+      if (retval_wifi_fpm_do_sleep == 0){
+        PMSLEEP_DBG("wifi_fpm_do_sleep success, starting wifi_suspend_test timer");
+        os_timer_disarm(&wifi_suspended_test_timer);
+        os_timer_setfn(&wifi_suspended_test_timer, (os_timer_func_t*)wifi_suspended_timer_cb, NULL);
+        os_timer_arm(&wifi_suspended_test_timer, 1, 1);
+      }
+      else{ // This should never happen. if it does, return the value for error reporting
+        wifi_fpm_close();
+        PMSLEEP_ERR("wifi_fpm_do_sleep returned %d", retval_wifi_fpm_do_sleep);
+      }
+    }
+    ets_timer_disarm(&null_mode_check_timer);
+    return;
+  }
+}
+
+
+//function to register a lua callback function in the LUA_REGISTRYINDEX for later execution
+static inline void register_lua_cb(lua_State* L, int* cb_ref){
+  int ref = luaL_ref(L, LUA_REGISTRYINDEX);
+  if( *cb_ref != LUA_NOREF) luaL_unref(L, LUA_REGISTRYINDEX, *cb_ref);
+  *cb_ref = ref;
+}
+
+// C callback for bringing WiFi back from forced sleep
+static void resume_cb(void){
+  PMSLEEP_DBG("START");
+  //TODO: Add support for extended light sleep duration
+  wifi_fpm_close(); // Disable force sleep API
+  PMSLEEP_DBG("WiFi resume");
+
+  resume_all_timers();
+
+  //this section restores null mode auto sleep setting
+  if(autosleep_setting_temp == 1)  {
+    wifi_fpm_auto_sleep_set_in_null_mode(autosleep_setting_temp);
+    autosleep_setting_temp = 0;
+  }
+
+  //this section restores previous wifi mode
+  if (resume_opmode == STATION_MODE || resume_opmode == SOFTAP_MODE || resume_opmode == STATIONAP_MODE){
+    if (wifi_set_opmode_current(resume_opmode)){
+      if (resume_opmode == STATION_MODE || resume_opmode == STATIONAP_MODE){
+        wifi_station_connect(); // Connect to currently configured Access Point
+      }
+      PMSLEEP_DBG("WiFi mode restored");
+      resume_opmode = 0; // reset variable to default value
+    }
+  }
+  else{
+    wifi_set_opmode_current(NULL_MODE);
+  }
+
+  //execute the external resume callback
+  if (user_resume_cb != NULL){
+    PMSLEEP_DBG("calling user resume cb (%p)", user_resume_cb);
+    user_resume_cb();
+    user_resume_cb = NULL;
+  }
+
+  PMSLEEP_DBG("END");
+  return;
+}
+
+// This callback executes the suspended callback when Wifi suspension is in effect
+static void wifi_suspended_timer_cb(int arg){
+  // check if wifi is suspended.
+  if (pmSleep_get_state() == PMSLEEP_SUSPENDED){
+    os_timer_disarm(&wifi_suspended_test_timer); // Stop rf_closed_timer
+    PMSLEEP_DBG("WiFi is suspended");
+
+    //execute the external suspended callback
+    if (user_suspend_cb != NULL){
+      PMSLEEP_DBG("calling user suspend cb (%p)", user_suspend_cb);
+      user_suspend_cb();
+      user_suspend_cb = NULL;
+    }
+    PMSLEEP_DBG("END");
+  }
+}
+
+/*  EXTERNAL FUNCTIONS  */
+
+//this function executes the application developer's Lua callback
+void pmSleep_execute_lua_cb(int* cb_ref){
+  if (*cb_ref != LUA_NOREF){
+    lua_State* L = lua_getstate(); // Get Lua main thread pointer
+    lua_rawgeti(L, LUA_REGISTRYINDEX, *cb_ref); // Push resume callback onto the stack
+    lua_unref(L, *cb_ref); // Remove resume callback from registry
+    *cb_ref = LUA_NOREF; // Update variable since reference is no longer valid
+    lua_call(L, 0, 0); // Execute resume callback
+  }
+
+}
+
+//this function checks current wifi suspension state and returns the result
+uint8 pmSleep_get_state(void){
+  if (fpm_rf_is_closed()) return PMSLEEP_SUSPENDED;
+  else if (fpm_is_open()) return PMSLEEP_SUSPENSION_PENDING;
+  else return PMSLEEP_AWAKE;
+}
+
+//this function parses the lua configuration table provided by the application developer
+int pmSleep_parse_table_lua( lua_State* L, int table_idx, pmSleep_param_t *cfg, int *suspend_lua_cb_ref, int *resume_lua_cb_ref){
+  lua_Integer Linteger_tmp = 0;
+
+  lua_getfield(L, table_idx, "duration");
+  if( !lua_isnil(L, -1) ){  /* found? */
+    if( lua_isnumber(L, -1) ){
+      lua_Integer Linteger=luaL_checkinteger(L, -1);
+      luaL_argcheck(L,(((Linteger >= PMSLEEP_SLEEP_MIN_TIME) && (Linteger <= PMSLEEP_SLEEP_MAX_TIME)) ||
+          (Linteger == 0)), table_idx, PMSLEEP_DURATION_ERR_STR);
+      cfg->sleep_duration = (uint32)Linteger; // Get suspend duration
+    }
+    else{
+      return luaL_argerror( L, table_idx, "duration: must be number" );
+    }
+  }
+  else{
+    return luaL_argerror( L, table_idx, PMSLEEP_DURATION_ERR_STR );
+  }
+  lua_pop(L, 1);
+
+  if( cfg->sleep_mode == MODEM_SLEEP_T ){ //WiFi suspend
+    lua_getfield(L, table_idx, "suspend_cb");
+    if( !lua_isnil(L, -1) ){  /* found? */
+      if( lua_isfunction(L, -1) ){
+        lua_pushvalue(L, -1); // Push resume callback to the top of the stack
+        register_lua_cb(L, suspend_lua_cb_ref);
+      }
+      else{
+        return luaL_argerror( L, table_idx, "suspend_cb: must be function" );
+      }
+    }
+    lua_pop(L, 1);
+  }
+  else if (cfg->sleep_mode == LIGHT_SLEEP_T){ //CPU suspend
+#ifdef ENABLE_TIMER_SUSPEND
+    lua_getfield(L, table_idx, "wake_pin");
+    if( !lua_isnil(L, -1) ){  /* found? */
+      if( lua_isnumber(L, -1) ){
+        Linteger_tmp=lua_tointeger(L, -1);
+        luaL_argcheck(L, (platform_gpio_exists(Linteger_tmp) && Linteger_tmp > 0), table_idx, "wake_pin: Invalid interrupt pin");
+        cfg->wake_pin = Linteger_tmp;
+      }
+      else{
+        return luaL_argerror( L, table_idx, "wake_pin: must be number" );
+      }
+    }
+    else if(cfg->sleep_duration == 0){
+      return luaL_argerror( L, table_idx, "wake_pin: must specify pin if sleep duration is indefinite" );
+    }
+    lua_pop(L, 1);
+
+    lua_getfield(L, table_idx, "int_type");
+    if( !lua_isnil(L, -1) ){  /* found? */
+      if( lua_isnumber(L, -1) ){
+        Linteger_tmp=lua_tointeger(L, -1);
+        luaL_argcheck(L, (Linteger_tmp == GPIO_PIN_INTR_ANYEDGE || Linteger_tmp == GPIO_PIN_INTR_HILEVEL
+            || Linteger_tmp == GPIO_PIN_INTR_LOLEVEL || Linteger_tmp == GPIO_PIN_INTR_NEGEDGE
+            || Linteger_tmp == GPIO_PIN_INTR_POSEDGE ), 1, "int_type: invalid interrupt type");
+        cfg->int_type = Linteger_tmp;
+      }
+      else{
+        return luaL_argerror( L, table_idx, "int_type: must be number" );
+      }
+    }
+    else{
+      cfg->int_type = GPIO_PIN_INTR_LOLEVEL;
+    }
+    lua_pop(L, 1);
+#endif
+  }
+  else{
+    return luaL_error(L, "FPM Sleep mode not available");
+  }
+
+  lua_getfield(L, table_idx, "resume_cb");
+  if( !lua_isnil(L, -1) ){  /* found? */
+    if( lua_isfunction(L, -1) ){
+      lua_pushvalue(L, -1); // Push resume callback to the top of the stack
+      register_lua_cb(L, resume_lua_cb_ref);
+    }
+    else{
+      return luaL_argerror( L, table_idx, "resume_cb: must be function" );
+    }
+  }
+  lua_pop(L, 1);
+
+  lua_getfield(L, table_idx, "preserve_mode");
+  if( !lua_isnil(L, -1) ){  /* found? */
+    if( lua_isboolean(L, -1) ){
+      cfg->preserve_opmode=lua_toboolean(L, -1);
+    }
+    else{
+      return luaL_argerror( L, table_idx, "preseve_mode: must be boolean" );
+    }
+  }
+  else{
+    cfg->preserve_opmode = true;
+  }
+  lua_pop(L, 1);
+
+  //if sleep duration is zero, set indefinite sleep duration
+  if( cfg->sleep_duration == 0 ){
+    cfg->sleep_duration = FPM_SLEEP_MAX_TIME;
+  }
+
+  return 0;
+}
+
+//This function resumes ESP from MODEM_SLEEP
+void pmSleep_resume(void (*resume_cb_ptr)(void)){
+  PMSLEEP_DBG("START");
+  uint8 fpm_state = pmSleep_get_state();
+
+  if(fpm_state>0){
+    if(resume_cb_ptr != NULL){
+      user_resume_cb = resume_cb_ptr;
+    }
+    wifi_fpm_do_wakeup(); // Wake up from sleep
+    resume_cb(); // Finish WiFi wakeup
+  }
+
+  PMSLEEP_DBG("END");
+  return;
+}
+
+//this function puts the ESP8266 into MODEM_SLEEP or LIGHT_SLEEP
+void pmSleep_suspend(pmSleep_param_t *cfg){
+  PMSLEEP_DBG("START");
+
+  lua_State* L = lua_getstate();
+#ifndef ENABLE_TIMER_SUSPEND
+  if(cfg->sleep_mode == LIGHT_SLEEP_T){
+    luaL_error(L, "timer suspend API is disabled, light sleep unavailable");
+    return;
+  }
+#endif
+  uint8 current_wifi_mode = wifi_get_opmode(); // Get Current WiFi mode
+
+  user_resume_cb = cfg->resume_cb_ptr; //pointer to hold address of user_cb
+  user_suspend_cb = cfg->suspend_cb_ptr; //pointer to hold address of user_cb
+
+  // If Preserve_wifi_mode parameter is TRUE and current WiFi mode is not NULL_MODE
+  if (cfg->preserve_opmode && current_wifi_mode != 0){
+    resume_opmode = current_wifi_mode;
+  }
+
+
+  if (current_wifi_mode == STATION_MODE || current_wifi_mode == STATIONAP_MODE){
+    wifi_station_disconnect(); // Disconnect from Access Point
+  }
+
+  //the null mode sleep functionality interferes with the forced sleep API and must be disabled
+  if(get_fpm_auto_sleep_flag() == 1){
+    autosleep_setting_temp = 1;
+    wifi_fpm_auto_sleep_set_in_null_mode(0);
+  }
+
+  // If wifi_set_opmode_current is successful
+  if (wifi_set_opmode_current(NULL_MODE)){
+    PMSLEEP_DBG("sleep_mode is %s", cfg->sleep_mode == MODEM_SLEEP_T ? "MODEM_SLEEP_T" : "LIGHT_SLEEP_T");
+
+    wifi_fpm_set_sleep_type(cfg->sleep_mode);
+
+    wifi_fpm_open(); // Enable force sleep API
+
+    if (cfg->sleep_mode == LIGHT_SLEEP_T){
+#ifdef ENABLE_TIMER_SUSPEND
+      if(platform_gpio_exists(cfg->wake_pin) && cfg->wake_pin > 0){
+        PMSLEEP_DBG("Wake-up pin is %d\t interrupt type is %d", cfg->wake_pin, cfg->int_type);
+
+        if((cfg->int_type != GPIO_PIN_INTR_ANYEDGE && cfg->int_type != GPIO_PIN_INTR_HILEVEL
+            && cfg->int_type != GPIO_PIN_INTR_LOLEVEL && cfg->int_type != GPIO_PIN_INTR_NEGEDGE
+            && cfg->int_type != GPIO_PIN_INTR_POSEDGE )){
+          wifi_fpm_close();
+          PMSLEEP_DBG("Invalid interrupt type");
+          return;
+        }
+
+        GPIO_DIS_OUTPUT(pin_num[cfg->wake_pin]);
+        PIN_FUNC_SELECT(pin_mux[cfg->wake_pin], pin_func[cfg->wake_pin]);
+        wifi_enable_gpio_wakeup(pin_num[cfg->wake_pin], cfg->int_type);
+      }
+      else if(cfg->sleep_duration == FPM_SLEEP_MAX_TIME && cfg->wake_pin == 255){
+        wifi_fpm_close();
+        PMSLEEP_DBG("No wake-up pin defined");
+        return;
+      }
+#endif
+
+    }
+
+    wifi_fpm_set_wakeup_cb(resume_cb); // Set resume C callback
+
+    c_memcpy(&current_config, cfg, sizeof(pmSleep_param_t));
+    PMSLEEP_DBG("sleep duration is %d", current_config.sleep_duration);
+
+    //this timer intentionally bypasses the swtimer timer registration process
+    ets_timer_disarm(&null_mode_check_timer);
+    ets_timer_setfn(&null_mode_check_timer, null_mode_check_timer_cb, false);
+    ets_timer_arm_new(&null_mode_check_timer, 1, 1, 1);
+  }
+  else{
+    PMSLEEP_ERR("opmode change fail");
+  }
+  PMSLEEP_DBG("END");
+  return;
+}
+
+#endif

+ 69 - 0
app/pm/pmSleep.h

@@ -0,0 +1,69 @@
+#ifndef __FPM_SLEEP_H__
+#define __FPM_SLEEP_H__
+#include "user_interface.h"
+#include "c_types.h"
+#include "lauxlib.h"
+#include "gpio.h"
+#include "platform.h"
+#include "task/task.h"
+#include "c_string.h"
+
+#if defined(DEVELOP_VERSION)
+#define PMSLEEP_DEBUG
+#endif
+
+#if defined(PMSLEEP_DEBUG)
+  #define PMSLEEP_DBG(fmt, ...) dbg_printf("\tPMSLEEP(%s):"fmt"\n", __FUNCTION__, ##__VA_ARGS__)
+#else
+  #define PMSLEEP_DBG(...) //c_printf(__VA_ARGS__)
+#endif
+
+#if defined(NODE_ERROR)
+  #define PMSLEEP_ERR(fmt, ...) NODE_ERR("%s"fmt"\n", "PMSLEEP:", ##__VA_ARGS__)
+#else
+  #define PMSLEEP_ERR(...)
+#endif
+
+
+
+
+
+#define PMSLEEP_SLEEP_MIN_TIME 50000
+#define PMSLEEP_SLEEP_MAX_TIME 268435454 //FPM_MAX_SLEEP_TIME-1
+#define pmSleep_INIT_CFG(X) pmSleep_param_t X = {.sleep_duration=0, .wake_pin=255, \
+    .preserve_opmode=TRUE, .suspend_cb_ptr=NULL, .resume_cb_ptr=NULL}
+
+#define PMSLEEP_INT_MAP \
+  { LSTRKEY( "INT_BOTH" ),      LNUMVAL( GPIO_PIN_INTR_ANYEDGE ) }, \
+  { LSTRKEY( "INT_UP" ),        LNUMVAL( GPIO_PIN_INTR_POSEDGE ) }, \
+  { LSTRKEY( "INT_DOWN" ),      LNUMVAL( GPIO_PIN_INTR_NEGEDGE ) }, \
+  { LSTRKEY( "INT_HIGH" ),      LNUMVAL( GPIO_PIN_INTR_HILEVEL ) }, \
+  { LSTRKEY( "INT_LOW" ),       LNUMVAL( GPIO_PIN_INTR_LOLEVEL ) }
+
+
+
+typedef struct pmSleep_param{
+  uint32 sleep_duration;
+  uint8 sleep_mode;
+  uint8 wake_pin;
+  uint8 int_type;
+  bool preserve_opmode;
+  void (*suspend_cb_ptr)(void);
+  void (*resume_cb_ptr)(void);
+}pmSleep_param_t; //structure to hold pmSleep configuration
+
+
+enum PMSLEEP_STATE{
+  PMSLEEP_AWAKE = 0,
+  PMSLEEP_SUSPENSION_PENDING = 1,
+  PMSLEEP_SUSPENDED = 2
+};
+
+uint8 pmSleep_get_state(void);
+void pmSleep_resume(void (*resume_cb_ptr)(void));
+void pmSleep_suspend(pmSleep_param_t *param);
+void pmSleep_execute_lua_cb(int* cb_ref);
+int pmSleep_parse_table_lua( lua_State* L, int table_idx, pmSleep_param_t *cfg, int *suspend_lua_cb_ref, int *resume_lua_cb_ref);
+
+
+#endif // __FPM_SLEEP_H__

+ 52 - 0
app/swTimer/Makefile

@@ -0,0 +1,52 @@
+
+#############################################################
+# Required variables for each makefile
+# Discard this section from all parent makefiles
+# Expected variables (with automatic defaults):
+#   CSRCS (all "C" files in the dir)
+#   SUBDIRS (all subdirs with a Makefile)
+#   GEN_LIBS - list of libs to be generated ()
+#   GEN_IMAGES - list of images to be generated ()
+#   COMPONENTS_xxx - a list of libs/objs in the form
+#     subdir/lib to be extracted and rolled up into
+#     a generated lib/image xxx.a ()
+#
+ifndef PDIR
+GEN_LIBS = libswtimer.a
+endif
+
+STD_CFLAGS=-std=gnu11 -Wimplicit
+
+#############################################################
+# Configuration i.e. compile options etc.
+# Target specific stuff (defines etc.) goes in here!
+# Generally values applying to a tree are captured in the
+#   makefile at its root level - these are then overridden
+#   for a subtree within the makefile rooted therein
+#
+#DEFINES += 
+
+#############################################################
+# Recursion Magic - Don't touch this!!
+#
+# Each subtree potentially has an include directory
+#   corresponding to the common APIs applicable to modules
+#   rooted at that subtree. Accordingly, the INCLUDE PATH
+#   of a module can only contain the include directories up
+#   its parent path, and not its siblings
+#
+# Required for each makefile to inherit from the parent
+#
+
+INCLUDES := $(INCLUDES) -I $(PDIR)include
+INCLUDES += -I ./
+INCLUDES += -I ./include
+INCLUDES += -I ../include
+INCLUDES += -I ../../include
+INCLUDES += -I ../lua
+INCLUDES += -I ../platform
+INCLUDES += -I ../libc
+
+PDIR := ../$(PDIR)
+sinclude $(PDIR)Makefile
+

+ 632 - 0
app/swTimer/swTimer.c

@@ -0,0 +1,632 @@
+/* swTimer.c SDK timer suspend API
+ *
+ * SDK software timer API info:
+ *
+ * The SDK software timer uses a linked list called `os_timer_t* timer_list` to keep track of
+ * all currently armed timers.
+ *
+ * The SDK software timer API executes in a task. The priority of this task in relation to the
+ * application level tasks is unknown (at time of writing).
+ *
+ *
+ * To determine when a timer's callback should be executed, the respective timer's `timer_expire`
+ *  variable is compared to the hardware counter(FRC2), then, if the timer's `timer_expire` is
+ *  less than the current FRC2 count, the timer's callback is fired.
+ *
+ * The timers in this list are organized in an ascending order starting with the timer
+ * with the lowest timer_expire.
+ *
+ * When a timer expires that has a timer_period greater than 0, timer_expire is changed to
+ * current FRC2 + timer_period, then the timer is inserted back in to the list in the correct position.
+ *
+ * when using millisecond(default) timers, FRC2 resolution is 312.5 ticks per millisecond.
+ *
+ *
+ * TIMER SUSPEND API:
+ *
+ *  Timer registry:
+ * void sw_timer_register(void* timer_ptr);
+ * - Adds timers to the timer registry by adding it to a queue that is later
+ *   processed by timer_register_task that performs the registry maintenance
+ *
+ * void sw_timer_unregister(void* timer_ptr);
+ * - Removes timers from the timer registry by adding it to a queue that is later
+ *   processed by timer_unregister_task that performs the registry maintenance
+ *
+ *
+ * int sw_timer_suspend(os_timer_t* timer_ptr);
+ * - Suspend a single active timer or suspend all active timers.
+ * - if no timer pointer is provided, timer_ptr == NULL, then all currently active timers will be suspended.
+ *
+ * int sw_timer_resume(os_timer_t* timer_ptr);
+ * - Resume a single suspended timer or resume all suspended timers.
+ * - if no timer pointer is provided, timer_ptr == NULL, then all currently suspended timers will be resumed.
+ *
+ *
+ *
+ */
+#include "swTimer/swTimer.h"
+#include "c_stdio.h"
+#include "misc/dynarr.h"
+#include "task/task.h"
+
+#ifdef ENABLE_TIMER_SUSPEND
+
+/*      Settings      */
+#define TIMER_REGISTRY_INITIAL_SIZE 10
+#ifdef USE_SWTMR_ERROR_STRINGS
+static const char* SWTMR_ERROR_STRINGS[]={
+    [SWTMR_MALLOC_FAIL] = "Out of memory!",
+    [SWTMR_TIMER_NOT_ARMED] = "Timer is not armed",
+//    [SWTMR_NULL_PTR] = "A NULL pointer was passed to timer suspend api",
+    [SWTMR_REGISTRY_NO_REGISTERED_TIMERS] = "No timers in registry",
+//    [SWTMR_SUSPEND_ARRAY_INITIALIZATION_FAILED] = "Suspend array init fail",
+//    [SWTMR_SUSPEND_ARRAY_ADD_FAILED] = "Unable to add suspended timer to array",
+//    [SWTMR_SUSPEND_ARRAY_REMOVE_FAILED] = "Unable to remove suspended timer from array",
+    [SWTMR_SUSPEND_TIMER_ALREADY_SUSPENDED] = "Already suspended",
+    [SWTMR_SUSPEND_TIMER_ALREADY_REARMED] = "Already been re-armed",
+    [SWTMR_SUSPEND_NO_SUSPENDED_TIMERS] = "No suspended timers",
+    [SWTMR_SUSPEND_TIMER_NOT_SUSPENDED] = "Not suspended",
+
+};
+#endif
+
+/*     Private Function Declarations      */
+static inline bool timer_armed_check(os_timer_t* timer_ptr);
+static inline int timer_do_suspend(os_timer_t* timer_ptr);
+static inline int timer_do_resume_single(os_timer_t** suspended_timer_ptr);
+static void timer_register_task(task_param_t param, uint8 priority);
+static inline os_timer_t** timer_registry_check(os_timer_t* timer_ptr);
+static inline void timer_registry_remove_unarmed(void);
+static inline os_timer_t** timer_suspended_check(os_timer_t* timer_ptr);
+static void timer_unregister_task(task_param_t param, uint8 priority);
+
+/*      Private Variable Definitions      */
+  static task_handle_t timer_reg_task_id = false;
+  static task_handle_t timer_unreg_task_id = false;
+
+  static dynarr_t timer_registry = {0};
+  static dynarr_t suspended_timers = {0};
+
+  typedef struct registry_queue{
+    struct registry_queue* next;
+    os_timer_t* timer_ptr;
+  }registry_queue_t;
+
+  static registry_queue_t* register_queue = NULL;
+  static registry_queue_t* unregister_queue = NULL;
+
+/*      Private Function Definitions     */
+
+//NOTE: Interrupts are temporarily blocked during the execution of this function
+static inline bool timer_armed_check(os_timer_t* timer_ptr){
+  bool retval = FALSE;
+
+  // we are messing around with the SDK timer structure here, may not be necessary, better safe than sorry though.
+  ETS_INTR_LOCK();
+
+  os_timer_t* timer_list_ptr = timer_list; //get head node pointer of timer_list
+
+  //if present find timer_ptr in timer_list rand return result
+  while(timer_list_ptr != NULL){
+    if(timer_list_ptr == timer_ptr){
+      retval = TRUE;
+      break;
+    }
+    timer_list_ptr = timer_list_ptr->timer_next;
+  }
+
+  //we are done with timer_list, it is now safe to unlock interrupts
+  ETS_INTR_UNLOCK();
+
+  //return value
+  return retval;
+}
+
+static inline int timer_do_suspend(os_timer_t* timer_ptr){
+  if(timer_ptr == NULL){
+    SWTMR_ERR("timer_ptr is invalid");
+    return SWTMR_FAIL;
+  }
+
+  volatile uint32 frc2_count = RTC_REG_READ(FRC2_COUNT_ADDRESS);
+
+  if(timer_armed_check(timer_ptr) == FALSE){
+    return SWTMR_TIMER_NOT_ARMED;
+  }
+
+  os_timer_t** suspended_timer_ptr = timer_suspended_check(timer_ptr);
+
+  uint32 expire_temp = 0;
+  uint32 period_temp = timer_ptr->timer_period;
+
+  if(timer_ptr->timer_expire < frc2_count){
+    expire_temp = 5; //  16 us in ticks (1 tick = ~3.2 us) (arbitrarily chosen value)
+  }
+  else{
+    expire_temp = timer_ptr->timer_expire - frc2_count;
+  }
+
+  ets_timer_disarm(timer_ptr);
+  timer_unregister_task((task_param_t)timer_ptr, false);
+
+  timer_ptr->timer_expire = expire_temp;
+  timer_ptr->timer_period = period_temp;
+
+  if(suspended_timers.data_ptr == NULL){
+    if(!dynarr_init(&suspended_timers, 10, sizeof(os_timer_t*))){
+      SWTMR_ERR("Suspend array init fail");
+      return SWTMR_FAIL;
+    }
+  }
+
+  if(suspended_timer_ptr == NULL){
+//    return SWTMR_SUSPEND_TIMER_ALREADY_SUSPENDED;
+    if(!dynarr_add(&suspended_timers, &timer_ptr, sizeof(timer_ptr))){
+      SWTMR_ERR("Unable to add suspended timer to array");
+      return SWTMR_FAIL;
+    }
+  }
+
+  return SWTMR_OK;
+}
+
+//NOTE: Interrupts are temporarily blocked during the execution of this function
+static inline int timer_do_resume_single(os_timer_t** suspended_timer_ptr){
+  if(suspended_timer_ptr == NULL){
+    SWTMR_ERR("suspended_timer_ptr is invalid");
+    return SWTMR_FAIL;
+  }
+
+  os_timer_t* timer_list_ptr = NULL;
+  os_timer_t* resume_timer_ptr = *suspended_timer_ptr;
+  volatile uint32 frc2_count = RTC_REG_READ(FRC2_COUNT_ADDRESS);
+
+  //verify timer has not been rearmed
+  if(timer_armed_check(resume_timer_ptr) == TRUE){
+    SWTMR_DBG("Timer(%p) already rearmed, removing from array", resume_timer_ptr);
+    if(!dynarr_remove(&suspended_timers, suspended_timer_ptr)){
+      SWTMR_ERR("Failed to remove timer from suspend array");
+      return SWTMR_FAIL;
+    }
+    return SWTMR_OK;
+  }
+
+  //Prepare timer for resume
+  resume_timer_ptr->timer_expire += frc2_count;
+
+  timer_register_task((task_param_t)resume_timer_ptr, false);
+  SWTMR_DBG("Removing timer(%p) from suspend array", resume_timer_ptr);
+
+  //This section performs the actual resume of the suspended timer
+
+  // we are messing around with the SDK timer structure here, may not be necessary, better safe than sorry though.
+  ETS_INTR_LOCK();
+
+  timer_list_ptr = timer_list;
+
+  while(timer_list_ptr != NULL){
+    if(resume_timer_ptr->timer_expire > timer_list_ptr->timer_expire){
+      if(timer_list_ptr->timer_next != NULL){
+        if(resume_timer_ptr->timer_expire < timer_list_ptr->timer_next->timer_expire){
+          resume_timer_ptr->timer_next = timer_list_ptr->timer_next;
+          timer_list_ptr->timer_next = resume_timer_ptr;
+          break;
+        }
+        else{
+          //next timer in timer_list
+        }
+      }
+      else{
+        timer_list_ptr->timer_next = resume_timer_ptr;
+        resume_timer_ptr->timer_next = NULL;
+        break;
+      }
+    }
+    else if(timer_list_ptr == timer_list){
+      resume_timer_ptr->timer_next=timer_list_ptr;
+      timer_list = timer_list_ptr = resume_timer_ptr;
+      break;
+    }
+
+    timer_list_ptr = timer_list_ptr->timer_next;
+  }
+
+  //we no longer need to block interrupts
+  ETS_INTR_UNLOCK();
+
+  return SWTMR_OK;
+}
+
+static void timer_register_task(task_param_t param, uint8 priority){
+  if(timer_registry.data_ptr==NULL){
+    if(!dynarr_init(&timer_registry, TIMER_REGISTRY_INITIAL_SIZE, sizeof(os_timer_t*))){
+      SWTMR_ERR("timer registry init Fail!");
+      return;
+    }
+  }
+
+  os_timer_t* timer_ptr = NULL;
+
+  //if a timer pointer is provided, override normal queue processing behavior
+  if(param != 0){
+    timer_ptr = (os_timer_t*)param;
+  }
+  else{
+    //process an item in the register queue
+    if(register_queue == NULL){
+      /**/SWTMR_ERR("ERROR: REGISTER QUEUE EMPTY");
+      return;
+    }
+
+    registry_queue_t* queue_temp = register_queue;
+    register_queue = register_queue->next;
+
+    timer_ptr = queue_temp->timer_ptr;
+
+    c_free(queue_temp);
+
+    if(register_queue != NULL){
+      SWTMR_DBG("register_queue not empty, posting task");
+      task_post_low(timer_reg_task_id, false);
+    }
+  }
+
+  os_timer_t** suspended_tmr_ptr = timer_suspended_check(timer_ptr);
+  if(suspended_tmr_ptr != NULL){
+    if(!dynarr_remove(&suspended_timers, suspended_tmr_ptr)){
+      SWTMR_ERR("failed to remove %p from suspend registry", suspended_tmr_ptr);
+    }
+    SWTMR_DBG("removed timer from suspended timers");
+  }
+
+  if(timer_registry_check(timer_ptr) != NULL){
+    /**/SWTMR_DBG("timer(%p) found in registry, returning", timer_ptr);
+    return;
+  }
+
+  if(!dynarr_add(&timer_registry, &timer_ptr, sizeof(timer_ptr))){
+    /**/SWTMR_ERR("Registry append failed");
+    return;
+  }
+
+  return;
+}
+
+static inline os_timer_t** timer_registry_check(os_timer_t* timer_ptr){
+  if(timer_registry.data_ptr == NULL){
+    return NULL;
+  }
+  if(timer_registry.used > 0){
+
+    os_timer_t** timer_registry_array = timer_registry.data_ptr;
+
+    for(uint32 i=0; i < timer_registry.used; i++){
+      if(timer_registry_array[i] == timer_ptr){
+        /**/SWTMR_DBG("timer(%p) is registered", timer_registry_array[i]);
+        return &timer_registry_array[i];
+      }
+    }
+  }
+
+  return NULL;
+}
+
+static inline void timer_registry_remove_unarmed(void){
+  if(timer_registry.data_ptr == NULL){
+    return;
+  }
+  if(timer_registry.used > 0){
+    os_timer_t** timer_registry_array = timer_registry.data_ptr;
+    for(uint32 i=0; i < timer_registry.used; i++){
+      if(timer_armed_check(timer_registry_array[i]) == FALSE){
+        timer_unregister_task((task_param_t)timer_registry_array[i], false);
+      }
+    }
+  }
+}
+
+
+static inline os_timer_t** timer_suspended_check(os_timer_t* timer_ptr){
+  if(suspended_timers.data_ptr == NULL){
+    return NULL;
+  }
+  if(suspended_timers.used > 0){
+
+    os_timer_t** suspended_timer_array = suspended_timers.data_ptr;
+
+    for(uint32 i=0; i < suspended_timers.used; i++){
+      if(suspended_timer_array[i] == timer_ptr){
+        return &suspended_timer_array[i];
+      }
+    }
+  }
+
+  return NULL;
+}
+
+static void timer_unregister_task(task_param_t param, uint8 priority){
+  if(timer_registry.data_ptr == NULL){
+    return;
+  }
+  os_timer_t* timer_ptr = NULL;
+
+  if(param != false){
+    timer_ptr = (os_timer_t*)param;
+  }
+  else{
+
+    if(unregister_queue == NULL) {
+      SWTMR_ERR("ERROR register queue empty");
+      return;
+    }
+      registry_queue_t* queue_temp = unregister_queue;
+      timer_ptr = queue_temp->timer_ptr;
+      unregister_queue = unregister_queue->next;
+      c_free(queue_temp);
+      if(unregister_queue != NULL){
+        SWTMR_DBG("unregister_queue not empty, posting task");
+        task_post_low(timer_unreg_task_id, false);
+      }
+  }
+  if(timer_armed_check(timer_ptr) == TRUE){
+    SWTMR_DBG("%p still armed, can't remove from registry", timer_ptr);
+    return;
+  }
+
+
+  os_timer_t** registry_ptr = timer_registry_check(timer_ptr);
+  if(registry_ptr != NULL){
+    if(!dynarr_remove(&timer_registry, registry_ptr)){
+
+      /**/SWTMR_ERR("Failed to remove timer from registry");
+      /**/SWTMR_DBG("registry_ptr = %p", registry_ptr);
+      return;
+    }
+  }
+  else{
+    //timer not in registry
+  }
+  return;
+}
+
+/*      Global Function Definitions      */
+
+#if defined(SWTMR_DEBUG)
+
+void swtmr_print_registry(void){
+  volatile uint32 frc2_count = RTC_REG_READ(FRC2_COUNT_ADDRESS);
+  uint32 time_till_fire = 0;
+  uint32 time = system_get_time();
+
+  timer_registry_remove_unarmed();
+  time = system_get_time()-time;
+
+  /**/SWTMR_DBG("registry_remove_unarmed_timers() took %u us", time);
+
+  os_timer_t** timer_array = timer_registry.data_ptr;
+
+  c_printf("\n  array used(%u)/size(%u)\ttotal size(bytes)=%u\n  FRC2 COUNT %u\n",
+      timer_registry.used, timer_registry.array_size, timer_registry.array_size * timer_registry.data_size, frc2_count);
+  c_printf("\n Registered timer array contents:\n");
+  c_printf(" %-5s  %-10s  %-10s  %-13s  %-10s  %-10s  %-10s\n", "idx", "ptr", "expire", "period(tick)", "period(ms)", "fire(tick)", "fire(ms)");
+
+  for(uint32 i=0; i < timer_registry.used; i++){
+     time_till_fire = (timer_array[i]->timer_expire - frc2_count);
+     c_printf(" %-5d  %-10p  %-10d  %-13d  %-10d  %-10d  %-10d\n", i, timer_array[i], timer_array[i]->timer_expire, timer_array[i]->timer_period, (uint32)(timer_array[i]->timer_period/312.5), time_till_fire, (uint32)(time_till_fire/312.5));
+
+   }
+
+  return;
+}
+
+void swtmr_print_suspended(void){
+  os_timer_t** susp_timer_array = suspended_timers.data_ptr;
+
+  c_printf("\n array used(%u)/size(%u)\ttotal size(bytes)=%u\n",
+      suspended_timers.used, suspended_timers.array_size, suspended_timers.array_size * suspended_timers.data_size);
+  c_printf("\n Suspended timer array contents:\n");
+  c_printf(" %-5s  %-10s  %-15s  %-15s  %-14s  %-10s\n", "idx", "ptr", "time left(tick)", "time left(ms)", "period(tick)", "period(ms)");
+
+  for(uint32 i=0; i < suspended_timers.used; i++){
+    c_printf(" %-5d  %-10p  %-15d  %-15d  %-14d  %-10d\n", i, susp_timer_array[i], susp_timer_array[i]->timer_expire, (uint32)(susp_timer_array[i]->timer_expire/312.5), susp_timer_array[i]->timer_period, (uint32)(susp_timer_array[i]->timer_period/312.5));
+
+   }
+  return;
+}
+
+void swtmr_print_timer_list(void){
+  volatile uint32 frc2_count=RTC_REG_READ(FRC2_COUNT_ADDRESS);
+  os_timer_t* timer_list_ptr=NULL;
+  uint32 time_till_fire=0;
+  c_printf("\n\tcurrent FRC2 count:%u\n", frc2_count);
+  c_printf(" timer_list contents:\n");
+  c_printf(" %-10s  %-10s  %-10s  %-10s  %-10s  %-10s  %-10s\n", "ptr", "expire", "period", "func", "arg", "fire(tick)", "fire(ms)");
+
+  ETS_INTR_LOCK();
+
+  timer_list_ptr=timer_list;
+
+  while(timer_list_ptr != NULL){
+    time_till_fire=(timer_list_ptr->timer_expire - frc2_count) / 312.5;
+
+  c_printf(" %-10p  %-10u  %-10u  %-10p  %-10p  %-10u  %-10u\n",
+        timer_list_ptr, (uint32)(timer_list_ptr->timer_expire),
+        (uint32)(timer_list_ptr->timer_period ), timer_list_ptr->timer_func,
+        timer_list_ptr->timer_arg, (timer_list_ptr->timer_expire - frc2_count), time_till_fire);
+
+    timer_list_ptr=timer_list_ptr->timer_next;
+  }
+  ETS_INTR_UNLOCK();
+  c_printf(" NOTE: some timers in the above list belong to the SDK and can not be suspended\n");
+  return;
+}
+
+#endif
+
+int swtmr_suspend(os_timer_t* timer_ptr){
+  int return_value = SWTMR_OK;
+
+  if(timer_ptr != NULL){
+    // Timer pointer was provided, suspending specified timer
+
+    return_value = timer_do_suspend(timer_ptr);
+
+    if(return_value != SWTMR_OK){
+      return return_value;
+    }
+  }
+  else{
+    //timer pointer not found, suspending all timers
+
+    if(timer_registry.data_ptr == NULL){
+      return SWTMR_REGISTRY_NO_REGISTERED_TIMERS;
+    }
+
+    timer_registry_remove_unarmed();
+
+    os_timer_t** tmr_reg_arr = timer_registry.data_ptr;
+    os_timer_t* temp_ptr = tmr_reg_arr[0];
+
+    while(temp_ptr != NULL){
+      return_value = timer_do_suspend(temp_ptr);
+
+      if(return_value != SWTMR_OK){
+        return return_value;
+      }
+
+      temp_ptr = tmr_reg_arr[0];
+    }
+  }
+  return return_value;
+}
+
+int swtmr_resume(os_timer_t* timer_ptr){
+
+  if(suspended_timers.data_ptr == NULL){
+    return SWTMR_SUSPEND_NO_SUSPENDED_TIMERS;
+  }
+
+  os_timer_t** suspended_tmr_array = suspended_timers.data_ptr;
+  os_timer_t** suspended_timer_ptr = NULL;
+  int retval=SWTMR_OK;
+
+  if(timer_ptr != NULL){
+    suspended_timer_ptr = timer_suspended_check(timer_ptr);
+    if(suspended_timer_ptr == NULL){
+      //timer not suspended
+      return SWTMR_SUSPEND_TIMER_NOT_SUSPENDED;
+    }
+
+    retval = timer_do_resume_single(suspended_timer_ptr);
+    if(retval != SWTMR_OK){
+      return retval;
+    }
+  }
+  else{
+    suspended_timer_ptr = &suspended_tmr_array[0];
+
+    while(suspended_timers.used > 0){
+      retval = timer_do_resume_single(suspended_timer_ptr);
+      if(retval != SWTMR_OK){
+        SWTMR_ERR("Unable to continue resuming timers, error(%u)", retval);
+        return retval;
+      }
+      suspended_timer_ptr = &suspended_tmr_array[0];
+    }
+  }
+  return SWTMR_OK;
+}
+
+void swtmr_register(void* timer_ptr){
+  if(timer_ptr == NULL){
+    SWTMR_DBG("error: timer_ptr is NULL");
+    return;
+  }
+
+  registry_queue_t* queue_temp = c_zalloc(sizeof(registry_queue_t));
+
+  if(queue_temp == NULL){
+    SWTMR_ERR("MALLOC FAIL! req:%u, free:%u", sizeof(registry_queue_t), system_get_free_heap_size());
+    return;
+  }
+  queue_temp->timer_ptr = timer_ptr;
+
+  if(register_queue == NULL){
+    register_queue = queue_temp;
+
+    if(timer_reg_task_id == false) timer_reg_task_id = task_get_id(timer_register_task);
+    task_post_low(timer_reg_task_id, false);
+    SWTMR_DBG("queue empty, adding timer(%p) to queue and posting task", timer_ptr);
+  }
+  else{
+    registry_queue_t* register_queue_tail = register_queue;
+
+    while(register_queue_tail->next != NULL){
+      register_queue_tail = register_queue_tail->next;
+    }
+
+    register_queue_tail->next = queue_temp;
+    SWTMR_DBG("queue NOT empty, appending timer(%p) to queue", timer_ptr);
+  }
+
+  return;
+}
+
+void swtmr_unregister(void* timer_ptr){
+  if(timer_ptr == NULL){
+    SWTMR_DBG("error: timer_ptr is NULL");
+    return;
+  }
+
+  registry_queue_t* queue_temp = c_zalloc(sizeof(registry_queue_t));
+
+  if(queue_temp == NULL){
+    SWTMR_ERR("MALLOC FAIL! req:%u, free:%u", sizeof(registry_queue_t), system_get_free_heap_size());
+    return;
+  }
+  queue_temp->timer_ptr=timer_ptr;
+
+  if(unregister_queue == NULL){
+    unregister_queue = queue_temp;
+    if(timer_unreg_task_id==false) timer_unreg_task_id=task_get_id(timer_unregister_task);
+    task_post_low(timer_unreg_task_id, false);
+    SWTMR_DBG("queue empty, adding timer(%p) to queue and posting task", timer_ptr);
+  }
+  else{
+    registry_queue_t* unregister_queue_tail=unregister_queue;
+    while(unregister_queue_tail->next != NULL){
+      unregister_queue_tail=unregister_queue_tail->next;
+    }
+    unregister_queue_tail->next = queue_temp;
+//    SWTMR_DBG("queue NOT empty, appending timer(%p) to queue", timer_ptr);
+  }
+
+  return;
+}
+
+const char* swtmr_errorcode2str(int error_value){
+#ifdef USE_SWTMR_ERROR_STRINGS
+  if(SWTMR_ERROR_STRINGS[error_value] == NULL){
+    SWTMR_ERR("error string %d not found", error_value);
+    return NULL;
+  }
+  else{
+    return SWTMR_ERROR_STRINGS[error_value];
+  }
+#else
+  SWTMR_ERR("error(%u)", error_value);
+  return "ERROR! for more info, use debug build";
+#endif
+
+}
+
+bool swtmr_suspended_test(os_timer_t* timer_ptr){
+  os_timer_t** test_var = timer_suspended_check(timer_ptr);
+  if(test_var == NULL){
+    return false;
+  }
+  return true;
+}
+
+#endif

+ 70 - 0
docs/en/modules/node.md

@@ -127,6 +127,11 @@ node.dsleep(1000000, 4)
 node.dsleep(nil,4)
 node.dsleep(nil,4)
 ```
 ```
 
 
+#### See also
+[`wifi.suspend()`](wifi.md#wifisuspend)
+[`wifi.resume()`](wifi.md#wifiresume)
+[`node.sleep()`](#nodesleep)
+
 ## node.flashid()
 ## node.flashid()
 
 
 Returns the flash chip ID.
 Returns the flash chip ID.
@@ -321,6 +326,71 @@ target CPU frequency (number)
 node.setcpufreq(node.CPU80MHZ)
 node.setcpufreq(node.CPU80MHZ)
 ```
 ```
 
 
+
+## node.sleep()
+
+Put NodeMCU in light sleep mode to reduce current consumption. 
+
+* NodeMCU can not enter light sleep mode if wifi is suspended.
+* All active timers will be suspended and then resumed when NodeMCU wakes from sleep. 
+* Any previously suspended timers will be resumed when NodeMCU wakes from sleep.
+
+#### Syntax
+`node.sleep({wake_gpio[, duration, int_type, resume_cb, preserve_mode]})`
+
+#### Parameters
+- `duration` Sleep duration in microseconds(μs). If a sleep duration of `0` is specified, suspension will be indefinite (Range: 0 or 50000 - 268435454 μs (0:4:28.000454))
+- `wake_pin` 1-12, pin to attach wake interrupt to. Note that pin 0(GPIO 16) does not support interrupts. 
+ - If sleep duration is indefinite, `wake_pin` must be specified
+ - Please refer to the [`GPIO module`](gpio.md) for more info on the pin map.
+- `int_type` type of interrupt that you would like to wake on. (Optional, Default: `node.INT_LOW`)
+ - valid interrupt modes:
+  - `node.INT_UP`   Rising edge
+  - `node.INT_DOWN` Falling edge
+  - `node.INT_BOTH` Both edges
+  - `node.INT_LOW`  Low level
+  - `node.INT_HIGH` High level
+- `resume_cb` Callback to execute when WiFi wakes from suspension. (Optional)
+- `preserve_mode` preserve current WiFi mode through node sleep. (Optional, Default: true)  
+ - If true, Station and StationAP modes will automatically reconnect to previously configured Access Point when NodeMCU resumes.
+ - If false, discard WiFi mode and leave NodeMCU in `wifi.NULL_MODE`. WiFi mode will be restored to original mode on restart.
+
+#### Returns
+- `nil`
+
+#### Example
+
+```lua
+
+--Put NodeMCU in light sleep mode indefinitely with resume callback and wake interrupt
+ cfg={}
+ cfg.wake_pin=3
+ cfg.resume_cb=function() print("WiFi resume") end
+
+ node.sleep(cfg)
+
+--Put NodeMCU in light sleep mode with interrupt, resume callback and discard WiFi mode
+ cfg={}
+ cfg.wake_pin=3 --GPIO0
+ cfg.resume_cb=function() print("WiFi resume") end
+ cfg.preserve_mode=false
+
+ node.sleep(cfg)
+
+--Put NodeMCU in light sleep mode for 10 seconds with resume callback
+ cfg={}
+ cfg.duration=10*1000*1000
+ cfg.resume_cb=function() print("WiFi resume") end
+
+ node.sleep(cfg)
+
+```
+
+#### See also
+[`wifi.suspend()`](wifi.md#wifisuspend)
+[`wifi.resume()`](wifi.md#wifiresume)
+[`node.dsleep()`](#nodedsleep)
+
 ## node.stripdebug()
 ## node.stripdebug()
 
 
 Controls the amount of debug information kept during [`node.compile()`](#nodecompile), and allows removal of debug information from already compiled Lua code.
 Controls the amount of debug information kept during [`node.compile()`](#nodecompile), and allows removal of debug information from already compiled Lua code.

+ 118 - 0
docs/en/modules/tmr.md

@@ -62,9 +62,11 @@ Functions supported in timer object:
 - [`t:alarm()`](#tmralarm)
 - [`t:alarm()`](#tmralarm)
 - [`t:interval()`](#tmrinterval)
 - [`t:interval()`](#tmrinterval)
 - [`t:register()`](#tmrregister)
 - [`t:register()`](#tmrregister)
+- [`t:resume()`](#tmrresume)
 - [`t:start()`](#tmrstart)
 - [`t:start()`](#tmrstart)
 - [`t:state()`](#tmrstate)
 - [`t:state()`](#tmrstate)
 - [`t:stop()`](#tmrstop)
 - [`t:stop()`](#tmrstop)
+- [`t:suspend()`](#tmrsuspend)
 - [`t:unregister()`](#tmrunregister)
 - [`t:unregister()`](#tmrunregister)
 
 
 #### Parameters
 #### Parameters
@@ -182,6 +184,61 @@ mytimer:start()
 - [`tmr.create()`](#tmrcreate)
 - [`tmr.create()`](#tmrcreate)
 - [`tmr.alarm()`](#tmralarm)
 - [`tmr.alarm()`](#tmralarm)
 
 
+## tmr.resume()
+
+Resume an individual timer.
+
+Resumes a timer that has previously been suspended with either `tmr.suspend` or `tmr.suspend_all` 
+
+#### Syntax
+`tmr.resume(id/ref)`
+
+#### Parameters
+`id/ref` timer id (0-6) or object, obsolete for OO API (→ [`tmr.create()`](#tmrcreate))
+
+#### Returns
+`true` if timer was resumed successfully 
+
+#### Example
+```lua
+--resume timer mytimer
+mytimer:resume()
+
+--alternate metod
+tmr.resume(mytimer)
+
+```
+#### See also
+[`tmr.suspend()`](#tmrsuspend)
+[`tmr.suspend_all()`](#tmrsuspendall)
+[`tmr.resume_all()`](#tmrresumeall)
+
+## tmr.resume_all()
+
+Resume all timers.
+
+Resumes all timers including those previously been suspended with either `tmr.suspend` or `tmr.suspend_all` 
+
+#### Syntax
+`tmr.resume_all()`
+
+#### Parameters
+none
+
+#### Returns
+`true` if timers were resumed successfully 
+
+#### Example
+```lua
+--resume all previously suspended timers
+tmr.resume_all()
+
+```
+#### See also
+[`tmr.suspend()`](#tmrsuspend)
+[`tmr.suspend_all()`](#tmrsuspendall)
+[`tmr.resume()`](#tmrresume)
+
 ## tmr.softwd()
 ## tmr.softwd()
 
 
 Provides a simple software watchdog, which needs to be re-armed or disabled before it expires, or the system will be restarted.
 Provides a simple software watchdog, which needs to be re-armed or disabled before it expires, or the system will be restarted.
@@ -279,6 +336,67 @@ if not mytimer:stop() then print("timer not stopped, not registered?") end
 - [`tmr.stop()`](#tmrstop)
 - [`tmr.stop()`](#tmrstop)
 - [`tmr.unregister()`](#tmrunregister)
 - [`tmr.unregister()`](#tmrunregister)
 
 
+## tmr.suspend()
+
+Suspend an armed timer. 
+
+* Timers can be suspended at any time after they are armed.
+* If a timer is rearmed with `tmr.start` or `tmr.alarm` any matching suspended timers will be discarded.
+
+#### Syntax
+`tmr.suspend(id/ref)`
+
+#### Parameters
+`id/ref` timer id (0-6) or object, obsolete for OO API (→ [`tmr.create()`](#tmrcreate))
+
+#### Returns
+`true` if timer was resumed successfully 
+
+#### Example
+```lua
+--suspend timer mytimer
+mytimer:suspend()
+
+--alternate metod
+tmr.suspend(mytimer)
+
+```
+#### See also
+[`tmr.suspend_all()`](#tmrsuspendall)
+[`tmr.resume()`](#tmrresume)
+[`tmr.resume_all()`](#tmrresumeall)
+
+
+## tmr.suspend_all()
+
+Suspend all currently armed timers. 
+
+!!! Warning
+	This function suspends ALL active timers, including any active timers started by the NodeMCU subsystem or other modules. this may cause parts of your program to stop functioning properly. 
+	USE THIS FUNCTION AT YOUR OWN RISK!
+
+
+#### Syntax
+`tmr.suspend_all()`
+
+#### Parameters
+none
+
+#### Returns
+`true` if timers were suspended successfully 
+
+#### Example
+```lua
+--suspend timer mytimer
+tmr.suspend_all()
+
+```
+#### See also
+[`tmr.suspendl()`](#tmrsuspend)
+[`tmr.resume()`](#tmrresume)
+[`tmr.resume_all()`](#tmrresumeall)
+
+
 ## tmr.time()
 ## tmr.time()
 
 
 Returns the system uptime, in seconds. Limited to 31 bits, after that it wraps around back to zero.
 Returns the system uptime, in seconds. Limited to 31 bits, after that it wraps around back to zero.

+ 87 - 1
docs/en/modules/wifi.md

@@ -74,6 +74,37 @@ The current physical mode as one of `wifi.PHYMODE_B`, `wifi.PHYMODE_G` or `wifi.
 #### See also
 #### See also
 [`wifi.setphymode()`](#wifisetphymode)
 [`wifi.setphymode()`](#wifisetphymode)
 
 
+## wifi.resume()
+
+Wake up WiFi from suspended state or cancel pending wifi suspension
+
+#### Syntax
+`wifi.resume([resume_cb])`
+
+#### Parameters
+- `resume_cb` Callback to execute when WiFi wakes from suspension. 
+ !!! note "Note:"
+
+    Any previously provided callbacks will be replaced!
+
+#### Returns
+`nil`
+
+#### Example
+
+```lua
+--Resume wifi from timed or indefinite sleep
+wifi.resume()
+
+--Resume wifi from timed or indefinite sleep w/ resume callback
+wifi.resume(function() print("WiFi resume") end)
+```
+
+#### See also
+[`wifi.suspend()`](#wifisuspend)
+[`node.sleep()`](node.md#nodesleep)
+[`node.dsleep()`](node.md#nodedsleep)
+
 ## wifi.setmode()
 ## wifi.setmode()
 
 
 Configures the WiFi mode to use. NodeMCU can run in one of four WiFi modes:
 Configures the WiFi mode to use. NodeMCU can run in one of four WiFi modes:
@@ -222,6 +253,61 @@ none
 #### See also
 #### See also
 [`wifi.startsmart()`](#wifistartsmart)
 [`wifi.startsmart()`](#wifistartsmart)
 
 
+## wifi.suspend()
+
+Suspend Wifi to reduce current consumption. 
+
+This function is also useful for preventing WiFi stack related crashes when executing functions or tasks that take longer than ~500ms 
+
+#### Syntax
+`wifi.suspend({duration[, suspend_cb, resume_cb, preserve_mode]})`
+
+#### Parameters
+- `duration` Suspend duration in microseconds(μs). If a suspend duration of `0` is specified, suspension will be indefinite (Range: 0 or 50000 - 268435454 μs (0:4:28.000454))
+- `suspend_cb` Callback to execute when WiFi is suspended. (Optional)
+- `resume_cb` Callback to execute when WiFi wakes from suspension. (Optional)
+- `preserve_mode` preserve current WiFi mode through node sleep. (Optional, Default: true)  
+ - If true, Station and StationAP modes will automatically reconnect to previously configured Access Point when NodeMCU resumes.
+ - If false, discard WiFi mode and leave NodeMCU in [`wifi.NULL_MODE`](#wifigetmode). WiFi mode will be restored to original mode on restart.
+
+#### Returns
+- `suspend_state` if no parameters are provided, current WiFi suspension state will be returned
+ - States:
+  - `0` WiFi is awake.
+  - `1` WiFi suspension is pending. (Waiting for idle task)
+  - `2` WiFi is suspended.
+
+
+#### Example
+
+```lua
+--get current wifi suspension state
+print(wifi.suspend())
+
+--Suspend WiFi for 10 seconds with suspend/resume callbacks
+ cfg={}
+ cfg.duration=10*1000*1000
+ cfg.resume_cb=function() print("WiFi resume") end
+ cfg.suspend_cb=function() print("WiFi suspended") end
+
+ wifi.suspend(cfg)
+
+--Suspend WiFi for 10 seconds with suspend/resume callbacks and discard WiFi mode
+ cfg={}
+ cfg.duration=10*1000*1000
+ cfg.resume_cb=function() print("WiFi resume") end
+ cfg.suspend_cb=function() print("WiFfi suspended") end
+ cfg.preserve_mode=false
+
+ wifi.suspend(cfg)
+
+```
+
+#### See also
+[`wifi.resume()`](#wifiresume)
+[`node.sleep()`](node.md#nodesleep)
+[`node.dsleep()`](node.md#nodedsleep)
+
 # wifi.sta Module
 # wifi.sta Module
 
 
 ## wifi.sta.autoconnect()
 ## wifi.sta.autoconnect()
@@ -1010,7 +1096,7 @@ The current state which can be one of the following:
 
 
 ## wifi.ap.config()
 ## wifi.ap.config()
 
 
-Sets SSID and password in AP mode. Be sure to make the password at least 8 characters long! If you don't it will default to *no* password and not set the SSID! It will still work as an access point but use a default SSID like e.g. NODE-9997C3.
+Sets SSID and password in AP mode. Be sure to make the password at least 8 characters long! If you don't it will default to *no* password and not set the SSID! It will still work as an access point but use a default SSID like e.g. NODE_9997C3.
 
 
 #### Syntax
 #### Syntax
 `wifi.ap.config(cfg)`
 `wifi.ap.config(cfg)`

+ 2 - 0
sdk-overrides/include/ets_sys.h

@@ -9,4 +9,6 @@ int ets_sprintf(char *str, const char *format, ...)  __attribute__ ((format (pri
 
 
 int ets_vsprintf (char *d, const char *s, va_list ap);
 int ets_vsprintf (char *d, const char *s, va_list ap);
 
 
+extern ETSTimer *timer_list;
+
 #endif /* SDK_OVERRIDES_INCLUDE_ETS_SYS_H_ */
 #endif /* SDK_OVERRIDES_INCLUDE_ETS_SYS_H_ */

+ 11 - 0
sdk-overrides/include/osapi.h

@@ -16,4 +16,15 @@ void call_user_start(void);
 
 
 #include_next "osapi.h"
 #include_next "osapi.h"
 
 
+#ifdef ENABLE_TIMER_SUSPEND
+extern void swtmr_register(void* timer_ptr);
+#undef os_timer_arm
+#define os_timer_arm(timer_ptr, duration, mode) do{swtmr_register(timer_ptr); \
+  ets_timer_arm_new(timer_ptr, duration, mode, 1);}while(0);
+
+extern void swtmr_unregister(void* timer_ptr);
+#undef os_timer_disarm
+#define os_timer_disarm(timer_ptr) do{swtmr_unregister(timer_ptr); ets_timer_disarm(timer_ptr);}while(0);
+#endif
+
 #endif
 #endif

+ 9 - 0
sdk-overrides/include/user_interface.h

@@ -15,4 +15,13 @@ enum ext_flash_size_map {
 // Documented in section 4.5 of 9b-esp8266_low_power_solutions_en.pdf
 // Documented in section 4.5 of 9b-esp8266_low_power_solutions_en.pdf
 void system_deep_sleep_instant(uint32 time_in_us);
 void system_deep_sleep_instant(uint32 time_in_us);
 
 
+//force sleep API
+#define FPM_SLEEP_MAX_TIME 268435455 //0xFFFFFFF
+void wifi_fpm_set_wakeup_cb(void (*fpm_wakeup_cb_func)(void));
+bool fpm_is_open(void);
+bool fpm_rf_is_closed(void);
+uint8 get_fpm_auto_sleep_flag(void);
+
+
+
 #endif /* SDK_OVERRIDES_INCLUDE_USER_INTERFACE_H_ */
 #endif /* SDK_OVERRIDES_INCLUDE_USER_INTERFACE_H_ */