Why ESP32 Light Sleep Drops Your WiFi Without Telling You
You call esp_light_sleep_start() with a timer wakeup, the chip sleeps, wakes, and carries on as if nothing happened. Then the next HTTP request or MQTT publish fails, or just hangs. No disconnect event fired. The ESP32 still believes it is connected. Sometimes it is not, and sometimes the link is half-dead.
This is less a bug than a mismatch between what "light sleep" sounds like and what it does. Here is the mechanism, and two ways to cope.
Manual light sleep pauses the WiFi stack too
Light sleep stops the CPU clock and gates parts of the chip, but keeps RAM. Your variables survive and execution resumes after the call. That part matches the name.
The catch is that the WiFi driver, the lwIP timers and the radio activity are all driven by that same paused CPU. While the CPU sleeps, nothing is listening for beacons or answering the access point. Your code sees a function that took ten seconds to return. The network sees a station that went quiet.
What the AP does about that depends on the AP. Many drop idle stations after a timeout, and the length varies a lot between routers. So the symptoms are inconsistent: short sleeps seem fine, long ones fail, and a different router behaves differently. That inconsistency is what makes it so annoying to debug.
Why you get no event
The ESP32 only learns the link is gone when something tells it: a deauthentication frame, or enough missed beacons. Both have to be processed after wake-up, and until then the driver's state still says "connected".
Your TCP sockets are worse. They are plain lwIP state in RAM, so they survive sleep perfectly and look healthy. The server, meanwhile, may have timed the connection out, or the AP may have forgotten you. The first write after waking is what finds out, and by then you are in the error path.
Quick detour: what is the AP actually doing?
Hang on, why would an AP care if you were asleep? Because WiFi has a proper power-save protocol, and manual light sleep does not use it. In legitimate power-save, the station tells the AP "I am dozing" with a flag in a frame. The AP then buffers traffic and announces it in beacons, which the station wakes to read at DTIM intervals.
Manual light sleep skips the announcement. From the AP's side the station simply stopped responding, which looks like a dead client rather than a sleepy one.
Fix one: let ESP-IDF sleep for you
ESP-IDF has automatic light sleep, which cooperates with WiFi modem sleep. The idle task enters light sleep only when nothing needs the CPU, and the WiFi driver wakes it for the beacons it needs. The station stays associated.
You need power management enabled in menuconfig (CONFIG_PM_ENABLE, plus tickless idle via CONFIG_FREERTOS_USE_TICKLESS_IDLE), then:
#include "esp_pm.h"
#include "esp_wifi.h"
void power_setup(void)
{
esp_pm_config_t pm = {
.max_freq_mhz = 160,
.min_freq_mhz = 40,
.light_sleep_enable = true,
};
ESP_ERROR_CHECK(esp_pm_configure(&pm));
/* WIFI_PS_MIN_MODEM wakes every DTIM; MAX_MODEM uses listen_interval */
ESP_ERROR_CHECK(esp_wifi_set_ps(WIFI_PS_MIN_MODEM));
}
Check the frequency limits against your chip and IDF version; they are in the power management docs. The trade-off: the radio still wakes for beacons, so idle current is higher than true deep sleep. You are buying a live connection with milliamps.
One Arduino-specific warning: the prebuilt Arduino core libraries may not have power management compiled in, so esp_pm_configure can fail there. Check its return value rather than assuming it worked.
Fix two: assume the link died, and rebuild it
If you want long sleeps, the honest approach is to treat every wake-up as a cold start for the network. Stop WiFi before sleeping, start it after, and open fresh connections.
void sleep_for_us(uint64_t us)
{
esp_wifi_stop();
esp_sleep_enable_timer_wakeup(us);
esp_light_sleep_start();
esp_wifi_start(); /* STA_START handler calls esp_wifi_connect() */
}
Then close and reopen sockets, TLS sessions and MQTT clients after the got-IP event. It is slower than staying associated, but it is predictable, and predictable beats "works on my router".
Checking which case you are in
- Log the reason and time in the
WIFI_EVENT_STA_DISCONNECTEDhandler; a late event right after wake-up is the giveaway. - Look at the AP's station list or logs to see whether it expired you during the sleep.
- Try a sleep of a few seconds against one of several minutes; if only the long one breaks, you are hitting the AP's idle timeout.
- Measure current with a meter or power profiler, because "it works" can still mean "it never actually slept".
If you only need to send a reading every few minutes, deep sleep with a quick reconnect is usually simpler than either fix, though it brings its own costs, such as losing RAM and the channel. Light sleep earns its place when you need to stay reachable.