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.
Basic 5 min
Goal: Add a fourth entity: an illuminance sensor (LDR on A0). Use device_class: illuminance, units lx.
Challenge 1 5 min
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.
Challenge 2 5 min
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.
Challenge 3 · Fix the discovery Home Assistant ignores 10 min
A classmate's bedroom node should make two entities. After a Home Assistant restart, neither works. The loop publishes readings to advaslearning/you/bedroom/temp and advaslearning/you/bedroom/humidity. There are four faults.
void publishDiscovery() {
String t = "{\"name\":\"Bedroom Temperature\",";
t += "\"state_topic\":\"advaslearning/you/bedroom/temperature\",";
t += "\"unique_id\":\"you-bedroom\",";
t += "\"unit_of_measurement\":\"°C\",";
t += "\"device_class\":\"temperature\"}";
mqtt.publish("homeassistant/sensor/you-bedroom-temp/config", t.c_str());
String h = "{\"name\":\"Bedroom Humidity\",";
h += "\"state_topic\":\"advaslearning/you/bedroom/humidity\",";
h += "\"unique_id\":\"you-bedroom\",";
h += "\"unit_of_measurement\":\"%\"";
h += "\"device_class\":\"humidity\"}";
mqtt.publish("homeassistant/sensor/you-bedroom-humidity/config", h.c_str(), true);
}Fix the function, upload it, and check both config topics in MQTT Explorer. You do not need Home Assistant.
It works if both configs are retained and valid JSON in MQTT Explorer. Each state_topic must match a topic that gets readings.
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.
Extra Mission 5 min
Part 1 — Design a Home Assistant node for a room
Pick a room. Decide what Home Assistant should know or control there, such as a door sensor or a night light. Design the node on paper.
Your design must include:
- Every entity: its component type,
device_classand unit. - The config topic and the state (or command) topic for each.
- A
unique_idfor each, and the shareddeviceblock. - The boot order of the publishes.
Part 2 — Make it
Build at least two of your entities on the ESP. Publish their discovery configs (retained) and live states. Check them in Home Assistant, or in MQTT Explorer if you do not have it.
Bring back next class: the sketch, and a screenshot of the entities in Home Assistant or the configs in MQTT Explorer. For ARD-L04-17, bring a LoRa module if you have one.