An ESP32 with Wi-Fi switched on draws tens of milliamps, and short transmit bursts go much higher. Espressif's datasheet lists 240 mA for an 802.11b transmission at +19.5 dBm. A battery will not last long at that rate. In deep sleep the same chip needs about 10 µA (RTC timer and RTC memory running), so a sensor that wakes up, reports and goes straight back to sleep spends almost all of its time in the cheapest state.

ESPHome handles deep sleep with one component. The work is in everything around it: keeping the awake window short, getting the readings out before the chip sleeps, still being able to update the firmware, and choosing hardware that does not waste the savings.

What deep sleep actually does

In deep sleep only the RTC memory and RTC peripherals stay powered. Wi-Fi, Bluetooth and the main CPU are off. Waking up is close to a reboot: the chip starts the firmware from the beginning, connects to Wi-Fi again and sets up every component again.

Two consequences follow:

  • The device is unreachable while asleep. ESPHome's documentation says it plainly: a sleeping node does no work and does not respond to network traffic, OTA updates included.
  • Every wake-up costs a Wi-Fi connection. Connecting, not measuring, is usually the most expensive part of the cycle, so most of the tuning below aims at shortening it.

The ESP32 can wake on a timer, on an RTC-capable GPIO pin (GPIO0, 2, 4, 12-15, 25-27, 32 and 39 according to ESPHome's list) or on touch. The ESP8266 can only wake on a timer, and only if GPIO16 is wired to the RST pin.

Hardware: the board matters more than the chip

The 10 µA figure is for the bare chip. A typical development board adds a USB-to-serial converter, a power LED and a voltage regulator, and they all keep drawing current while the ESP32 sleeps. The AMS1117-3.3, a regulator common on cheap boards, lists a typical quiescent current of 5 mA in its datasheet. That alone is five hundred times what the sleeping chip needs.

If you want a long battery life:

  • Use a board designed for batteries or a bare module with your own power circuit. Measure the sleep current with a multimeter in series with the battery before you trust any figure, including the one on the product page.
  • Pick a regulator by quiescent current and peak output. It should draw microamps at idle, yet still deliver the transmit peaks. Espressif recommends a 3.3 V supply able to provide at least 500 mA.
  • Watch the voltage range. The ESP32 runs from 2.3 to 3.6 V. A fully charged lithium-ion cell sits at about 4.2 V, so it cannot go straight to the 3.3 V pin. The regulator's dropout matters too: the AMS1117 datasheet gives a typical dropout of 1.1 V at 1 A, headroom a discharging lithium-ion cell quickly runs out of.
  • ESP8266 boards with a USB chip (the D1 mini, for example) may not wake properly when powered over USB, because the USB chip also drives the RST line. ESPHome suggests powering such a board from a separate 3.3 V source.

Measuring the battery voltage

To see the battery level in Home Assistant, connect the battery through a voltage divider to an ADC pin. On the original ESP32, use an ADC1 pin (GPIO32-39). Espressif's ADC documentation notes that ADC2 is also used by the Wi-Fi driver, which is exactly what is running when the sensor wakes up.

Two equal resistors halve the voltage: 4.2 V becomes 2.1 V. ESPHome's ADC on the ESP32 reads only up to about 1.1 V at the default 0db attenuation, so set attenuation: 12db. In ESPHome's tests the usable range reached about 3.12 V, so 2.1 V fits comfortably.

Keep in mind that the divider drains the battery all the time. With two 100 kΩ resistors, 4.2 V across 200 kΩ is 21 µA, twice the chip's deep sleep current. Higher resistor values cut that loss, but a high-impedance source makes ADC readings less stable, so test what your board reads before you build it into a case.

The configuration

The example below is for an ESP32 with an SHT4x temperature and humidity sensor on I²C. Instead of letting sensors poll on their own schedule, it waits for Home Assistant to connect, takes one reading of each sensor, gives the values a moment to reach Home Assistant and goes to sleep for ten minutes.

esphome:
  name: garden-sensor
  on_boot:
    priority: -100
    then:
      - wait_until:
          condition: api.connected
          timeout: 20s
      - component.update: climate
      - component.update: battery_voltage
      - delay: 2s
      - if:
          condition:
            binary_sensor.is_off: ota_mode
          then:
            - deep_sleep.enter: deep_sleep_1

esp32:
  board: esp32dev

logger:

wifi:
  ssid: !secret wifi_ssid
  password: !secret wifi_password
  fast_connect:
    enabled: true
    storage: rtc
  manual_ip:
    static_ip: 192.168.1.60
    gateway: 192.168.1.1
    subnet: 255.255.255.0

api:
  encryption:
    key: !secret api_encryption_key

ota:
  platform: esphome

deep_sleep:
  id: deep_sleep_1
  run_duration: 30s
  sleep_duration: 10min

i2c:
  sda: GPIO21
  scl: GPIO22

sensor:
  - platform: sht4x
    id: climate
    update_interval: never
    temperature:
      name: Temperature
    humidity:
      name: Humidity

  - platform: adc
    id: battery_voltage
    name: Battery voltage
    pin: GPIO35
    attenuation: 12db
    samples: 8
    update_interval: never
    filters:
      - multiply: 2.0

binary_sensor:
  - platform: homeassistant
    id: ota_mode
    entity_id: input_boolean.garden_sensor_ota_mode
    on_state:
      - if:
          condition:
            lambda: 'return x;'
          then:
            - deep_sleep.prevent: deep_sleep_1
          else:
            - deep_sleep.allow: deep_sleep_1

The parts that matter:

  • on_boot with priority: -100. ESPHome describes this priority as the point where "pretty much everything should already be initialized", so Wi-Fi and the API are already set up when the script starts.
  • wait_until: api.connected with a timeout. There is no point measuring before Home Assistant is listening. The 20 s timeout keeps a device that cannot connect from waiting forever.
  • update_interval: never plus component.update. The sensors measure only when the script asks, once per wake-up, at a moment when the values can actually be delivered.
  • run_duration: 30s as a safety net. If something hangs, the node still goes to sleep after 30 seconds instead of draining the battery.
  • fast_connect and manual_ip. Both shorten the connection. fast_connect skips the full scan and joins the last known access point. With storage: rtc, it keeps that BSSID and channel in RTC memory, which survives deep sleep and saves flash wear. A static IP skips DHCP. ESPHome's Wi-Fi documentation recommends a static IP because it "can dramatically improve connection times". Change the addresses to match your own network.
  • GPIO21 and GPIO22 are only an example. Any free pins will work for I²C.

Keeping a sleeping sensor updatable

A node that is awake for a few seconds every ten minutes is hard to catch for an OTA update. The ota_mode binary sensor above solves that from Home Assistant:

  1. Create a toggle helper in Settings > Devices & services > Helpers and give it the entity ID input_boolean.garden_sensor_ota_mode.
  2. When you want to update, switch the helper on and wait for the next wake-up. The device gets the helper's state from Home Assistant, calls deep_sleep.prevent and the if in on_boot skips going to sleep.
  3. Install the new firmware. Then switch the helper off: deep_sleep.allow runs and the device goes back to its normal cycle.

ESPHome's documentation describes the same trick with MQTT and a retained message, if you use MQTT instead of the native API.

One more thing applies to OTA updates on ESP32 with the ESP-IDF framework, where rollback is on by default: new firmware has to be confirmed as working, or the bootloader reverts it on the next reset. Since ESPHome 2026.8.0, entering deep sleep counts as a successful boot. On older versions, a device that slept before boot_is_good_after (one minute by default) never confirmed the update and silently rolled back on every wake. If you are stuck on an older version, set boot_is_good_after in safe_mode: to less than the awake time, for example 10s.

What Home Assistant shows while the sensor sleeps

Entities should keep their last value while the device sleeps. If they turn Unavailable instead, ESPHome's documentation says the deep_sleep component was probably added after the device had been added to Home Assistant. Removing the device from Home Assistant and adding it again fixes it.

Estimating battery life

Do not trust the datasheet alone. Measure two currents and two times: the current while awake and the length of a typical wake-up (watch the logs over USB once), and the current while asleep. Then compute the average:

average current = (awake current x awake time + sleep current x sleep time) / cycle time

As a purely hypothetical example: if a board drew 100 mA for 5 s and 20 µA for the remaining 595 s of a ten-minute cycle, the average would be about 0.85 mA, and almost all of it would come from the awake part. Divide the battery capacity by that average for a rough runtime, then knock off a margin for cold, battery ageing and failed Wi-Fi attempts. The formula also shows where to look for savings: a shorter wake-up and a longer sleep_duration matter much more than shaving a few microamps off the sleep current, as long as the board itself is not burning milliamps.

Common problems

  • The node never wakes up (ESP8266). GPIO16 is not connected to RST, or the USB chip on the board interferes with the reset line.
  • It wakes, but Home Assistant gets no values. The node went to sleep before the API connected. Check the logs for how long the connection takes, raise the wait_until timeout and leave a short delay after the updates.
  • The battery voltage reads 0 or jumps around. Check that the pin is on ADC1, that attenuation is set and that the multiply filter matches your divider ratio.
  • The battery drains in days, not months. Measure the sleep current. If it is in milliamps, the cause is the board (regulator, LED, USB converter) or the divider, not ESPHome.
  • An OTA update disappears after the next wake-up. That is the rollback described above. Update ESPHome or shorten boot_is_good_after.