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

Enable WiFi/BLE automatic light sleep mode on ESP32 #9463

Open
EternityForest opened this issue Jul 26, 2024 · 21 comments
Open

Enable WiFi/BLE automatic light sleep mode on ESP32 #9463

EternityForest opened this issue Jul 26, 2024 · 21 comments

Comments

@EternityForest
Copy link

The ESP32 can do sub-milliamp current while remaining connected, but I don't see anything about enabling automatic light sleep for WiFi.

Since there's already alarms, would it be possible to add some way to idle in the ESP's automatic light sleep mode rather than sleeping the whole time and messing up WiFi?

@tannewt
Copy link
Member

tannewt commented Jul 26, 2024

I don't think we need another API. Seems like the existing light sleep API should work. Is this ESP32 only or does it apply to more of the ESP line?

@EternityForest
Copy link
Author

As far as I know, every popular Espressif chip has an equivalent, I don't think I've ever seen or used one without it.

Adding it to the existing API seems reasonable, but I'm not sure what it would look like, since there are several similar modes, and the WiFi and CPU have separate sleep settings, and the power consumption is more than full light sleep without automatic wakeups for the WiFi hardware.

@dhalbert
Copy link
Collaborator

When I was working on ESP32-nn sleep, I gave up on using light sleep because of the first paragraph below:

WiFi/BT and sleep modes

In Deep-sleep and Light-sleep modes, the wireless peripherals are powered down. Before entering Deep-sleep or Light-sleep modes, the application must disable Wi-Fi and Bluetooth using the appropriate calls (i.e., nimble_port_stop(), nimble_port_deinit(), esp_bluedroid_disable(), esp_bluedroid_deinit(), esp_bt_controller_disable(), esp_bt_controller_deinit(), esp_wifi_stop()). Wi-Fi and Bluetooth connections are not maintained in Deep-sleep or Light-sleep mode, even if these functions are not called.

If Wi-Fi/Bluetooth connections need to be maintained, enable Wi-Fi/Bluetooth Modem-sleep mode and automatic Light-sleep feature (see Power Management APIs). This will allow the system to wake up from sleep automatically when required by the Wi-Fi/Bluetooth driver, thereby maintaining the connection.

But the second paragraph says you can... I don't remember exactly why I gave up. Perhaps it was because I could not maintain the state of certain other peripherals, and our intention was that a light sleep did not break anything else.

@EternityForest
Copy link
Author

I had it mostly working at one point in Micropython, but never made a PR because nobody replied to the thread and eventually someone else made one.

But now with the C6 and 802.11ax, there's also target wake time, which I believe they say can last for years on a battery, so the APIs should probably support that.

And it seems there's also the ability to sleep for more than one DTIM interval for a similar effect on older WiFi, but it can break broadcast traffic.

Seems like everyone else is using an enum to let you set whether to care about broadcast traffic or not, and we could add an extra wake_interval param for TWT capable boards?

So the API would be:

# For WiFi 5 and earlier, sleep 3 beacons
radio.beacon_sleep=3

# For WiFi 6, sleep 0.3 seconds
radio.wake_interval=0.3

# Idle using automatic light sleep,
# waking as needed for WiFi, until an alarm
# wakes us.  Can't just use time.sleep as we want
# to be able to handle pin interrupts
idle_until_alarm()

@tannewt
Copy link
Member

tannewt commented Jul 29, 2024

Could you translate a wake_interval to beacon count? That way you only need one more setting.

There is an existing light_sleep API: alarm.light_sleep_until_alarms()

We'd be happy to merge this in if you get it working.

@EternityForest
Copy link
Author

EternityForest commented Jul 29, 2024 via email

@tannewt
Copy link
Member

tannewt commented Jul 29, 2024

Start with the bare minimum. We can always add things that folks want.

So, I'd suggest getting the light sleep working under the existing API first.

@EternityForest
Copy link
Author

I think I might have some free time so I was planning to work on this in a bit.

If I understand correctly, the idea for now is:

Have the existing alarm.light_sleep_until_alarms use automatic light sleep, letting the chip handle actually entering sleep and waking as needed so peripherals keep doing their thing, preferring correctness over minimum power consumption.

Add a radio.listen_interval property that enables WiFi power saving, matching the API that the IDF exposes, letting you set it in terms of number of DTIM intervals, since the most common bare minimum is just going to be setting it to 1

Target Wake Time gets ignored for now, and can be figured out later.

@tannewt
Copy link
Member

tannewt commented Jul 30, 2024

Sounds good @EternityForest! Let us know if you need any guidance about where code lives. #circuitpython-dev on Discord is a good place for realtime help.

@EternityForest
Copy link
Author

EternityForest commented Jul 30, 2024

Right now the only thing that seems to be confusing is this file: https://github.com/adafruit/circuitpython/blob/2f626121867efa429b7484fa3ca5a315021cc506/ports/espressif/common-hal/alarm/__init__.c

It uses port_idle_until_interrupt(), which seems to call xTaskNotifyWait, but I don't see how that enters actual light sleep, unless CONFIG_FREERTOS_USE_TICKLESS_IDLE is set somewhere outside the repo.

Does light sleep until alarms actually sleep? The comment // We cannot esp_light_sleep_start() here because it shuts down all non-RTC peripherals. seems to suggest it doesn't and can't sleep without being a breaking API change?

Then, in the documentation, they seem to say things like audio playback are supposed to keep going in light sleep, but the ESP's implementation of light sleep seems to pause things like the UART(You can receive but the send buffer just waits).

Is it sleeping somewhere I'm not seeing, or did they at some point decide to not bother with that to keep the strong guarantees of their sleep API?

Maybe I need to dig out my USB power meter for this and see what it's actually doing!

Edit: I am not actually seeing any power savings in sleep mode. It just hovers around 80mA with 160mA spikes, that's with the screen on powered from a USB wall charger, 5 seconds of time.sleep(), 5 seconds of light sleep until alarms.

Unless I'm missing something, it seems like significant power savings would be a somewhat breaking change or require a new parameter.

ESP does have a wake locks feature and the set of peripherals that stays on is pretty vaguely specified though, so could we change the API of light sleep to just accept that some peripherals might not work unless they use the locks?

Or, could we add a new full_sleep_until_alarms() that turns off peripherals if needed?

This seems like a bigger project that it looked like even though the actual implementation is simple, just because of the API guarantees!

@tannewt
Copy link
Member

tannewt commented Jul 31, 2024

Right now the only thing that seems to be confusing is this file: https://github.com/adafruit/circuitpython/blob/2f626121867efa429b7484fa3ca5a315021cc506/ports/espressif/common-hal/alarm/__init__.c

It uses port_idle_until_interrupt(), which seems to call xTaskNotifyWait, but I don't see how that enters actual light sleep, unless CONFIG_FREERTOS_USE_TICKLESS_IDLE is set somewhere outside the repo.

Does light sleep until alarms actually sleep? The comment // We cannot esp_light_sleep_start() here because it shuts down all non-RTC peripherals. seems to suggest it doesn't and can't sleep without being a breaking API change?

I think it idles but it hasn't been optimized. We focused on deep sleep when we were working on this.

I don't remember if I tried tickless idle. It was my intention to rely on freertos idle for light sleep.

Then, in the documentation, they seem to say things like audio playback are supposed to keep going in light sleep, but the ESP's implementation of light sleep seems to pause things like the UART(You can receive but the send buffer just waits).

Is it sleeping somewhere I'm not seeing, or did they at some point decide to not bother with that to keep the strong guarantees of their sleep API?

Ya, we focused on deep sleep. To optimize light sleep you could track what user code is doing and either freertos idle or actually call the IDF's light sleep.

Unless I'm missing something, it seems like significant power savings would be a somewhat breaking change or require a new parameter.

ESP does have a wake locks feature and the set of peripherals that stays on is pretty vaguely specified though, so could we change the API of light sleep to just accept that some peripherals might not work unless they use the locks?

No, light sleep should keep everything running. However, if nothing is running, then it should sleep as deep as possible. The main distinction between deep sleep vs light sleep is that light keeps ram and cpu state so you pick up where you left off. Deep sleep turns everything it can off and restarts on wake.

Or, could we add a new full_sleep_until_alarms() that turns off peripherals if needed?

No, let's not add another API. That would just add another thing to implement. Instead, make light sleep smarter.

This seems like a bigger project that it looked like even though the actual implementation is simple, just because of the API guarantees!

Yup! Most optimization tasks are a rabbit hole of improvements. Any improvements you can do are welcome.

@EternityForest
Copy link
Author

EternityForest commented Jul 31, 2024 via email

@EternityForest
Copy link
Author

More updates: My very early progress on using auto light sleep is here. Sleeping for too long will trigger the watchdog and there's some peripherals that may break, but it's a start.

Considering the USB chip likely use 18mA or so, 20mA power consumption is pretty good!

https://github.com/EternityForest/circuitpython/tree/auto-light-sleep

@tannewt
Copy link
Member

tannewt commented Aug 5, 2024

What about waiting a few minutes after boot for a serial connection before enabling sleep, and just accepting any new connections after that will lose the first char and give some kind of error, unless you have native USB?

You don't need to wait too long I think. I think the first character is known to be unreliable. I think it is common to do multiple ctrl-c.

I'm not sure how to handle the supervisor serial console through, and other similar background tasks.

Can you run CP background tasks after every time the chip wakes for wifi work?

@EternityForest
Copy link
Author

I haven't seen a lot of unreliability with the first char myself but it does seem like the sort of thing applications and users should be able to handle.

We could just limit the sleep time to 1s or 100ms at a time(I think usual WiFi sleep is 100ms but will be longer when target wake time is common), regardless of what WiFi is up to, and run a CP/supervisor tick in between each sleep?

@tannewt
Copy link
Member

tannewt commented Aug 6, 2024

We could just limit the sleep time to 1s or 100ms at a time(I think usual WiFi sleep is 100ms but will be longer when target wake time is common), regardless of what WiFi is up to, and run a CP/supervisor tick in between each sleep?

I'd only do it if the tick timer is enabled. Will light sleep be woken by interrupts? Much of what background tasks are used for is queued by an interrupt.

@EternityForest
Copy link
Author

EternityForest commented Aug 6, 2024

The tick timer doesn't seem to be compatible with sleep at all, since the IDF automatic light sleep doesn't trigger when there's stuff scheduled every millisecond, right now I'm just disabling it when entering sleep and re-enabling it on exit.

Does that conflict with some other process that might be enabling or disabling ticks?

Interrupts work in light sleep unless the peripheral is just disabled entirely like most of them are, but I'm not sure if/how you can detect the automatic background wakeups in general, so the main thread is only going to get notified by stuff that specifically calls the port wake main thread function.

What about the keypad and the other activities that happen in the polled supervisor tick? Could we make it so disabling ticks still keeps the supervisor awake at 5Hz or so?

@tannewt
Copy link
Member

tannewt commented Aug 7, 2024

Does that conflict with some other process that might be enabling or disabling ticks?

Do you know what is enabling ticks? In theory it should only be on if used.

so the main thread is only going to get notified by stuff that specifically calls the port wake main thread function.

I think its safe to assume we should wake the main thread when needed. Doing so is required.

What about the keypad and the other activities that happen in the polled supervisor tick? Could we make it so disabling ticks still keeps the supervisor awake at 5Hz or so?

When using keypad, we shouldn't light sleep. We can sleep less deep because we need the tick.

@EternityForest
Copy link
Author

Looks like the ticks getting enabled was probably just some other unrelated code that I didn't notice because I thought they were supposed to be always-on.

I think this might be closer to being ready than I thought!

It seems like a lot of ESP32 boards don't have the alarm module at all though, I wonder how many actually have space for it?

@tannewt
Copy link
Member

tannewt commented Aug 8, 2024

It seems like a lot of ESP32 boards don't have the alarm module at all though, I wonder how many actually have space for it?

Is that due to the 4MB flash limit? I think we plan on changing the partition layout with CP 10 so that those boards can have more features like BLE.

@EternityForest
Copy link
Author

EternityForest commented Aug 8, 2024 via email

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

No branches or pull requests

3 participants