pmSleep.c 13 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389
  1. #include <pm/pmSleep.h>
  2. #ifdef PMSLEEP_ENABLE
  3. #define STRINGIFY_VAL(x) #x
  4. #define STRINGIFY(x) STRINGIFY_VAL(x)
  5. //TODO: figure out why timed light_sleep doesn't work
  6. //holds duration error string
  7. //uint32 PMSLEEP_SLEEP_MAX_TIME=FPM_SLEEP_MAX_TIME-1;
  8. const char *PMSLEEP_DURATION_ERR_STR="duration: 0 or "STRINGIFY(PMSLEEP_SLEEP_MIN_TIME)"-"STRINGIFY(PMSLEEP_SLEEP_MAX_TIME)" us";
  9. /* INTERNAL VARIABLES */
  10. static void (*user_suspend_cb)(void) = NULL;
  11. static void (*user_resume_cb)(void) = NULL;
  12. static uint8 resume_opmode = 0;
  13. static os_timer_t wifi_suspended_test_timer;
  14. static uint8 autosleep_setting_temp = 0;
  15. static os_timer_t null_mode_check_timer;
  16. static pmSleep_param_t current_config;
  17. /* INTERNAL FUNCTION DECLARATIONS */
  18. static void suspend_all_timers(void);
  19. static void null_mode_check_timer_cb(void* arg);
  20. static void resume_all_timers(void);
  21. static inline void register_lua_cb(lua_State* L,int* cb_ref);
  22. static void resume_cb(void);
  23. static void wifi_suspended_timer_cb(int arg);
  24. /* INTERNAL FUNCTIONS */
  25. static void suspend_all_timers(void){
  26. #ifdef TIMER_SUSPEND_ENABLE
  27. extern void swtmr_suspend_timers();
  28. swtmr_suspend_timers();
  29. #endif
  30. return;
  31. }
  32. static void resume_all_timers(void){
  33. #ifdef TIMER_SUSPEND_ENABLE
  34. extern void swtmr_resume_timers();
  35. swtmr_resume_timers();
  36. #endif
  37. return;
  38. }
  39. static void null_mode_check_timer_cb(void* arg){
  40. if (wifi_get_opmode() == NULL_MODE){
  41. //check if uart 0 tx buffer is empty and uart 1 tx buffer is empty
  42. if(current_config.sleep_mode == LIGHT_SLEEP_T){
  43. if((READ_PERI_REG(UART_STATUS(0)) & (UART_TXFIFO_CNT<<UART_TXFIFO_CNT_S)) == 0 &&
  44. (READ_PERI_REG(UART_STATUS(1)) & (UART_TXFIFO_CNT<<UART_TXFIFO_CNT_S)) == 0){
  45. os_timer_disarm(&null_mode_check_timer);
  46. suspend_all_timers();
  47. //Ensure UART 0/1 TX FIFO is clear
  48. SET_PERI_REG_MASK(UART_CONF0(0), UART_TXFIFO_RST);//RESET FIFO
  49. CLEAR_PERI_REG_MASK(UART_CONF0(0), UART_TXFIFO_RST);
  50. SET_PERI_REG_MASK(UART_CONF0(1), UART_TXFIFO_RST);//RESET FIFO
  51. CLEAR_PERI_REG_MASK(UART_CONF0(1), UART_TXFIFO_RST);
  52. wifi_fpm_do_sleep(current_config.sleep_duration);
  53. return;
  54. }
  55. else{
  56. return;
  57. }
  58. }
  59. else{ //MODEM_SLEEP_T
  60. sint8 retval_wifi_fpm_do_sleep = wifi_fpm_do_sleep(current_config.sleep_duration); // Request WiFi suspension and store return value
  61. // If wifi_fpm_do_sleep success
  62. if (retval_wifi_fpm_do_sleep == 0){
  63. PMSLEEP_DBG("wifi_fpm_do_sleep success, starting wifi_suspend_test timer");
  64. os_timer_disarm(&wifi_suspended_test_timer);
  65. os_timer_setfn(&wifi_suspended_test_timer, (os_timer_func_t*)wifi_suspended_timer_cb, NULL);
  66. //The callback wifi_suspended_timer_cb detects when the esp8266 has successfully entered modem_sleep and executes the developer's suspend_cb.
  67. //Since this timer is only used in modem_sleep and will never be active outside of modem_sleep, it is unnecessary to register the cb with SWTIMER_REG_CB.
  68. os_timer_arm(&wifi_suspended_test_timer, 1, 1);
  69. }
  70. else{ // This should never happen. if it does, return the value for error reporting
  71. wifi_fpm_close();
  72. PMSLEEP_ERR("wifi_fpm_do_sleep returned %d", retval_wifi_fpm_do_sleep);
  73. }
  74. }
  75. os_timer_disarm(&null_mode_check_timer);
  76. return;
  77. }
  78. }
  79. //function to register a lua callback function in the LUA_REGISTRYINDEX for later execution
  80. static inline void register_lua_cb(lua_State* L, int* cb_ref){
  81. int ref = luaL_ref(L, LUA_REGISTRYINDEX);
  82. if( *cb_ref != LUA_NOREF) luaL_unref(L, LUA_REGISTRYINDEX, *cb_ref);
  83. *cb_ref = ref;
  84. }
  85. // C callback for bringing WiFi back from forced sleep
  86. static void resume_cb(void){
  87. PMSLEEP_DBG("START");
  88. //TODO: Add support for extended light sleep duration
  89. wifi_fpm_close(); // Disable force sleep API
  90. PMSLEEP_DBG("WiFi resume");
  91. resume_all_timers();
  92. //this section restores null mode auto sleep setting
  93. if(autosleep_setting_temp == 1) {
  94. wifi_fpm_auto_sleep_set_in_null_mode(autosleep_setting_temp);
  95. autosleep_setting_temp = 0;
  96. }
  97. //this section restores previous wifi mode
  98. if (resume_opmode == STATION_MODE || resume_opmode == SOFTAP_MODE || resume_opmode == STATIONAP_MODE){
  99. if (wifi_set_opmode_current(resume_opmode)){
  100. if (resume_opmode == STATION_MODE || resume_opmode == STATIONAP_MODE){
  101. wifi_station_connect(); // Connect to currently configured Access Point
  102. }
  103. PMSLEEP_DBG("WiFi mode restored");
  104. resume_opmode = 0; // reset variable to default value
  105. }
  106. }
  107. else{
  108. wifi_set_opmode_current(NULL_MODE);
  109. }
  110. //execute the external resume callback
  111. if (user_resume_cb != NULL){
  112. PMSLEEP_DBG("calling user resume cb (%p)", user_resume_cb);
  113. user_resume_cb();
  114. user_resume_cb = NULL;
  115. }
  116. PMSLEEP_DBG("END");
  117. return;
  118. }
  119. // This callback executes the suspended callback when Wifi suspension is in effect
  120. static void wifi_suspended_timer_cb(int arg){
  121. // check if wifi is suspended.
  122. if (pmSleep_get_state() == PMSLEEP_SUSPENDED){
  123. os_timer_disarm(&wifi_suspended_test_timer); // Stop rf_closed_timer
  124. PMSLEEP_DBG("WiFi is suspended");
  125. //execute the external suspended callback
  126. if (user_suspend_cb != NULL){
  127. PMSLEEP_DBG("calling user suspend cb (%p)", user_suspend_cb);
  128. user_suspend_cb();
  129. user_suspend_cb = NULL;
  130. }
  131. PMSLEEP_DBG("END");
  132. }
  133. }
  134. /* EXTERNAL FUNCTIONS */
  135. //this function executes the application developer's Lua callback
  136. void pmSleep_execute_lua_cb(int* cb_ref){
  137. if (*cb_ref != LUA_NOREF){
  138. lua_State* L = lua_getstate(); // Get Lua main thread pointer
  139. lua_rawgeti(L, LUA_REGISTRYINDEX, *cb_ref); // Push resume callback onto the stack
  140. lua_unref(L, *cb_ref); // Remove resume callback from registry
  141. *cb_ref = LUA_NOREF; // Update variable since reference is no longer valid
  142. luaL_pcallx(L, 0, 0); // Execute resume callback
  143. }
  144. }
  145. //this function checks current wifi suspension state and returns the result
  146. uint8 pmSleep_get_state(void){
  147. if (fpm_rf_is_closed()) return PMSLEEP_SUSPENDED;
  148. else if (fpm_is_open()) return PMSLEEP_SUSPENSION_PENDING;
  149. else return PMSLEEP_AWAKE;
  150. }
  151. //this function parses the lua configuration table provided by the application developer
  152. 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){
  153. lua_Integer Linteger_tmp = 0;
  154. if( cfg->sleep_mode == MODEM_SLEEP_T ){ //WiFi suspend
  155. lua_getfield(L, table_idx, "duration");
  156. if( !lua_isnil(L, -1) ){ /* found? */
  157. if( lua_isnumber(L, -1) ){
  158. lua_Integer Linteger=luaL_checkinteger(L, -1);
  159. luaL_argcheck(L,(((Linteger >= PMSLEEP_SLEEP_MIN_TIME) && (Linteger <= PMSLEEP_SLEEP_MAX_TIME)) ||
  160. (Linteger == 0)), table_idx, PMSLEEP_DURATION_ERR_STR);
  161. cfg->sleep_duration = (uint32)Linteger; // Get suspend duration
  162. }
  163. else{
  164. return luaL_argerror( L, table_idx, "duration: must be number" );
  165. }
  166. }
  167. else{
  168. return luaL_argerror( L, table_idx, PMSLEEP_DURATION_ERR_STR );
  169. }
  170. lua_pop(L, 1);
  171. lua_getfield(L, table_idx, "suspend_cb");
  172. if( !lua_isnil(L, -1) ){ /* found? */
  173. if( lua_isfunction(L, -1) ){
  174. lua_pushvalue(L, -1); // Push resume callback to the top of the stack
  175. register_lua_cb(L, suspend_lua_cb_ref);
  176. }
  177. else{
  178. return luaL_argerror( L, table_idx, "suspend_cb: must be function" );
  179. }
  180. }
  181. lua_pop(L, 1);
  182. }
  183. else if (cfg->sleep_mode == LIGHT_SLEEP_T){ //CPU suspend
  184. #ifdef TIMER_SUSPEND_ENABLE
  185. lua_getfield(L, table_idx, "wake_pin");
  186. if( !lua_isnil(L, -1) ){ /* found? */
  187. if( lua_isnumber(L, -1) ){
  188. Linteger_tmp=lua_tointeger(L, -1);
  189. luaL_argcheck(L, (platform_gpio_exists(Linteger_tmp) && Linteger_tmp > 0), table_idx, "wake_pin: Invalid interrupt pin");
  190. cfg->wake_pin = Linteger_tmp;
  191. }
  192. else{
  193. return luaL_argerror( L, table_idx, "wake_pin: must be number" );
  194. }
  195. }
  196. else{
  197. return luaL_argerror( L, table_idx, "wake_pin: must specify pin" );
  198. // else if(cfg->sleep_duration == 0){
  199. // return luaL_argerror( L, table_idx, "wake_pin: must specify pin if sleep duration is indefinite" );
  200. }
  201. lua_pop(L, 1);
  202. lua_getfield(L, table_idx, "int_type");
  203. if( !lua_isnil(L, -1) ){ /* found? */
  204. if( lua_isnumber(L, -1) ){
  205. Linteger_tmp=lua_tointeger(L, -1);
  206. luaL_argcheck(L, (Linteger_tmp == GPIO_PIN_INTR_ANYEDGE || Linteger_tmp == GPIO_PIN_INTR_HILEVEL
  207. || Linteger_tmp == GPIO_PIN_INTR_LOLEVEL || Linteger_tmp == GPIO_PIN_INTR_NEGEDGE
  208. || Linteger_tmp == GPIO_PIN_INTR_POSEDGE ), 1, "int_type: invalid interrupt type");
  209. cfg->int_type = Linteger_tmp;
  210. }
  211. else{
  212. return luaL_argerror( L, table_idx, "int_type: must be number" );
  213. }
  214. }
  215. else{
  216. cfg->int_type = GPIO_PIN_INTR_LOLEVEL;
  217. }
  218. lua_pop(L, 1);
  219. #endif
  220. }
  221. else{
  222. return luaL_error(L, "FPM Sleep mode not available");
  223. }
  224. lua_getfield(L, table_idx, "resume_cb");
  225. if( !lua_isnil(L, -1) ){ /* found? */
  226. if( lua_isfunction(L, -1) ){
  227. lua_pushvalue(L, -1); // Push resume callback to the top of the stack
  228. register_lua_cb(L, resume_lua_cb_ref);
  229. }
  230. else{
  231. return luaL_argerror( L, table_idx, "resume_cb: must be function" );
  232. }
  233. }
  234. lua_pop(L, 1);
  235. lua_getfield(L, table_idx, "preserve_mode");
  236. if( !lua_isnil(L, -1) ){ /* found? */
  237. if( lua_isboolean(L, -1) ){
  238. cfg->preserve_opmode=lua_toboolean(L, -1);
  239. }
  240. else{
  241. return luaL_argerror( L, table_idx, "preseve_mode: must be boolean" );
  242. }
  243. }
  244. else{
  245. cfg->preserve_opmode = true;
  246. }
  247. lua_pop(L, 1);
  248. //if sleep duration is zero, set indefinite sleep duration
  249. if( cfg->sleep_duration == 0 ){
  250. cfg->sleep_duration = FPM_SLEEP_MAX_TIME;
  251. }
  252. return 0;
  253. }
  254. //This function resumes ESP from MODEM_SLEEP
  255. void pmSleep_resume(void (*resume_cb_ptr)(void)){
  256. PMSLEEP_DBG("START");
  257. uint8 fpm_state = pmSleep_get_state();
  258. if(fpm_state>0){
  259. if(resume_cb_ptr != NULL){
  260. user_resume_cb = resume_cb_ptr;
  261. }
  262. wifi_fpm_do_wakeup(); // Wake up from sleep
  263. resume_cb(); // Finish WiFi wakeup
  264. }
  265. PMSLEEP_DBG("END");
  266. return;
  267. }
  268. //this function puts the ESP8266 into MODEM_SLEEP or LIGHT_SLEEP
  269. void pmSleep_suspend(pmSleep_param_t *cfg){
  270. PMSLEEP_DBG("START");
  271. lua_State* L = lua_getstate();
  272. #ifndef TIMER_SUSPEND_ENABLE
  273. if(cfg->sleep_mode == LIGHT_SLEEP_T){
  274. luaL_error(L, "timer suspend API is disabled, light sleep unavailable");
  275. return;
  276. }
  277. #endif
  278. uint8 current_wifi_mode = wifi_get_opmode(); // Get Current WiFi mode
  279. user_resume_cb = cfg->resume_cb_ptr; //pointer to hold address of user_cb
  280. user_suspend_cb = cfg->suspend_cb_ptr; //pointer to hold address of user_cb
  281. // If Preserve_wifi_mode parameter is TRUE and current WiFi mode is not NULL_MODE
  282. if (cfg->preserve_opmode && current_wifi_mode != 0){
  283. resume_opmode = current_wifi_mode;
  284. }
  285. if (current_wifi_mode == STATION_MODE || current_wifi_mode == STATIONAP_MODE){
  286. wifi_station_disconnect(); // Disconnect from Access Point
  287. }
  288. //the null mode sleep functionality interferes with the forced sleep API and must be disabled
  289. if(get_fpm_auto_sleep_flag() == 1){
  290. autosleep_setting_temp = 1;
  291. wifi_fpm_auto_sleep_set_in_null_mode(0);
  292. }
  293. // If wifi_set_opmode_current is successful
  294. if (wifi_set_opmode_current(NULL_MODE)){
  295. PMSLEEP_DBG("sleep_mode is %s", cfg->sleep_mode == MODEM_SLEEP_T ? "MODEM_SLEEP_T" : "LIGHT_SLEEP_T");
  296. wifi_fpm_set_sleep_type(cfg->sleep_mode);
  297. wifi_fpm_open(); // Enable force sleep API
  298. if (cfg->sleep_mode == LIGHT_SLEEP_T){
  299. #ifdef TIMER_SUSPEND_ENABLE
  300. if(platform_gpio_exists(cfg->wake_pin) && cfg->wake_pin > 0){
  301. PMSLEEP_DBG("Wake-up pin is %d\t interrupt type is %d", cfg->wake_pin, cfg->int_type);
  302. if((cfg->int_type != GPIO_PIN_INTR_ANYEDGE && cfg->int_type != GPIO_PIN_INTR_HILEVEL
  303. && cfg->int_type != GPIO_PIN_INTR_LOLEVEL && cfg->int_type != GPIO_PIN_INTR_NEGEDGE
  304. && cfg->int_type != GPIO_PIN_INTR_POSEDGE )){
  305. wifi_fpm_close();
  306. PMSLEEP_DBG("Invalid interrupt type");
  307. return;
  308. }
  309. GPIO_DIS_OUTPUT(pin_num[cfg->wake_pin]);
  310. PIN_FUNC_SELECT(pin_mux[cfg->wake_pin], pin_func[cfg->wake_pin]);
  311. wifi_enable_gpio_wakeup(pin_num[cfg->wake_pin], cfg->int_type);
  312. }
  313. else if(cfg->sleep_duration == FPM_SLEEP_MAX_TIME && cfg->wake_pin == 255){
  314. wifi_fpm_close();
  315. PMSLEEP_DBG("No wake-up pin defined");
  316. return;
  317. }
  318. #endif
  319. }
  320. wifi_fpm_set_wakeup_cb(resume_cb); // Set resume C callback
  321. memcpy(&current_config, cfg, sizeof(pmSleep_param_t));
  322. PMSLEEP_DBG("sleep duration is %d", current_config.sleep_duration);
  323. os_timer_disarm(&null_mode_check_timer);
  324. os_timer_setfn(&null_mode_check_timer, null_mode_check_timer_cb, false);
  325. //The function null_mode_check_timer_cb checks that the esp8266 has successfully changed the opmode to NULL_MODE prior to entering LIGHT_SLEEP
  326. //This callback doesn't need to be registered with SWTIMER_REG_CB since the timer will have terminated before entering LIGHT_SLEEP
  327. os_timer_arm(&null_mode_check_timer, 1, 1);
  328. }
  329. else{
  330. PMSLEEP_ERR("opmode change fail");
  331. }
  332. PMSLEEP_DBG("END");
  333. return;
  334. }
  335. #endif