esp32 intermediate 45 min

ESP32: connect to Home Assistant two ways (ESPHome and MQTT)

Wire an ESP32 sensor into a self-hosted Home Assistant: flash it with ESPHome from the HA UI, or run native firmware and publish MQTT discovery. Both paths, one tutorial.

Code available for: ESP32 ArduinoArduino C
Published Sep 22, 2026

You have a Raspberry Pi running Home Assistant (the HA install tutorial covers that part), and you have an ESP32 with a BME280 on it. The question is how the two of them talk. There are two good answers and they both work: flash the ESP32 with ESPHome, which turns the whole firmware into a YAML config file that Home Assistant manages for you, or keep your own Arduino code and publish MQTT discovery messages that make the sensor show up in HA on its own.

I run both in my house. ESPHome for the boring sensors (temperature, door contacts, the mailbox), native MQTT for anything where I want full control of the loop (e.g. the projects where the ESP32 is also driving neopixels or reading a weird sensor that needs custom timing).

The trap: people try to do both at once on one device, or they flash ESPHome and then wonder why their Arduino sketch is gone. Flashing ESPHome replaces the firmware completely. Pick one path per device. You can reflash to the other path any time over USB.

What you need

  • ESP32 dev board (the 38-pin ESP32-WROOM-32 devkit is the default)
  • A BME280 sensor module for the example (or any I2C sensor you have; the wiring is the same for the ESPHome version either way)
  • A Raspberry Pi (or any machine) running Home Assistant, reachable from your network
  • A Mosquitto MQTT broker if you take the MQTT path (the MQTT broker on Pi tutorial covers the install)
  • 4 jumper wires and a breadboard
  • Micro-USB cable for flashing

Needed

PartWhy this one
ESP32-WROOM-32 devkitThe board every other tutorial here assumes
BME280 breakoutI2C, 3.3 V safe, HA has a native sensor entity for it
4 jumper wiresSDA, SCL, VCC, GND

Nice to have

  • Soldering iron and solder (if the BME280 header came loose)
  • Multimeter (check 3.3 V actually reaches the sensor before blaming the config)
  • Helping hands (holding a header while soldering)

Wiring

Wire key: VCC3.3VGNDSDAGPIOSCL
BME280ESP32
VCC3.3V
GNDGND
SDAGPIO 21
SCLGPIO 22

Same wiring for both paths. The ESP32 default I2C pins (SDA 21, SCL 22) work in ESPHome and in Arduino code alike.

The BME280 is 3.3 V only. Do not feed it 5 V. And check the module: some GY-BME280 boards want VCC on 3.3 V and some silk says 3.3 V but the board has a regulator. Either way, 3.3 V is the safe answer.

Path 1: ESPHome

Install

ESPHome lives inside Home Assistant. Open your HA UI and go to Settings >> Add-ons >> Add-on Store >> search ESPHome Device Builder >> Install >> Start. That is the whole install. If you run HA Container (not HAOS), you instead run the ESPHome standalone tool in Docker; the config format is identical.

The config

In the ESPHome builder: + NEW DEVICE >> name it desk-sensor >> pick ESP32 >> skip the Wi-Fi step (paste your own). You get a YAML file. Replace the sensor section with this:

esphome:
  name: desk-sensor

esp32:
  board: esp32dev

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

api:
  encryption:
    key: "paste-the-key-esphome-generated"

ota:
  - platform: esphome

i2c:
  sda: 21
  scl: 22
  scan: true

sensor:
  - platform: bme280
    address: 0x76
    temperature:
      name: "Desk temperature"
    pressure:
      name: "Desk pressure"
    humidity:
      name: "Desk humidity"
  - platform: wifi_signal
    name: "Desk WiFi"
    update_interval: 60s

Click Install, pick “Plug into this computer” the first time (the flash happens from the browser over USB, no Arduino IDE involved), and after about two minutes the device appears in Home Assistant’s device list with three sensor entities. No discovery config, no MQTT broker, no code. That is the ESPHome pitch and it is real.

Path 2: native firmware with MQTT discovery

Install

If you want your own loop instead, install the libraries in the Arduino IDE: Sketch >> Include Library >> Manage Libraries >> search PubSubClient by Nick O’Leary and Adafruit BME280 Library. Install both.

The code

The trick is the discovery message. Home Assistant listens for retain-flagged JSON on homeassistant/sensor/<node_id>/<object_id>/config describing the entity. Publish that once at boot and HA builds the entity for you. Then publish state on the topic the config points to.

#include <WiFi.h>
#include <PubSubClient.h>
#include <Wire.h>
#include <Adafruit_BME280.h>

const char* WIFI_SSID = "your-wifi-ssid";
const char* WIFI_PASS = "your-wifi-password";
const char* MQTT_HOST = "192.168.1.50";   // the Pi running Mosquitto

WiFiClient net;
PubSubClient mqtt(net);
Adafruit_BME280 bme;

char stateTopic[] = "home/desk-sensor/state";
char cfgTemplate[] =
  "{\"device_class\":\"temperature\",\"unit_of_measurement\":\"°C\","
  "\"value_template\":\"{{ value_json.temperature }}\","
  "\"state_topic\":\"home/desk-sensor/state\","
  "\"unique_id\":\"desk_sensor_temp\","
  "\"device\":{\"name\":\"Desk sensor\",\"identifiers\":[\"desk-sensor\"]}}";

void publishDiscovery() {
  String topic = "homeassistant/sensor/desk-sensor/temperature/config";
  mqtt.publish(topic.c_str(), cfgTemplate, true);   // retain = true, HA remembers it
}

void setup() {
  Serial.begin(115200);
  Wire.begin(21, 22);
  if (!bme.begin(0x76)) {
    Serial.println("BME280 not found. Check wiring/address.");
    while (true) delay(100);
  }
  WiFi.begin(WIFI_SSID, WIFI_PASS);
  while (WiFi.status() != WL_CONNECTED) { delay(300); }
  mqtt.setServer(MQTT_HOST, 1883);
  while (!mqtt.connect("desk-sensor")) { delay(300); }
  publishDiscovery();
}

void loop() {
  mqtt.loop();
  static uint32_t last = 0;
  if (millis() - last < 30000) return;   // every 30 s
  last = millis();
  float t = bme.readTemperature();
  float h = bme.readHumidity();
  float p = bme.readPressure() / 100.0F;
  char payload[96];
  snprintf(payload, sizeof(payload),
           "{\"temperature\":%.2f,\"humidity\":%.2f,\"pressure\":%.2f}",
           t, h, p);
  mqtt.publish(stateTopic, payload);
  Serial.println(payload);
}

Upload, then in Home Assistant: Settings >> Devices & services >> the MQTT integration shows “Desk sensor” with a temperature entity. If HA was already running while you published, the entity appears within seconds because the message was retained.

Keep the discovery message small and retain it. A lost retained discovery message after a broker restart is the number-one “my sensor vanished from HA” cause. Republishing at boot (this sketch does) fixes it.

Which path when

  • ESPHome when the device is sensors and switches, when you want OTA updates from the HA UI, and when you would rather edit YAML than C++.
  • Native MQTT when you need timing-sensitive code, custom protocols, or you already have a working sketch and just want it in HA.

What you learned

  • ESPHome turns the firmware into YAML and flashes it from the browser; HA picks up every entity automatically.
  • Native MQTT needs one retained discovery JSON per entity, then HA builds the sensor itself.
  • Same wiring, same I2C pins for both. The paths differ only in the firmware.
  • Pick one path per device; flashing one replaces the other.

When something breaks

  • ESPHome flash fails at “Connecting…” Hold the BOOT button on the devkit while the flash starts, release when it connects. The classic CP2102 driver issue on Windows shows up as the COM port never appearing at all.
  • The MQTT sensor never appears in HA. The discovery message needs retain=true and HA’s MQTT integration needs to be enabled (Settings >> Devices & services >> MQTT). Watch the topic with mosquitto_sub -t 'homeassistant/#' -v to see whether your JSON actually went out.
  • The sensor appears but values show “unknown”. The value_template in the config JSON does not match your state payload’s keys. If you publish {"temperature":22.1} the template must say {{ value_json.temperature }} exactly.
  • ESPHome device shows “offline” in HA. Wrong API encryption key after regenerating, or the YAML still has a placeholder Wi-Fi secret. The device logs (in the ESPHome builder) say which.
  • Both paths on one device fight each other. They cannot coexist; the flash overwrote whatever was there. Reflash with the path you want and move on.

What to build next

  • Pair this with the MQTT publish-subscribe tutorial if you want to understand the topic structure the discovery messages ride on.
  • The raspberry-pi-home-assistant tutorial covers the HA server side, including the backup you should take before you flash anything.
  • Once sensors are in HA, the ntfy notifications tutorial shows the self-hosted way to get your phone to buzz when a threshold trips.
  • The book IoT with ESP32 bundles the sensor, MQTT, and automation chapters into one project arc.