Michał Jonik
2026-06-13

Firmware-as-Config: devices you declare instead of program

About seventeen ESP32 and ESP8266 boards run my house. They sit in the boiler room, on the floor-heating valves, in the air conditioners, at the gate and on the water meter. Each one cost a few dollars, and I have written only a few lines of C++ for all of them together.

They run ESPHome. You describe a board in a YAML file: which pin drives a relay, which one reads a temperature sensor, what to report to Home Assistant. ESPHome generates the C++ from that description, compiles it and uploads it to the board. A board with a pump and a temperature probe looks like this:

esphome:
  name: boiler-room

esp32:
  board: esp32dev

wifi:
  ssid: !secret wifi_ssid
  password: !secret wifi_password

api:
  encryption:
    key: !secret api_key

ota:
  - platform: esphome

one_wire:
  - platform: gpio
    pin: GPIO4

sensor:
  - platform: dallas_temp
    name: "Buffer tank temperature"
    update_interval: 30s

switch:
  - platform: gpio
    pin: GPIO26
    name: "Circulation pump"

After the first flash over USB, every change is: edit the file, upload, wait for the build. The sensor and the switch appear in Home Assistant on their own.

Firmware-as-Config

Firmware has always been code. What ESPHome changes is who writes it. I declare what the board has and what it should do, and a generator writes the code. I call this Firmware-as-Config: the whole behaviour of a device declared in one configuration (a text file and the files it includes), from which the firmware can be rebuilt at any time.

The format matters more than the convenience, because a declaration can be treated like any other configuration file.

A diff shows a change in behaviour. “The pump now starts above 60 °C” is one changed line in a file that anyone can read.

When a board dies, a new board, the same file and one flash bring back the same device. After one Migrate click in Home Assistant it also has the same entities.

Repeated pieces become shared ones. ESPHome packages let one device include a fragment of configuration from a file or a git repository, with its own parameters. A water-meter counter or a set of safety limits for a heater is written once and included wherever it is needed. I keep the fragments I reuse in home-automation-snippets.

Where the declaration ends

YAML covers most of what a home device does. When it does not, ESPHome accepts C++ in lambda blocks, inside the same file. The device is still described in one place, but a lambda has to be read as code, and a reviewer has to know which parts of the file are declaration and which are program.

A declaration also does not make a device safe. A thermostat for a heater takes a dozen lines of YAML, and those lines decide what happens after a power cut or when a sensor stops reporting.

My own files live in the ESPHome dashboard on my NAS, edited in a browser. The format allows everything above, but how much of it I get depends on where the file lives.

Takeaways

Infrastructure went through the same change: from servers configured by hand to servers declared in files. The benefit came from being able to review a change before it ships, rebuild a system from its description and see who changed what, and none of that depends on YAML or Terraform files. A declaration that lives only inside the tool that applies it gives you none of that.

The same holds for AI agents. An agent can read a device declaration, explain it and propose a change as a diff that a person approves. A sequence of clicks in a web UI leaves no diff to review.

esphomehome-assistantfirmware-as-config