14 Lab 3: MQTT Home Automation
This is the third bounded MQTT lab. Keep the publisher and dashboard evidence from Labs 1-2 nearby: this lab adds an actuator and asks whether the complete event-to-command path behaves safely.
14.1 Lab 3: MQTT Home Automation - Lights and Motion
Objective: Build a complete home automation system with motion detection and automated lighting control.
Materials:
- 2x ESP32 boards (one for motion sensor, one for light control)
- PIR motion sensor (HC-SR501)
- LED (or relay module for real lights)
- 220 ohm resistor
- Breadboard and wires
Circuit 1 - Motion Sensor (ESP32 #1):
PIR Sensor ESP32
---------- -----
VCC ------> 5V
OUT ------> GPIO 13
GND ------> GND
Circuit 2 - Light Control (ESP32 #2):
ESP32 LED
----- ---
GPIO 2 ----> LED Anode (through 220 ohm resistor)
GND ----> LED Cathode
Code for Motion Sensor (ESP32 #1):
#include <WiFi.h>
#include <PubSubClient.h>
const char* ssid = "YOUR_WIFI_SSID";
const char* password = "YOUR_WIFI_PASSWORD";
const char* mqtt_server = "test.mosquitto.org";
WiFiClient espClient;
PubSubClient client(espClient);
#define PIR_PIN 13
#define ROOM_ID "living_room"
char motion_topic[50];
char light_command_topic[50];
bool last_motion_state = false;
unsigned long motion_start_time = 0;
const unsigned long AUTO_OFF_DELAY = 30000; // 30 seconds
void setup() {
Serial.begin(115200);
pinMode(PIR_PIN, INPUT);
sprintf(motion_topic, "home/%s/motion", ROOM_ID);
sprintf(light_command_topic, "home/%s/light/command", ROOM_ID);
WiFi.begin(ssid, password);
while (WiFi.status() != WL_CONNECTED) {
delay(500);
Serial.print(".");
}
Serial.println("\nWi-Fi connected");
client.setServer(mqtt_server, 1883);
reconnect();
}
void reconnect() {
while (!client.connected()) {
Serial.print("Connecting to MQTT...");
if (client.connect("ESP32_MotionSensor")) {
Serial.println(" Connected");
} else {
delay(5000);
}
}
}
void loop() {
if (!client.connected()) {
reconnect();
}
client.loop();
bool motion_detected = digitalRead(PIR_PIN) == HIGH;
// Motion started
if (motion_detected && !last_motion_state) {
Serial.println("Motion detected!");
client.publish(motion_topic, "true", true);
// Turn on light
client.publish(light_command_topic, "ON");
motion_start_time = millis();
last_motion_state = true;
}
// Motion stopped
if (!motion_detected && last_motion_state) {
Serial.println(" Motion cleared");
client.publish(motion_topic, "false", true);
last_motion_state = false;
}
// Auto turn off light after delay
if (!motion_detected && (millis() - motion_start_time > AUTO_OFF_DELAY)) {
client.publish(light_command_topic, "OFF");
Serial.println("Auto turning off light");
motion_start_time = millis() + 1000000; // Prevent repeated commands
}
delay(200);
}
Code for Light Control (ESP32 #2):
#include <WiFi.h>
#include <PubSubClient.h>
const char* ssid = "YOUR_WIFI_SSID";
const char* password = "YOUR_WIFI_PASSWORD";
const char* mqtt_server = "test.mosquitto.org";
WiFiClient espClient;
PubSubClient client(espClient);
#define LED_PIN 2
#define ROOM_ID "living_room"
char light_command_topic[50];
char light_state_topic[50];
void mqtt_callback(char* topic, byte* payload, unsigned int length) {
String message = "";
for (int i = 0; i < length; i++) {
message += (char)payload[i];
}
Serial.print("Received command: ");
Serial.println(message);
if (message == "ON") {
digitalWrite(LED_PIN, HIGH);
client.publish(light_state_topic, "ON", true);
Serial.println("Light turned ON");
} else if (message == "OFF") {
digitalWrite(LED_PIN, LOW);
client.publish(light_state_topic, "OFF", true);
Serial.println("Light turned OFF");
}
}
void setup() {
Serial.begin(115200);
pinMode(LED_PIN, OUTPUT);
digitalWrite(LED_PIN, LOW);
sprintf(light_command_topic, "home/%s/light/command", ROOM_ID);
sprintf(light_state_topic, "home/%s/light/state", ROOM_ID);
WiFi.begin(ssid, password);
while (WiFi.status() != WL_CONNECTED) {
delay(500);
Serial.print(".");
}
Serial.println("\nWi-Fi connected");
client.setServer(mqtt_server, 1883);
client.setCallback(mqtt_callback);
reconnect();
}
void reconnect() {
while (!client.connected()) {
Serial.print("Connecting to MQTT...");
if (client.connect("ESP32_LightControl")) {
Serial.println(" Connected");
// Subscribe to light commands
client.subscribe(light_command_topic);
Serial.print("Subscribed to: ");
Serial.println(light_command_topic);
// Publish initial state
client.publish(light_state_topic, "OFF", true);
} else {
delay(5000);
}
}
}
void loop() {
if (!client.connected()) {
reconnect();
}
client.loop();
}
Expected Serial Output (Motion Sensor):
Wi-Fi connected
Connecting to MQTT... Connected
Motion detected!
Motion cleared
Auto turning off light
Expected Serial Output (Light Control):
Wi-Fi connected
Connecting to MQTT... Connected
Subscribed to: home/living_room/light/command
Received command: ON
Light turned ON
Received command: OFF
Light turned OFF
What This Simulates: ESP32 subscribing to MQTT commands and controlling an LED based on received messages - the other half of publish-subscribe.
Learning Outcomes:
Build the decision in sequence. Begin with Build multi-device MQTT communication. Then consider Implement automation logic with sensors and actuators. Then consider Use retained messages for state synchronization. Then consider Create topic naming conventions for home automation. Close by considering Handle timing and auto-off functionality.
Challenges:
Build the decision in sequence. Begin with Add manual control via MQTT (smartphone app or Node-RED). Then consider Implement brightness control with PWM. Then consider Add multiple rooms with independent control. Close by considering Create schedules (morning/evening modes).
Checkpoint: Multi-Device Automation
You now know:
Read the checkpoint as one evidence chain. Begin with The home automation lab splits motion sensing on GPIO 13 from light control on GPIO 2, so publish and subscribe roles are visible on separate ESP32 boards. Then connect The topic plan keeps motion state, light commands, and light state separate instead of mixing sensor evidence with actuator commands. Finish with The default AUTO_OFF_DELAY is 30 seconds, so PIR hold time, polling rate, and command timing all become part of the acceptance record.
Keep the motion timestamp, command timestamp, actuator response, and auto-off result together. Continue to Lab 4: Secure MQTT Broker and Reliability to replace the public exercise boundary with authenticated, encrypted, and permissioned broker behaviour.
