Learning Goals 5 min
Cluster C's capstone: an ESP that auto-appears as a device in Home Assistant — no manual config in Home Assistant's YAML. By publishing one extra "discovery" message in the right format, your device shows up with the right icon, units, and entity type. By the end of this lesson you will:
- Understand Home Assistant's MQTT Discovery convention (auto-add devices).
- Publish discovery configs that turn your ESP into 3 Home Assistant entities: a temperature sensor, a humidity sensor, an LED light.
- Verify the entities appear in Home Assistant's UI with the right icon and units.
Warm-Up 10 min
Hardware: ESP + DHT11 sensor + LED + 220 Ω. Same wiring as L04-12 but now talking MQTT instead of Arduino IoT Cloud.
If you don't have Home Assistant
You can install Home Assistant on a Raspberry Pi (recommended) or in Docker / a VM on your laptop. Free, open-source. 30-minute setup from home-assistant.io.
If you skip Home Assistant for now, you can still complete this lesson's code — just verify the discovery messages in MQTT Explorer. They'll be ready to be picked up when you do install HA.
What MQTT Discovery does
Home Assistant subscribes to homeassistant/#. When your ESP publishes a specially-formatted message to homeassistant/sensor/livingroom-temp/config, HA reads it and auto-creates a "Living Room Temperature" entity with the right type and units. No YAML editing.
New Concept · Discovery messages 25 min
The discovery topic structure
homeassistant/<component>/<object_id>/config. Components: sensor, binary_sensor, switch, light, cover, etc. Object ID is yours to choose.
A temperature-sensor config payload
{
"name": "Living Room Temperature",
"state_topic": "advaslearning/aliya/livingroom/temp",
"unique_id": "aliya-livingroom-temp",
"unit_of_measurement": "°C",
"device_class": "temperature",
"availability_topic": "advaslearning/aliya/livingroom/status",
"payload_available": "online",
"payload_not_available": "offline"
}
Publish this once (retained) to homeassistant/sensor/aliya-livingroom-temp/config. Home Assistant auto-creates the entity, knows it reads from state_topic, displays values with °C units, uses the "thermometer" icon (because of device_class), and shows online/offline state from the availability topic.
A light entity
{
"name": "Aliya Desk Lamp",
"command_topic": "advaslearning/aliya/lamp/cmd",
"state_topic": "advaslearning/aliya/lamp/state",
"brightness_command_topic": "advaslearning/aliya/lamp/brightness",
"brightness_state_topic": "advaslearning/aliya/lamp/brightness/state",
"unique_id": "aliya-desk-lamp",
"availability_topic": "advaslearning/aliya/lamp/status",
"payload_available": "online",
"payload_not_available": "offline"
}
Published to homeassistant/light/aliya-desk-lamp/config. HA shows a switch + brightness slider in its UI. Tapping the switch sends ON to the command topic — your ESP's callback fires.
Device grouping
Multiple entities can belong to one "device". Add a device object:
"device": {
"identifiers": ["aliya-living-room-esp"],
"name": "Aliya Living Room ESP",
"manufacturer": "Advaslearning",
"model": "ESP8266 v1",
"sw_version": "0.1"
}
All entities with the same identifiers appear grouped under one device in Home Assistant's UI. Standard pattern.
Publish order on boot
- Connect WiFi + MQTT.
- Publish discovery configs (retained) — one per entity.
- Publish availability = online.
- Subscribe to command topics.
- Publish initial state.
- Enter the publish-sensor-readings loop.
Worked Example · Three-entity sensor node 25 min
Wire
DHT11 on D2; LED on D5 (PWM); ESP USB-powered.
The sketch (abridged — focus on the discovery section)
// L04-16 · Home Assistant MQTT discovery
#if defined(ESP8266)
#include <ESP8266WiFi.h>
#elif defined(ESP32)
#include <WiFi.h>
#endif
#include <PubSubClient.h>
#include <DHT.h>
const char* CLIENT_ID = "aliya-living-room-esp";
const char* BASE = "advaslearning/aliya/livingroom";
const char* STATUS_T = "advaslearning/aliya/livingroom/status";
#define DHTPIN 2
#define LED_PIN 14
DHT dht(DHTPIN, DHT11);
WiFiClient netClient;
PubSubClient mqtt(netClient);
bool ledOn = false;
int brightness = 128;
void publishDiscovery() {
// Temperature sensor
String t = "{\"name\":\"Living Room Temperature\",";
t += "\"state_topic\":\"advaslearning/aliya/livingroom/temp\",";
t += "\"unique_id\":\"aliya-livingroom-temp\",";
t += "\"unit_of_measurement\":\"°C\",";
t += "\"device_class\":\"temperature\",";
t += "\"availability_topic\":\"advaslearning/aliya/livingroom/status\",";
t += "\"payload_available\":\"online\",";
t += "\"payload_not_available\":\"offline\",";
t += "\"device\":{\"identifiers\":[\"aliya-living-room-esp\"],";
t += "\"name\":\"Aliya Living Room ESP\",\"manufacturer\":\"Advaslearning\",\"model\":\"ESP8266\"}}";
mqtt.publish("homeassistant/sensor/aliya-livingroom-temp/config", t.c_str(), true);
// Humidity sensor (similar, abridged)
String h = "{\"name\":\"Living Room Humidity\",";
h += "\"state_topic\":\"advaslearning/aliya/livingroom/humidity\",";
h += "\"unique_id\":\"aliya-livingroom-humidity\",";
h += "\"unit_of_measurement\":\"%\",";
h += "\"device_class\":\"humidity\",";
h += "\"availability_topic\":\"advaslearning/aliya/livingroom/status\",";
h += "\"payload_available\":\"online\",\"payload_not_available\":\"offline\",";
h += "\"device\":{\"identifiers\":[\"aliya-living-room-esp\"]}}";
mqtt.publish("homeassistant/sensor/aliya-livingroom-humidity/config", h.c_str(), true);
// Light entity
String l = "{\"name\":\"Aliya Desk Lamp\",";
l += "\"command_topic\":\"advaslearning/aliya/lamp/cmd\",";
l += "\"state_topic\":\"advaslearning/aliya/lamp/state\",";
l += "\"brightness_command_topic\":\"advaslearning/aliya/lamp/brightness\",";
l += "\"brightness_state_topic\":\"advaslearning/aliya/lamp/brightness/state\",";
l += "\"unique_id\":\"aliya-desk-lamp\",";
l += "\"availability_topic\":\"advaslearning/aliya/livingroom/status\",";
l += "\"payload_available\":\"online\",\"payload_not_available\":\"offline\",";
l += "\"device\":{\"identifiers\":[\"aliya-living-room-esp\"]}}";
mqtt.publish("homeassistant/light/aliya-desk-lamp/config", l.c_str(), true);
}
void onMessage(char* topic, byte* payload, unsigned int length) {
char msg[32]; unsigned int n = length < sizeof(msg) - 1 ? length : sizeof(msg) - 1;
memcpy(msg, payload, n); msg[n] = 0;
if (strcmp(topic, "advaslearning/aliya/lamp/cmd") == 0) {
ledOn = (strcmp(msg, "ON") == 0);
analogWrite(LED_PIN, ledOn ? brightness : 0);
mqtt.publish("advaslearning/aliya/lamp/state", ledOn ? "ON" : "OFF", true);
}
else if (strcmp(topic, "advaslearning/aliya/lamp/brightness") == 0) {
brightness = constrain(atoi(msg), 0, 255);
if (!ledOn) ledOn = true;
analogWrite(LED_PIN, ledOn ? brightness : 0);
mqtt.publish("advaslearning/aliya/lamp/state", ledOn ? "ON" : "OFF", true);
mqtt.publish("advaslearning/aliya/lamp/brightness/state", msg, true);
}
}
bool connectMQTT() {
if (mqtt.connect(CLIENT_ID, STATUS_T, 0, true, "offline")) {
mqtt.publish(STATUS_T, "online", true);
publishDiscovery();
mqtt.subscribe("advaslearning/aliya/lamp/cmd");
mqtt.subscribe("advaslearning/aliya/lamp/brightness");
return true;
}
return false;
}
void setup() {
Serial.begin(115200);
pinMode(LED_PIN, OUTPUT);
WiFi.begin("YourNetwork", "YourPassword");
while (WiFi.status() != WL_CONNECTED) delay(500);
dht.begin();
mqtt.setServer("test.mosquitto.org", 1883); // or your local broker
mqtt.setCallback(onMessage);
}
void loop() {
if (!mqtt.connected()) { connectMQTT(); delay(500); return; }
mqtt.loop();
static unsigned long lastPub = 0;
if (millis() - lastPub >= 15000) {
lastPub = millis();
float t = dht.readTemperature();
float h = dht.readHumidity();
if (!isnan(t)) { char buf[8]; dtostrf(t, 4, 1, buf); mqtt.publish("advaslearning/aliya/livingroom/temp", buf, true); }
if (!isnan(h)) { char buf[8]; dtostrf(h, 4, 1, buf); mqtt.publish("advaslearning/aliya/livingroom/humidity", buf, true); }
}
}Verify in Home Assistant
- If HA is running and connected to the same broker, go to Settings → Devices & Services → MQTT → Devices.
- You should see "Aliya Living Room ESP" with three entities: temperature, humidity, lamp.
- The temperature shows in your dashboard with a thermometer icon and °C.
- The lamp has a switch + brightness slider; tapping them controls the ESP.
Verify without Home Assistant
In MQTT Explorer, browse to homeassistant/sensor/aliya-livingroom-temp/config. The JSON config you published should be there (retained). Same for the humidity sensor and lamp light. When you eventually run HA, it'll pick them up automatically.
Try It Yourself 15 min
Goal: Add a fourth entity: an illuminance sensor (LDR on A0). Use device_class: illuminance, units lx.
Goal: Build a binary_sensor entity for a door switch (reed switch on a digital pin). Publish "ON"/"OFF" on state changes. Use device_class: door so HA shows the right icon.
Goal: Add HA-friendly RGB light: color_mode: rgb, rgb_command_topic, etc. Wire an RGB LED. The HA UI gets a colour picker that drives the LED.
Mini-Challenge · Build the dashboard 10 min
- In Home Assistant, create a new dashboard called "Aliya Living Room".
- Add cards for: the temperature, the humidity, the lamp.
- Take a screenshot — that's your finished smart-home dashboard.
You've replaced a £30 commercial smart-home bridge + £20 smart bulb + £15 smart sensor with one $5 ESP + open-source software.
Recap 5 min
Home Assistant + MQTT Discovery = auto-add Arduino devices to the most popular open-source smart-home platform. One JSON config message per entity, published retained, and your device is fully integrated. Cluster D starts tomorrow — long-range and industrial radios.
- Home Assistant
- Free open-source smart-home dashboard / automation engine. Runs on Raspberry Pi, NUC, Docker. Talks to ~3000 device types.
- MQTT Discovery
- Convention where devices publish their own config to a specific topic; HA auto-creates entities from it. No YAML editing.
- Entity
- The Home Assistant abstraction for "a thing that has a state" (sensor, switch, light, cover, etc.).
- Device class
- Hint to HA about what kind of measurement (temperature, humidity, illuminance, door...). Determines icon and default unit handling.
- Availability topic
- The MQTT topic Home Assistant watches to know if a device is online or offline. Connected to your LWT.
- Device grouping
- Linking multiple entities to one logical device via the
device.identifiersfield in their discovery config. - command_topic / state_topic
- Pair of topics for actuators: command_topic = HA sends to it; state_topic = device confirms state. Same two-topic pattern from L04-15.
Homework 5 min
- If you have Home Assistant: confirm the three entities appear and work.
- If not: confirm the discovery messages exist in MQTT Explorer with retain flag.
- Read ahead to ARD-L04-17 (LoRa Fundamentals). Bring a LoRa breakout (RFM95 / RA-02 module) if you have one — otherwise we'll do theory.