Chapters

14 Lab 3: MQTT Home Automation

mqtt
implementation
labs

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
Interactive Simulator: MQTT Subscriber (Light Control)

What This Simulates: ESP32 subscribing to MQTT commands and controlling an LED based on received messages - the other half of publish-subscribe.

Key Points

MQTT Subscription Flow:

Build the decision in sequence. Begin with ESP32 subscribes to home/living_room/light/command. Then consider Broker forwards matching messages to this client. Then consider mqtt_callback() function executes when message arrives. Then consider LED state changes based on “ON”/“OFF” payload. Close by considering ESP32 publishes new state to home/living_room/light/state (retained).

Real-World: Smart home lights, automated curtains, door locks, HVAC controls

Experiment: Add dimming levels (0-100), multiple rooms, motion sensor integration

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).

Broker BexCheckpoint: 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.

Try It: Motion-to-Light Automation Timing

Experiment with motion sensor timing parameters to understand how AUTO_OFF_DELAY, PIR hold time, and sensor polling rate affect automation behavior and power consumption.


Lab 3 handoff

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.