Contents

ESPHome: Rolling Your Own Sensors for Pennies

What a £3 microcontroller and forty lines of YAML actually get you

Contents

The moment ESPHome clicked for me was standing in a hardware shop holding a temperature sensor with a £34 price tag on it. It measured one thing. It needed a hub. It needed an account. The account needed an app, and the app wanted my location so it could tell me the temperature in a room I was standing in.

The same measurement, taken by a BME280 breakout soldered to an ESP32, costs about £4.50 all in and reports to a broker I own. The money is the smaller half of the difference. The £34 sensor is a rental; the £4.50 one is a possession. When the manufacturer of the first one decides the cloud service is no longer commercially viable, the sensor becomes a small plastic paperweight. The second one keeps working until the capacitors dry out.

That is the “why”. Here is the “how”, along with the parts nobody puts in the getting-started guide.

What ESPHome actually is

Advertisement

ESPHome is a compiler. That framing helps more than “it’s a smart home thing”, because it explains every strange behaviour you will later encounter.

You write a YAML file describing what you want: this board, this Wi-Fi network, this sensor on these pins, reporting every 60 seconds. ESPHome reads that file and generates C++ source code. It hands the C++ to PlatformIO, which compiles it against the ESP-IDF or Arduino framework, links it, and produces a firmware binary. That binary gets flashed to the microcontroller over USB the first time and over the air every time after.

There is no runtime interpreting your YAML on the device. The device is running purpose-built compiled firmware that contains exactly the features you asked for and nothing else. This is why an ESP32 running four sensors uses about 40 KB of RAM and stays up for months. It is also why a full compile takes two minutes and a typo in your YAML surfaces as a C++ template error that runs to forty lines.

The device talks to Home Assistant over the native ESPHome API — a small binary protocol on port 6053 — or over MQTT if you prefer a broker in the middle. I use the native API for anything that lives in Home Assistant and MQTT for anything I want other consumers to read too. If you have not settled that question yet, MQTT explained for people who just want sensors covers the trade-off in more depth.

The shopping list

Keep it boring. I have wasted more evenings on exotic boards than on any software problem.

  • ESP32 dev board (~£3–5). Wi-Fi and Bluetooth, dual core, plenty of GPIO. Get one with a USB-C socket and a CP2102 or CH340 serial chip. The ESP8266 is cheaper and still fine for a single sensor, though it has one ADC pin and no Bluetooth.
  • BME280 (~£2). Temperature, humidity and barometric pressure over I²C. The BME680 adds a gas resistance reading that people misread as an air-quality index; it needs weeks of burn-in before its numbers mean anything.
  • A USB-C power supply you already trust. Half the “my ESP32 keeps rebooting” threads on the internet are a phone charger browning out during Wi-Fi transmit peaks.
  • Dupont jumper wires and a breadboard for the prototype. Solder it properly once you know it works.

Total for a working temperature, humidity and pressure sensor: under a fiver, and the second one is cheaper because you already own the spare wire.

Forty lines of YAML

Advertisement

Here is a real configuration for a BME280 on an ESP32, reporting over the native API. This is the whole thing.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
esphome:
  name: study-climate
  friendly_name: Study Climate

esp32:
  board: esp32dev
  framework:
    type: esp-idf

logger:
  level: INFO

api:
  encryption:
    key: !secret api_encryption_key

ota:
  - platform: esphome
    password: !secret ota_password

wifi:
  ssid: !secret wifi_ssid
  password: !secret wifi_password
  domain: .mylab.local
  ap:
    ssid: "Study Climate Fallback"
    password: !secret fallback_password

captive_portal:

i2c:
  sda: GPIO21
  scl: GPIO22
  scan: true
  id: bus_a

sensor:
  - platform: bme280_i2c
    address: 0x76
    temperature:
      name: "Study Temperature"
      oversampling: 16x
      filters:
        - offset: -0.8
    humidity:
      name: "Study Humidity"
    pressure:
      name: "Study Pressure"
    update_interval: 60s

  - platform: wifi_signal
    name: "Study WiFi Signal"
    update_interval: 120s

  - platform: uptime
    name: "Study Uptime"

The !secret references pull from a secrets.yaml that sits alongside your device configs and never enters a git repository. If you want them in a repository anyway, encrypt them — secrets management with SOPS and age is the approach I settled on for exactly this kind of file.

Three details in that config earn their place:

The offset: -0.8 filter. Every BME280 mounted near an ESP32 reads high, because the ESP32’s regulator and radio dump heat into the same few square centimetres of PCB. Mine ran 0.8 °C hot against a reference thermometer. Yours will be different. Measure it before you trust it, because an uncalibrated sensor produces confident, precise, wrong numbers, and confident wrong numbers are worse than no numbers.

The fallback AP. When Wi-Fi credentials change or the router is down, the device raises its own access point so you can reach it without a USB cable and a ladder. This has saved me twice.

The wifi_signal and uptime sensors. They cost nothing and turn “the sensor is being flaky” into “the sensor is at -87 dBm and rebooting every 40 minutes”, which is an answer.

Compile and flash the first one over USB:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
$ esphome run study-climate.yaml
INFO Reading configuration study-climate.yaml...
INFO Detected timezone 'Europe/Copenhagen'
INFO Generating C++ source...
INFO Compiling app...
Processing study-climate (board: esp32dev; framework: espidf)
[...]
RAM:   [=         ]  12.4% (used 40632 bytes from 327680 bytes)
Flash: [=====     ]  50.8% (used 931736 bytes from 1835008 bytes)
INFO Successfully compiled program.
Found multiple serial ports:
[1] /dev/ttyUSB0 (CP2102 USB to UART Bridge Controller)
INFO Uploading .pioenvs/study-climate/firmware.bin (931736 bytes)
Writing at 0x00060000... (100 %)
INFO Successfully uploaded program.
INFO Starting log output from /dev/ttyUSB0
[10:14:02][I][app:102]: ESPHome version 2025.1.0 compiled on Jan 20 2025
[10:14:03][I][wifi:303]: WiFi Connected!
[10:14:05][I][bme280.sensor:071]: Temperature=21.3°C Humidity=44.2% Pressure=1013.8hPa

Every subsequent flash goes over the air. You edit the YAML at your desk, run esphome run study-climate.yaml, and the device on the shelf upstairs reboots into new firmware ninety seconds later. This is the part that turns a soldering project into something you actually iterate on.

Where the pennies stop being the point

Once you have one working device, the marginal cost of the next idea drops to near zero, and that changes what you build. A few that earned their keep here:

  • A soil moisture probe in the big plant pot, capacitive rather than resistive, because resistive probes corrode into uselessness within a season. Deep sleep, one reading an hour, running on a battery since spring.
  • A washing machine sensor — an accelerometer glued to the drum housing, publishing a “vibrating” state. It replaced the household ritual of forgetting the wash for six hours.
  • A letterbox flap sensor, a reed switch and a magnet, £0.60, that has never once needed maintenance.

None of those exist as a product you can buy. That is the actual argument for ESPHome: the £34 sensor solves a problem someone else decided was worth solving at scale, and your house has problems that are not worth anyone’s scale.

The other half of the argument is control over the update path. A commercial sensor updates when the vendor pushes firmware, which occasionally means it acquires a new cloud dependency you did not ask for. An ESPHome device updates when you type a command. If you want it on a segment that cannot reach the internet at all, that works fine — mine live on an IoT VLAN with no outbound route, which is roughly the arrangement described in VLAN segmentation at home.

Troubleshooting: the five failures you will actually hit

The device compiles and flashes, then never appears. Check the log output over USB before blaming the network. Nine times in ten the ESP32 is looping on WiFi Connecting... because the SSID is 5 GHz only. ESP32s are 2.4 GHz. If your access point uses band steering with a single SSID, the device may pick a band it cannot hear and sulk.

Component i2c took a long time for an operation. Your I²C wiring is marginal. Long dupont jumpers on a breadboard are an antenna and a resistor. Shorten the wires, or drop the bus frequency with frequency: 50kHz under the i2c: block. If the address scan finds nothing at all, try 0x77 — BME280 breakouts ship with either address depending on how the manufacturer strapped the SDO pin, and the counterfeit BMP280s that flood the market only report temperature and pressure.

Random reboots, especially during Wi-Fi transmit. Power. Always power. The ESP32 pulls short current spikes of several hundred milliamps when the radio fires, and a thin USB cable with a couple of ohms of resistance turns that spike into a brownout. Use a shorter, thicker cable and a supply rated well above what you think you need. If reboots persist, the log prints the reset reason on boot — Brownout detector was triggered is unambiguous.

OTA updates fail halfway with Error: Connection reset. The device ran out of free flash for the staging partition, or Wi-Fi is too weak to sustain the transfer. Check the wifi_signal sensor you helpfully added earlier. Below about -80 dBm, OTA becomes a coin flip while normal sensor reporting still works fine, which makes it feel like an ESPHome bug.

Home Assistant shows the device as unavailable every few hours. If several ESPHome devices do this in unison, suspect the access point rather than the devices. Cheap APs have client tables that mishandle devices which sleep or idle. Mine stopped entirely after I gave the IoT radio its own AP and turned off the “smart” client-steering feature.

A sensor reports nan. The component initialised but the read failed. On I²C that usually means intermittent contact; on the ADC it usually means you have used one of the ADC2 pins on an ESP32, which stop working the moment Wi-Fi is enabled. This is a hardware limitation of the chip and no amount of YAML will fix it. Move to an ADC1 pin (GPIO32–39).

The case against

I want to be honest about the ceiling here.

The compile-and-flash loop is slow enough to be annoying. Two minutes per iteration, plus a reboot, means fiddling with a filter chain is a coffee-break activity rather than a tweak. ESPHome caches builds well, though a version bump invalidates everything and you get to watch a full rebuild of every device you own.

Wi-Fi is a poor choice for battery sensors. A Wi-Fi association costs a few hundred milliamps for two or three seconds, which dominates the power budget entirely. If you want a door sensor that runs three years on a coin cell, buy a Zigbee one — that is what the radio was designed for, and the comparison in Zigbee vs Z-Wave vs Matter lays out why. ESPHome with deep sleep gets you months on an 18650, which is a different order of convenience.

And the aesthetics are real. A breadboard with wires hanging out of it looks like a breadboard with wires hanging out of it. Enclosures are a whole second hobby involving 3D printing, and things you make yourself will look made yourself.

Verdict

ESPHome is worth it if you have already decided to run a local smart home and you enjoy the tinkering as much as the outcome. The economics are genuinely absurd — a fiver for a sensor that costs thirty-four in a shop and answers to you rather than to a vendor’s cloud roadmap. The reliability, once you fix the power supply and stop using ADC2 pins, is better than most of the commercial kit I have owned.

It is not worth it if you want the sensor working tonight and never thinking about it again. Buy the Zigbee one. There is no shame in that; I have plenty of Zigbee sensors doing the jobs where an off-the-shelf part is simply the right answer.

Where ESPHome has no competition at all is the middle ground: the measurement nobody sells, in the shape your house happens to need. That is where the £4.50 board stops being a cheaper sensor and starts being the only sensor.

Advertisement
Advertisement
Smarc
Written by Smarc

Founder and editor of vo.rs. A lifelong tinkerer who self-hosts far more than is sensible, hardens Linux boxes for fun, and prods the latest AI tools to see what they can really do. The how-to guides here are the notes Smarc wishes had existed the first time round.