Skip to content

ports/esp32-esp8266: Add support for set/get the wifi power saving mode. #8993

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

Merged
merged 1 commit into from
May 6, 2023

Conversation

glenn20
Copy link
Contributor

@glenn20 glenn20 commented Jul 31, 2022

This PR adds support for setting and getting the wifi power saving mode.

Adds the pm option to WLAN.config() to support setting/getting the wifi power saving mode.

Also adds the PM_NONE, PM_MIN_MODEM and PM_MAX_MODEM constants to the WLAN class.

Example:

import network
sta = network.WLAN(0); sta.active(True)
sta.connect(ssid, password)
sta.config(pm=sta.PM_NONE)

This is especially useful for controlling power consumption on battery powered devices and is also required by PR #6515 (ESPNow support).

@glenn20 glenn20 changed the title ESP32: Add support for set/get the wifi power saving mode. ports/esp32: Add support for set/get the wifi power saving mode. Aug 1, 2022
@glenn20 glenn20 changed the title ports/esp32: Add support for set/get the wifi power saving mode. ports/esp32-esp8266: Add support for set/get the wifi power saving mode. Aug 7, 2022
@peterhinch
Copy link
Contributor

This sounds similar to functionality in the esp module. Please could you explain the relationship between the two.

@glenn20
Copy link
Contributor Author

glenn20 commented Aug 7, 2022

This sounds similar to functionality in the esp module. Please could you explain the relationship between the two.

@peterhinch Good pickup - thanks Peter. I was not aware this functionality was available in the esp module (ESP8266 only).

I confess that I find it odd that this one wifi option is set in the esp module and everything else is set in WLAN.config(). It is hardly surprising that I and other users have overlooked this till now. Nonetheless, precedent is an important factor.

I guess I could add support for the ESP32 to ports/esp32/modesp.c. However, I really do think users are going to continue to overlook this feature if it is not in WLAN.config().

Thoughts?

@dpgeorge
Copy link
Member

dpgeorge commented Aug 9, 2022

I think settings should be consolidated to WLAN.config(). Older functions like phy_mode() and sleep_type() were added before .config() was introduced.

But we also need to standardise on naming for setting the power saving mode. In the CYW43 driver it uses .config(pm=...) (for power-management).

@glenn20
Copy link
Contributor Author

glenn20 commented Aug 12, 2022

@dpgeorge Ok - I can add a commit renaming the option to "pm" to be consistent with the cyw43 driver.

I am assuming it is too difficult to attempt to standardise the values (and constant names) for those arguments as the semantics are likely to vary considerably across devices (even between the esp32 and esp8266).

@dpgeorge
Copy link
Member

I think the constants need to go inside the WLAN class itself. Because if there are multiple NICs then they could all have different options/values for the power saving configuration.

I would suggest naming the constants PM_xxx: the "PM" matches the name of the config argument, and it doesn't need to be prefixed with WIFI_ if it lives in the WLAN class.

@glenn20
Copy link
Contributor Author

glenn20 commented Aug 24, 2022

I think the constants need to go inside the WLAN class itself. Because if there are multiple NICs then they could all have different options/values for the power saving configuration.

I would suggest naming the constants PM_xxx: the "PM" matches the name of the config argument, and it doesn't need to be prefixed with WIFI_ if it lives in the WLAN class.

Ok. Shall do.

@dpgeorge : This may start to create a consistency issue with respect to many existing constants in the network module which would by the same logic belong in the WLAN class. I'm very happy that we can start with just this set of constants (which I agree makes sense in the light of wider network adaptor support in micropython) - just flagging that it will seem a bit arbitrary to users which constants are in network and which in WLAN().

@glenn20 glenn20 force-pushed the esp32_wifi_config_ps_mode branch from a10101a to 1a93ea6 Compare August 24, 2022 08:21
@glenn20
Copy link
Contributor Author

glenn20 commented Aug 24, 2022

I have rebased against master and pushed a commit to implement (as requested):

  • Move WIFI_PS_ constants from network module to WLAN() class.
  • Rename WIFI_PS_* constants to PM_*

for esp32 and esp8266.

Update - and squashed commits.

@glenn20 glenn20 force-pushed the esp32_wifi_config_ps_mode branch 2 times, most recently from 11cb259 to 595f4d4 Compare August 27, 2022 08:26
@glenn20
Copy link
Contributor Author

glenn20 commented Aug 27, 2022

Whoops - I forgot to rename the PS_ constants on esp32. Fixed now.

@AmirHmZz
Copy link
Contributor

Any updates on this one?

@glenn20
Copy link
Contributor Author

glenn20 commented Nov 27, 2022

Any updates on this one?

No updates. Just waiting for further review.

@dakoner
Copy link

dakoner commented Jan 12, 2023

FWIW I patched this and it worked great; my ping times go from 100ms to 2-3ms.

Can you update the first comment to show the current configuration/code example, it should be:

import network
sta = network.WLAN(0)
sta.active(True)
sta.connect(ssid, password)
sta.config(pm=sta.PM_NONE)

@glenn20
Copy link
Contributor Author

glenn20 commented Jan 23, 2023

@dakoner - Glad it worked for you and thanks for the tips to fix the docs. Done.

@jjsuwa-sys3175
Copy link

This is just what I want and searched, because WebREPL is unbearably slow in the current situation.
I think the negative side of power savings can get in the way when trying out various things.

I wonder why it's still not committed...

@glenn20 glenn20 force-pushed the esp32_wifi_config_ps_mode branch from d09e762 to 98e31b8 Compare April 1, 2023 06:21
@glenn20
Copy link
Contributor Author

glenn20 commented Apr 1, 2023

Rebased against master to resolve conflicts.

@ThinkTransit
Copy link
Contributor

Does this PR allow wifi/bt modems to be completely switched off as described as "modem sleep" here?

https://lastminuteengineers.com/esp32-sleep-modes-power-consumption/#:~:text=another%20power%20mode.-,ESP32%20Modem%20Sleep,20%20mA%20at%20high%20speed.

@glenn20
Copy link
Contributor Author

glenn20 commented Apr 10, 2023

Does this PR allow wifi/bt modems to be completely switched off as described as "modem sleep" here?.

This looks like the PS_MIN_MODEM mode described here.

That wifi ps mode is automatically enabled when the esp32 connects to a wifi access point. You can use this PR to doable this power saving mode or select the high power savings of PS_MAX_MODEM.

Note that in these modes, the radio is only turned off intermittently (set by the access points DTIM interval).

@ThinkTransit
Copy link
Contributor

Does this PR allow wifi/bt modems to be completely switched off as described as "modem sleep" here?.

This looks like the PS_MIN_MODEM mode described here.

That wifi ps mode is automatically enabled when the esp32 connects to a wifi access point. You can use this PR to doable this power saving mode or select the high power savings of PS_MAX_MODEM.

Thanks @glenn20 I will test this out with a power meter. Reading further I think what I need to call is esp_wifi_stop();

@glenn20
Copy link
Contributor Author

glenn20 commented Apr 11, 2023

Thanks @glenn20 I will test this out with a power meter. Reading further I think what I need to call is esp_wifi_stop();

You can see the results of some of my experiments with optimising wifi power consumption. In particular, the wifi graphs show clearly the steps in power consumption in PS_MIN_MODEM mode as the radio is toggled on and off in sync with the Access Point's DTIM timing.

@ThinkTransit
Copy link
Contributor

You can see the results of some of my experiments with optimising wifi power consumption. In particular, the wifi graphs show clearly the steps in power consumption in PS_MIN_MODEM mode as the radio is toggled on and off in sync with the Access Point's DTIM timing

Wow some great work there Glenn, the optimisations are impressive.

@glenn20
Copy link
Contributor Author

glenn20 commented May 2, 2023

We need to also consider WiFi modules based on the cyw43. The existing extmod/network_cyw43.c driver actually includes an (undocumented) wlan.config(pm=...) option. It has 3 predefined power-management settings called DEFAULT_PM, AGGRESSIVE_PM and PERFORMANCE_PM (which aren't yet exposed to Python). But you can actually pass an integer value to the pm option and you get quite a lot of control over how active the WiFi is:

  • no power saving (never turn off) vs full power saving (turn off whenever possible) vs balanced (turn off after some configurable time T)
  • the association interval sent to the AP (ie how long the AP should wait between pings of the STA)
  • how often the STA should wake up to listen for frames from the AP

@glenn20 do you know if you have such control over the WiFi power management in the esp32? Or, what do the PS_MIN_MODEM/MAX_MODEM settings actually correspond to in terms of the above parameters?

The ESP32 implements only the wifi modem-sleep mode as described at ESP32 Wifi Power Saving Mode:

  • WIFI_PS_MIN_MODEM: Wakes every DTIM to receive beacon. The DTIM interval is set by the AP. No data is lost.
  • WIFI_PS_MAX_MODEM: Wakes up every "listen interval". The listen interval can be set in the wifi config. It can be set longer than the AP's DTIM interval, but data may be lost. (NOTE. setting this listen interval is not exposed by micropython).
  • WIFI_PS_NONE: Does not shut down radio while connected to AP.

which "conceptually" map to the modes in the cyw43 driver. (Note that the ESP32 automatically enters WIFI_PS_MIN_MODEM when it connects to an AP - hence the initial motivation for this PR).

However, looking at the docs for cyw43_pm_value() (used to encode that integer argument) it must be using some other 802.11 PM extensions beyond modem-sleep, as the docs imply the client tells the AP what listen interval to use for this client.

The signature for the "pm" config option in this PR is compatible with the cyw43 driver, BUT the meaning of the values of the arg are different. Are you thinking to find a common base naming convention for a few constants that map to the port/device specific settings (eg. PM_NONE, PM_MIN, PM_MAX)?

Also, we could consider encoding the "listen interval" into the "pm" argument like cyw43_pm_value() does, to give more control, but I don't think the demands is high for that at this stage.

@glenn20
Copy link
Contributor Author

glenn20 commented May 2, 2023

Aside: The cyw43 docs suggest that DEFAULT_PM saves more power than AGGRESSIVE_PM, which is not what I "expected" from the naming convention.

@dpgeorge
Copy link
Member

dpgeorge commented May 2, 2023

Are you thinking to find a common base naming convention for a few constants that map to the port/device specific settings

At the very least, yes.

Also, we could consider encoding the "listen interval" into the "pm" argument like cyw43_pm_value() does, to give more control

I would like to do this. Eg there could be a few options like pm_mode, pm_interval, pm_dtim. Or a single pm option that takes an opaque integer which:

  • there are default values provided as constants, eg WLAN.PM_PERFORMANCE
  • there's a function used to construct a value, eg WLAN.pm_value(mode, dtim)

In most cases users would just do wlan.config(pm=WLAN.PM_PERFORMANCE), but they could also do wlan.config(pm=WLAN.pm_value(...)).

@dpgeorge
Copy link
Member

dpgeorge commented May 2, 2023

The cyw43 docs suggest that DEFAULT_PM saves more power than AGGRESSIVE_PM, which is not what I "expected" from the naming convention

Yes, the naming of the default constant is poor. We can change that.

@glenn20
Copy link
Contributor Author

glenn20 commented May 2, 2023

I would like to do this. Eg there could be a few options like pm_mode, pm_interval, pm_dtim. Or a single pm option that takes an opaque integer which:

  • there are default values provided as constants, eg WLAN.PM_PERFORMANCE
  • there's a function used to construct a value, eg WLAN.pm_value(mode, dtim)

In most cases users would just do wlan.config(pm=WLAN.PM_PERFORMANCE), but they could also do wlan.config(pm=WLAN.pm_value(...)).

I like this. You get a simple, portable method for setting common wifi PM settings and an extension api for tuning (which may be supported by only some drivers and may have different signatures and semantics on different drivers).

(Pauses to think....) However, is this the cleanest method? If a WLAN.pm_value() method is being introduced, wouldn't it be simpler to just have a WLAN.set_pm() extension method to set the PM value, rather than encoding settings into an int and then passing that to config(). (ie. config(pm=..) for portable api and set_pm(...) for extension api.

  • Or config(pm=n) for portable and config(pm=(tuple...)) for tuning extension.

@dpgeorge
Copy link
Member

dpgeorge commented May 2, 2023

  • Or config(pm=n) for portable and config(pm=(tuple...)) for tuning extension.

Yes, I like that idea.

@glenn20
Copy link
Contributor Author

glenn20 commented May 2, 2023

  • Or config(pm=n) for portable and config(pm=(tuple...)) for tuning extension.

Yes, I like that idea.

Ok. Just need a naming convention for portable constants. I suggest a "none", "default" and 2-3 power level settings, eg: PM_NONE and PM_DEFAULT as well as something like:

  • PM_PERFORMANCE, PM_POWERSAVE or
  • PM_MIN_POWER, PM_MED_POWER, PM_MAX_POWER

PM_NONE - disable power saving
PM_DEFAULT - restores the default power saving setting of the chip/driver.

Let me know what you like. I'll update this PR with performance/powersave/default/none but can change at any time. I'll also investigate supporting the "listen interval". If I get the time this weekend, I'll test with the PPK2.

@glenn20 glenn20 force-pushed the esp32_wifi_config_ps_mode branch from 47593ed to 03bd089 Compare May 2, 2023 04:38
@glenn20
Copy link
Contributor Author

glenn20 commented May 2, 2023

Now supports:

WLAN.config(pm=WLAN.PM_NONE)  # WIFI_PS_NONE
WLAN.config(pm=WLAN.PM_DEFAULT)  # WIFI_PS_MIN_MODEM
WLAN.config(pm=WLAN.PM_PERFORMANCE)  # WIFI_PS_MIN_MODEM
WLAN.config(pm=WLAN.PM_POWERSAVE)  # WIFI_PS_MAX_MODEM
WLAN.config(pm=(WLAN.PM_POWERSAVE, listen_interval))  # WIFI_PS_MAX_MODEM and set listen_address parameter in sta config.

Happy to change-up constant names or anything else. It might be helpful to have standardised named for 3 PM_ levels (this scheme only has 2).

@glenn20 glenn20 force-pushed the esp32_wifi_config_ps_mode branch 2 times, most recently from 9cced64 to b1fff6a Compare May 5, 2023 07:47
@glenn20 glenn20 force-pushed the esp32_wifi_config_ps_mode branch from b1fff6a to 4f56f71 Compare May 6, 2023 03:33
For esp32 and esp8266 this commit adds:
- a 'pm' option to WLAN.config() to set/get the wifi power saving mode; and
- PM_NONE, PM_PERFORMANCE and PM_POWERSAVE constants to the WLAN class.

This API should be general enough to use with all WLAN drivers.

Documentation is also added.
@dpgeorge dpgeorge force-pushed the esp32_wifi_config_ps_mode branch from 4f56f71 to 1093dea Compare May 6, 2023 03:54
@dpgeorge
Copy link
Member

dpgeorge commented May 6, 2023

Thanks, that looks better.

I squashed and rebased this PR.

@dpgeorge dpgeorge merged commit 1093dea into micropython:master May 6, 2023
@dpgeorge
Copy link
Member

dpgeorge commented May 6, 2023

Merged!

A concise PR is always best.

@glenn20 glenn20 deleted the esp32_wifi_config_ps_mode branch May 6, 2023 04:19
@inovatorius
Copy link

Thank you for fixing this!

This one line:
network.WLAN(network.STA_IF).config(pm=network.WLAN(network.STA_IF).PM_NONE)

replaces my entire "workaround" on ESP8266 for ESP-NOW:

async def _event_keep_alive(self):
    while True:
        await self._on_sta_connected.wait()
        network.WLAN(network.STA_IF).connect()
        await uasyncio.sleep_ms(self._DTIM)

I noticed, that while you ping ESP8266, it can receive ESP-NOW messages, but once you stop, power saving mode kicks in and packets be gone.

@dpgeorge
Copy link
Member

You can simplify that one line to:

network.WLAN(network.STA_IF).config(pm=network.WLAN.PM_NONE)

@glenn20
Copy link
Contributor Author

glenn20 commented May 11, 2023

You can simplify that one line to:

network.WLAN(network.STA_IF).config(pm=network.WLAN.PM_NONE)

That does not work anymore - since network.WLAN was replaced by a function to return the WLAN instances rather than being a class in the network module.

I usually use:

sta = network.WLAN(network.STA_IF)
sta.config(pm=sta.PM_NONE)

@dpgeorge
Copy link
Member

That does not work anymore - since network.WLAN was replaced by a function to return the WLAN instances rather than being a class in the network module.

Oh, you're right! And that's been there since the beginning, originating on esp8266.

That should definitely be fixed, to match other ports and network classes.

@dpgeorge
Copy link
Member

That should definitely be fixed, to match other ports and network classes.

See #11465.

tannewt pushed a commit to tannewt/circuitpython that referenced this pull request Mar 5, 2024
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

Successfully merging this pull request may close these issues.

8 participants