Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Please document wifi.sleep function in official API docs #1115

Closed
alex3kov opened this issue Mar 4, 2016 · 34 comments
Closed

Please document wifi.sleep function in official API docs #1115

alex3kov opened this issue Mar 4, 2016 · 34 comments

Comments

@alex3kov
Copy link

alex3kov commented Mar 4, 2016

Did #725 and #731 made it into official public API? I don't see this function in the docs.
If it is official - can you document its behaviour please? What should users expect from it, what will wifi.sta.status() return when in sleep, do I need to set wifi config and/or connect anew after waking up, how it works with wifi.autoconnect(1) etc.
I tried to use it using cloud-built nodemcu-master-11-modules-2016-02-29-10-49-49-integer firmware - after putting wifi to sleep with wifi.sleep(1) wifi.sta.status() returns 255. After waking it up (wifi.sleep(0)) wifi.sta.status() also returns 255 and after trying to connect to WiFi I'm getting some garbage on serial console (using ESPlorer), losing serial communication with the module and have to restart it. Tried all of the above using either wifi.sleeptype(wifi.LIGHT_SLEEP) or wifi.sleeptype(wifi.MODEM_SLEEP) - same results. I'm not claiming it's a bug (yet) - because I don't know what to expect at this point :)

@dnc40085
Copy link
Contributor

dnc40085 commented Mar 4, 2016

I must have forgot to add the documentation after the PRs were merged.

After you bring wifi out of sleep with wifi.sleep(0), the command wifi.getmode() will return 0 indicating that the ESP8266 is in NULL_MODE. from there you just use wifi.setmode() to change back to Station, SoftAP, or StationAP mode and go about your business.

I'll submit a PR with the documentation as soon as I get the chance.

@TerryE
Copy link
Collaborator

TerryE commented Mar 4, 2016

@dnc40085 This issue of how to safely use non-compliant (see #991) modules is possibly linked to this. I'd like to chat to you about this either as an issue here, by email or Skype. Up to you. I want to document a simple sequence / process for winding down and restarting the WiFi / net stacks so that is you want to run something with interrupts masked for an extended period then you can recover you comms without the ESP cratering. If you want you can PM me your email details at esp8266.com

@alex3kov
Copy link
Author

alex3kov commented Mar 4, 2016

@dnc40085 Thanks for clarification.
After playing with it adding "sleeping" (and "awake"?) to the list of states for wifi.sta.eventMonReg seems like a reasonable idea to me. What do you think?

@dnc40085
Copy link
Contributor

dnc40085 commented Mar 5, 2016

@alex3kov You're welcome.
As for adding to wifi.sta.eventMonxxx(), I'm not sure if it would fit well there since those states are the same that are returned by wifi.sta.status and only runs in Station and StationAP modes.

On a side note, I was thinking before documenting this that I should expose the timed sleep functionality that also has a callback that can execute on wake up, or not at all if user wakes the chip before the timer runs out.

@dnc40085
Copy link
Contributor

dnc40085 commented Mar 5, 2016

@TerryE I think you're right, forcing the wifi into sleep mode before using non-compliant functions would most likely stop any wifi related crashes. I'll do what i can to help.

I don't mind talking here in the issues section, unless you think it would be better to talk on esp8266.com or via email. I just like having conversations in the open, so if someone is looking for the same info it can be found and maybe even be helpful.

@alex3kov
Copy link
Author

alex3kov commented Mar 5, 2016

@dnc40085

As for adding to wifi.sta.eventMonxxx(), I'm not sure if it would fit well there since those states are the same that are returned by wifi.sta.status and only runs in Station and StationAP modes.

Can it fit somewhere else? :) I'm just looking to set up a callback function on some event monitor (not necessarily wifi.sta.eventMonxxx()) that gets triggered when WiFi is guaranteed to have woken up; so I don't have to worry about my wifi.sta.connect() or other operations occuring when WiFi is in the process of waking up. EDD and all that.

On a side note, I was thinking before documenting this that I should expose the timed sleep functionality that also has a callback that can execute on wake up, or not at all if user wakes the chip before the timer runs out.

Can callback be used without the timer please? I'm trying to react to events - as opposed to doing things at time intervals.

@dnc40085
Copy link
Contributor

dnc40085 commented Mar 6, 2016

@alex3kov

I'm just looking to set up a callback function on some event monitor that gets triggered when WiFi is guaranteed to have woken up

When using the forced sleep API (wifi.sleep), the application developer determines when the transition occurs, it does not occur automatically.
So after you run wifi.sleep(0) you can immediately set station mode and connect with no problems.

If you really need this functionality, you can setup a timer(tmr.alarm) to monitor the return value of wifi.sleep and when there is a sleep to wake(1->0) transition, change mode and connect.

Can callback be used without the timer please?

Unfortunately, I have no control over this as it is part of the SDK functionality.

@TerryE
Copy link
Collaborator

TerryE commented Mar 6, 2016

@dnc40085, a question. (I am on my tablet so I don't have easy access to the source.) Is this SDK call async? I assume it is. In which case the application programmer will need to do a one shot alarm and cb to allow the sleep to occur. It's just that if the app does a sleep API call and immediately starts bit banging or whatever, then the WiFi stack might still due messily. Just a thought, and time for shut-eye for me.

@dnc40085
Copy link
Contributor

dnc40085 commented Mar 6, 2016

@TerryE I'm pretty sure it's asynchronous.

From the notes section of wifi_fpm_do_sleep in the Espressif programming guide:

...the ESP8266 will not enter sleep mode immediately, it is going to
sleep in the system idle task. Please do not call other WiFi related
function right after calling this API.

When wifi.sleep(1) is executed, the following is done to force wifi in to sleep mode(MODEM_SLEEP_T):

  • (If in station mode,) Disconnect from AP (wifi_station_disconnect)
  • Switch wifi opmode to NULL (wifi_set_opmode(NULL_MODE))
  • Enable forced sleep function (wifi_fpm_open)
  • Force wifi into sleep mode (wifi_fpm_do_sleep(FPM_SLEEP_MAX_TIME))

Then when it is time to wake up (wifi.sleep(0)):

  • Wake wifi up from forced sleep (wifi_fpm_do_wakeup)
  • Disable forced sleep function (wifi_fpm_close)

@alex3kov
Copy link
Author

alex3kov commented Mar 6, 2016

@dnc40085 Doesn't your last comment confirm that we need callbacks on wifi.sleep for proper EDD coding? In line with what @TerryE wrote - if it's all asynchronous, then even this code could fail:

wifi.sleep(0)
wifi.setmode(wifi.STATION)

A timer can be set up as you mentioned - I just noticed in your post that wifi.sleep() returns a value, so I'm guessing something like this:

tmr.alarm(0, 200, tmr.ALARM_AUTO, function()
    if wifi.sleep(0) = 0 then
        wifi.setmode(wifi.STATION)
        -- connect to WiFi, do some things
        tmr.unregister(0)
    end
end)

But that's a rather hackish approach, no? This would be cleaner and more EDD-like:

wifi.sleep(0, function() -- <- callback that will be called when WiFi fully wakes up
    wifi.setmode(wifi.STATION)
    -- do things with WiFi
    end
)

@alex3kov
Copy link
Author

alex3kov commented Mar 6, 2016

@dnc40085 And another thing:

On a side note, I was thinking before documenting this that I should expose the timed sleep functionality that also has a callback that can execute on wake up, or not at all if user wakes the chip before the timer runs out.
Can callback be used without the timer please? I'm trying to react to events - as opposed to doing things at time intervals.
Unfortunately, I have no control over this as it is part of the SDK functionality.

Can this "wake up timer" be set up while WiFi is sleeping? If yes, then it can be used with a timeout of 1 ms and a callback function, accomplishing what I described in previous comment.

@TerryE
Copy link
Collaborator

TerryE commented Mar 6, 2016

@alex3kov Aleksei, your model isn't quite right here. Think of turning off the WiFi in the same way that you might ask the power utility to turn off power to your house because you need to do essential repairs. Just because you have made the request doesn't mean that it is going to have been done instantly. Start sticking your fingers into the electrical sockets and you will get electrocuted.

In this case the SDK will let any in progress activities complete gracefully, for example the network chip might still be in the process of sending a packet. The "in system idle task" comment is important. This means that even a node.task.post() call wouldn't be sufficient as this is still at a higher priority than idle. You need to schedule an alarm after say X msec and when this is delivered then the WiFi will be off. You can now bit-bang and ignore the 15 msec max task time rule, and when you are done then wake up the WiFi again. You don't necessarily want to wake it up immediately. You might also want to sleep for 10 mins or whatever until time to take your next reading.

@alex3kov
Copy link
Author

alex3kov commented Mar 6, 2016

@TerryE

Aleksei, your model isn't quite right here. Think of turning off the WiFi in the same way that you might ask the power utility to turn off power to your house because you need to do essential repairs. Just because you have made the request doesn't mean that it is going to have been done instantly. Start sticking your fingers into the electrical sockets and you will get electrocuted.

If you're talking about my last comment regarding setting wake up timer while WiFi is sleeping - that was just a wild guess at a possible workaround. I have no knowledge of Espressif SDK, so I don't claim that things work one way or another - just asking questions.

In this case the SDK will let any in progress activities complete gracefully, for example the network chip might still be in the process of sending a packet. The "in system idle task" comment is important. This means that even a node.task.post() call wouldn't be sufficient as this is still at a higher priority than idle. You need to schedule an alarm after say X msec and when this is delivered then the WiFi will be off. You can now bit-bang and ignore the 15 msec max task time rule, and when you are done then wake up the WiFi again. You don't necessarily want to wake it up immediately. You might also want to sleep for 10 mins or whatever until time to take your next reading.

It seems you're taking me for a more SDK-savvy person than I am :) I'm just looking for "as pure EDD as possible" ways to do things on Lua side. Do you agree with my reasoning in previous comment?

@TerryE
Copy link
Collaborator

TerryE commented Mar 6, 2016

@alex3kov Aleksei, We've been sorting issue out for a couple of years and are still trying to understand some things! This is as much a 2-way discussion.

@alex3kov alex3kov changed the title Is wifi.sleep part of an official API? Please document wifi.sleep function in official API docs Mar 6, 2016
@dnc40085
Copy link
Contributor

dnc40085 commented Mar 7, 2016

Before wifi.sleep can be documented (read: exposed to the masses), we need to have a better understanding of how the forced sleep API works and I would also like to add the option for Lua callbacks that execute when WiFi has successfully shut down and when WiFi wakes up, in addition to exposing the timed WiFi sleep functionality.

Earlier today I experimented a little with the API and found:

  • As @TerryE said about the idle task, the WiFi will indeed not enter sleep mode until the system task queue is empty (system idling)
  • A wifi_fpm_do_sleep timer can not be set after wifi_fpm_do_sleep is executed (returns -1).
  • After fpm_close is called, wifi is immediately re-enabled, regardless of pending tasks in the system task queue.

I was looking in the mapfile, I found two useful functions in the power management API:

  • bool fpm_is_open(void): This function returns true if the forced sleep API is enabled.
  • bool fpm_rf_is_closed(void): This function returns true when WiFi modem is disabled.

Also, I was thinking that it might be a good idea to change the name of this function from wifi.sleep to wifi.forcedsleep.

It's kind of unrelated to the forced sleep API, but when I was searching for details about how the power management works, I found some useful information, A PDF and A thread on the Espressif forum that clarify the sleep modes a bit better than just reading the programming guide

Also somewhat related, I found that the function wifi.sleeptype() only applies to station mode, so IMHO it should be moved to wifi.sta.sleeptype.

@TerryE
Copy link
Collaborator

TerryE commented Mar 7, 2016

In short the first mode powers down the WiFi module but the rest of the ESP keeps running; the second powers down both CPUs but keeps the RAM and CPU internal state so the CPU can be reawakened on a GPIO pin; deep sleep is just a power-off, but the RTC if left running and can re-coldstart the CPU on a timer. In the first two modes the shutdown of the subsystem(s) has to be orderly.

@TerryE
Copy link
Collaborator

TerryE commented Mar 7, 2016

This type of work is where the role of dev differs from master. Here we have a feature that is sufficiently stable to be released to the developer community but not fully formed.

@dnc40085
Copy link
Contributor

dnc40085 commented Mar 7, 2016

@TerryE Here's an idea for an implementation...

wifi.forceSleep()

Forces WiFi modem into sleep mode

Syntax

wifi.forceSleep([sleep_cfg])

Parameters

  • sleep_cfg
    • sleep_time Sleep time in us. range: 1-268435454 us or 0 to sleep forever
    • sleeping_cb A callback function that executes when WiFi goes to sleep. (Optional)
    • wakeup_cb A callback function that executes when WiFi wakes up. (Optional)

Returns

  • If no arguments are passed to wifi.forceSleep() then current WiFi modem sleep state will be returned
    • true WiFi modem is asleep
    • false WiFi modem is awake

Example

-- Put WiFi modem to sleep for 5 seconds
sleep_cfg={}
sleep_cfg.sleep_time=(5*1000*1000)
sleep_cfg.sleeping_cb=non_compliant_function
sleep_cfg.wakeup_cb=wifi_setup
wifi.forceSleep(sleep_cfg)

-- Get current WiFi modem sleep state 
sleep_state=wifi.forceSleep()
print("wifi forced sleep is "..(sleep_state and "ENABLED" or "DISABLED"))

wifi.forceWakeup()

Wake WiFi modem from forced sleep

Syntax

wifi.forceWakeup()

Parameters

none

Returns

nil

Example

-- Wake WiFi modem from sleep
wifi.forceWakeup()

For sleeping_cb, I was planning on using a timer to check fpm_rf_is_closed and trigger the callback when it returns true, then terminate the timer.

I was thinking this could be in it's own submodule. wifi.powerctl seems like a good idea.
Also, maybe it could be split off into a separate file similar to wifi.eventmon and be optional as well.

Any suggestions?

@TerryE
Copy link
Collaborator

TerryE commented Mar 7, 2016

From an application programmer's PoV these three mode are very different:

  1. Suspend WiFi. The rest of the system is running normally including the ability to programmatically use the GPIOs. In some ways it's safer than normal running since you don't need to worry about timing out WiFi processes when bit banging.
  2. Suspend All. Here everything goes to sleep until a timer or GPIO interrupt triggers resumming. RAM is retained over the suspension.
  3. Powerdown and reboot on timer. This is deep sleep but death and resurrection would be a closer description. To me "sleep" implies that you know who you are when you wake up.

The word "modem" is extremely misleading. The WiFi has absolutely nothing to do with a modem, other than Espressif use the ESP-01 module with a modem-like AT command scripting framework on their AT builds. None of this applies to Lua firmware builds, so my personal preference is to avoid using this word at all in our API or documentation.

"force" implies some form of immediacy, and this isn't the case here as it's an orderly request. The safest thing that the application programmer can do is to tailcall this before ending the current task. And given that these three modes are very different, then how about keeping it simple and having four different calls:

  • wifi.suspend(cb) equates to mode (1) and the cb fires once the WiFi has suspended.
  • wifi.resume(cb) equates to mode (1) WiFi restart and the cb fires once the WiFi has resumed.
  • node.sleep([time,] cb_function [, wakeup_pin])equates to mode (2), and because the two integer type args proceed and follow the function arg, there is no possible confusion and we can just use a simple list rather than a keyed table. This is really a node function since its the system than sleeps. The cb fires on wakeup
  • node.deepsleep(time) equates to mode (3) as currently documented.

Thoughts? @jmattsson @devsaurus @pjsg etc. any comments?

We also need to play with latencies etc. The Espressif document seems to imply that any Wifi station connection will persist across the suspension, and I guess UDP stuff since this is stateless (need to check). I also suspect that any TCP stack will die across the suspension, so it would be better booked any suspension by ensuring any TCP services and sockets are closed, but again something to check.

@karrots
Copy link
Contributor

karrots commented Mar 8, 2016

Yes, while the ESP8266 is not a dial-up modem using acoustic tones. By its purest definition, the wifi chip is a modem. Modulating and demodulating the digital data using different modulation schemes so it can be sent or received using radio waves. So if the wifi portion of the chip turns off it is correctly stating that the modem is being turned off while the general purpose CPU is staying active.

@TerryE
Copy link
Collaborator

TerryE commented Mar 8, 2016

@karrots Johnathon, I don't find this rationale compelling. The whole WiFi subsystem on the die is powered down. I have had a quick look and haven't found a single article which uses the word in the context you suggest, for example the Wikipedia WiFi article doesn't.

@jmattsson
Copy link
Member

I agree with both of you. @karrots on the technical level, and @TerryE on the user-niceness level.

The API as exposed by Espressif isn't the most intuitive, and let's not even get started on their naming of things. Language differences probably account for most of it, and techie jargon for the rest. That shouldn't prevent us from exposing a nicer interface to the NodeMCU users though. wifi.suspend()/wifi.resume() seem quite clear in what they do.

@mjmcginty
Copy link

mjmcginty commented May 24, 2016

node.sleep([time,] cb_function [, wakeup_pin])equates to mode (2), and because the two integer type args proceed and follow the function arg, there is no possible confusion and we can just use a simple list rather than a keyed table. This is really a node function since its the system than sleeps. The cb fires on wakeup

This would be perfect and I need it desperately! Any idea if/when it will be added?

@dnc40085
Copy link
Contributor

@mjmcginty
Copy link

@mjmcginty please see http://nodemcu.readthedocs.io/en/dev/en/support/

My post was a feature request, so I don't think that applies. I edited it for clarity -- I was hoping it would make it to the attention of TerryE.

@dnc40085
Copy link
Contributor

@mjmcginty My apologies, I'll work on implementing this feature so I can add it to PR #1231, that way they can both be merged at the same time.

@mjmcginty
Copy link

@dnc40085 That would be awesome! My application will need to set a short timer to assign an interrupt to the same pin used to wake it. Also it's possible the pin will transition states multiple times while it's waking. I would be happy to help you test it! :-)

@mjmcginty
Copy link

@dnc40085 I got your branch of the code from git, built it and burned it to an 8266, I see that node.sleep is there and that it accepts a table object as an argument. I see from your test code that one member of the table is named wake_gpio.

I'm assuming there are two other named members of this table, an integer for the timeout and a callback function. Could you tell me the names of those members, please?

@dnc40085
Copy link
Contributor

dnc40085 commented Jul 27, 2016

I'm assuming there are two other named members of this table, an integer for the timeout and a callback function. Could you tell me the names of those members, please?

@mjmcginty the documentation for node.sleep() can be found HERE, however, in previous testing I could not get the timed light sleep to work(current consumption == modem_sleep), therefore it is currently disabled and only wake by gpio is available.

Be aware that in addition to the UART being in an unpredictable state when entering light sleep causing current draw to fluctuate from sleep to sleep, there is a problem with node.sleep() in that it will spit out an error (fpm 758) if ESP is not in null_mode before wifi_fpm_do_sleep() is executed. I am currently refactoring the code to fix this problem.

If you re-download the branch (I made changes for this workaround to function) and run the following code it should make node.sleep() work properly, till I get a fix committed:

resume_func=function()
  print("node is awake")
  wifi.setmode(wifi_mode_temp)
  if(wifi_mode_temp==wifi.STATION or wifi_mode_temp==wifi.STATIONAP) then
    wifi.sta.connect()
  end
  wifi_mode_temp=nil
end

enter_light_sleep=function() 
  wifi_mode_temp=wifi.getmode()
  wifi.setmode(wifi.NULLMODE) 
  tmr.alarm(0, 100, 1,function()
    if (wifi.getmode()==0) then
      tmr.stop(0)
      node.sleep({wake_gpio=0, resume_cb=resume_func})
    end
  end)
end

enter_light_sleep()

I hope to get the code refactoring done in the next week or so, until then good luck!

@mjmcginty
Copy link

@dnc40085 very cool, thank you! Just double-checking, the correct branch is dev_wifi_suspend, correct?

One thing: your code in pmSleep_parse_table_lua ignored the duration value for light sleep, the code to extract it is under the if (cfg->sleep_mode==MODEM_SLEEP_T) conditional branch. I copied it to the else branch and defaulted it to 0 if not present (rather than throwing an error), and it works, but... why is PMSLEEP_SLEEP_MAX_TIME only a 28 bit value? It's max is only 268 seconds?

My plan was to have it sleep for an hour, check when the last log was sent and send an unconditional log if 24 hours has past, even if no input occurred. (That way the absence of logs would unambiguously indicate device failure.)

I suppose the paradigm still flies with 4.5 minute naps instead of an hour, it only needs a second or two to check time elapsed since last... Just wondering why its max would be less than 32 bits?

@dnc40085
Copy link
Contributor

dnc40085 commented Aug 2, 2016

@mjmcginty

branch is dev_wifi_suspend

Yes, that is correct.

your code in pmSleep_parse_table_lua ignored the duration value for light sleep

This was on purpose. The problem with light sleep is if you try to specify a duration other than zero, which gets changed to FPM_SLEEP_MAX_TIME, the ESP8266 does not enter LIGHT_SLEEP(~3mA or less), but instead goes into MODEM_SLEEP (~15 mA) and does not suspend the CPU, therefore, Lua is still able to respond to commands received via serial. This is not the expected behavior for the function node.sleep(), so I removed the option to set sleep duration.

why is PMSLEEP_SLEEP_MAX_TIME only a 28 bit value?

PMSLEEP_SLEEP_MAX_TIME == FPM_SLEEP_MAX_TIME-1

It's max is only 268 seconds?
Just wondering why its max would be less than 32 bits?

The limit FPM_SLEEP_MAX_TIME(268435455) is provided by Espressif in the ESP8266 SDK API Guide v1.5.4 pg 89

@mjmcginty
Copy link

@dnc40085 So passing it max time -1 causes it to sleep indefinitely... that's intuitive! :-)

It doesn't seem like any of the edge transitions wake it up, only level. And interesting that pin value is the numeric part of its GPIO name, e.g., passing 0 for GPIO0 instead of its IO index. (Not complaining, just observing.) :-)

The problem I'm having is that my code, which runs fine on a Docker build, now runs out of memory, it's not even close. I still start out with 45K -- maybe it isn't releasing modules like it used to? There's a point in my app just before it loads a fair-sized module, I used to have 23K available, now I have 8.5K.

I looked through user_*.h... is there something I need to comment out?

@dnc40085
Copy link
Contributor

dnc40085 commented Apr 5, 2017

@marcelstoer I believe that this issue can be closed now that PR #1231 has been merged

@marcelstoer
Copy link
Member

Yep, the only documentation that we're currently missing is IMO #1861.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

7 participants