Use ESPHome to Build Smart Devices
Install ESPHome on Linux, write YAML configs with a secrets file, flash ESP32/ESP8266 over USB and OTA, then integrate live sensor data into Home Assistant.
Before you start
- ▸Python 3.10 or newer installed on the Linux host
- ▸USB cable and physical access to the ESP32/ESP8266 for the initial serial flash
- ▸The ESP device and the Linux host on the same Wi-Fi network for OTA and Home Assistant discovery
- ▸Home Assistant instance already running (if using HA integration)
ESPHome turns cheap ESP8266 and ESP32 microcontrollers into first-class Home Assistant citizens with nothing more than a YAML file and a USB cable for the first flash. After that, every update lands over the air. This guide covers installing ESPHome on a Linux host, writing a real YAML configuration, protecting credentials with a secrets file, flashing the device, enabling OTA updates, and wiring the integration into Home Assistant.
Install ESPHome
ESPHome is a Python application. The cleanest install is inside a virtual environment so it does not conflict with system packages.
sudo apt install python3 python3-pip python3-venv git # Debian/Ubuntu
sudo dnf install python3 python3-pip git # Fedora/RHEL
sudo pacman -S python python-pip git # Arch
python3 -m venv ~/.venv/esphome
source ~/.venv/esphome/bin/activate
pip install esphome
Confirm the install:
esphome version
# ESPHome 2024.x.x
Add the activation line to your shell profile if you want ESPHome available in every session, or wrap it in a small shell script. Alternatively, if you already run Home Assistant OS or Supervised, install ESPHome as an add-on from the add-on store instead — that instance manages its own Python environment and exposes the dashboard on port 6052.
Create a Project Directory
mkdir ~/esphome-devices && cd ~/esphome-devices
All YAML configs and the secrets file live here. Keep this directory under version control (git) but add secrets.yaml to .gitignore.
Write the Secrets File
Never hard-code Wi-Fi passwords or API keys inside a device YAML. ESPHome resolves !secret references at compile time from secrets.yaml in the same directory.
cat > secrets.yaml <<'EOF'
wifi_ssid: "MyHomeNetwork"
wifi_password: "hunter2"
ota_password: "change-me-strong-ota-pass"
api_encryption_key: "base64encodedkey32byteslong=="
EOF
Generate a proper 32-byte base64 key for the API encryption field:
python3 -c "import base64, os; print(base64.b64encode(os.urandom(32)).decode())"
Set strict permissions so other users on the machine cannot read it:
chmod 600 secrets.yaml
Write a Device YAML Configuration
The example below targets an ESP32 dev board with a DHT22 temperature/humidity sensor on GPIO 4 and a relay on GPIO 16. Adapt pin numbers and board to your hardware.
cat > bedroom-sensor.yaml <<'EOF'
esphome:
name: bedroom-sensor
friendly_name: Bedroom Sensor
esp32:
board: esp32dev
framework:
type: arduino
# Wi-Fi — credentials come from secrets.yaml
wifi:
ssid: !secret wifi_ssid
password: !secret wifi_password
ap:
ssid: "Bedroom-Sensor Fallback"
password: "fallbackpass"
# Captive portal shown when fallback AP is active
captive_portal:
# Over-the-air updates
ota:
- platform: esphome
password: !secret ota_password
# Native API for Home Assistant
api:
encryption:
key: !secret api_encryption_key
# Optional: enable logging over serial during development
logger:
level: DEBUG
sensor:
- platform: dht
pin: GPIO4
model: DHT22
temperature:
name: "Bedroom Temperature"
unit_of_measurement: "°C"
humidity:
name: "Bedroom Humidity"
update_interval: 30s
switch:
- platform: gpio
pin: GPIO16
name: "Bedroom Relay"
id: relay_1
EOF
Key Sections Explained
- esphome / board block — identifies the chip family and which Arduino or ESP-IDF framework to compile against. Use
esp8266devand theesp8266top-level key for ESP8266 boards. - wifi.ap + captive_portal — the device hosts its own access point if it cannot join your network, letting you reconfigure it without serial access.
- ota — after the first serial flash, all subsequent uploads go over Wi-Fi. The password prevents unauthorized firmware pushes.
- api — ESPHome's native API is faster and more feature-rich than MQTT for Home Assistant. The encryption key is mandatory since ESPHome 2023.6.
Validate the Configuration
Catch YAML and schema errors before compiling:
esphome config bedroom-sensor.yaml
ESPHome prints the resolved config (with secrets substituted) and exits non-zero on any error.
First Flash Over USB (Serial)
The very first upload must be serial. Identify your device node:
ls /dev/ttyUSB* /dev/ttyACM*
# Example output: /dev/ttyUSB0
Your user needs to be in the dialout group (Debian/Ubuntu/Fedora) or uucp (Arch) to access the port without sudo:
sudo usermod -aG dialout $USER # Debian/Ubuntu/Fedora
sudo usermod -aG uucp $USER # Arch
# Log out and back in, or run: newgrp dialout
esphome run bedroom-sensor.yaml
ESPHome compiles the firmware (this takes 1–3 minutes on first run; subsequent builds are faster due to caching), detects the serial port automatically, flashes the device, and tails the serial log. You should see it connect to Wi-Fi and print its IP address. Press Ctrl-C once you have confirmed connectivity.
Subsequent OTA Uploads
After the first flash, esphome run automatically detects the device on the network and uploads via OTA. You can also target an IP directly:
esphome run bedroom-sensor.yaml --device 192.168.1.42
To compile only (produce a .bin file without flashing):
esphome compile bedroom-sensor.yaml
OTA uploads the binary in chunks and reboots the ESP atomically. If the new firmware fails to boot and connect within the rollback window, the device reverts to the previous image — a built-in safety net that makes remote updates on inaccessible hardware viable.
Integrate with Home Assistant
Home Assistant discovers ESPHome devices automatically via mDNS as long as it shares the same L2 network segment as the ESP. If auto-discovery does not appear within a minute:
- Go to Settings → Devices & Services → Add Integration and search for ESPHome.
- Enter the device hostname (
bedroom-sensor.local) or its IP address. - Paste the same encryption key you put in
secrets.yamlwhen prompted.
Home Assistant adds every named sensor, switch, and binary sensor from the YAML as individual entities. No MQTT broker, no custom component, no restart of Home Assistant required. The native API connection is persistent and bidirectional — state changes on either side propagate in under 100 ms.
Using the ESPHome Dashboard
The dashboard provides a browser UI for editing configs, watching logs, and triggering OTA updates without the CLI:
esphome dashboard ~/esphome-devices/
Open http://localhost:6052 in a browser. On a remote server, either SSH-tunnel the port or bind it with --address 0.0.0.0 and restrict access via your firewall.
Verification
After integration, confirm end-to-end data flow:
- In Home Assistant, go to Developer Tools → States and filter by
bedroom. You should seesensor.bedroom_temperature,sensor.bedroom_humidity, andswitch.bedroom_relaywith live values. - Toggle the relay switch in the UI and verify the physical output changes.
- Tail the ESPHome log to confirm the sensor is updating on the 30-second interval:
esphome logs bedroom-sensor.yaml
Troubleshooting
Device not found on serial flash
Check dmesg | tail -20 immediately after plugging in the ESP. If no ttyUSB node appears, the CH340/CP2102 USB-UART driver may not be loaded. On Debian/Ubuntu it ships in the kernel; on some minimal installs run sudo modprobe ch341. Some clone boards require holding the BOOT button during flash start.
OTA update fails or times out
Verify the OTA password in secrets.yaml exactly matches what was compiled into the running firmware. A mismatch silently rejects the upload. Also check that UDP port 3232 (ESP32) or 8266 (ESP8266) is not blocked between your Linux host and the device's VLAN.
Home Assistant shows "Connection error"
mDNS can fail across VLANs or when Avahi is not running on the HA host. Use a static IP in the device YAML (wifi: manual_ip:) and enter that IP in the integration dialog instead of the .local hostname. Also confirm the encryption key matches — there is no fallback to unencrypted after 2023.6.
Compilation errors about unknown components
Run pip install --upgrade esphome in your virtualenv. Component APIs change between releases, and old installs silently produce broken firmware for newer YAML syntax.
Frequently asked questions
- Do I need MQTT to use ESPHome with Home Assistant?
- No. ESPHome's native API is the recommended transport and requires no broker. MQTT is still supported if you need cross-platform compatibility or want to publish to other systems alongside Home Assistant.
- Can I use ESPHome without Home Assistant at all?
- Yes. You can expose sensors via MQTT to any broker, use the HTTP REST API, or trigger automations entirely on-device with ESPHome's built-in lambda and automation engine. Home Assistant is optional.
- What happens if the OTA update crashes the device?
- ESP32 and ESP8266 both support a two-partition OTA scheme. If the newly flashed firmware does not call app_update_mark_valid (which ESPHome's boot sequence does automatically on successful Wi-Fi connection), the bootloader rolls back to the last known-good image on the next reboot.
- How do I update the Wi-Fi password after a device is deployed with no serial access?
- If the device cannot join the network with the old credentials it falls back to the captive portal AP (if you included captive_portal: in the YAML). Connect to that AP, enter the new credentials, and the device reboots onto your main network. You can then push a new OTA build with the updated secrets.
- Is the secrets.yaml format shared with Home Assistant's own secrets.yaml?
- The format is identical, but ESPHome reads its own secrets.yaml from the project directory, not from the Home Assistant config directory. They are separate files, though you can symlink them or use a shared path if you manage everything from one directory.
Related guides
Configure Prometheus Alertmanager
Configure Prometheus Alertmanager with routing trees, receivers, inhibition rules, grouping, Go templates, and PagerDuty/Slack on-call integrations.
Build an Intranet Server on Linux
Set up a complete small-office intranet on one Linux box: Nginx web server, dnsmasq local DNS, Samba file sharing, and a Wiki.js team wiki.
Build an nftables Firewall Script
Build a complete nftables firewall from scratch: tables, chains, sets, default-deny input policy, service allowlisting, and persistent systemd configuration.
Caddy as a Reverse Proxy
Set up Caddy as a reverse proxy with automatic HTTPS, load balancing, WebSocket passthrough, reusable snippets, and header control — no certbot required.