Project 3: Multi-Sensor Data Logger with Deep Sleep

Project 3: Multi-Sensor Data Logger with Deep Sleep

Project Overview

Attribute Value
Difficulty Intermediate-Advanced
Time Estimate 2-3 weeks
Main Language C
Alternatives MicroPython, Rust, Arduino C++
Primary Book Making Embedded Systems by Elecia White
Knowledge Areas Power Management, RTC Memory, ESP-NOW, Battery

What Youโ€™ll Build

A battery-powered remote sensor node that wakes up periodically, reads sensors, transmits data over WiFi or ESP-NOW, then goes back to deep sleepโ€”lasting weeks to months on a single battery.

Physical Setup:

  • ESP32 powered by 3.7V 2000mAh LiPo battery
  • BME280 sensor for temperature, humidity, pressure
  • Optional: soil moisture sensor, PIR motion sensor
  • Weatherproof enclosure for outdoor use

Power Consumption Profile:

Deep Sleep Mode:
โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”
โ”‚                                          โ”‚
โ”‚  Current: 10-150ยตA (bare module)         โ”‚
โ”‚  Duration: 14:55 of every 15 minutes     โ”‚
โ”‚  RTC timer running, GPIO wake possible   โ”‚
โ”‚                                          โ”‚
โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜

Active Mode (WiFi transmitting):
โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”
โ”‚                                          โ”‚
โ”‚  Current: 160-260mA                      โ”‚
โ”‚  Duration: 2-5 seconds per cycle         โ”‚
โ”‚  Sensor read, WiFi connect, transmit     โ”‚
โ”‚                                          โ”‚
โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜

Expected Battery Life (2000mAh, bare module):
- Wake every 15 minutes โ†’ 60-75 days
- Wake on motion only โ†’ 300+ days

Learning Objectives

By completing this project, you will be able to:

  1. Configure ESP32 deep sleep modes and understand power consumption
  2. Preserve state across sleep cycles using RTC memory
  3. Implement efficient WiFi connection with channel/BSSID caching
  4. Use ESP-NOW for ultra-low-power wireless transmission
  5. Design a power budget and calculate expected battery life
  6. Handle edge cases in battery-powered systems (low voltage, failed transmissions)
  7. Implement graceful degradation when resources are limited

Deep Theoretical Foundation

ESP32 Sleep Modes

The ESP32 offers multiple power states, each with different trade-offs between power consumption and wake-up latency.

Sleep Mode Comparison

โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ฌโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ฌโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ฌโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”
โ”‚ Mode            โ”‚ Current Draw  โ”‚ Wake Time     โ”‚ RAM Preserved โ”‚
โ”œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ค
โ”‚ Active          โ”‚ 160-260mA     โ”‚ N/A           โ”‚ Yes           โ”‚
โ”‚ Modem Sleep     โ”‚ 20-30mA       โ”‚ <1ms          โ”‚ Yes           โ”‚
โ”‚ Light Sleep     โ”‚ 0.8-1.1mA     โ”‚ <1ms          โ”‚ Yes           โ”‚
โ”‚ Deep Sleep      โ”‚ 10-150ยตA      โ”‚ ~300ms        โ”‚ RTC only (8KB)โ”‚
โ”‚ Hibernation     โ”‚ 2.5ยตA         โ”‚ ~300ms        โ”‚ RTC slow only โ”‚
โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ดโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ดโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ดโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜

What Remains Powered in Deep Sleep

Deep Sleep State:
โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”
โ”‚                                                                  โ”‚
โ”‚  POWERED OFF:                    POWERED ON:                    โ”‚
โ”‚  โ”œโ”€โ”€ Main CPU cores (both)       โ”œโ”€โ”€ RTC controller             โ”‚
โ”‚  โ”œโ”€โ”€ Main RAM (520KB)            โ”œโ”€โ”€ RTC fast memory (8KB)      โ”‚
โ”‚  โ”œโ”€โ”€ WiFi/BT radios              โ”œโ”€โ”€ RTC slow memory (8KB)      โ”‚
โ”‚  โ”œโ”€โ”€ Most peripherals            โ”œโ”€โ”€ RTC timer                  โ”‚
โ”‚  โ””โ”€โ”€ Flash (in standby)          โ”œโ”€โ”€ ULP coprocessor            โ”‚
โ”‚                                  โ”œโ”€โ”€ Touch sensors (optional)   โ”‚
โ”‚                                  โ””โ”€โ”€ GPIO wake circuits         โ”‚
โ”‚                                                                  โ”‚
โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜

RTC Memory: Preserving State Across Sleep

When entering deep sleep, all main RAM is lost. RTC memory is your lifeline for maintaining state.

RTC Memory Attributes

// Fast RTC memory - accessible from main code
RTC_DATA_ATTR uint32_t boot_count = 0;
RTC_DATA_ATTR float last_temperature = 0.0;
RTC_DATA_ATTR uint8_t failed_transmissions = 0;

// Slow RTC memory - accessible from ULP coprocessor
RTC_SLOW_ATTR uint32_t ulp_counter = 0;

// NOINIT - survives software reset but not power cycle
RTC_NOINIT_ATTR uint32_t debug_flag = 0;
Attribute Size Survives Deep Sleep Survives Reset
RTC_DATA_ATTR 8KB Yes No
RTC_SLOW_ATTR 8KB Yes No
RTC_NOINIT_ATTR Part of above Yes Yes
Normal variables 520KB No No

Wake-Up Sources

ESP32 supports multiple wake-up sources that can be combined:

// Timer wake-up (most common)
esp_sleep_enable_timer_wakeup(15 * 60 * 1000000ULL);  // 15 minutes in ยตs

// GPIO ext0 (single GPIO, any level)
esp_sleep_enable_ext0_wakeup(GPIO_NUM_33, 1);  // Wake on HIGH

// GPIO ext1 (multiple GPIOs, any HIGH or all LOW)
esp_sleep_enable_ext1_wakeup(mask, ESP_EXT1_WAKEUP_ANY_HIGH);

// Touch pad
esp_sleep_enable_touchpad_wakeup();

// ULP coprocessor
esp_sleep_enable_ulp_wakeup();

// Enter deep sleep
esp_deep_sleep_start();  // Code execution stops here!

After wake-up, determine what caused it:

esp_sleep_wakeup_cause_t cause = esp_sleep_get_wakeup_cause();
switch (cause) {
    case ESP_SLEEP_WAKEUP_TIMER:
        printf("Woke from timer\n");
        break;
    case ESP_SLEEP_WAKEUP_EXT0:
        printf("Woke from GPIO\n");
        break;
    case ESP_SLEEP_WAKEUP_UNDEFINED:
        printf("First boot (power-on reset)\n");
        break;
}

Power Budget Calculation

Designing a battery-powered device requires careful power budgeting.

Example: 15-Minute Wake Interval with WiFi

Given:
- Battery capacity: 2000mAh
- Wake interval: 15 minutes (900 seconds)
- Active time: 3 seconds per wake
- Sleep current: 150ยตA (DevKit) or 10ยตA (bare module)
- Active current: 200mA average

Calculation (DevKit board):
โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”
โ”‚ Sleep energy per cycle:                                         โ”‚
โ”‚   150ยตA ร— 897s = 0.0374mAh                                      โ”‚
โ”‚                                                                  โ”‚
โ”‚ Active energy per cycle:                                        โ”‚
โ”‚   200mA ร— 3s = 0.167mAh                                         โ”‚
โ”‚                                                                  โ”‚
โ”‚ Total per cycle: 0.204mAh                                       โ”‚
โ”‚                                                                  โ”‚
โ”‚ Cycles per day: 24h ร— 4/hour = 96                               โ”‚
โ”‚ Daily consumption: 96 ร— 0.204mAh = 19.6mAh                      โ”‚
โ”‚                                                                  โ”‚
โ”‚ Battery life: 2000mAh / 19.6mAh = 102 days                      โ”‚
โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜

Calculation (bare module, 10ยตA sleep):
โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”
โ”‚ Sleep energy: 10ยตA ร— 897s = 0.0025mAh                           โ”‚
โ”‚ Active energy: 200mA ร— 3s = 0.167mAh                            โ”‚
โ”‚ Total per cycle: 0.169mAh                                       โ”‚
โ”‚ Daily: 96 ร— 0.169 = 16.2mAh                                     โ”‚
โ”‚ Battery life: 2000mAh / 16.2mAh = 123 days                      โ”‚
โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜

Key insight: For infrequent wake intervals, active-mode power dominates. For frequent wake intervals, sleep-mode current matters more.

WiFi Fast Connect

Standard WiFi connection takes 2-5 seconds. You can reduce this dramatically:

Connection Time Breakdown

Standard Connection:
โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”
โ”‚ WiFi init       โ”‚ ~200ms                                       โ”‚
โ”‚ Scan all channelsโ”‚ ~1500ms  โ† Major time sink!                  โ”‚
โ”‚ Associate with APโ”‚ ~500ms                                       โ”‚
โ”‚ WPA2 handshake  โ”‚ ~300ms                                       โ”‚
โ”‚ DHCP request    โ”‚ ~500ms   โ† Another time sink!                 โ”‚
โ”‚ โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€ โ”‚
โ”‚ Total           โ”‚ ~3000ms                                       โ”‚
โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜

Optimized Connection:
โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”
โ”‚ WiFi init       โ”‚ ~200ms                                       โ”‚
โ”‚ Connect to cachedโ”‚ ~300ms  (skip scan, use saved channel/BSSID)โ”‚
โ”‚   channel/BSSID โ”‚                                              โ”‚
โ”‚ WPA2 handshake  โ”‚ ~300ms                                       โ”‚
โ”‚ Static IP       โ”‚ ~0ms    (skip DHCP)                          โ”‚
โ”‚ โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€ โ”‚
โ”‚ Total           โ”‚ ~800ms                                        โ”‚
โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜

Implementation

// Store connection info in RTC memory
RTC_DATA_ATTR uint8_t saved_channel = 0;
RTC_DATA_ATTR uint8_t saved_bssid[6] = {0};

void fast_wifi_connect() {
    wifi_config_t wifi_config = {
        .sta = {
            .ssid = "YourSSID",
            .password = "YourPassword",
            .channel = saved_channel,  // Use cached channel
        },
    };

    // Copy cached BSSID
    if (saved_channel != 0) {
        memcpy(wifi_config.sta.bssid, saved_bssid, 6);
        wifi_config.sta.bssid_set = true;
    }

    // Use static IP (skip DHCP)
    tcpip_adapter_dhcpc_stop(TCPIP_ADAPTER_IF_STA);
    tcpip_adapter_ip_info_t ip_info = {
        .ip = { .addr = IP4_ADDR(192,168,1,200) },
        .gw = { .addr = IP4_ADDR(192,168,1,1) },
        .netmask = { .addr = IP4_ADDR(255,255,255,0) },
    };
    tcpip_adapter_set_ip_info(TCPIP_ADAPTER_IF_STA, &ip_info);

    esp_wifi_set_config(ESP_IF_WIFI_STA, &wifi_config);
    esp_wifi_start();
    esp_wifi_connect();
}

// After successful connection, save for next time
void save_connection_info() {
    wifi_ap_record_t ap_info;
    esp_wifi_sta_get_ap_info(&ap_info);
    saved_channel = ap_info.primary;
    memcpy(saved_bssid, ap_info.bssid, 6);
}

ESP-NOW: Ultra-Low-Power Alternative

ESP-NOW is Espressifโ€™s proprietary protocol for peer-to-peer communication without WiFi infrastructure.

ESP-NOW vs WiFi

WiFi Connection:                   ESP-NOW:

Phone/PC โ”€โ”€โ†’ Router โ”€โ”€โ†’ ESP32      ESP32 โ—€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ–ถ ESP32
                                   (Direct, no router)
- Requires router                  - No router needed
- 2-5 second connection            - Instant (10-50ms)
- Works with internet              - Local only (250m range)
- Higher power (160-260mA)         - Lower power (80-150mA)
- Unlimited payload                - 250 bytes max

When to use ESP-NOW:

  • Hub + sensor node architecture
  • No internet required
  • Maximum battery life critical
  • Low data volume

ESP-NOW Implementation

// Initialize ESP-NOW (on sender)
esp_now_init();

// Add peer (receiver's MAC address)
esp_now_peer_info_t peer = {
    .channel = 0,
    .encrypt = false,
};
memcpy(peer.peer_addr, receiver_mac, 6);
esp_now_add_peer(&peer);

// Send data
typedef struct {
    float temperature;
    float humidity;
    uint8_t battery_percent;
} sensor_data_t;

sensor_data_t data = { 22.5, 48.0, 87 };
esp_now_send(receiver_mac, (uint8_t*)&data, sizeof(data));

ESP-NOW Power Comparison

WiFi transmission cycle:
โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”
โ”‚ WiFi connect: 200mA ร— 2.5s = 0.139mAh                          โ”‚
โ”‚ Data transmit: 200mA ร— 0.5s = 0.028mAh                         โ”‚
โ”‚ Total: 0.167mAh per transmission                                โ”‚
โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜

ESP-NOW transmission cycle:
โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”
โ”‚ Radio enable: 150mA ร— 0.05s = 0.002mAh                         โ”‚
โ”‚ Data transmit: 150mA ร— 0.05s = 0.002mAh                        โ”‚
โ”‚ Total: 0.004mAh per transmission                                โ”‚
โ”‚                                                                  โ”‚
โ”‚ Savings: ~40x less energy per transmission!                     โ”‚
โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜

Battery Voltage Monitoring

Monitoring battery voltage prevents deep discharge damage and enables low-battery warnings.

Voltage to Percentage Mapping

LiPo Battery Discharge Curve:
โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”
โ”‚ Voltage โ”‚ Percentage โ”‚ Notes                                    โ”‚
โ”œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ค
โ”‚ 4.20V   โ”‚ 100%       โ”‚ Fully charged                            โ”‚
โ”‚ 4.10V   โ”‚ 90%        โ”‚                                          โ”‚
โ”‚ 4.00V   โ”‚ 80%        โ”‚                                          โ”‚
โ”‚ 3.90V   โ”‚ 60%        โ”‚ Discharge curve flattens here            โ”‚
โ”‚ 3.80V   โ”‚ 40%        โ”‚                                          โ”‚
โ”‚ 3.70V   โ”‚ 20%        โ”‚ Low battery warning                      โ”‚
โ”‚ 3.60V   โ”‚ 10%        โ”‚ Critical                                 โ”‚
โ”‚ 3.30V   โ”‚ 0%         โ”‚ Empty - do not discharge below this!     โ”‚
โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ดโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ดโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜

Reading Battery Voltage

ESP32โ€™s ADC can only read up to 3.3V. Use a voltage divider:

Battery (4.2V max)
       โ”‚
      [R1] 100kฮฉ
       โ”œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ†’ ESP32 ADC (GPIO34)
      [R2] 100kฮฉ
       โ”‚
      GND

Vout = Vin ร— R2/(R1+R2) = 4.2 ร— 0.5 = 2.1V (safe for ADC)
float read_battery_voltage() {
    int raw = adc1_get_raw(ADC1_CHANNEL_6);  // GPIO34
    float voltage = (raw / 4095.0) * 3.3;    // ADC to voltage
    voltage *= 2;                             // Compensate for divider
    return voltage;
}

uint8_t voltage_to_percent(float voltage) {
    if (voltage >= 4.20) return 100;
    if (voltage <= 3.30) return 0;

    // Linear approximation (more accurate: use lookup table)
    return (uint8_t)((voltage - 3.30) / (4.20 - 3.30) * 100);
}

Project Specification

Hardware Requirements

Component Quantity Purpose
ESP32 DevKit or bare module 1 Main MCU
BME280 Sensor 1 Environmental sensing
3.7V 2000mAh LiPo 1 Power source
TP4056 module 1 Battery charging
100kฮฉ resistors 2 Voltage divider
Weatherproof enclosure 1 Outdoor protection

Wiring Diagram

Battery (3.7V LiPo)
       โ”‚
       โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ†’ TP4056 (charging module)
                        โ”‚
                        โ””โ”€โ”€โ”€โ”€โ”€โ”€โ†’ ESP32 VIN
                                    โ”‚
ESP32 DevKit                        โ”‚
โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”                     โ”‚
โ”‚         VIN โ”‚โ†โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜
โ”‚         GND โ”‚โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”
โ”‚             โ”‚                        โ”‚
โ”‚      GPIO34 โ”‚โ†โ”€โ”€โ”ฌโ”€โ”€[100k]โ”€โ”€โ†’ Battery+โ”‚
โ”‚             โ”‚   โ”‚                    โ”‚
โ”‚             โ”‚  [100k]                โ”‚
โ”‚             โ”‚   โ”‚                    โ”‚
โ”‚             โ”‚   โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ†’ GND โ†โ”€โ”€โ”€โ”˜
โ”‚             โ”‚
โ”‚      GPIO21 โ”‚โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”‚ BME280 SDA
โ”‚      GPIO22 โ”‚โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”‚ BME280 SCL
โ”‚        3.3V โ”‚โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”‚ BME280 VCC
โ”‚         GND โ”‚โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”‚ BME280 GND
โ”‚             โ”‚
โ”‚      GPIO33 โ”‚โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”‚ PIR Motion (optional)
โ”‚       GPIO2 โ”‚โ”€โ”€โ”€โ”€[220ฮฉ]โ”€โ”€โ”€โ”€โ”€โ”€โ”‚ LED (status)
โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜

Functional Requirements

  1. Sensor Reading
    • Read BME280 every wake cycle
    • Calculate battery percentage
    • Store in RTC memory for comparison
  2. Deep Sleep Management
    • Sleep for configurable interval (5-60 minutes)
    • Support timer and GPIO wake sources
    • Maintain boot count in RTC memory
  3. Data Transmission
    • Support both WiFi and ESP-NOW modes
    • Retry failed transmissions (max 3 attempts)
    • Queue data locally if transmission fails
  4. Power Optimization
    • Fast WiFi connect (<1 second target)
    • Minimize active time
    • Enter low-power mode for sensors
  5. Error Handling
    • Detect and report low battery
    • Handle WiFi connection failures
    • Implement watchdog timer

Solution Architecture

System State Machine

                    โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”
                    โ”‚    BOOT      โ”‚
                    โ”‚   (Reset)    โ”‚
                    โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”ฌโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜
                           โ”‚
                           โ–ผ
                    โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”
                    โ”‚  INITIALIZE  โ”‚
                    โ”‚ - Check wake โ”‚
                    โ”‚   cause      โ”‚
                    โ”‚ - Restore    โ”‚
                    โ”‚   RTC data   โ”‚
                    โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”ฌโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜
                           โ”‚
            โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”
            โ”‚              โ”‚              โ”‚
            โ–ผ              โ–ผ              โ–ผ
     โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”  โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”  โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”
     โ”‚  TIMER    โ”‚  โ”‚   GPIO    โ”‚  โ”‚ FIRST_BOOTโ”‚
     โ”‚  WAKE     โ”‚  โ”‚   WAKE    โ”‚  โ”‚           โ”‚
     โ””โ”€โ”€โ”€โ”€โ”€โ”ฌโ”€โ”€โ”€โ”€โ”€โ”˜  โ””โ”€โ”€โ”€โ”€โ”€โ”ฌโ”€โ”€โ”€โ”€โ”€โ”˜  โ””โ”€โ”€โ”€โ”€โ”€โ”ฌโ”€โ”€โ”€โ”€โ”€โ”˜
           โ”‚              โ”‚              โ”‚
           โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜
                          โ”‚
                          โ–ผ
                   โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”
                   โ”‚ READ_SENSORS โ”‚
                   โ”‚ - Temperatureโ”‚
                   โ”‚ - Humidity   โ”‚
                   โ”‚ - Battery    โ”‚
                   โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”ฌโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜
                          โ”‚
                          โ–ผ
                   โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”
               โ”Œโ”€โ”€โ†’โ”‚   TRANSMIT   โ”‚
               โ”‚   โ”‚ - WiFi/ESP-NOWโ”‚
               โ”‚   โ”‚ - Send data  โ”‚
               โ”‚   โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”ฌโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜
               โ”‚          โ”‚
               โ”‚    โ”Œโ”€โ”€โ”€โ”€โ”€โ”ดโ”€โ”€โ”€โ”€โ”€โ”
               โ”‚    โ–ผ           โ–ผ
               โ”‚ [Success]  [Failure]
               โ”‚    โ”‚           โ”‚
               โ”‚    โ”‚     retry < 3?
               โ”‚    โ”‚     โ”Œโ”€โ”€โ”€โ”€โ”€โ”ดโ”€โ”€โ”€โ”€โ”€โ”
               โ”‚    โ”‚     โ–ผ           โ–ผ
               โ”‚    โ”‚   [Yes]       [No]
               โ”‚    โ”‚     โ”‚           โ”‚
               โ”‚    โ”‚     โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ค
               โ””โ”€โ”€โ”€โ”€โ”˜                 โ”‚
                                      โ–ผ
                              โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”
                              โ”‚  STORE_LOCAL  โ”‚
                              โ”‚ (queue for    โ”‚
                              โ”‚  next cycle)  โ”‚
                              โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ฌโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜
                                      โ”‚
                                      โ–ผ
                               โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”
                               โ”‚ DEEP_SLEEP   โ”‚
                               โ”‚ - Set timer  โ”‚
                               โ”‚ - Save state โ”‚
                               โ”‚ - Enter sleepโ”‚
                               โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜

Data Flow

โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”
โ”‚                        Sensor Node                               โ”‚
โ”œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ค
โ”‚                                                                  โ”‚
โ”‚  Wake                                                            โ”‚
โ”‚   โ”‚                                                              โ”‚
โ”‚   โ–ผ                                                              โ”‚
โ”‚  โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”    โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”    โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”                     โ”‚
โ”‚  โ”‚ BME280  โ”‚โ”€โ”€โ”€โ†’โ”‚ Process โ”‚โ”€โ”€โ”€โ†’โ”‚ Format  โ”‚                     โ”‚
โ”‚  โ”‚ I2C Readโ”‚    โ”‚ & Store โ”‚    โ”‚  JSON   โ”‚                     โ”‚
โ”‚  โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜    โ”‚ in RTC  โ”‚    โ””โ”€โ”€โ”€โ”€โ”ฌโ”€โ”€โ”€โ”€โ”˜                     โ”‚
โ”‚                 โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜         โ”‚                           โ”‚
โ”‚                                     โ”‚                           โ”‚
โ”‚  โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”                       โ”‚                           โ”‚
โ”‚  โ”‚ Battery โ”‚โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ค                           โ”‚
โ”‚  โ”‚ ADC Readโ”‚                       โ”‚                           โ”‚
โ”‚  โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜                       โ”‚                           โ”‚
โ”‚                                     โ–ผ                           โ”‚
โ”‚                              โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”                      โ”‚
โ”‚                              โ”‚   Send    โ”‚                      โ”‚
โ”‚                              โ”‚ via WiFi/ โ”‚                      โ”‚
โ”‚                              โ”‚ ESP-NOW   โ”‚                      โ”‚
โ”‚                              โ””โ”€โ”€โ”€โ”€โ”€โ”ฌโ”€โ”€โ”€โ”€โ”€โ”˜                      โ”‚
โ”‚                                    โ”‚                            โ”‚
โ”‚                                    โ–ผ                            โ”‚
โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜
                                     โ”‚
                                     โ”‚ Wireless
                                     โ”‚
                                     โ–ผ
โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”
โ”‚                    Hub / MQTT Broker / Cloud                     โ”‚
โ”‚                                                                  โ”‚
โ”‚  Receives: {"temp":22.4,"hum":48,"press":1013,"bat":87,"boot":50}โ”‚
โ”‚                                                                  โ”‚
โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜

Key Data Structures

// Preserved across deep sleep in RTC memory
typedef struct {
    uint32_t boot_count;
    float last_temperature;
    float last_humidity;
    uint8_t failed_transmissions;
    uint8_t wifi_channel;
    uint8_t wifi_bssid[6];
    uint32_t pending_readings;
} rtc_state_t;

RTC_DATA_ATTR rtc_state_t state = {0};

// Sensor reading packet
typedef struct {
    float temperature;
    float humidity;
    float pressure;
    float battery_voltage;
    uint8_t battery_percent;
    uint32_t boot_count;
    uint32_t timestamp;
} sensor_packet_t;

Phased Implementation Guide

Phase 1: Basic Deep Sleep (Day 1-2)

Goal: Device sleeps and wakes on timer

  1. Verify Deep Sleep Entry
    void app_main() {
        printf("Boot! Going to sleep for 10 seconds...\n");
        esp_sleep_enable_timer_wakeup(10 * 1000000);
        esp_deep_sleep_start();
    }
    
    • Serial monitor should print โ€œBoot!โ€ every 10 seconds
  2. Measure Sleep Current
    • Use multimeter in series with battery
    • DevKit: expect 10-15mA (disappointing)
    • Bare module: expect 10-150ยตA (excellent)
  3. Add Boot Counter
    RTC_DATA_ATTR uint32_t boot_count = 0;
    
    void app_main() {
        boot_count++;
        printf("Boot #%d\n", boot_count);
        // ...
    }
    

Checkpoint: Boot count increments across sleep cycles

Phase 2: Sensor Integration (Day 3-4)

Goal: Read and display sensor data each wake

  1. Initialize BME280
    • Use I2C with short wires for reliability
    • Put sensor in forced mode (single measurement)
    • Read immediately after wake
  2. Optimize Sensor Power
    • BME280 draws ~3.6ยตA in sleep mode
    • Wake sensor, read, put back to sleep
    • Consider powering sensor from GPIO for zero sleep current
  3. Store Last Reading in RTC
    • Compare to previous reading
    • Detect anomalies (sensor failure = reading = 0 or NaN)

Checkpoint: Serial shows sensor readings each boot

Phase 3: WiFi Transmission (Day 5-7)

Goal: Send data to server/MQTT broker

  1. Standard WiFi Connect
    • Connect with SSID and password
    • Time the connection (expect 2-5 seconds)
    • Send MQTT/HTTP message
  2. Implement Fast Connect
    • Cache channel and BSSID in RTC
    • Use static IP
    • Time improvement (target <1 second)
  3. Handle Failures
    • Retry up to 3 times
    • Store failed data in RTC for next cycle
    • Increment failure counter

Checkpoint: Data appears on MQTT broker/server

Phase 4: ESP-NOW Alternative (Day 8-10)

Goal: Replace WiFi with ESP-NOW for better power

  1. Set Up Hub Device
    • Separate ESP32 as receiver
    • Print MAC address
    • Forward to WiFi/MQTT
  2. Implement ESP-NOW Sender
    • Add hub as peer
    • Send sensor data struct
    • Verify receipt (callback or timeout)
  3. Compare Power
    • Measure active time with ESP-NOW vs WiFi
    • Calculate new battery life estimate

Checkpoint: Hub receives data from sensor node

Phase 5: Power Optimization (Day 11-14)

Goal: Maximize battery life

  1. Battery Monitoring
    • Add voltage divider circuit
    • Read and report battery percentage
    • Implement low-voltage shutdown
  2. Reduce Active Time
    • Profile each step (sensor, WiFi, transmit)
    • Optimize slow operations
    • Target <2 seconds total active time
  3. Implement GPIO Wake
    • Add PIR sensor or door switch
    • Wake on motion instead of timer
    • Calculate new battery life

Testing Strategy

Unit Tests

Component Test Expected Result
Deep Sleep Set 10s timer Wakes after ~10s
RTC Memory Increment counter Persists across sleep
Sensor Read BME280 Valid temp (15-35ยฐC)
ADC Read battery 3.3-4.2V range
WiFi Connect Success in <5s
ESP-NOW Send packet Callback confirms

Power Measurement

  1. Measure Sleep Current
    • Set long sleep interval (5 minutes)
    • Measure with ยตA-capable multimeter
    • Record for battery life calculation
  2. Measure Active Current
    • Use current sensing resistor (1ฮฉ)
    • Monitor voltage across resistor
    • Calculate peak and average current
  3. Validate Battery Life Estimate
    • Run for 24 hours
    • Measure voltage drop
    • Compare to prediction

Stress Tests

  1. WiFi Failure Simulation
    • Turn off router during operation
    • Verify retry logic works
    • Check data is queued for next cycle
  2. Low Battery Behavior
    • Drain battery to 3.5V
    • Verify warning triggers
    • Check graceful shutdown

Common Pitfalls and Debugging

Sleep Current Too High

Problem: DevKit draws 10-15mA in deep sleep

  • USB-UART chip stays powered
  • Power LED draws current
  • Voltage regulator has high quiescent current

Solutions:

  • Use bare ESP32 module for production
  • Remove power LED
  • Use low-quiescent regulator (e.g., MCP1700)

WiFi Wonโ€™t Connect After Sleep

Problem: First boot works, subsequent donโ€™t

  • Channel changed (router switched channels)
  • BSSID changed (mesh router)
  • DHCP lease expired

Solutions:

  • Clear cached data if connection fails
  • Fall back to full scan periodically
  • Use static IP to avoid DHCP

Sensor Readings Are Wrong

Problem: Temperature reads 0 or NaN

  • Sensor didnโ€™t wake up properly
  • I2C timing issues after sleep
  • Sensor needs reset after deep sleep

Solutions:

  • Add delay after power-on
  • Re-initialize I2C after wake
  • Power cycle sensor from GPIO

Battery Drains Quickly

Problem: Battery lasts days, not months

  • Sleep current higher than expected
  • Active time too long
  • Too frequent wake-ups

Solutions:

  • Measure actual current draw
  • Profile each operation
  • Increase sleep interval
  • Use ESP-NOW instead of WiFi

Extensions and Challenges

Beginner Extensions

  1. Multiple Sensor Types
    • Add soil moisture sensor
    • Add light sensor (BH1750)
    • Report all in same packet
  2. Alert Thresholds
    • Wake immediately if temperature too high
    • Send urgent alert
    • Return to sleep

Intermediate Challenges

  1. Solar Power
    • Add solar panel and charging circuit
    • Implement MPPT or simple diode charging
    • Run indefinitely outdoors
  2. Mesh Network
    • Multiple sensor nodes
    • Relay data through each other
    • Extend range to 500m+

Advanced Challenges

  1. ULP Coprocessor
    • Run simple program while main CPU sleeps
    • Monitor sensor threshold
    • Wake main CPU only when needed
    • Achieve <5ยตA average current
  2. Secure Communication
    • Encrypt ESP-NOW packets
    • Implement AES-128 encryption
    • Secure against replay attacks

Real-World Connections

Commercial Products

Product Your Project Skill
Nest Temperature Sensors Deep sleep, battery optimization
Tile Trackers BLE + ultra-low power
Agricultural Sensors ESP-NOW mesh, solar power
Door/Window Sensors GPIO wake, years of battery life

Industry Applications

  • Precision Agriculture: Soil sensors across fields
  • Cold Chain Logistics: Temperature monitoring in transit
  • Wildlife Tracking: GPS + sensor loggers
  • Smart Buildings: Distributed sensor networks

Resources

Official Documentation

Resource URL
ESP-IDF Sleep Modes docs.espressif.com/projects/esp-idf/en/latest/esp32/api-reference/system/sleep_modes.html
ESP-NOW Guide docs.espressif.com/projects/esp-idf/en/latest/esp32/api-reference/network/esp_now.html
ULP Coprocessor docs.espressif.com/projects/esp-idf/en/latest/esp32/api-guides/ulp.html

Books

Book Author Relevant Chapters
Making Embedded Systems Elecia White Ch. 11: Power
IoT Product Development Sai Yamanoor ESP-NOW chapter
ESP32 Technical Reference Espressif Ch. 28: RTC

Self-Assessment Checklist

Fundamentals

  • I can explain the difference between light sleep and deep sleep
  • I understand what RTC memory is and how to use it
  • I can calculate expected battery life from current measurements
  • I know when to use WiFi vs ESP-NOW

Implementation

  • Device sleeps with <200ยตA current (or measured and documented)
  • Data transmits successfully on each wake
  • Boot count increments correctly across power cycles
  • Battery percentage is accurate

Production Ready

  • Failed transmissions are retried and queued
  • Low battery triggers appropriate action
  • Device recovers from WiFi failures
  • Active time is minimized (<3 seconds)

Interview Preparation

Be ready to answer these questions:

  1. โ€œExplain the difference between light sleep, modem sleep, and deep sleep.โ€
    • Power levels, RAM preservation, wake latency, use cases
  2. โ€œHow do you preserve data across deep sleep?โ€
    • RTC_DATA_ATTR, RTC fast/slow memory, NVS for persistent data
  3. โ€œWhy does an ESP32 DevKit have higher sleep current than a bare module?โ€
    • Onboard components: regulator, USB-UART, LEDs
  4. โ€œCalculate battery life given specific parameters.โ€
    • Show the sleep energy + active energy calculation
  5. โ€œWhat is ESP-NOW and when should you use it?โ€
    • Peer-to-peer, no router, lower power, 250 bytes max, local only

Next Project: P04-audio-spectrum-analyzer.md - Real-Time Audio Spectrum Analyzer