Table of Contents
The ESP32 is one of the most capable microcontrollers available, but all that capability draws current. In this article, we will look at the four low-power modes built into the ESP32 – and use them to stretch a single battery from hours to months or even years.
Introduction
Today, we are going to put our ESP32 to sleep and dramatically cut its power consumption. If you have ever built a battery-powered ESP32 project and been disappointed by how quickly it drains the battery, then keep reading.
The ESP32 has earned its place as the workshop’s favorite microcontroller. It has dual CPU cores, a rich set of peripherals, built-in Wi-Fi, Bluetooth, and, in some variants, Thread and Zigbee as well. That feature set makes it a natural choice for IoT sensors, home automation nodes, remote monitors, and a wide range of battery-powered gadgets. The problem is that all of that capability draws current. In some situations, an ESP32 can consume more than 240 milliamps – a figure that will drain a small LiPo battery in just a few hours.

Fortunately, Espressif has engineered four low-power modes into the ESP32. Used correctly, these modes can reduce current consumption by a factor of more than 10,000, extending battery life from hours to months – or in extreme cases, to decades. In this article, we will walk through all four modes:
- Modem Sleep – the lightest mode, which turns off the radio while keeping the CPU running
- Light Sleep – pauses the CPU and peripherals but preserves all RAM and registers
- Deep Sleep – powers down almost everything, leaving only the RTC module alive
- Hibernation – the deepest mode, where even the RTC oscillator is switched off
For each mode, we will explain how it works, walk through a practical demonstration sketch in the Arduino IDE, and discuss the applications for which it is best suited. We will then wrap up with a complete project: a Mailbox or Door Alert Sensor that uses Deep Sleep to consume microamps of current, waking only when the door opens to send you an instant message via Telegram.
Along the way, we will examine the internals of the ESP32 chip to understand which sections are powered down in each mode.
ESP32 Power Consumption
The ESP32 has relatively high power consumption for a microcontroller, and there are two main reasons. First, it runs a fast clock – up to 240 MHz – and has a large number of GPIO pins and onboard peripherals. Second, it contains an integrated 2.4 GHz radio for both Wi-Fi and Bluetooth. Radios are extremely power-hungry, and the ESP32’s Wi-Fi transmitter is the largest single contributor to its current draw.
In a typical Wi-Fi sketch, the ESP32 can draw 50 mA or more during normal operation, with spikes up to 240 mA or more when the transmitter fires. For a project running off a 3000 mAh 18650 lithium-ion cell, that translates to roughly 12 hours of operation. For a device that needs to run unattended for weeks or months, that is simply not good enough.
The four low-power modes address this directly by selectively shutting down or pausing the chip’s most power-hungry sections when they are not needed. The art of low-power design with the ESP32 lies in choosing the right mode for your application and understanding the trade-offs that come with each.
Inside the ESP32
To understand how low-power modes work, it helps to know the main functional blocks of the ESP32 and the role each plays. The diagram below shows a simplified view of the ESP32’s internal architecture.

Here is what each block does:
Digital Peripherals
The collection of general-purpose interfaces and controllers – GPIO pins, I²C, SPI, UART, PWM, ADC, and DAC. These are the pins and buses your sketch uses to communicate with sensors, displays, and other external components.
Bluetooth / BLE
The radio hardware and baseband controller for Bluetooth Classic and Bluetooth Low Energy. This is a high-current device; switching it off saves significant power in applications that do not use it.
Wi-Fi
The 2.4 GHz 802.11 b/g/n radio and MAC controller. The single largest current consumer of the chip when transmitting. Managing when the radio is active is the foundation of all four power-saving modes.
ESP32 Core & Memory
The two Xtensa LX6 CPU cores and the main SRAM (up to 520 KB). This is where your sketch code runs and where program variables live. When this block is powered off, your code stops – and any variables stored here are lost.
ULP Co-processor
The Ultra-Low-Power co-processor is a small, simple processor that can run basic programs and read sensors while the main CPU cores are sleeping. It consumes only a few microamps and can wake the main CPU when a threshold is met.
RTC Controller
The Real-Time Clock and power management controller. This block acts as the ‘alarm clock’ for the chip – it keeps track of time, manages wakeup timers, and controls which power domains are switched on or off when entering and leaving sleep modes.
RTC Memory
A small block of memory (up to 8 KB slow + 8 KB fast) that stays powered during Deep Sleep. This is how you pass information – such as a boot counter or the last sensor reading – across a Deep Sleep cycle. It is not available in Hibernation.
RTC Peripherals
A set of low-power peripherals connected to the always-on RTC domain, including capacitive touch sensors and a subset of GPIO pins. Because they remain powered in Deep Sleep, they can serve as wakeup triggers without needing the main processor to be running.
ESP32 Low Power Modes
The ESP32 can operate in five power states, including the normal active state. The table below summarises all five and shows typical current consumption for each.
| Mode | CPU / Peripherals | Radio | Typical Current |
|---|---|---|---|
| Active Mode | On | On / Off | 240 mA (Wi-Fi Tx peak) |
| Modem Sleep | On | Off (periodic) | ~3 – 20 mA |
| Light Sleep | Paused | Off | ~0.8 mA |
| Deep Sleep | Off (ULP on) | Off | ~10 – 25 µA |
| Hibernation | Off | Off | ~2.5 µA |
The table below shows which internal blocks are powered on, paused, or off in each mode. A paused block retains its state and can resume instantly; a block that is off must be fully re-initialized when power is restored.
| Block | Active | Modem Sleep | Light Sleep | Deep Sleep | Hibernation |
|---|---|---|---|---|---|
| Digital Peripherals | ✔ | ✔ | Paused | ✘ | ✘ |
| Bluetooth / BLE | ✔ | ✘ | ✘ | ✘ | ✘ |
| Wi-Fi | ✔ | Paused | ✘ | ✘ | ✘ |
| ESP32 Core & Memory | ✔ | ✔ | Paused | ✘ | ✘ |
| ULP Co-processor | ✔ | ✔ | ✔ | ✔ | ✘ |
| RTC Controller | ✔ | ✔ | ✔ | ✔ | ✔ |
| RTC Memory | ✔ | ✔ | ✔ | ✔ | ✘ |
| RTC Peripherals | ✔ | ✔ | ✔ | ✔ | ✘ |
NOTE: In Active Mode, the only way to reduce power consumption is to lower the CPU clock frequency (using the setCpuFrequencyMhz() function). This can provide a modest reduction without entering a sleep state.
Active Mode
This is the normal operating mode. All CPU cores, peripherals, and the radio are fully powered and available. No special configuration is needed. This is where your ESP32 spends its time when it is not in a sleep mode.
Modem Sleep
The CPU cores remain active, and your sketch continues to run normally, but the Wi-Fi and Bluetooth radios are switched off between transmissions. The ESP32 can maintain a Wi-Fi association with your router by periodically waking the radio to check for buffered data. Current falls to roughly 3–20 mA.
Light Sleep
The CPU clock is paused, and most peripherals are powered down, but the contents of RAM and all CPU registers are fully preserved. When a wakeup event occurs, the CPU resumes execution from the exact line after the sleep call – there is no reboot, and nothing needs to be re-initialized. Current falls to around 0.8 mA.
Deep Sleep
Almost everything is powered off: both CPU cores, the main SRAM, the radio, and most peripherals. Only the RTC module, RTC memory, and the ULP co-processor (if used) remain powered. On wakeup, the ESP32 reboots completely from the top of setup(). Any variables not stored in RTC memory are lost. Current falls to 10–25 µA.
Hibernation
The deepest sleep state. Even the internal 8 MHz RTC oscillator and the ULP co-processor are powered off, and RTC memory is no longer guaranteed to retain its contents. The only wakeup source is an EXT0 GPIO interrupt on an RTC-capable pin. Current falls to around 2.5 µA. The ESP32 reboots completely on wakeup.
Battery Life with Low Power Modes
To put the power savings in concrete terms, let us use a single 3000 mAh 18650 lithium-ion cell as a reference – a very common choice for ESP32-based battery projects. The table below shows the theoretical battery life in each mode.
| Mode | Avg. Current | Battery Capacity | Estimated Life |
|---|---|---|---|
| Active Mode | 240 mA | 3000 mAh (18650) | ~12.5 hours |
| Modem Sleep | ~15 mA | 3000 mAh (18650) | ~8.3 days |
| Light Sleep | ~0.8 mA | 3000 mAh (18650) | ~5.1 months |
| Deep Sleep | ~15 µA | 3000 mAh (18650) | ~22.8 years |
| Hibernation | ~2.5 µA | 3000 mAh (18650) | ~137 years |
NOTE: These figures are theoretical maximums that assume the ESP32 chip alone is being powered, with no other current consumers. In practice, real battery life will be lower due to the quiescent current of voltage regulators, attached sensors and peripherals, and the battery’s self-discharge. The Deep Sleep and Hibernation figures also exceed the typical storage life of a lithium-ion cell.
The numbers are dramatic nonetheless. Moving from Active Mode to Deep Sleep reduces average current consumption by a factor of around 10,000. That is the difference between a battery lasting 12 hours and one that, in principle, would last over 22 years.
For a practical example, consider a weather station that wakes every 5 minutes, spends 10 seconds connecting to Wi-Fi and uploading a reading, then returns to Deep Sleep. That duty cycle averages just a few milliamps – giving several months of battery life from a single cell.
A Note About Development Boards
Before we start running experiments, there is something important to understand: the current figures in the table above apply to the ESP32 chip itself, not to a development board. This makes a significant difference in what you will measure.
A typical ESP32 development board – including the Espressif ESP32-DevKitC V4 we are using today – contains several components in addition to the ESP32 chip:
- A USB-to-UART bridge chip (e.g. CP2102 or CH340), which draws current continuously
- A 3.3 V voltage regulator (typically an AMS1117), which has a quiescent current of 5–10 mA
- One or more indicator LEDs, which can draw 1–3 mA each
In Deep Sleep, the ESP32 chip itself draws around 15 µA. But the voltage regulator alone draws 5–10 mA on top of that, completely masking the chip’s sleep current. You will not see microamp-level readings on a standard DevKit board without either bypassing the regulator (powering the 3.3V pin directly) or removing the indicator LED.
For the demonstrations in this article, we measure the total current of the entire board assembly, not just the chip. This means our readings will be higher than the datasheet figures – but they accurately reflect what you will see in your own experiments.
TIP: To get closer to the chip’s actual Deep Sleep current, power the ESP32 via its 3V3 pin directly from a regulated 3.3 V supply, bypassing the onboard regulator entirely. Also, remove the power LED if possible.
Test Hardware
We will use the same simple circuit for all four of our demonstrations today. The pushbutton provides a convenient way to generate a GPIO wake-up event for the Light Sleep, Deep Sleep, and Hibernation demos without additional components.
Components Required
- 1 × Espressif ESP32-DevKitC V4 (or equivalent ESP32 development board)
- 1 × SPST pushbutton switch (momentary, normally open)
- 1 × 10 kΩ resistor
- Jumper wires and breadboard
- USB power meter (optional, but recommended for demonstrations)
Wiring
The wiring is the same for all demonstrations:
- Connect one terminal of the pushbutton to GPIO 33 on the ESP32.
- Connect the other terminal of the pushbutton to the 3.3 V pin.
- Connect a 10 kΩ resistor between GPIO 33 and GND. This is the pull-down resistor.
When the button is not pressed, GPIO 33 reads LOW. When the button is pressed, 3.3 V is applied to the pin, and it reads HIGH. The wakeup sources in our sketches are configured to trigger on this HIGH level.
NOTE: GPIO 33 is an RTC-capable pin on the standard ESP32, which means it can be used as a wakeup source for Deep Sleep and Hibernation. Not all GPIO pins have this capability. RTC-capable GPIOs on the standard ESP32 include: 0, 2, 4, 12–15, 25–27, 32–39.
Modem Sleep Mode

How Modem Sleep Works
Modem Sleep is the lightest of the four power-saving modes. The name tells you exactly what happens: the modem – Espressif’s term for the Wi-Fi and Bluetooth radio – is put to sleep, while the two CPU cores continue running normally. Your sketch keeps executing without interruption.
In Modem Sleep, the ESP32 must be operating in station mode (STA); it will not work if configured as an access point. The mode uses a Wi-Fi protocol called DTIM (Delivery Traffic Indication Message) to coordinate radio activity with your router:
- The ESP32 negotiates a DTIM interval with your router – typically around 100 milliseconds.
- The router agrees to buffer any data destined for the ESP32 during that interval.
- The ESP32 keeps its radio off for the duration of the interval, saving power.
- At the end of the interval, the radio wakes, checks the router for buffered data, then sleeps again.
This cycle happens automatically in the background, completely transparent to your sketch. The only line of code you need to enable it is a single function call.
Applications for Modem Sleep
Modem Sleep is ideal when the CPU must stay active, but the radio is only needed occasionally:
- Live sensor dashboards that stream data every few seconds
- Voice-activated smart home nodes that listen continuously but transmit rarely
- Industrial machine monitors that sample sensors nonstop and upload batched data
- Smart displays that keep the screen and UI active but fetch content updates at intervals
- GPS trackers that continuously log position but report to a server once per minute
Modem Sleep Sketch
The sketch below connects to Wi-Fi, enables Modem Sleep with a single function call, then reports the Wi-Fi signal strength (RSSI) on the Serial Monitor every 5 seconds. A USB power meter will show the current rising when the radio is active and dropping between reports.
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 |
// ============================================================ // Modem Sleep Mode Demonstration // DroneBot Workshop // ------------------------------------------------------------ // The CPU continues to run normally; the Wi-Fi / Bluetooth // radio (the "modem") is put to sleep between DTIM beacon // intervals. Typical current: 3–20 mA (vs 240 mA peak active). // // Hardware required: // - ESP32 development board // - USB cable (power + Serial Monitor) // - Optional: USB power meter to observe current reduction // // Instructions: // 1. Replace YOUR_SSID and YOUR_PASSWORD with your Wi-Fi details. // 2. Upload and open Serial Monitor at 115200 baud. // 3. Observe uptime reports every 5 seconds. // If using a power meter you should see the current drop // once Modem Sleep is activated. // ============================================================ #include <WiFi.h> const char* ssid = "YOUR_SSID"; const char* password = "YOUR_PASSWORD"; void setup() { Serial.begin(115200); delay(1000); Serial.println("Connecting to Wi-Fi..."); WiFi.begin(ssid, password); while (WiFi.status() != WL_CONNECTED) { delay(500); Serial.print("."); } Serial.println(); Serial.print("Connected! IP: "); Serial.println(WiFi.localIP()); // ── Activate Modem Sleep ────────────────────────────────── // WiFi.setSleep(true) enables automatic DTIM-based radio sleep. // The radio wakes at each DTIM beacon (typically every 100 ms) // to check for buffered data, then sleeps again. WiFi.setSleep(true); Serial.println("Modem Sleep ACTIVE"); } unsigned long lastReport = 0; void loop() { if (millis() - lastReport >= 5000) { lastReport = millis(); Serial.print("Uptime: "); Serial.print(millis() / 1000); Serial.print("s | RSSI: "); Serial.print(WiFi.RSSI()); Serial.println(" dBm"); } } |
Upload the sketch, fill in your Wi-Fi credentials, and open the Serial Monitor at 115200 baud. Every 5 seconds, you will see the uptime and RSSI reported. If you have a USB power meter in line, you will observe the current spike each time the radio wakes to communicate with the router, then drops back down during the sleep interval.
TIP: WiFi.setSleep(true) is the only line needed to enable Modem Sleep. The CPU continues to run exactly as normal and there is no sleep function to call and no wakeup to handle. This makes Modem Sleep the easiest power-saving mode to add to an existing Wi-Fi sketch.
Modem Sleep Results

As you can see on the meter, the current rises each time the ESP32 needs to communicate and drops in between. Over hours and days, this adds up to a significant reduction in total current consumed from your battery compared to active mode.
Light Sleep Mode

How Light Sleep Works
Light Sleep takes power saving a step further by pausing the CPU itself. When the ESP32 enters Light Sleep, the CPU clock is gated – the processor is frozen – and most peripherals are powered down. Crucially, however, the entire contents of RAM and all CPU registers are preserved throughout. When a wake-up event occurs, the CPU simply resumes execution at the exact instruction it was executing when it went to sleep, as if nothing happened.
From your sketch’s perspective, the esp_light_sleep_start() function call simply takes a little longer than usual to return. There is no reboot, no re-initialization of peripherals, and no lost variables. This is what makes Light Sleep the best choice for event-driven applications.
Multiple wakeup sources can be active simultaneously:
- RTC timer – wake after a specified interval
- GPIO pin – wake when a designated pin changes level
- UART RX – wake when data arrives on a serial port
- Touch sensor – wake on capacitive touch
Whichever wakeup source fires first will end the sleep. In our demonstration, we configure both a timer and a GPIO button so you can see both in action.
Applications for Light Sleep
Light Sleep is the right choice when response time matters – devices that must react quickly to events but can afford to pause between them:
- Wireless keyboards and mice – wake instantly on keypress or movement
- Smart doorbells – GPIO wakeup on button press, responds in milliseconds
- Motion-activated cameras – PIR sensor triggers GPIO wakeup, captures image
- Baby and noise monitors – ADC wakes on audio level threshold
- Occupancy sensors – IR beam break wakes the device to control lights or HVAC
- Gesture-controlled devices – touch or gesture pin triggers instant wakeup
Light Sleep Sketch
Our Light Sleep demonstration uses both wakeup sources described above. The ESP32 stays awake for 2 seconds – simulating the time spent doing useful work – then enters Light Sleep. It will wake automatically after 10 seconds via the timer, or immediately if the pushbutton is pressed. After waking, it prints the reason to the Serial Monitor and repeats the cycle.
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 |
// ============================================================ // Light Sleep Mode Demonstration – GPIO + Timer Wakeup // DroneBot Workshop // ------------------------------------------------------------ // The CPU clock is gated (paused) and peripherals are powered // down, but RAM and register state are fully preserved. // Execution resumes from the exact line after esp_light_sleep_start(). // Typical current during sleep: ~0.8 mA. // // Hardware required: // - ESP32 development board (e.g. Espressif ESP32-DevKitC V4) // - 1 x Pushbutton (one leg → GPIO 33, other leg → 3.3 V) // - 1 x 10 kΩ resistor (GPIO 33 → GND, pull-down) // - Jumper wires and breadboard // // Wiring summary: // GPIO 33 ── Button ── 3.3 V // GPIO 33 ── 10 kΩ ── GND (pull-down) // // Instructions: // 1. Wire the circuit as above. // 2. Upload and open Serial Monitor at 115200 baud. // 3. The ESP32 stays awake for 2 s, then enters Light Sleep. // 4. Press the button to wake immediately, or wait 10 s for // the timer wakeup. // ============================================================ #include "esp_sleep.h" #define BUTTON_PIN 33 // RTC-capable GPIO for wakeup #define SLEEP_SECS 10 // Timer fallback wakeup (seconds) void printWakeupReason() { esp_sleep_wakeup_cause_t cause = esp_sleep_get_wakeup_cause(); switch (cause) { case ESP_SLEEP_WAKEUP_GPIO: Serial.println("Woke up: GPIO button press"); break; case ESP_SLEEP_WAKEUP_TIMER: Serial.println("Woke up: Timer expired"); break; default: Serial.println("First boot or unknown wakeup source"); break; } } void setup() { Serial.begin(115200); delay(500); pinMode(BUTTON_PIN, INPUT); // External pull-down resistor fitted } void loop() { // ── Awake phase ────────────────────────────────────────── Serial.println("Awake! Doing work for 2 seconds..."); printWakeupReason(); delay(2000); // ── Configure wakeup sources ───────────────────────────── // Timer: wake after SLEEP_SECS seconds esp_sleep_enable_timer_wakeup((uint64_t)SLEEP_SECS * 1000000ULL); // GPIO: wake when BUTTON_PIN goes HIGH (button pressed) esp_sleep_enable_gpio_wakeup(); gpio_wakeup_enable((gpio_num_t)BUTTON_PIN, GPIO_INTR_HIGH_LEVEL); // ── Enter Light Sleep ──────────────────────────────────── Serial.println("Entering Light Sleep..."); Serial.flush(); // Drain UART before sleeping esp_light_sleep_start(); // ← CPU pauses here // Execution resumes on the very next line after wakeup Serial.println("Resumed from Light Sleep!"); } |
Upload the sketch and open the Serial Monitor at 115200 baud. Press the button at any time during the sleep phase to see an immediate GPIO wakeup. Wait 10 seconds without pressing it to see the timer wake up. Notice that the wakeup reason is printed on the serial monitor in both cases.
NOTE: The Serial.flush() call before esp_light_sleep_start() is essential. Without it, any Serial output that has not yet been transmitted will be corrupted or lost when the CPU clock is gated. Always flush before sleeping.
Light Sleep Results

Here are the results on the serial monitor. If you are also monitoring power consumption, you can clearly see the current drop when the ESP32 enters Light Sleep and the rise again during the active 2-second work period. Press the button during the sleep phase, and the current jumps up immediately.
Deep Sleep Mode

How Deep Sleep Works
Deep Sleep is where things get really interesting from a battery life perspective. In Deep Sleep, almost everything on the ESP32 is powered off: both CPU cores, the main SRAM, the Wi-Fi and Bluetooth radio, and most peripherals. Current consumption drops to just 10–25 µA – roughly 10,000 times lower than active Wi-Fi mode.
Only three things remain powered in Deep Sleep:
- The RTC module and its slow memory (up to 8 KB)
- The ULP co-processor, if you have programmed it to run
- RTC-capable GPIO pins configured as wakeup sources
There is an important difference between Deep Sleep and the modes above: the ESP32 does not resume from where it left off. Instead, when it wakes from Deep Sleep, it reboots completely and starts execution from the beginning of setup(). Think of it as pressing the reset button.
The only way to carry information across a Deep Sleep cycle is to store it in RTC memory using the RTC_DATA_ATTR attribute. Variables declared this way are placed in RTC slow memory, which remains powered throughout the sleep. All other variables are lost.
Wakeup sources for Deep Sleep include:
- RTC timer – wake after a specified interval (most common)
- EXT0 – a single RTC-capable GPIO, configurable to trigger on HIGH or LOW
- EXT1 – multiple RTC-capable GPIO pins
- Touch sensor
- ULP co-processor activity
Applications for Deep Sleep
Deep Sleep is the most widely used power-saving mode for battery-operated IoT projects. It suits any application where the device wakes periodically, does something brief, then sleeps again:
- Weather stations – wake every 10–15 minutes, read sensors, upload to cloud, sleep
- Soil moisture monitors – periodic readings that alert when plants need watering
- Mailbox and door alert sensors – EXT0 wakeup sends instant notification when triggered
- Remote water and gas meters – read and transmit meter data hourly
- Cold-chain data loggers – log temperature at intervals, flag excursions automatically
- Asset and vehicle trackers – wake on timer to report GPS position
- Wildlife camera triggers – PIR wakeup captures an image, runs unattended for months
Deep Sleep Sketch
Our Deep Sleep demonstration uses an RTC timer to wake the board every 10 seconds. Each time it wakes, it increments a boot counter stored in RTC memory and prints the count and wakeup reason to the Serial Monitor. This demonstrates that RTC_DATA_ATTR variables persist across sleep cycles and confirms that the board reboots from setup() each time.
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 |
// ============================================================ // Deep Sleep Mode Demonstration – Timer Wakeup // DroneBot Workshop // ------------------------------------------------------------ // Almost everything is powered off: both CPU cores, most RAM, // Wi-Fi radio, and peripherals. Only the RTC module and RTC // memory remain on. Current during sleep: ~10–25 µA. // // IMPORTANT: On wakeup the ESP32 reboots completely and starts // from the top of setup() – it does NOT resume like Light Sleep. // Use RTC_DATA_ATTR to store variables that must survive sleep. // // Hardware required: // - ESP32 development board // - USB cable (power + Serial Monitor) // - Optional: USB power meter to observe µA-level current // // Instructions: // 1. Upload and open Serial Monitor at 115200 baud. // 2. Watch the boot counter increment every SLEEP_SECONDS. // 3. The RTC_DATA_ATTR bootCount persists across sleep cycles // but resets to 0 when power is removed or the chip is // physically reset. // ============================================================ #include "esp_sleep.h" #define SLEEP_SECONDS 10 // Sleep duration between wakeups // ── RTC memory variable ────────────────────────────────────── // RTC_DATA_ATTR places this variable in RTC slow memory, // which stays powered during Deep Sleep. RTC_DATA_ATTR int bootCount = 0; // ── Helper: print the wakeup reason ───────────────────────── void printWakeupReason() { esp_sleep_wakeup_cause_t cause = esp_sleep_get_wakeup_cause(); switch (cause) { case ESP_SLEEP_WAKEUP_TIMER: Serial.println("Wakeup cause: RTC Timer"); break; case ESP_SLEEP_WAKEUP_EXT0: Serial.println("Wakeup cause: EXT0 GPIO"); break; case ESP_SLEEP_WAKEUP_EXT1: Serial.println("Wakeup cause: EXT1 GPIO"); break; case ESP_SLEEP_WAKEUP_TOUCHPAD: Serial.println("Wakeup cause: Touch sensor"); break; default: Serial.println("Wakeup cause: Power-on / hard reset"); break; } } void setup() { Serial.begin(115200); delay(500); // Let UART settle ++bootCount; Serial.println(); Serial.print("==== Boot #"); Serial.print(bootCount); Serial.println(" ===="); printWakeupReason(); // ── Your sensor reading / work goes here ───────────────── Serial.println("Doing work..."); delay(1000); // ───────────────────────────────────────────────────────── // ── Configure timer wakeup and sleep ──────────────────── esp_sleep_enable_timer_wakeup((uint64_t)SLEEP_SECONDS * 1000000ULL); Serial.print("Entering Deep Sleep for "); Serial.print(SLEEP_SECONDS); Serial.println(" seconds. Goodbye!"); Serial.flush(); esp_deep_sleep_start(); // Does not return } void loop() { // Never reached in Deep Sleep sketches } |
Upload the sketch and open the Serial Monitor at 115200 baud. You will see Boot #1 with a power-on reason, then Boot #2, #3, and so on with the RTC Timer reason, each 10 seconds apart. Remove USB power and reconnect – the boot count resets to zero, confirming that RTC memory is powered from the main supply and does not survive a full power-off.
TIP: esp_deep_sleep_start() does not return. Code placed after this call will never execute. There is also no point in writing anything in loop() for a Deep Sleep sketch, as the device reboots to setup() on every wakeup and never reaches loop() at all.
Deep Sleep Results

Here are the results of the Deep Sleep demo on the serial monitor.
Hibernation Mode

How Hibernation Works
Hibernation is the deepest sleep state the ESP32 offers – the closest thing to completely switching the chip off. In Hibernation, the internal 8 MHz RTC oscillator is powered down along with the ULP co-processor and RTC slow memory. Only the absolute minimum needed to detect a wakeup signal is kept alive.
As a result of shutting down the RTC oscillator, timer-based wakeup is unavailable. The only way to wake the chip from Hibernation is via the EXT0 GPIO interrupt on a single RTC-capable pin. This makes Hibernation suitable only for event-driven rather than time-driven applications.
Current drops to around 2.5 µA – even lower than Deep Sleep. The savings sound modest in absolute terms, but over weeks and months on a coin cell, it can make a meaningful difference.
There is no separate enter_hibernation() function in the ESP32 API. Hibernation is achieved by putting the ESP32 into Deep Sleep after explicitly powering down the three additional domains that would otherwise remain active. This is exactly what the three esp_sleep_pd_config() calls in the sketch do. Omitting any one of them means the chip does not enter true Hibernation.
NOTE: RTC memory contents are not guaranteed to survive Hibernation. Do not use RTC_DATA_ATTR variables in Hibernation sketches – the values may be corrupted or lost on wakeup. If you need persistent storage, use the ESP32’s NVS (Non-Volatile Storage) in flash memory instead.
Applications for Hibernation
Hibernation is the right choice for devices that sleep for very long periods and only ever wake in response to a physical event:
- Emergency panic buttons – draws 2.5 µA for years; fires instantly when pressed
- Anti-theft tags – sleeps indefinitely on an asset; wakes and alerts when tampered with
- Structural integrity sensors – vibration threshold wakeup; monitors bridges or buildings for years
- Pipeline leak detectors – pressure-drop GPIO wakeup raises an alert
- Long-term door and gate sensors – reed switch wakeup on rarely-opened access points
- Medical alert wearables – patient presses button to summon help; device lasts months between charges
Hibernation Sketch
The Hibernation sketch starts, checks whether it was woken by the EXT0 GPIO, prints a message, waits 2 seconds, then enters Hibernation. It will remain in Hibernation indefinitely until the pushbutton is pressed, at which point it reboots, prints its wakeup message, and returns to Hibernation.
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 |
// ============================================================ // Hibernation Mode Demonstration – EXT0 GPIO Wakeup // DroneBot Workshop // ------------------------------------------------------------ // The deepest sleep state. The internal 8 MHz oscillator, // ULP co-processor, and RTC slow memory are all powered off. // Only the EXT0 GPIO interrupt can wake the chip. // Current during sleep: ~2.5 µA. // // Because the RTC oscillator is off, timer wakeup is NOT // available in Hibernation. This mode is only suitable for // event-driven applications (button, reed switch, PIR, etc.). // // Like Deep Sleep, the ESP32 reboots from setup() on wakeup. // // Hardware required: // - ESP32 development board // - 1 x Pushbutton (one leg → GPIO 33, other leg → 3.3 V) // - 1 x 10 kΩ resistor (GPIO 33 → GND, pull-down) // - Jumper wires and breadboard // // Wiring summary: // GPIO 33 ── Button ── 3.3 V // GPIO 33 ── 10 kΩ ── GND (pull-down) // // NOTE: The wakeup GPIO MUST be an RTC-capable pin. // RTC-capable GPIOs on standard ESP32: 0,2,4,12-15,25-27,32-39 // // Instructions: // 1. Wire the circuit as above. // 2. Upload and open Serial Monitor at 115200 baud. // 3. The ESP32 enters Hibernation immediately after setup(). // 4. Press the button to wake, then watch it return to // Hibernation after 2 seconds. // ============================================================ #include "esp_sleep.h" #include "driver/rtc_io.h" #define WAKEUP_PIN GPIO_NUM_33 // Must be an RTC-capable GPIO void enterHibernation() { // Configure EXT0: wake when WAKEUP_PIN goes HIGH (button press) esp_sleep_enable_ext0_wakeup(WAKEUP_PIN, 1); // Power down all optional domains to achieve true Hibernation esp_sleep_pd_config(ESP_PD_DOMAIN_RTC_SLOW_MEM, ESP_PD_OPTION_OFF); esp_sleep_pd_config(ESP_PD_DOMAIN_RTC_FAST_MEM, ESP_PD_OPTION_OFF); esp_sleep_pd_config(ESP_PD_DOMAIN_XTAL, ESP_PD_OPTION_OFF); Serial.println("Hibernating. Press button to wake."); Serial.flush(); esp_deep_sleep_start(); // Enters Hibernation due to domain config } void setup() { Serial.begin(115200); delay(500); esp_sleep_wakeup_cause_t cause = esp_sleep_get_wakeup_cause(); if (cause == ESP_SLEEP_WAKEUP_EXT0) { Serial.println("Woke from Hibernation!"); } else { Serial.println("First boot – entering Hibernation"); } // Brief active window before returning to sleep delay(2000); enterHibernation(); // Does not return } void loop() { // Never reached } |
Upload the sketch and open the Serial Monitor at 115200 baud. After the first boot message, the ESP32 immediately enters Hibernation. Press the pushbutton at any time – the current on the meter will spike briefly as the board boots and processes the wakeup, then drop back to near-zero as it returns to Hibernation.
Hibernation Results

The current draw between button presses is almost entirely the voltage regulator and LEDs on the development board – the ESP32 chip itself is drawing only about 2.5 µA. This is truly the ultimate power-saving mode: the device is essentially off until you need it.
Project: Mailbox / Door Alert Sensor
Now, let us put everything we have learned to work in a practical project. We are going to build a Mailbox or Door Alert Sensor – a device that sleeps in Deep Sleep, drawing just microamps, and wakes up the moment someone opens a mailbox or door. It then connects to your Wi-Fi network and sends you an instant alert via Telegram.
This is exactly the kind of project that makes the ESP32’s Deep Sleep capability so compelling. The device could run for many months on a single 18650 battery, and yet it responds to an event within seconds of it occurring.
Project Overview
The project replaces the pushbutton from our earlier demonstrations with a magnetic reed switch. A reed switch contains two small metal contacts inside a glass or plastic capsule. When a magnet is brought close, the contacts close; when the magnet moves away, they open. Mounted on a door or mailbox flap with a magnet on the moving part, a reed switch gives you a reliable way to detect opening and closing events.
Here is how the project works end-to-end:
- The ESP32 sits in Deep Sleep, consuming around 15 µA from the board assembly.
- The mailbox or door is closed – the magnet is adjacent to the reed switch, which is in its default state.
- Someone opens the mailbox or door – the magnet moves away from the reed switch.
- The reed switch changes state, pulling GPIO 33 HIGH and triggering the EXT0 wakeup.
- The ESP32 reboots into setup(), connects to Wi-Fi, and sends a Telegram alert.
- After sending the message, the ESP32 disconnects from Wi-Fi and returns to Deep Sleep.
Why Telegram?
Telegram is an excellent choice for notifications in maker projects:
- Free to use with no rate limits for personal projects
- Official Bot API is stable and well-documented
- Messages arrive instantly on your phone, even with the app in the background
- Excellent Arduino library: UniversalTelegramBot by Brian Lough
- No dedicated server needed – the ESP32 connects directly to Telegram’s HTTPS API
Components Required
- 1 × ESP32 development board (e.g. Espressif ESP32-DevKitC V4)
- 1 × Magnetic reed switch (normally open, standard PCB or cylinder type)
- 1 × Small magnet (usually supplied with reed switch modules)
- 1 × 10 kΩ resistor (pull-down for GPIO 33)
- 1 × 3.7 V LiPo battery + TP4056 USB-C LiPo charger module (for battery power)
- Jumper wires and breadboard (for prototyping)
For a permanent installation, you will also want a small weatherproof enclosure and a prototyping PCB or mini breadboard to mount the components securely.
Wiring
The reed switch replaces the pushbutton from our earlier demonstrations. The wiring is identical:
- Reed switch terminal A → GPIO 33
- Reed switch terminal B → 3.3 V
- 10 kΩ resistor → between GPIO 33 and GND (pull-down)

The EXT0 wakeup is configured to trigger when GPIO 33 goes HIGH (wakes on HIGH). This means:
- Reed switch OPEN (magnet present, door/mailbox closed) → GPIO 33 LOW → ESP32 sleeping
- Reed switch CLOSED (magnet moved away, door/mailbox opened) → GPIO 33 HIGH → ESP32 wakes
NOTE: There are two types of reed switches: normally open (NO) and normally closed (NC). The sketch uses a normally open switch. If you prefer to use a normally closed switch, swap the resistor to a pull-up (3.3 V to GPIO 33) and change the wakeup trigger from HIGH (1) to LOW (0) in the esp_sleep_enable_ext0_wakeup() call.
Setting Up Telegram
Before uploading the sketch, you need to create a Telegram bot. This takes about two minutes:
- Open Telegram and search for BotFather (look for the account with the blue verified tick).
- Send BotFather the command: /newbot
- Follow the prompts: give your bot a friendly name (e.g. Mailbox Alert) and a username that ends in ‘bot’ (e.g. MailboxAlertBot).
- BotFather will reply with your bot’s HTTP API token. Copy this – you will need it in the sketch.
- To find your Chat ID, search for userinfobot in Telegram and send it the message /start. It will reply with your numeric chat ID.
You will also need to install two libraries via the Arduino Library Manager (Sketch → Include Library → Manage Libraries):
- UniversalTelegramBot by Brian Lough
- ArduinoJson by Benoit Blanchon (v6.x) – this is a dependency of UniversalTelegramBot and will be offered for installation automatically
TIP: If you want to send alerts to a group rather than just yourself, add your bot to a Telegram group and use the group’s chat ID (which will be a negative number) instead of your personal chat ID in the sketch.
The Sketch
Open the sketch and fill in the four credential placeholders at the top: your Wi-Fi SSID and password, your Telegram bot token, and your Telegram chat ID. Everything else is ready to go.
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 |
// ============================================================ // Mailbox / Door Alert Sensor // DroneBot Workshop // ------------------------------------------------------------ // Uses Deep Sleep + EXT0 wakeup on a reed switch. // When the door / mailbox is opened the reed switch closes, // pulling GPIO 33 HIGH and waking the ESP32. // The sketch then connects to Wi-Fi and sends a Telegram alert. // Current during Deep Sleep: ~10–25 µA. // // ── Libraries required (install via Arduino Library Manager) ── // • UniversalTelegramBot by Brian Lough // • ArduinoJson by Benoit Blanchon (v6.x) // // ── Telegram Bot Setup ──────────────────────────────────────── // 1. Open Telegram → search for "BotFather" (blue verified tick). // 2. Send /newbot and follow the prompts. // 3. Copy the HTTP API token BotFather gives you → BOT_TOKEN below. // 4. Find your Chat ID: search "userinfobot", send /start, // copy the numeric ID → CHAT_ID below. // // ── Hardware required ───────────────────────────────────────── // - ESP32 development board // - 1 x Reed switch (one terminal → GPIO 33, other → 3.3 V) // - 1 x 10 kΩ resistor (GPIO 33 → GND, pull-down) // - 1 x TP4056 LiPo charger module + 3.7 V LiPo battery // (or USB power bank for testing) // - Jumper wires // // ── Wiring summary ──────────────────────────────────────────── // GPIO 33 ── Reed switch terminal A // 3.3 V ── Reed switch terminal B // GPIO 33 ── 10 kΩ ── GND (pull-down) // // When the magnet is present → switch OPEN → GPIO 33 LOW (sleeping) // When the magnet moves away → switch CLOSES → GPIO 33 HIGH (wakeup!) // // ── Customisation tips ──────────────────────────────────────── // • Change CHAT_ID to a group chat ID to alert the whole family. // • Add a voltage divider (100 kΩ + 100 kΩ) on the LiPo line and // read it with analogRead() to include battery voltage in the alert. // • Add debounce: store millis() in RTC memory and ignore wakeups // within 2–3 seconds of the previous one. // ============================================================ #include <WiFi.h> #include <WiFiClientSecure.h> #include <UniversalTelegramBot.h> #include <ArduinoJson.h> #include "esp_sleep.h" #include "driver/rtc_io.h" // ── Credentials – REPLACE THESE ───────────────────────────── const char* WIFI_SSID = "YOUR_WIFI_SSID"; const char* WIFI_PASS = "YOUR_WIFI_PASSWORD"; const char* BOT_TOKEN = "YOUR_TELEGRAM_BOT_TOKEN"; // from BotFather const char* CHAT_ID = "YOUR_TELEGRAM_CHAT_ID"; // numeric ID // ───────────────────────────────────────────────────────────── #define WAKEUP_PIN GPIO_NUM_33 // Reed switch input (RTC-capable GPIO) #define WIFI_TIMEOUT 20000 // Max time (ms) to wait for Wi-Fi // Persists across Deep Sleep cycles RTC_DATA_ATTR int alertCount = 0; WiFiClientSecure secureClient; UniversalTelegramBot bot(BOT_TOKEN, secureClient); // ── Connect to Wi-Fi, return true on success ───────────────── bool connectWiFi() { Serial.print("Connecting to Wi-Fi"); WiFi.mode(WIFI_STA); WiFi.begin(WIFI_SSID, WIFI_PASS); unsigned long startMs = millis(); while (WiFi.status() != WL_CONNECTED) { if (millis() - startMs > WIFI_TIMEOUT) { Serial.println(" TIMEOUT – could not connect."); return false; } delay(500); Serial.print("."); } Serial.println(" Connected!"); Serial.print("IP: "); Serial.println(WiFi.localIP()); return true; } void setup() { Serial.begin(115200); delay(500); esp_sleep_wakeup_cause_t cause = esp_sleep_get_wakeup_cause(); // ── Only act if we were woken by the reed switch ────────── if (cause == ESP_SLEEP_WAKEUP_EXT0) { alertCount++; Serial.print("Reed switch triggered! Alert #"); Serial.println(alertCount); if (connectWiFi()) { secureClient.setInsecure(); // Accept Telegram's certificate // Build the alert message String msg = "\U0001F4EC Mailbox opened! Alert #" + String(alertCount); Serial.print("Sending Telegram message... "); bool sent = bot.sendMessage(CHAT_ID, msg, ""); Serial.println(sent ? "OK" : "FAILED"); } // Clean up Wi-Fi before sleeping to minimise current WiFi.disconnect(true); WiFi.mode(WIFI_OFF); delay(500); } else { // First power-on or hard reset Serial.println("First boot – no action needed. Going to sleep."); } // ── Configure EXT0 wakeup and enter Deep Sleep ─────────── // Wake when WAKEUP_PIN goes HIGH (reed switch closes when door opens) esp_sleep_enable_ext0_wakeup(WAKEUP_PIN, 1); Serial.println("Entering Deep Sleep. Waiting for door..."); Serial.flush(); esp_deep_sleep_start(); // Does not return } void loop() { // Never reached } |
Running the Project
Upload the sketch and open the Serial Monitor at 115200 baud. On first boot, the ESP32 will print a first boot message and immediately enter Deep Sleep. Bring a magnet near the reed switch and then move it away to simulate the door opening. You should see:
- The ESP32 boots and prints the alert count to the Serial Monitor.
- It connects to Wi-Fi (this takes a few seconds – the first connection after Deep Sleep always does).
- It sends the Telegram message and confirms success on the Serial Monitor.
- It disconnects from Wi-Fi and returns to Deep Sleep.
- A notification appears on your phone via Telegram almost immediately.

The alert count increments on each wakeup because it is stored in RTC memory with RTC_DATA_ATTR. Disconnect the power and reconnect and you will see it reset to zero, confirming that RTC memory requires continuous power from the supply to maintain its contents.

For a real installation, consider mounting the ESP32 and LiPo inside a small weatherproof enclosure with the reed switch wires passing through a grommet. Surface-mount alarm reed switches – available with built-in adhesive strips – are ideal for doors and windows. For a mailbox, a cylindrical reed switch (5 mm × 2 mm type) and a small N52 neodymium magnet on the flap work very well.
Conclusion
The ESP32 is a power-hungry microcontroller by default – it can draw over 240 mA in active Wi-Fi mode. But Espressif has built four low-power modes into the chip that progressively reduce current consumption, down to 2.5 µA in Hibernation.
Here is a quick summary of when to use each mode:
- Modem Sleep – when the CPU must stay active, but Wi-Fi is only needed occasionally. A single line of code enables it.
- Light Sleep – when the device needs to respond quickly to events but can pause between them. RAM and registers are preserved; execution resumes exactly where it left off.
- Deep Sleep – the most widely used IoT power mode. Almost everything powers off; the device reboots on wakeup. Use RTC_DATA_ATTR for variables that must survive sleep cycles.
- Hibernation – the ultimate power-saving mode. Only a GPIO interrupt can wake the device. Suited to applications that sleep for very long periods between infrequent external events.
Our Mailbox Alert project brought Deep Sleep to life in a practical context: a device that sleeps for essentially free, wakes on a physical event, sends a Wi-Fi notification, and immediately returns to sleep. The same pattern – Deep Sleep with an EXT0 or timer wakeup, followed by a brief active period to do useful work – forms the foundation of the vast majority of battery-powered ESP32 IoT projects.
If you want to experiment further, try combining the ULP co-processor with Deep Sleep to take sensor readings during the sleep phase without waking the main CPU, or try using EXT1 to monitor multiple GPIO pins simultaneously. The ESP32’s power management system is surprisingly deep once you start exploring it.
The sketches used in this article are available for download below. If you have questions or build your own variation of the mailbox alert project, share it in the comments – we love to see what you make here in the workshop. Have fun with your sleepy ESP32!
Resources
- Code for this Article – All the sketches in a ZIP file
- PDF Version – A PDF version of the article
- Espressif ESP32 Technical Reference Manual – Reference manual for the ESP32
- Espressif ESP32 API Reference: Sleep Modes – Detailed reference for four sleep modes
- Espressif ESP32-DevKitC V4 Getting Started Guide – Reference for ESP32 DevKit used in these experiments






HI – great article! I’m building an ESP32 based solar powered weather station and this answers a bunch of questions for me. Thank you