ESPHome: Building Custom Sensors for Home Assistant
Turn a three-quid microcontroller into a first-class Home Assistant device with a few lines of YAML

There is a particular joy in solving a problem with three quid of silicon instead of forty quid of someone else’s cloud-tethered gadget. ESPHome is the tool that makes that joy repeatable. You describe a device — its WiFi, its sensors, its outputs — in YAML, ESPHome compiles real C++ firmware, flashes it onto an ESP32 or ESP8266, and the device shows up in Home Assistant automatically with no cloud, no app, and no telemetry leaving your house. I’ve built temperature monitors, soil moisture sensors, a letterbox-open detector, and a CO₂ meter this way, and every one was easier than wiring up the equivalent off-the-shelf product.
1 The model
ESPHome is firmware-from-config. You write a YAML file per device. Each entry maps to a component: a sensor, a binary sensor, a switch, a display, whatever. ESPHome generates the firmware, and the device speaks the native ESPHome API to Home Assistant over your LAN. Discovery is automatic — flash it, and HA offers to add it.
You run ESPHome either as a Home Assistant add-on (the easy path) or as a standalone Docker container, which is what I do because I like keeping HA lean:
services:
esphome:
image: ghcr.io/esphome/esphome:latest
restart: unless-stopped
volumes:
- ./esphome-config:/config
- /etc/localtime:/etc/localtime:ro
network_mode: host
privileged: true # needed for direct USB flashingnetwork_mode: host matters: the device discovery and the dashboard both want to see the LAN directly. After the first flash over USB, every subsequent update goes over-the-air.
2 A real sensor: temperature, humidity, and a leak alarm
Here’s a genuinely useful device — a BME280 environmental sensor plus a water-leak probe on a digital pin, on an ESP32:
esphome:
name: utility-room
friendly_name: Utility Room
esp32:
board: esp32dev
wifi:
ssid: !secret wifi_ssid
password: !secret wifi_password
api:
encryption:
key: !secret api_key
ota:
- platform: esphome
password: !secret ota_password
logger:
i2c:
sda: GPIO21
scl: GPIO22
sensor:
- platform: bme280_i2c
address: 0x76
temperature:
name: "Utility Temperature"
humidity:
name: "Utility Humidity"
pressure:
name: "Utility Pressure"
update_interval: 60s
binary_sensor:
- platform: gpio
pin:
number: GPIO27
mode:
input: true
pullup: true
inverted: true
name: "Utility Leak Detected"
device_class: moistureThe !secret references pull from a shared secrets.yaml, so your WiFi password and API keys never live in the device config — and you can commit the configs to git without leaking credentials. The api.encryption.key is the modern, encrypted native API; the ota block lets you push updates wirelessly forever after the first cable flash.
Compile and flash from the CLI:
$ esphome run utility-room.yaml
INFO Compiling app...
INFO Successfully compiled program.
INFO Detecting upload method...
INFO Resolving IP address of utility-room.local
INFO Connecting to 192.168.1.84
INFO Uploading utility-room.bin (892 kB)
[========================] 100%
INFO OTA successful
INFO Starting log output from 192.168.1.84
[D][bme280:130]: Got temperature=19.4°C humidity=58.1%
[D][binary_sensor:036]: 'Utility Leak Detected': Sending state OFF
Within seconds those entities appear in Home Assistant. Now I can trigger an automation that screams at me if the washing machine springs a leak.
3 On-device logic, not just sensing
The thing that elevates ESPHome above “dumb sensor” is that it can run logic locally. A button can toggle a relay without a round-trip to HA, so it still works if HA is down:
switch:
- platform: gpio
pin: GPIO26
name: "Utility Heater"
id: heater
binary_sensor:
- platform: gpio
pin:
number: GPIO25
mode: { input: true, pullup: true }
inverted: true
name: "Heater Button"
on_press:
then:
- switch.toggle: heaterThat on_press automation lives on the chip. The relay responds instantly and keeps working during a HA restart or a WiFi blip — local-first behaviour you simply don’t get from cloud gadgets.
4 The honest caveats
It is not all sunshine. You will, at some point, fight a soldering iron, a flaky breadboard jumper, or a sensor with a poorly documented I²C address. ESP8266 boards are cheaper but tighter on memory and pins — for anything non-trivial I default to ESP32. WiFi sensors are power-hungry, so true battery devices need deep-sleep configuration and careful thought, which is fiddlier than the always-powered case shown above. And the first USB flash on Linux occasionally needs you to sort out serial port permissions, which is a five-minute annoyance the docs cover but everyone hits once.
5 Verdict
ESPHome is absolutely worth it if you enjoy building things, want to keep your smart home local and private, or have a sensing need no commercial product quite covers. The economics are silly in your favour — a sensor that would cost forty quid as a finished product is a few quid in parts and twenty minutes of YAML. It’s not for someone who wants to unbox a polished retail device and never see a config file; that’s a perfectly valid preference, and ESPHome will feel like homework to them. But if you self-host Home Assistant and have ever thought “I wish I could just measure X” — this is the most rewarding rabbit hole in the hobby.



