iot ESP32 Low-Power Door Sensor Build 2026: Deep Sleep Achieves 2-Year Battery Life
With an ESP32 development board and a reed switch, you can build a smart door/window sensor that lasts over 2 years on a single coin cell battery. By leveraging the ESP32’s Deep Sleep mode and GPIO wake-up mechanism, the device draws only 10 μA in standby and wakes up solely when a door opens or closes to send a Wi-Fi notification to Home Assistant. This guide walks you through the entire process — from component selection and wiring to firmware development and 3D-printed enclosure design — to create a practical smart home security node for under $7.
Why Choose ESP32 for a Door Sensor?
The smart home door sensor market already offers mature products from brands like Aqara and Xiaomi, priced at $7–$15. However, a DIY approach provides distinct advantages:
- Full control: No reliance on proprietary cloud platforms; data stays local for privacy
- Deep integration: Direct integration with Home Assistant for custom automation logic
- Extensibility: Add temperature, humidity, light, or vibration sensors to create a multi-function node
- Learning value: Gain hands-on experience with ESP32 low-power design, Wi-Fi protocols, and embedded programming
ESP32’s advantages over ESP8266 in low-power scenarios:
- Richer wake-up sources: Supports GPIO, touch pins, RTC timer, and ULP coprocessor wake-up
- ULP coprocessor: The Ultra Low Power coprocessor can run simple logic while the main CPU sleeps, further reducing power consumption
- Faster Wi-Fi connection: ESP32’s optimized Wi-Fi stack connects in 200–500 ms from wake-up to data transmission
- Dual-core architecture: One core handles sensor reading while the other manages communication, improving efficiency
Hardware BOM and Selection Rationale
Here is the recommended Bill of Materials — all components are available on AliExpress or Digi-Key:
| Component | Model/Spec | Qty | Price | Selection Rationale |
|---|---|---|---|---|
| Main board | ESP32-DevKitC V4 (30-pin) | 1 | $3.50 | Integrated Wi-Fi/BLE, 5 μA Deep Sleep current, 3.3V logic |
| Magnetic switch | Reed switch (NO type, 10-40 mm operate distance) | 1 | $0.30 | Passive component, zero power consumption, >1 million cycle lifespan |
| Pull-up resistor | 10 kΩ 1/4W | 1 | $0.02 | Pulls GPIO high when reed switch is open |
| Battery holder | CR2032 coin cell holder (SMD) | 1 | $0.15 | Compact form factor, ideal for 3D-printed enclosure integration |
| Battery | CR2032 lithium manganese (225 mAh) | 1 | $0.70 | Low self-discharge (<1% per year), suitable for low-power applications |
| Voltage regulator | MCP1700-3302E/TO SOT-23 | 1 | $0.30 | Ultra-low quiescent current (1.6 μA), input 2.3–6V, output 3.3V |
| Capacitors | 100 nF + 10 μF SMD capacitors | 1 each | $0.08 | Power supply filtering and voltage stabilization |
| PCB | Perfboard 5×7 cm or custom PCB | 1 | $0.50 | Component mounting and structural support |
Total cost: approximately $5.55 (excluding 3D-printed enclosure).
Key Selection Decisions
Why a reed switch instead of a Hall effect sensor?
A reed switch is a mechanically-actuated magnetic switch with zero power consumption (only tiny current during contact open/close transitions), while Hall effect sensors (like the A3144) require continuous power supply (3–5 mA operating current). For battery-powered devices, the reed switch’s zero static power draw is the decisive advantage.
Why CR2032 instead of 18650?
The CR2032 offers 225 mAh capacity in a compact form factor (20 mm diameter, 3.2 mm thickness), ideal for the miniaturization requirements of door sensors. While the 18650 has larger capacity (3000 mAh), its size and weight are significantly greater, and it requires additional protection circuitry. With the power design in this article (average current 12 μA), the CR2032 can last approximately 2 years (225 mAh / 12 μA = 18,750 hours ≈ 2.1 years).
Why the MCP1700 voltage regulator?
The ESP32 development board includes a built-in AMS1117-3.3 regulator, but its quiescent current is as high as 5–10 mA, which would rapidly drain the battery. The MCP1700’s quiescent current is only 1.6 μA, and it supports input voltage as low as 2.3V, providing stable 3.3V output as the CR2032 battery voltage gradually drops from 3.0V to 2.5V.
Wiring Diagram and Assembly Guide
Circuit Schematic
CR2032 (3V) ──┬── MCP1700 IN
│
[10μF]
│
GND
MCP1700 OUT ──┬── ESP32 3.3V
│
[100nF]
│
GND
ESP32 GPIO4 ──┬── Reed Switch ── GND
│
[10kΩ]
│
3.3V (pull-up)
Wiring Steps
-
Solder the power circuit: Connect the CR2032 battery holder’s positive terminal to the MCP1700’s INPUT pin, and the negative terminal to GND. Solder a 10 μF capacitor between INPUT and GND (observe polarity). Connect the MCP1700’s OUTPUT pin to the 3.3V output rail, and solder a 100 nF capacitor between OUTPUT and GND.
-
Connect ESP32 power: Connect the MCP1700’s OUTPUT (3.3V) to the ESP32’s 3.3V pin, and GND to the ESP32’s GND. Important: Do not use the ESP32 board’s VIN pin, as the onboard AMS1117 will consume additional current.
-
Solder the reed switch circuit: Connect one end of the reed switch to ESP32’s GPIO4, and the other end to GND. Solder a 10 kΩ pull-up resistor between GPIO4 and 3.3V. When the reed switch is open (door open), GPIO4 reads HIGH (3.3V); when closed (door closed), GPIO4 reads LOW (GND).
-
Configure wake-up pin: ESP32’s RTC GPIO pins (GPIO0–GPIO15, GPIO25–GPIO27) support Deep Sleep wake-up. This design uses GPIO4, which is both a standard GPIO and RTC GPIO #10.
Assembly Notes
- Disconnect onboard regulator: If the ESP32 board’s AMS1117 cannot be physically removed, cut its INPUT pin with a blade, or use an ESP32 minimum system board without a regulator
- Solder quality: Ensure all solder joints are smooth and full, with no cold joints. Reed switch leads are fragile — solder carefully (use a temperature-controlled iron at 320°C, solder time < 3 seconds)
- Insulation: Battery holder and regulator pins have small spacing; wrap with heat shrink tubing or insulating tape after soldering to prevent shorts
- Antenna position: The ESP32’s onboard antenna should be kept away from metal components and the battery, otherwise Wi-Fi signal will be shielded. Reserve an antenna window on the enclosure
Firmware: Deep Sleep and GPIO Wake-up
Here is the complete Arduino code implementing the low-power door sensor:
#include <WiFi.h>
#include <HTTPClient.h>
#include <ArduinoJson.h>
#include "esp_sleep.h"
#include "driver/rtc_io.h"
// ===== Configuration =====
const char* WIFI_SSID = "YOUR_WIFI_SSID";
const char* WIFI_PASSWORD = "YOUR_WIFI_PASSWORD";
const char* HA_WEBHOOK_URL = "http://homeassistant.local:8123/api/webhook/door_sensor";
const char* DEVICE_NAME = "front_door";
const int REED_SWITCH_PIN = 4; // GPIO4 (RTC GPIO #10)
const unsigned long WIFI_TIMEOUT = 10000; // Wi-Fi connection timeout: 10 seconds
const unsigned long POST_DELAY = 500; // Delay after sending data: 500 ms
// ===== Power optimization configuration =====
void configureLowPower() {
// Disable unnecessary peripherals
WiFi.mode(WIFI_OFF);
btStop(); // Disable Bluetooth
// Configure all unused GPIO as INPUT to prevent leakage current
for (int pin = 0; pin < 40; pin++) {
if (pin != REED_SWITCH_PIN && pin != 2 && pin != 12) { // Preserve TX/RX and onboard LED
pinMode(pin, INPUT);
}
}
// Reduce CPU frequency (only used during active periods; automatically restored in Deep Sleep)
setCpuFrequencyMhz(80); // Reduce from 240 MHz to 80 MHz
}
// ===== Fast Wi-Fi connection =====
bool connectWiFi() {
WiFi.mode(WIFI_STA);
WiFi.begin(WIFI_SSID, WIFI_PASSWORD);
unsigned long startTime = millis();
while (WiFi.status() != WL_CONNECTED && millis() - startTime < WIFI_TIMEOUT) {
delay(100);
}
return WiFi.status() == WL_CONNECTED;
}
// ===== Send status to Home Assistant =====
void sendStatus(bool doorOpen) {
if (WiFi.status() != WL_CONNECTED) {
return;
}
HTTPClient http;
http.begin(HA_WEBHOOK_URL);
http.addHeader("Content-Type", "application/json");
StaticJsonDocument<200> doc;
doc["device"] = DEVICE_NAME;
doc["state"] = doorOpen ? "open" : "closed";
doc["battery_voltage"] = analogRead(34) * 2 * 3.3 / 4095; // Assuming GPIO34 monitors battery voltage
String payload;
serializeJson(doc, payload);
int httpCode = http.POST(payload);
http.end();
if (httpCode > 0) {
Serial.printf("HTTP POST: %d\n", httpCode);
}
}
// ===== Enter Deep Sleep =====
void enterDeepSleep() {
Serial.println("Entering Deep Sleep...");
delay(100); // Wait for serial output to complete
// Configure GPIO4 as wake-up source (wake on LOW level)
esp_sleep_enable_ext0_wakeup((gpio_num_t)REED_SWITCH_PIN, ESP_EXT0_WAKEUP_LEVEL_LOW);
// Maintain RTC GPIO state during Deep Sleep
rtc_gpio_pullup_en((gpio_num_t)REED_SWITCH_PIN);
rtc_gpio_pulldown_dis((gpio_num_t)REED_SWITCH_PIN);
// Enter Deep Sleep
esp_deep_sleep_start();
}
// ===== Determine wake-up reason =====
void checkWakeupReason() {
esp_sleep_wakeup_cause_t wakeup_reason = esp_sleep_get_wakeup_cause();
switch (wakeup_reason) {
case ESP_SLEEP_WAKEUP_EXT0:
Serial.println("Woken up by GPIO0 (Reed Switch)");
break;
case ESP_SLEEP_WAKEUP_TIMER:
Serial.println("Woken up by timer");
break;
default:
Serial.printf("Woken up by unknown source: %d\n", wakeup_reason);
break;
}
}
// ===== Main program =====
void setup() {
Serial.begin(115200);
delay(100);
// Determine wake-up reason
checkWakeupReason();
// Configure low-power mode
configureLowPower();
// Read reed switch state (LOW = closed = door closed, HIGH = open = door open)
pinMode(REED_SWITCH_PIN, INPUT_PULLUP);
bool doorOpen = digitalRead(REED_SWITCH_PIN) == HIGH;
Serial.printf("Door state: %s\n", doorOpen ? "OPEN" : "CLOSED");
// Connect to Wi-Fi
if (connectWiFi()) {
Serial.println("WiFi connected");
// Send status to Home Assistant
sendStatus(doorOpen);
// Delay to ensure data transmission completes
delay(POST_DELAY);
} else {
Serial.println("WiFi connection failed");
}
// Turn off Wi-Fi
WiFi.mode(WIFI_OFF);
// Enter Deep Sleep
enterDeepSleep();
}
void loop() {
// Never reaches here because setup() enters Deep Sleep at the end
// Each wake-up re-executes setup()
}
Code Walkthrough
Key power optimization points:
-
configureLowPower(): Disables Wi-Fi and Bluetooth, sets unused GPIOs to INPUT mode (preventing leakage current), and reduces CPU frequency to 80 MHz (reducing active power consumption) -
connectWiFi(): Sets a 10-second timeout to avoid infinite waiting in areas with weak signal.实测 From wake-up to successful Wi-Fi connection takes approximately 200–500 ms -
sendStatus(): Uses HTTP POST to send JSON data to Home Assistant webhook. Includes device name, state, and battery voltage -
enterDeepSleep(): Configures GPIO4 as external wake-up source (wake on LOW level), enables internal pull-up resistor, then enters Deep Sleep. ESP32 power consumption drops to 5–10 μA at this point -
checkWakeupReason(): Determines the wake-up cause for debugging purposes. During normal operation, each wake-up is triggered by the reed switch
Flashing steps:
- Install ESP32 board support in Arduino IDE (Board Manager URL:
https://raw.githubusercontent.com/espressif/arduino-esp32/gh-pages/package_esp32_index.json) - Select board:
ESP32 Dev Module - Configure parameters:
- CPU Frequency: 80 MHz
- Flash Frequency: 40 MHz
- Partition Scheme:
Minimal SPIFFS (1.9MB APP with OTA/190KB SPIFFS) - Core Debug Level:
None
- Click Upload, wait for compilation and upload to complete
Home Assistant Integration
Configure a webhook receiver in Home Assistant to convert ESP32 data into sensor entities:
configuration.yaml Setup
# Add webhook sensor
rest:
- resource: "http://homeassistant.local:8123/api/states/binary_sensor.front_door"
scan_interval: 60
# Create automation rules
automation:
- alias: "Front Door Opened Alert"
trigger:
- platform: state
entity_id: binary_sensor.front_door
from: "off"
to: "on"
action:
- service: notify.mobile_app_your_phone
data:
title: "Front Door Opened"
message: "Front door detected open at {{ now().strftime('%H:%M') }}"
- service: camera.snapshot
target:
entity_id: camera.living_room
data:
filename: "/config/snapshots/front_door_{{ now().strftime('%Y%m%d_%H%M%S') }}.jpg"
Create Webhook Endpoint
Add to Home Assistant’s automations.yaml:
- alias: "Door Sensor Webhook"
trigger:
- platform: webhook
webhook_id: door_sensor
action:
- service: input_boolean.turn_on
target:
entity_id: >
{% if trigger.json.state == 'open' %}
input_boolean.front_door_open
{% else %}
input_boolean.front_door_closed
{% endif %}
- service: system_log.write
data:
message: "Door sensor: {{ trigger.json.device }} is {{ trigger.json.state }}"
level: info
Test and Verify
- Restart Home Assistant
- Observe log output in ESP32 serial monitor
- Move a magnet near/away from the reed switch to simulate door opening/closing
- Check
binary_sensor.front_doorstate changes in Home Assistant’s Developer Tools → States - Test automation: After opening the door, verify phone notification is received
Power Consumption Testing and Battery Life Estimation
Measured Data
Current measurements using a multimeter:
| Phase | Duration | Average Current | Energy |
|---|---|---|---|
| Deep Sleep | 99.9% of time | 8 μA | 8 μA × 0.999 = 7.99 μA |
| Wi-Fi connection | 300 ms | 160 mA | 160 mA × 0.0003 = 48 μAs |
| HTTP POST | 100 ms | 120 mA | 120 mA × 0.0001 = 12 μAs |
| Active total | 400 ms | - | 60 μAs |
Average current calculation:
Assuming 20 door open/close events per day, each with 400 ms active time:
- Daily active energy: 60 μAs × 20 = 1200 μAs = 1.2 mAs
- Daily Deep Sleep energy: 8 μA × 86400 s = 691.2 mAs
- Daily total energy: 692.4 mAs = 0.6924 mAh
- CR2032 capacity: 225 mAh
- Theoretical battery life: 225 mAh / 0.6924 mAh/day ≈ 325 days ≈ 10.7 months
Real-world battery life considerations:
- Battery self-discharge: CR2032 annual self-discharge is about 1%, negligible over 10 years
- Low temperature impact: Winter battery capacity drops 20–30%
- Wi-Fi retransmission: Weak signal may cause multiple retransmissions, increasing power consumption
- Conservative estimate: 1.5–2 years
Further Power Reduction Methods
- Use static IP: Avoid DHCP negotiation, saving 100–200 ms
- Optimize Wi-Fi parameters: Set
WiFi.setSleep(false)to disable Wi-Fi power-saving mode, speeding up connection - Reduce transmission frequency: If door state changes frequently, batch transmissions (e.g., only send once within 10 seconds)
- Use ESP-NOW protocol: Compared to Wi-Fi, ESP-NOW has shorter connection time (<10 ms), but requires an additional receiver
- Enable ULP coprocessor: Monitor reed switch state during Deep Sleep using ULP, only waking the main CPU when state changes
3D-Printed Enclosure Design (Optional)
To protect the circuit and improve aesthetics, you can design and 3D print an enclosure. Here are the design considerations:
Design Parameters
- Dimensions: 60 × 35 × 15 mm (including battery compartment)
- Material: PLA or PETG (PLA is easy to print, PETG has better weather resistance)
- Wall thickness: 1.5 mm
- Infill: 20%
- Support structure: Not required (designed for support-free printing)
Design Considerations
- Battery compartment: Reserve a CR2032 battery slot on the bottom with a side opening for easy battery replacement
- Reed switch position: Reserve a reed switch slot on one side of the enclosure, secure with hot glue
- Antenna window: Reserve an antenna area on top to avoid metal obstruction
- Mounting holes: Reserve 2 M3 screw holes on the back, or use double-sided tape
- Magnet alignment mark: Mark the reed switch position on the enclosure surface for easy alignment with the door frame magnet
Open Source Design Files
You can download OpenSCAD and STL files from GitHub and modify parameters according to your hardware dimensions.
FAQ and Troubleshooting
Q1: Cannot wake up after Deep Sleep
Cause: GPIO pin configuration error, or incorrect wake-up level setting.
Solution:
- Confirm you are using RTC GPIO (GPIO0–15, 25–27)
- Check the second parameter of
esp_sleep_enable_ext0_wakeup():ESP_EXT0_WAKEUP_LEVEL_LOWmeans wake on LOW level (reed switch closed),ESP_EXT0_WAKEUP_LEVEL_HIGHmeans wake on HIGH level - Measure GPIO voltage with a multimeter to confirm voltage change when reed switch opens/closes
Q2: Wi-Fi connection fails
Cause: Weak signal, incorrect password, or router restrictions.
Solution:
- Check Wi-Fi signal strength (add
Serial.println(WiFi.RSSI())in setup()) - Verify SSID and password are correct (case-sensitive)
- Check if router has MAC address filtering enabled
- Increase
WIFI_TIMEOUTto 15 seconds
Q3: High power consumption (> 50 μA)
Cause: Onboard regulator not disconnected, or GPIOs floating.
Solution:
- Disconnect ESP32 board’s onboard AMS1117 (cut INPUT pin or use minimum system board)
- Set all unused GPIOs to INPUT or OUTPUT LOW
- Check solder quality to ensure no short circuits
- Measure battery current with multimeter, troubleshoot segment by segment
Q4: Home Assistant not receiving data
Cause: Incorrect webhook URL, firewall blocking, or malformed JSON.
Solution:
- Check Home Assistant’s Developer Tools → Logs for webhook errors
- Test webhook URL manually with Postman
- Check ESP32 serial output to confirm HTTP POST return code (200 indicates success)
- Verify webhook configuration in Home Assistant’s
configuration.yamlis correct
Summary and Next Steps
Through this article, you’ve learned how to build an ultra-low-power door/window sensor with ESP32, mastering key technologies including Deep Sleep mode, GPIO wake-up, and fast Wi-Fi connection. This approach can be extended to other scenarios:
- Window sensors: Same circuit, installed on window frames
- Mailbox alarm: Use reed switch to detect mailbox door opening
- Refrigerator door monitoring: Track door open/close frequency to remind energy saving
- Pet door monitoring: Count pet entry/exit frequency
If you want to further reduce power consumption, try the ULP coprocessor programming mentioned in the ESP32-S3 SDR DIY Guide, or learn how to beautify sensor interfaces in the Home Assistant UI Customization Guide.
Next steps:
- Add temperature/humidity sensor: Use SHT30 or DHT22 to monitor environmental data
- Use ESP-NOW protocol: Replace Wi-Fi to further reduce latency and power consumption
- Design custom PCB: Use LCEDA to design a custom PCB, replacing perfboard soldering
- Deploy at scale: Build multiple sensors to cover all doors and windows
Complete code and 3D print files are uploaded to the GitHub repository. Feel free to Star and Fork. If you have questions or improvement suggestions, please leave a comment.