Design a Custom ESPHome Sensor Board: Idea to Production

Design and manufacture a custom WiFi temperature and humidity sensor board running ESPHome for Home Assistant integration. This tutorial walks through selecting the ESP32-C3-MINI module for its tiny footprint and ultra-low deep sleep current, designing a 2-layer PCB with BME280 sensor and USB-C LiPo charging, ordering assembled boards from JLCPCB, and flashing ESPHome firmware.

Intermediate · 6 hours · 6 steps

What You Need

Reference design — same ESP32-C3-MINI-1 module used on custom PCB
Upgrade option — Thread/Zigbee support for future-proofing

Step-by-Step Instructions

  1. Step 1 Define Requirements and Choose the ESP32-C3 Module

    Start with a clear requirements list. This sensor board needs: WiFi for Home Assistant connectivity, temperature and humidity sensing, battery power with USB-C charging, compact form factor (under 40x30mm), deep sleep for months of battery life, and ESPHome firmware support. These requirements point directly to the ESP32-C3-MINI-1 module.

    The ESP32-C3-MINI-1 is the smallest and cheapest module in the ESP32 lineup. It measures 13.2mm x 16.6mm x 2.4mm — smaller than a US quarter coin. Inside is a single-core RISC-V processor at 160MHz, 400KB SRAM, 4MB flash, WiFi 802.11b/g/n, and Bluetooth 5.0 LE. Most importantly, its deep sleep current is just 5uA, meaning a 500mAh LiPo battery can power the module in sleep mode for over 11 years (theoretically). In practice, with periodic sensor readings and WiFi transmissions every 5 minutes, a 500mAh battery lasts 2-4 months.

    The ESP32-C6 is an upgrade option if you want Thread/Zigbee support alongside WiFi. It adds 802.15.4 radio capability for future Home Assistant Thread integration. However, the C6 module is larger (18x20mm), costs $1-2 more, and draws slightly more sleep current (7uA). For a pure WiFi ESPHome sensor, the C3 is the better choice.

    ESPHome has first-class support for both chips. The esp32 platform with framework: esp-idf supports C3 variants, and the BME280 sensor component works directly over I2C. Deep sleep is configured with a single YAML block that sets wake interval and sleep duration.

    Tip: Buy 2-3 ESP32-C3-DevKitM development boards to prototype your circuit on a breadboard before committing to a custom PCB. Verify the BME280 sensor reads correctly, deep sleep works, and WiFi range is adequate from your intended installation location.
  2. Step 2 Design the Schematic with BME280 Sensor and Battery Circuit

    Open KiCad and create a new project. Start the schematic with the ESP32-C3-MINI-1 module. The module has 53 pins but most are NC (no connect) or GND. The essential connections are: 3V3 (power), GND, EN (enable, pull up to 3V3 with 10K resistor), GPIO8 and GPIO9 for I2C (SDA/SCL to the BME280), GPIO18 and GPIO19 for USB D-/D+ (programming and charging), and GPIO2 for a status LED.

    Add the Bosch BME280 sensor (or its BOM-compatible variant, the BME680 for air quality). The BME280 uses I2C at address 0x76 (SDO pin pulled to GND) or 0x77 (SDO to 3V3). Connect SDA to GPIO8 and SCL to GPIO9 with 4.7K pull-up resistors to 3.3V. Add a 100nF decoupling capacitor directly at the BME280's VDD pin. The BME280 draws only 3.6uA during a forced measurement and 0.1uA in sleep mode — negligible compared to the ESP32-C3.

    Design the power system for a single-cell LiPo battery (3.7V nominal, 4.2V full, 3.0V cutoff). Use the TP4056 charging IC with a 2K charge current resistor (580mA — appropriate for 500mAh cells, which should charge at 0.5C to 1C). Add the DW01A + FS8205A battery protection circuit for over-discharge and over-charge protection.

    The ME6211C33 LDO regulator converts the 3.0-4.2V battery voltage to stable 3.3V for the ESP32-C3 and BME280. Its quiescent current is 40uA — significant relative to the ESP32-C3's 5uA deep sleep current. For even lower sleep power, consider the HT7333 LDO with 2.5uA quiescent current, though it has higher dropout voltage (250mV vs 100mV).

    Add a USB-C connector with 5.1K CC pull-down resistors. Route D+ and D- to the ESP32-C3's USB pins for programming. Route VBUS to the TP4056 input through a Schottky diode. Include a 2-pin JST-PH connector for the LiPo battery, matching the standard polarity used by Adafruit and SparkFun LiPo cells.

    Tip: Add a solder jumper or 0-ohm resistor between the LDO enable pin and 3V3. This lets you completely disconnect the LDO during development to measure actual battery current draw with a multimeter in series. Total sleep current should be under 50uA for the complete board.
  3. Step 3 PCB Layout: 2-Layer, Small Form Factor

    Set your board dimensions to 35mm x 25mm — roughly the size of a postage stamp. This is tight but achievable with careful component placement. The ESP32-C3-MINI module occupies the center of the board with its PCB antenna extending to one edge. The critical layout rule: keep a ground-plane clearance zone under and around the antenna area (the last 3-5mm of the module where the antenna trace is located). No copper, traces, or components should exist in this zone on either layer — copper near the antenna detunes it and reduces WiFi range.

    Place the BME280 sensor near the board edge, away from the ESP32-C3 module and power components. The ESP32-C3 generates heat during WiFi transmission (100-350mA for 50-200ms per wake cycle) that can bias temperature readings by 1-2 degrees Celsius if the sensor is too close. A minimum separation of 10mm between the ESP32 module and the BME280 reduces thermal coupling. Adding thermal relief vias between the two areas further isolates them.

    Route I2C traces (SDA, SCL) as short as possible with their pull-up resistors placed near the BME280. I2C at standard 100kHz is not RF-sensitive, so trace routing is straightforward — just avoid running them parallel to the SPI or USB data lines for more than 5mm to prevent crosstalk.

    Place all power components (TP4056, LDO, protection circuit, USB-C connector, JST battery connector) on the opposite end of the board from the antenna. Route power traces at 0.3mm minimum width for signal paths and 0.5mm for power (VBAT, 3V3). The ground plane on the bottom layer should be as continuous as possible — avoid routing traces on the bottom layer to maintain ground integrity.

    Add 4 M2 mounting holes (2.2mm drill) in the corners for enclosure mounting. If space permits, add silkscreen labels for the battery connector polarity (+/-) and USB-C port. Include your project name and version number (e.g., "ESPSensor v1.0") on the silkscreen — you will thank yourself when you have multiple board revisions on your desk.

    Tip: Use KiCad's 3D viewer (Alt+3) to verify component heights do not interfere with each other. The USB-C connector, battery connector, and BME280 all have different heights — check that they fit inside your intended enclosure before ordering.
  4. Step 4 Generate JLCPCB Files and Order Assembled Boards

    Run DRC and fix all errors. For a 35x25mm 2-layer board, JLCPCB's standard process handles everything — minimum trace width 0.127mm, minimum clearance 0.127mm, minimum via 0.3mm drill.

    Generate Gerbers with Protel extensions, Excellon drill files, BOM with LCSC part numbers, and CPL file. The key LCSC part numbers for this design: ESP32-C3-MINI-1 is C2838502 ($1.50-2.00), BME280 is C92489 ($2.50-3.50), TP4056 is C725790 ($0.05), ME6211C33 is C82942 ($0.08), DW01A is C351410 ($0.03), FS8205A is C908258 ($0.04), USB-C connector (mid-mount 16-pin) is C2765186 ($0.10). Total BOM cost per board: approximately $5-7.

    Upload to JLCPCB and select Economic PCBA. For 5 boards: PCB fabrication approximately $2 (35x25mm is well under the 100x100mm threshold), assembly setup $8, component costs approximately $30-35 (5 boards x $6 average), extended part surcharges $9 (ESP32-C3, BME280, and USB-C connector are Extended Parts at $3 each), standard shipping $7-10. Total: approximately $56-64 for 5 assembled boards, or $11-13 per unit.

    Exclude the JST-PH battery connector and any through-hole pin headers from the PCBA order — hand-solder these after the boards arrive. The JST connector costs $0.10-0.20 from LCSC; add a few to your component order.

    Verify every component in JLCPCB's placement viewer, paying special attention to the ESP32-C3-MINI module orientation (pin 1 is marked with a dot on the module) and the BME280's tiny 2.5x2.5mm LGA package. Rotation errors on the BME280 are common — its pin 1 indicator is subtle.

    Tip: Order 10 bare PCBs instead of 5 — the cost difference is typically $0-2. Having spare unassembled boards lets you rework failed assemblies or experiment with alternative components without waiting for a new PCB order.
  5. Step 5 Flash ESPHome Firmware

    Connect the assembled board to your computer via USB-C. If the ESP32-C3-MINI is properly connected and the LDO outputs 3.3V, the chip should enumerate as a USB-JTAG device. On macOS/Linux, check dmesg or system logs for a new USB serial device. On Windows, it appears as a COM port in Device Manager.

    If the chip does not enumerate, enter bootloader mode: hold the BOOT button (GPIO9 to GND — add a tactile switch in your design or use a jumper wire), press RESET (EN pin momentarily pulled to GND), then release BOOT. The ESP32-C3 enters USB download mode and appears as a serial device.

    Create an ESPHome YAML configuration file. The minimal configuration for this board includes: WiFi credentials, I2C bus definition (SDA: GPIO8, SCL: GPIO9), BME280 sensor component with temperature/humidity/pressure entities, deep sleep component (wake every 5 minutes, sleep the rest of the time), and Home Assistant API connection.

    The deep sleep configuration is critical for battery life. ESPHome's deep_sleep component puts the ESP32-C3 into 5uA sleep mode between readings. A typical wake cycle takes 3-8 seconds: 1-2 seconds to boot, 1-3 seconds to connect to WiFi, 0.5 seconds to read the BME280, and 0.5-2 seconds to transmit data to Home Assistant. At 3 seconds average wake time drawing 120mA, and 5-minute sleep intervals, the average current is approximately 0.12mA — giving roughly 170 days on a 500mAh battery.

    Flash the firmware using the ESPHome web interface or command line: esphome run sensor.yaml. After the first flash over USB, subsequent updates can be pushed over-the-air (OTA) via WiFi — no need to physically access the sensor. Verify the sensor appears in Home Assistant's ESPHome integration and reports temperature, humidity, and pressure values.

    Tip: Add a status LED on GPIO2 that blinks once during each wake cycle. This provides visual confirmation that the sensor is working without needing to check Home Assistant. Use ESPHome's light component with a brief 100ms flash during each sensor reading.
  6. Step 6 Integrate with Home Assistant and Optimize Battery Life

    In Home Assistant, the ESPHome device should auto-discover when the sensor is powered on and connected to WiFi. Accept the device in Settings > Devices & Services > ESPHome. You will see three sensor entities: temperature, humidity, and pressure from the BME280.

    Create automations based on the sensor data. Common examples: turn on a bathroom exhaust fan when humidity exceeds 65%, send a notification when temperature drops below 15C (frost warning for a garage sensor), or log historical data for HVAC optimization. The 5-minute update interval is sufficient for environmental monitoring — temperature and humidity rarely change faster than 1 degree per 5 minutes in indoor environments.

    Optimize battery life through three levers. First, increase the deep sleep interval. For a basement or attic sensor where conditions change slowly, 15-minute intervals triple battery life to approximately 500 days (16+ months) on a 500mAh cell. Second, reduce WiFi connection time by assigning a static IP address in the ESPHome YAML and configuring fast_connect: true with your WiFi BSSID — this cuts WiFi association from 2-3 seconds to 0.5-1 second. Third, disable the BME280's pressure reading if you do not need it — pressure measurement adds 0.5ms to each sensor read cycle (negligible, but demonstrates the optimization mindset).

    Monitor battery voltage through the ADC pin. Add a template sensor in ESPHome that converts the raw ADC reading to battery percentage using a lookup table for LiPo discharge curves: 4.2V = 100%, 3.9V = 75%, 3.7V = 50%, 3.5V = 25%, 3.3V = 5%. Set up a Home Assistant automation to notify you when battery drops below 20% so you can recharge before the sensor goes offline.

    For deployment, use a 3D-printed enclosure with ventilation slots over the BME280 sensor opening. Mount the sensor with double-sided tape or M2 screws. Keep the antenna side of the board facing outward (toward the WiFi access point) for best signal. In testing, the ESP32-C3-MINI's PCB antenna achieves reliable connectivity at 10-15 meters through one wall or 20-30 meters with line of sight.

    Tip: Print a small QR code label with the sensor's IP address and name, and stick it on the enclosure. When you have 10+ sensors deployed around your house, this saves time when troubleshooting or performing maintenance.

Frequently Asked Questions

Why choose ESP32-C3 over ESP32-S3 for a battery-powered sensor?

The ESP32-C3-MINI draws 5uA in deep sleep versus the ESP32-S3-WROOM's 7uA — a 30% reduction. More importantly, the C3 module is physically smaller (13.2x16.6mm vs 18x25.5mm), cheaper ($1.50 vs $2.80), and has everything a WiFi sensor needs. The S3's dual-core processor, camera interface, and USB OTG are wasted on a sensor that wakes for 3 seconds every 5 minutes.

How accurate is the BME280 sensor?

The BME280 provides +/-1C temperature accuracy, +/-3% relative humidity accuracy, and +/-1hPa pressure accuracy. These specs are from Bosch's datasheet at 25C — accuracy degrades slightly at extreme temperatures. For home automation purposes, this accuracy is more than sufficient. The BME280 also includes an internal temperature compensation algorithm that improves readings over the operating range of -40C to +85C.

Can I use a coin cell (CR2032) instead of a LiPo battery?

A CR2032 has only 220mAh capacity and a maximum continuous discharge current of 3-5mA — the ESP32-C3 draws 120mA during WiFi transmission, which exceeds the coin cell's capability and causes voltage droop below the LDO's minimum input. Use a LiPo cell (500mAh or larger) or a pair of AA lithium batteries (3000mAh at 3.0V) for reliable operation.

How long does the battery actually last in practice?

With a 500mAh LiPo and 5-minute wake intervals, expect 3-6 months depending on WiFi connection speed and distance to the access point. Static IP configuration and fast_connect mode cut wake time in half, pushing toward the 6-month end. With a 1000mAh cell and 15-minute intervals, 12-18 months is realistic. Real-world results vary based on WiFi environment and temperature.

Does ESPHome support over-the-air updates for the ESP32-C3?

Yes. After the initial USB flash, ESPHome supports OTA firmware updates over WiFi. The sensor must be awake and connected to WiFi during the update — which means you either need to temporarily disable deep sleep (set a long wake duration in the YAML) or physically press the reset button while your computer pushes the update. A practical approach is to add a GPIO-triggered 'stay awake' mode using a magnetic reed switch.

What is the total cost per sensor board?

Using JLCPCB Economic PCBA: approximately $11-13 per assembled board (5-unit order), plus $3-5 for a 500mAh LiPo cell, and $1-2 for a 3D-printed enclosure. Total: $15-20 per complete sensor. A comparable commercial WiFi sensor (like the Aqara Temperature Sensor) costs $15-20 but requires a proprietary hub and does not run ESPHome. Your custom sensor integrates directly with Home Assistant over native WiFi.

Can I add additional sensors to this board design?

Yes. The ESP32-C3 has 15 available GPIOs beyond what this design uses. You can add I2C devices on the same bus (light sensor like BH1750, air quality sensor like SGP40), SPI devices on dedicated GPIOs, or analog sensors on the 2 available ADC channels. Each additional sensor adds $0.50-3.00 to the BOM. Keep total wake-time current under 500mA (the LDO's limit) when adding power-hungry peripherals.