If you've blinked your LEDs, read data from a temperature sensor, and set up a simple web server on your microcontroller — you're ready for the next step. This guide takes you from basics to a fully networked, MQTT-based smart home system where dozens of sensors and relays communicate in real-time.
We'll connect the ESP32 to Home Assistant using MQTT: a password-protected Mosquitto broker, an ESP32 sketch that publishes a sensor and obeys commands, a test with the command-line tools, and two ways to make the entities appear in Home Assistant, by hand in YAML or automatically with MQTT Discovery.
Chapter 1: The Modern Smart Home Architecture
Before writing code, you need to understand how a modern, localized smart home is structured. A robust system doesn't rely on third-party cloud servers. It runs entirely on your local network.
The standard stack has three layers:
The Central Brain — The Server Typically a Raspberry Pi running a full Linux OS. It's always on, low-power, and hosts your smart home software.
The Ecosystem — The Software Home Assistant, Node-RED, and InfluxDB run here. These handle dashboards, automation logic (turn on a light when motion is detected), and historical data storage.
The Edge Nodes — The Hardware This is where your ESP32 and ESP8266 circuits live. They read sensors, toggle relays, and beam data back to the central brain over WiFi.
But how do these edge nodes talk to the brain? They need a protocol. HTTP works, but it's bulky and resource-hungry. This is where MQTT changes everything.
Chapter 2: Demystifying MQTT
MQTT stands for Message Queuing Telemetry Transport. It's a lightweight publish-subscribe protocol built for low-bandwidth, high-latency networks — the industry standard for IoT.
The Publish / Subscribe Model
Unlike HTTP (ask → receive), MQTT uses a Pub/Sub model. Think of it as a post office:
| Role | Description | Example |
|---|---|---|
| Broker | The post office. Routes all messages. | Mosquitto on Raspberry Pi |
| Publisher | Drops off a message to a topic. | ESP32 sending temperature |
| Subscriber | Checks their P.O. box for messages. | Home Assistant reading sensor data |
| Topic | The address on the envelope. | home/livingroom/temperature |
The ESP32 drops off its message and immediately returns to work — saving processing time and power. Combined with deep sleep techniques, an ESP32 can run on batteries for months rather than days.
Chapter 3: Setting Up the Broker — Raspberry Pi and Mosquitto
You need a broker before anything else. We'll install Mosquitto on a Raspberry Pi.
Installing Mosquitto
Open your Raspberry Pi terminal and run:
sudo apt update
sudo apt install mosquitto mosquitto-clients
Enable it to auto-start on reboot:
sudo systemctl enable mosquitto
Securing the Broker — Create a User
Never leave your broker open to any device on your network. Add authentication:
sudo mosquitto_passwd -c /etc/mosquitto/passwd mqtt_user
Enter a strong password when prompted. Then edit the Mosquitto config:
sudo nano /etc/mosquitto/mosquitto.conf
Add these three lines at the bottom:
listener 1883
allow_anonymous false
password_file /etc/mosquitto/passwd
The listener 1883 line matters. Since version 2.0, Mosquitto only accepts connections from the Pi itself unless you declare a listener, so without it the ESP32 gets rc=-2 forever even though everything else is right.
Save, exit, and restart:
sudo systemctl restart mosquitto
Your broker is now password-protected and listening on port 1883. Check it from the Pi with two terminals:
# Terminal 1: watch everything under home/
mosquitto_sub -h localhost -u mqtt_user -P 'YOUR_SECURE_PASSWORD' -t 'home/#' -v
# Terminal 2: send a test message
mosquitto_pub -h localhost -u mqtt_user -P 'YOUR_SECURE_PASSWORD' -t home/test -m hello
If home/test hello appears in the first terminal, the broker works. Keep that subscriber open: it shows every message the ESP32 sends in the next chapter. For a fuller broker setup, see installing Mosquitto on a Raspberry Pi 5.
Running Home Assistant OS? Skip the manual install and use the official Mosquitto broker add-on from the Add-on Store. It creates the broker, and you add a user for the ESP32 under its configuration.
Chapter 4: Programming the ESP32 Node
Time to write the firmware. This ESP32 will act as a dual-purpose node — it publishes temperature data and subscribes to a command topic so Home Assistant can toggle an onboard LED (representing a relay).
Install the Library
In Arduino IDE: Sketch → Include Library → Manage Libraries → search PubSubClient by Nick O'Leary → Install.
Understanding the Code
Publishing: The loop() uses a non-blocking millis() timer to publish simulated sensor data to home/livingroom/temperature every 10 seconds. Replace random() with real DHT22 or MQ135 readings when you're ready.
Subscribing: reconnect() subscribes to home/livingroom/light/set. The callback() function listens on that topic — when it receives "ON", it drives GPIO2 HIGH to turn the LED on, then publishes ON back to home/livingroom/light/status with the retained flag, so Home Assistant always knows the real state, even after it restarts.
Client ID: every device needs its own ID in client.connect(). Two ESP32s sharing ESP32_LivingRoom knock each other off the broker in an endless loop.
Chapter 5: Integrating with Home Assistant
Broker is running. ESP32 is publishing. Now give this data a proper UI inside Home Assistant.
Step 1: Add the MQTT Integration
Home Assistant has to be told where the broker is before any MQTT entity can work. Go to Settings → Devices & Services → Add Integration → MQTT, enter the broker's IP address, port 1883, and the mqtt_user username and password. (If you use the Mosquitto add-on, Home Assistant usually discovers it and only asks you to confirm.)
Step 2: Define the Entities in configuration.yaml
Add the following block to your configuration.yaml file:
Check it under Developer Tools → YAML → Check configuration, then restart Home Assistant. Later edits to these MQTT entities only need Developer Tools → YAML → Manually configured MQTT entities to reload.
What Happens Next
After HA reboots, navigate to your Lovelace dashboard and add an Entity card. You'll find two new entities:
sensor.living_room_temperature— updates every 10 seconds from the ESP32switch.living_room_lamp— toggling it publishesONorOFFto the broker, which routes it to the ESP32, which physically toggles the LED
You've just built a complete, end-to-end, local IoT network.
The Easier Way: MQTT Discovery
Writing YAML for every sensor gets tedious. With MQTT Discovery, the ESP32 announces its own entities: it publishes a retained JSON description to a topic under homeassistant/, and Home Assistant creates the entity automatically. Add this to the sketch and call it right after a successful client.connect():
void publishDiscovery() {
const char* config =
"{\"name\":\"Living Room Temperature\","
"\"state_topic\":\"home/livingroom/temperature\","
"\"unit_of_measurement\":\"°C\","
"\"device_class\":\"temperature\","
"\"state_class\":\"measurement\","
"\"unique_id\":\"esp32_livingroom_temp\"}";
client.publish("homeassistant/sensor/esp32_livingroom_temp/config", config, true);
}
One trap: PubSubClient's default packet buffer is 256 bytes, and discovery messages easily exceed it, in which case publish() silently returns false. Call client.setBufferSize(512); in setup() before connecting. The unique_id lets you rename the entity and change its icon from the Home Assistant UI later.
Show When the Node Goes Offline
If the ESP32 loses power, Home Assistant keeps showing the last temperature as if nothing happened. MQTT's Last Will and Testament fixes that: when connecting, the ESP32 asks the broker to publish offline on its behalf if it disappears.
// connect(clientId, user, pass, willTopic, willQos, willRetain, willMessage)
if (client.connect("ESP32_LivingRoom", mqtt_user, mqtt_password,
"home/livingroom/status", 0, true, "offline")) {
client.publish("home/livingroom/status", "online", true);
client.subscribe(led_command_topic);
}
Then add availability_topic: "home/livingroom/status" to each entity (or "availability_topic" to the discovery JSON). Home Assistant shows the entities as Unavailable within about a minute of the node dropping off. The retained messages and LWT guide explains the details.
Chapter 6: Expanding Your Ecosystem
MQTT scales effortlessly. To add a garage sensor, copy the same code, change the topic to home/garage/temperature, and power it on. The broker handles all routing automatically.
Where to go from here:
- Long-term storage: Route MQTT topics into an InfluxDB database for historical graphs
- Custom dashboards: Pipe MQTT data into Node-RED running alongside Home Assistant
- Vision projects: An ESP32-CAM can publish
parking/space1/status: occupiedvia MQTT after processing a camera frame locally - Battery-powered nodes: Use ESP32 deep sleep — wake, publish, sleep — and run on batteries for months
Common Issues & Fixes
| Error | Cause | Fix |
|---|---|---|
rc=-2 in Serial Monitor | Broker IP wrong, broker not running, or Mosquitto 2.x with no listener 1883 line | Check the Pi's IP, sudo systemctl status mosquitto, and the listener line |
rc=4 or rc=5 | Wrong username or password, or the user isn't in the password file | Recreate the user with mosquitto_passwd and restart Mosquitto |
| Two nodes keep disconnecting each other | Both use the same client ID | Give every device a unique ID in client.connect() |
| Discovery entity never appears | Payload bigger than PubSubClient's 256-byte buffer | client.setBufferSize(512) before connecting |
rc=-4 timeout | Firewall blocking port 1883 | Allow inbound port 1883 on your Pi's firewall |
| ESP32 keeps reconnecting | delay() blocking client.loop() | Use millis() for timing — never delay() in main loop |
| HA shows no data | MQTT integration not added, or a YAML indentation error | Add the MQTT integration; run Developer Tools → YAML → Check configuration |
| Switch in HA doesn't reflect LED state | ESP32 not publishing state back | Add client.publish("home/livingroom/light/status", "ON") inside the callback |
Conclusion
Transitioning from simple Arduino scripts to a fully networked, MQTT-based smart home is the defining moment in any maker's journey. You now have:
- A password-protected Mosquitto broker on a Raspberry Pi
- An ESP32 that both publishes sensor data and receives commands
- Home Assistant entities, defined in YAML or announced automatically with Discovery
- Offline detection with Last Will
The same pattern scales to any number of nodes. Add temperature sensors, motion detectors, soil moisture sensors, door contacts — each one just needs a unique topic and the same core code structure.
Next reads:




