ESPHome turns a $5 ESP32 into a native Home Assistant device using a single YAML file. You describe the sensors and pins, ESPHome compiles the firmware, and Home Assistant discovers the device automatically. There is no C++ to write, no MQTT broker to configure, and no custom integration to install.
This guide covers hardware selection, wiring, a complete working configuration, on-device automations, and a troubleshooting checklist.
Quick Takeaways
- ESPHome compiles YAML into firmware for ESP32, ESP8266, RP2040, and other supported chips. It exposes sensors to Home Assistant through the encrypted Native API.
- A temperature, humidity, and motion node needs about 15 lines of YAML per sensor and about 20 minutes of setup.
- Nodes keep running automations locally (filters, thresholds, timers) even if Home Assistant or Wi-Fi goes down.
- Flash the first build over USB. Every later update goes over the air (OTA).
Platform Comparison
| Feature | ESPHome | Tasmota | Arduino C++ + MQTT | Zigbee Sensors |
|---|---|---|---|---|
| Configuration | YAML | Web UI / templates | Hand-written code | Pairing only |
| Home Assistant discovery | Native API (automatic) | MQTT discovery | Manual MQTT config | ZHA / Zigbee2MQTT |
| Custom sensors | Hundreds of components | Limited list | Unlimited | Fixed by vendor |
| Skill level | Beginner | Beginner | Advanced | Beginner |
| Local automations | Yes (on-device) | Rules (limited) | Yes | Limited |
| Cost per node | $3–$10 | $3–$10 | $3–$10 | $10–$30 |
What ESPHome Actually Does
ESPHome is a firmware generator. The workflow has four steps:
- You write a YAML file describing the board, Wi-Fi, and each sensor.
- ESPHome converts it into C++ and compiles it with PlatformIO or the ESP-IDF toolchain.
- You flash the binary to the microcontroller.
- Home Assistant detects the device on your network and creates entities for every sensor.
The YAML is declarative. You never write Wire.begin() or parse a register map. You write platform: bme280_i2c, and ESPHome handles the I2C transactions, calibration math, and publishing.
This is “no code” in the sense that you don’t write firmware. You still write a configuration file, and you can drop into C++ lambdas when you need custom logic.
Hardware You Need
Recommended Starter Kit
| Component | Purpose | Key Spec | Approx. Cost |
|---|---|---|---|
| ESP32 DevKit (ESP32-WROOM-32) | Microcontroller | 240 MHz dual-core, Wi-Fi + BLE, 3.3V logic | $5–$8 |
| BME280 breakout | Temperature, humidity, pressure | I2C address 0x76 or 0x77, 3.3V | $3–$6 |
| HC-SR501 PIR sensor | Motion detection | 5V supply, 3.3V output | $2 |
| Capacitive soil moisture sensor v1.2 | Soil moisture | Analog 0–3.0V | $2 |
| USB data cable | Power and first flash | Must carry data, not just power | $3 |
| Breadboard + jumper wires | Prototyping | $5 |
ESP32 vs. ESP8266 for ESPHome
| Attribute | ESP32 | ESP8266 |
|---|---|---|
| CPU | 240 MHz dual-core | 80 MHz single-core |
| GPIO count | ~34 usable | ~11 usable |
| ADC | 12-bit, multiple channels | 10-bit, one channel |
| Bluetooth | BLE 4.2 | None |
| Deep-sleep current | ~10 µA | ~20 µA |
| Verdict | Best default for new builds | Fine for single-sensor nodes |
Choose the ESP32 unless you are reusing old hardware. The extra GPIO and ADC channels remove most wiring compromises.
Pinout and Wiring Configuration
Wire the BME280 on the default I2C bus and the other sensors on free GPIOs.
| Sensor Pin | ESP32 Pin | Notes |
|---|---|---|
| BME280 VCC | 3V3 | Never use 5V on bare breakouts without a regulator |
| BME280 GND | GND | Common ground |
| BME280 SDA | GPIO21 | Default I2C data |
| BME280 SCL | GPIO22 | Default I2C clock |
| PIR VCC | VIN (5V) | HC-SR501 needs 5V |
| PIR OUT | GPIO27 | 3.3V logic output, safe for ESP32 |
| PIR GND | GND | |
| Soil sensor AOUT | GPIO34 | Input-only ADC1 pin |
| Soil sensor VCC | 3V3 |
Use ADC1 pins (GPIO32–39) for analog reads. ADC2 pins conflict with Wi-Fi and return unreliable values while the radio is active.
Step 1: Install the ESPHome Device Builder
You have two common paths.
Home Assistant add-on (easiest). In Home Assistant, go to Settings → Add-ons → Add-on Store, search for ESPHome Device Builder, install it, and click Start. Enable Show in sidebar.
Standalone with Docker or pip. Run this on any Linux, macOS, or Windows (WSL) machine:
# Install the ESPHome command-line tool
pip install esphome
# Start the web dashboard on port 6052
esphome dashboard ./esphome-configs
Open the dashboard in a browser, click New Device, and follow the wizard. It generates a base file with your Wi-Fi credentials stored in secrets.yaml.
Step 2: Create secrets.yaml
Keep credentials out of your device files. Create or edit secrets.yaml:
# secrets.yaml - never commit this file to a public repo
wifi_ssid: "YourNetworkName"
wifi_password: "YourWiFiPassword"
api_key: "PASTE_32_BYTE_BASE64_KEY_HERE" # generate in the ESPHome wizard
ota_password: "choose-a-strong-password"
fallback_password: "another-strong-password"
Step 3: Write the Full Sensor Configuration
This single file builds a three-in-one environmental node: BME280 climate sensing, PIR motion detection, and soil moisture reading.
esphome:
name: greenhouse-node
friendly_name: Greenhouse Node
esp32:
board: esp32dev
framework:
type: esp-idf # lower overhead than the Arduino framework
logger: # serial + network logs, essential for debugging
api:
encryption:
key: !secret api_key # encrypts all traffic to Home Assistant
ota:
- platform: esphome
password: !secret ota_password # enables wireless updates
wifi:
ssid: !secret wifi_ssid
password: !secret wifi_password
power_save_mode: light # reduces heat and idle current
ap: # fallback hotspot if Wi-Fi fails
ssid: "Greenhouse Fallback"
password: !secret fallback_password
captive_portal: # lets you re-enter Wi-Fi from a phone
# I2C bus for the BME280
i2c:
sda: GPIO21
scl: GPIO22
scan: true # prints detected addresses in the log
frequency: 100kHz
sensor:
# Climate sensor: reads over I2C at address 0x76
- platform: bme280_i2c
address: 0x76
update_interval: 60s
temperature:
name: "Greenhouse Temperature"
oversampling: 16x # higher oversampling reduces noise
filters:
- sliding_window_moving_average:
window_size: 5
send_every: 1
humidity:
name: "Greenhouse Humidity"
pressure:
name: "Greenhouse Pressure"
# Capacitive soil moisture on ADC1 (GPIO34)
- platform: adc
pin: GPIO34
name: "Soil Moisture"
update_interval: 30s
attenuation: 12db # allows readings up to ~3.1V
unit_of_measurement: "%"
filters:
# Calibrate: ~2.80V = dry air (0%), ~1.30V = water (100%)
- calibrate_linear:
- 2.80 -> 0.0
- 1.30 -> 100.0
- clamp:
min_value: 0
max_value: 100
- median:
window_size: 7
send_every: 3
# Diagnostic: Wi-Fi signal strength
- platform: wifi_signal
name: "Greenhouse WiFi Signal"
update_interval: 120s
entity_category: diagnostic
binary_sensor:
# PIR motion sensor on GPIO27
- platform: gpio
pin:
number: GPIO27
mode: INPUT_PULLDOWN
name: "Greenhouse Motion"
device_class: motion
filters:
- delayed_off: 30s # hold "on" for 30s to prevent flicker
# Status LED mirrors connection state
status_led:
pin:
number: GPIO2
What Each Block Does
esp32/framework: Selects the board and the ESP-IDF toolchain.api+encryption: Creates the encrypted link to Home Assistant. No broker is needed.ota: Lets you push new firmware over Wi-Fi.i2c: Defines the bus.scan: trueprints every detected address at boot, which makes wiring faults obvious.filters: Smooth, calibrate, and clamp values on the chip before they reach Home Assistant.
Step 4: Flash the Firmware
- Connect the ESP32 to your computer with a data-capable USB cable.
- In the ESPHome dashboard, click Install → Plug into this computer. This option needs a Chromium-based browser (Chrome or Edge).
- Select the serial port and wait for the compile and upload to finish. The first build takes several minutes because the toolchain downloads.
- Open Logs. You should see Wi-Fi connect and sensor values stream.
Every later change uses Install → Wirelessly. No cable needed.
Step 5: Adopt the Device in Home Assistant
Home Assistant discovers ESPHome nodes through mDNS. Go to Settings → Devices & Services. A Discovered card for the ESPHome node appears.
- Click Configure.
- Enter the encryption key from
secrets.yamlif prompted. - Assign the device to an Area such as “Greenhouse.”
Your entities appear immediately:
| Entity ID | Type | Unit |
|---|---|---|
sensor.greenhouse_node_greenhouse_temperature |
Sensor | °C |
sensor.greenhouse_node_greenhouse_humidity |
Sensor | % |
sensor.greenhouse_node_soil_moisture |
Sensor | % |
binary_sensor.greenhouse_node_greenhouse_motion |
Binary sensor | on/off |
Entity IDs vary slightly with your device and friendly names. Check Settings → Entities.
On-Device Automations: Logic That Survives Outages
Home Assistant automations stop when the server is down. ESPHome automations run on the chip. This example turns on a relay-driven fan when humidity exceeds a threshold, with no server involved:
switch:
- platform: gpio
pin: GPIO26
name: "Greenhouse Fan"
id: fan_relay
sensor:
- platform: bme280_i2c
address: 0x76
humidity:
name: "Greenhouse Humidity"
on_value_range:
- above: 80.0 # turn fan on above 80% RH
then:
- switch.turn_on: fan_relay
- below: 65.0 # turn fan off below 65% RH (hysteresis)
then:
- switch.turn_off: fan_relay
The 80% and 65% thresholds create hysteresis. Without a gap, the relay would chatter around a single threshold and wear out.
If you switch a mains-powered fan, use a properly rated relay module with isolation. Never wire mains voltage on a breadboard.
Real-World Project: Greenhouse Monitor With Automatic Alerts
Combine the node above with a Home Assistant automation that notifies you when the soil is dry.
# Home Assistant automation (Settings → Automations → Edit in YAML)
alias: "Greenhouse - Dry Soil Alert"
trigger:
- platform: numeric_state
entity_id: sensor.greenhouse_node_soil_moisture
below: 25
for: "00:30:00" # must stay low for 30 minutes
action:
- service: notify.mobile_app_your_phone
data:
title: "Water the plants"
message: "Soil moisture is {{ states('sensor.greenhouse_node_soil_moisture') }}%."
mode: single
Extend the project with:
- A relay-controlled pump on GPIO25 for automatic watering.
- A deep sleep cycle for battery operation (see the table below).
- A history graph card on your dashboard for temperature and humidity trends.
Power Strategy Comparison
| Mode | Typical Current (ESP32) | Best For | Trade-off |
|---|---|---|---|
| Always on | ~80–240 mA (Wi-Fi active) | Mains-powered nodes | Highest consumption |
| Light Wi-Fi power save | ~20–50 mA average | USB-powered sensors | Slight latency |
| Deep sleep | ~10 µA asleep | Battery sensors | Delayed updates, no local automations |
For battery use, add the deep_sleep component with run_duration: 20s and sleep_duration: 10min. Set ota and api to stay awake long enough to receive updates.
Troubleshooting Common Failures
| Symptom | Likely Cause | Fix |
|---|---|---|
| Board not detected for flashing | Charge-only USB cable | Use a data cable; check drivers (CP2102 / CH340) |
Failed to connect during upload |
Boot mode not entered | Hold BOOT while flashing starts |
BME280 shows Communication failed |
Wrong address or wiring | Check log from scan: true; try 0x77 |
| Soil moisture jumps erratically | ADC2 pin or electrical noise | Use ADC1 (GPIO32–39); add median filter and a 0.1 µF capacitor |
| Device offline in Home Assistant | Weak Wi-Fi or duplicate hostname | Check signal sensor; keep node names unique |
| Encryption key mismatch | Key changed after adoption | Re-enter the key in the integration settings |
| PIR triggers constantly | Unstable 5V supply or sensitivity too high | Add a 100 µF capacitor near VIN; adjust the onboard potentiometer |
Reading the Log Effectively
Open Logs in the dashboard and look for three lines:
WiFi Connection: ConnectedFound I2C device at address 0x76API: Client connected
If one is missing, you know exactly which layer to debug: network, bus, or Home Assistant pairing.
Best Practices for Reliable Nodes
- Pin the framework. Stick with
esp-idforarduinoper device and avoid switching mid-project. - Use static IPs or DHCP reservations. They speed up reconnects.
- Add
entity_category: diagnosticto Wi-Fi and uptime sensors to keep dashboards clean. - Use
update_intervalwisely. Reading every second floods the database. Temperature rarely needs better than 30–60 seconds. - Keep ESPHome current. New releases fix component bugs and sometimes deprecate older syntax. Read the changelog before upgrading a fleet.
- Back up your YAML. Store the config folder in a private Git repository, with
secrets.yamlexcluded.
Strategic FAQ
Do I need to know how to code to use ESPHome?
No. Basic sensors, switches, and automations use only YAML configuration. Coding becomes optional, for custom logic through C++ lambdas when a built-in component doesn’t cover your case.
Does ESPHome work without Home Assistant?
Yes. An ESPHome node runs standalone with its own web server and on-device automations. It can also publish through MQTT. Home Assistant adds the dashboard, history, and cross-device automations through the Native API.
Is ESPHome better than Tasmota?
They solve different problems. ESPHome offers deeper Home Assistant integration and more sensor components through YAML. Tasmota is better for flashing commercial smart plugs and controlling devices through a web UI and MQTT. For custom DIY sensors with Home Assistant, ESPHome is usually the simpler choice.
Can I update ESPHome devices without unplugging them?
Yes. After the first USB flash, use Install → Wirelessly in the dashboard. The ota component accepts the new firmware over Wi-Fi, and the device reboots into it.




