Преглед на файлове

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 години
родител
ревизия
41a5736d78

+ 9 - 1
app/Makefile

@@ -45,7 +45,11 @@ SUBDIRS= 					\
 	http					\
 	fatfs					\
 	esp-gdbstub				\
-	websocket
+	websocket				\
+	swTimer					\
+	misc					\
+	pm						\
+	
 
 endif # } PDIR
 
@@ -90,11 +94,15 @@ COMPONENTS_eagle.app.v6 = 			\
 	dhtlib/libdhtlib.a 			\
 	tsl2561/tsl2561lib.a			\
 	http/libhttp.a				\
+	pm/libpm.a					\
 	websocket/libwebsocket.a		\
 	esp-gdbstub/libgdbstub.a		\
 	net/libnodemcu_net.a			\
 	mbedtls/libmbedtls.a      \
 	modules/libmodules.a			\
+	swTimer/libswtimer.a			\
+	misc/libmisc.a					\
+	
 
 # 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

+ 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_EVENT_MONITOR_DISCONNECT_REASON_LIST_ENABLE
 
+#define ENABLE_TIMER_SUSPEND
+#define PMSLEEP_ENABLE
+
+
 #define STRBUF_DEFAULT_INCREMENT 32
 
 #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 ../sjson
 INCLUDES += -I ../websocket
+INCLUDES += -I ../pm
 PDIR := ../$(PDIR)
 sinclude $(PDIR)Makefile
 

+ 34 - 13
app/modules/node.c

@@ -74,19 +74,36 @@ static int node_deepsleep( lua_State* L )
   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 )
 {
@@ -570,6 +587,10 @@ static const LUA_REG_TYPE node_map[] =
 {
   { LSTRKEY( "restart" ), LFUNCVAL( node_restart ) },
   { LSTRKEY( "dsleep" ), LFUNCVAL( node_deepsleep ) },
+#ifdef PMSLEEP_ENABLE
+  { LSTRKEY( "sleep" ), LFUNCVAL( node_sleep ) },
+  PMSLEEP_INT_MAP,
+#endif
   { LSTRKEY( "info" ), LFUNCVAL( node_info ) },
   { LSTRKEY( "chipid" ), LFUNCVAL( node_chipid ) },
   { LSTRKEY( "flashid" ), LFUNCVAL( node_flashid ) },

+ 131 - 15
app/modules/tmr.c

@@ -53,12 +53,14 @@ tmr.softwd(int)
 #include "platform.h"
 #include "c_types.h"
 #include "user_interface.h"
+#include "swTimer/swTimer.h"
 
 #define TIMER_MODE_OFF 3
 #define TIMER_MODE_SINGLE 0
 #define TIMER_MODE_SEMI 2
 #define TIMER_MODE_AUTO 1
-#define TIMER_IDLE_FLAG (1<<7) 
+#define TIMER_IDLE_FLAG (1<<7)
+
 
 #define STRINGIFY_VAL(x) #x
 #define STRINGIFY(x) STRINGIFY_VAL(x)
@@ -172,14 +174,14 @@ static int tmr_register(lua_State* L){
 	lua_pushvalue(L, 4);
 	sint32_t ref = luaL_ref(L, LUA_REGISTRYINDEX);
 	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
 	if(tmr->lua_ref != LUA_NOREF && tmr->lua_ref != ref)
 		luaL_unref(L, LUA_REGISTRYINDEX, tmr->lua_ref);
 	tmr->lua_ref = ref;
 	tmr->mode = mode|TIMER_IDLE_FLAG;
 	tmr->interval = interval;
-	ets_timer_setfn(&tmr->os, alarm_timer_common, tmr);
+	os_timer_setfn(&tmr->os, alarm_timer_common, tmr);
 	return 0;  
 }
 
@@ -197,7 +199,7 @@ static int tmr_start(lua_State* L){
 		lua_pushboolean(L, 0);
 	}else{
 		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);
 	}
 	return 1;
@@ -221,7 +223,7 @@ static int tmr_stop(lua_State* L){
 	//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;
-		ets_timer_disarm(&tmr->os);
+		os_timer_disarm(&tmr->os);
 		lua_pushboolean(L, 1);
 	}else{
 		lua_pushboolean(L, 0);
@@ -229,6 +231,73 @@ static int tmr_stop(lua_State* L){
 	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 )
 static int tmr_unregister(lua_State* L){
 	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)
-		ets_timer_disarm(&tmr->os);
+		os_timer_disarm(&tmr->os);
 	if(tmr->lua_ref != LUA_NOREF)
 		luaL_unref(L, LUA_REGISTRYINDEX, tmr->lua_ref);
 	tmr->lua_ref = LUA_NOREF;
@@ -256,8 +325,8 @@ static int tmr_interval(lua_State* L){
 	if(tmr->mode != TIMER_MODE_OFF){	
 		tmr->interval = interval;
 		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;
@@ -271,9 +340,15 @@ static int tmr_state(lua_State* L){
 		lua_pushnil(L);
 		return 1;
 	}
-	lua_pushboolean(L, (tmr->mode&TIMER_IDLE_FLAG)==0);
-	lua_pushinteger(L, tmr->mode&(~TIMER_IDLE_FLAG));
-	return 2;
+
+  lua_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
@@ -348,10 +423,27 @@ static int tmr_create( lua_State *L ) {
 	ud->lua_ref = LUA_NOREF;
 	ud->self_ref = LUA_NOREF;
 	ud->mode = TIMER_MODE_OFF;
-	ets_timer_disarm(&ud->os);
+	os_timer_disarm(&ud->os);
 	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
 
 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( "state" ),       LFUNCVAL( tmr_state ) },
 	{ 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( "__index" ),     LROVAL( tmr_dyn_map ) },
 	{ 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[] = {
 	{ LSTRKEY( "delay" ),        LFUNCVAL( tmr_delay ) },
 	{ LSTRKEY( "now" ),          LFUNCVAL( tmr_now ) },
@@ -376,11 +481,20 @@ static const LUA_REG_TYPE tmr_map[] = {
 	{ LSTRKEY( "register" ),     LFUNCVAL( tmr_register ) },
 	{ LSTRKEY( "alarm" ),        LFUNCVAL( tmr_alarm ) },
 	{ 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( "state" ),        LFUNCVAL( tmr_state ) },
 	{ LSTRKEY( "interval" ),     LFUNCVAL( tmr_interval ) },
 	{ 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_SEMI" ),   LNUMVAL( TIMER_MODE_SEMI ) },
 	{ 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].self_ref = LUA_REFNIL;
 		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);
 	}
 	last_rtc_time=system_get_rtc_time(); // Right now is time 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_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;
 }
 

+ 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"
 
-//wifi.sleep variables
-#define FPM_SLEEP_MAX_TIME 0xFFFFFFF
-static bool FLAG_wifi_force_sleep_enabled=0;
-
 #ifdef WIFI_SMART_ENABLE
 static void wifi_smart_succeed_cb(sc_status status, void *pdata){
   NODE_DBG("wifi_smart_succeed_cb is called.\n");
@@ -302,66 +298,92 @@ static int wifi_getphymode( lua_State* L )
   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
+    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()
 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( "setphymode" ),     LFUNCVAL( wifi_setphymode ) },
   { 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 ) },
 #ifdef WIFI_SMART_ENABLE 
   { 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__)
 #endif
 
+enum wifi_suspension_state
+{
+  WIFI_AWAKE = 0,
+  WIFI_SUSPENSION_PENDING = 1,
+  WIFI_SUSPENDED = 2
+};
+
+
+
 #ifdef WIFI_SDK_EVENT_MONITOR_ENABLE
   extern const LUA_REG_TYPE wifi_event_monitor_map[];
   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)
 ```
 
+#### See also
+[`wifi.suspend()`](wifi.md#wifisuspend)
+[`wifi.resume()`](wifi.md#wifiresume)
+[`node.sleep()`](#nodesleep)
+
 ## node.flashid()
 
 Returns the flash chip ID.
@@ -321,6 +326,71 @@ target CPU frequency (number)
 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()
 
 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:interval()`](#tmrinterval)
 - [`t:register()`](#tmrregister)
+- [`t:resume()`](#tmrresume)
 - [`t:start()`](#tmrstart)
 - [`t:state()`](#tmrstate)
 - [`t:stop()`](#tmrstop)
+- [`t:suspend()`](#tmrsuspend)
 - [`t:unregister()`](#tmrunregister)
 
 #### Parameters
@@ -182,6 +184,61 @@ mytimer:start()
 - [`tmr.create()`](#tmrcreate)
 - [`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()
 
 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.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()
 
 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
 [`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()
 
 Configures the WiFi mode to use. NodeMCU can run in one of four WiFi modes:
@@ -222,6 +253,61 @@ none
 #### See also
 [`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.autoconnect()
@@ -1010,7 +1096,7 @@ The current state which can be one of the following:
 
 ## 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
 `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);
 
+extern ETSTimer *timer_list;
+
 #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"
 
+#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

+ 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
 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_ */