Practice Lab Guide

Project 5: MQTT-Based Smart Device Network

Deploy an end-to-end publish/subscribe network connecting an ESP32 to a local Mosquitto broker using QoS levels and LWT status flags.
Domain
MQTT Networking
Difficulty
⭐⭐⭐☆☆ (Intermediate)
Course Module
IoT Networking
Deliverables
ESP32 Firmware, Python Controller Script
1. MQTT Broker & Client Topology

To enable bi-directional data flow without direct socket management, we use the lightweight MQTT protocol. The architecture below details the layout of our smart office pilot. The ESP32 edge node connects via Wi-Fi to the local Mosquitto broker running on the VirtualBox guest VM. It publishes temperature and light values to distinct sub-topics (`office/sensor1/temp` and `office/sensor1/light`) at QoS 0. It also subscribes to a command topic (`office/actuator/relay`) at QoS 1 to control a relay. A Python controller monitors these channels and publishes commands in response.

ESP32 Telemetry Client Publishes: office/sensor1/temp [QoS 0] office/sensor1/light [QoS 0] Subscribes: office/actuator/relay [QoS 1] LWT Topic: office/sensor1/status ("offline") MOSQUITTO BROKER VirtualBox VM Active Routing Table /temp -> /light /relay <- commands Python Controller Subscribes: office/sensor1/temp office/sensor1/light Publishes: office/actuator/relay Runs inside VirtualBox VM
2. Part 1: Step-by-Step Individual Installation Commands & Library Configuration

Follow these detailed steps to load the required C++ and Python MQTT libraries, configure your connection credentials, and build the command-line publisher scripts.

STEP 1

Install the PubSubClient Library in the Arduino IDE

Open the Library Manager inside the Arduino IDE to download Nick O'Leary's MQTT library, which provides communication functions for the ESP32.

Arduino IDE GUI Steps: 1. Open the Arduino IDE. 2. In the left vertical menu, click the Library Manager icon (stacked books) or go to "Tools" -> "Manage Libraries...". 3. In the search input field, type "PubSubClient". 4. Locate the package titled "PubSubClient" by Nick O'Leary. 5. Click the "Install" button. Wait for the installation to complete.
We download Nick O'Leary's `PubSubClient` library to access standard API bindings for managing broker connection handshakes, publishing messages, and processing incoming subscriptions.
STEP 2

Launch Terminal and Create Network Project Folder

Boot up your VirtualBox Ubuntu machine. Open the terminal (Ctrl+Alt+T) and create a directory to organize your C++ and Python source files.

ubuntu@iot-vm:~$ mkdir -p ~/workspace/mqtt_network && cd ~/workspace/mqtt_network
We run `mkdir -p` and `cd` to generate and enter a dedicated workspace directory named `~/workspace/mqtt_network` to isolate our Python controller script.
STEP 3

Install Paho-MQTT Library inside the virtual environment

Create an isolated virtual environment and install the Eclipse Paho MQTT client library for Python.

ubuntu@iot-vm:~/workspace/mqtt_network$ python3 -m venv env && source env/bin/activate && pip install paho-mqtt
We initialize the Python virtual environment and install `paho-mqtt` using pip. This library provides Python bindings for subscribing to telemetry topics and publishing actuator commands.
STEP 4

Create the Python Controller Script

Open the graphical text editor in the background to write the serial listener script.

(env) ubuntu@iot-vm:~/workspace/mqtt_network$ gedit controller.py &
We invoke `gedit` alongside the `&` symbol to open the graphical text editor in a background thread, leaving our terminal terminal prompt active for running commands.
STEP 5

Verify the Local IP Address of the VM

Locate the IP address of your virtual machine's bridged interface, which you will use in the ESP32 code configuration.

(env) ubuntu@iot-vm:~/workspace/mqtt_network$ ip a show dev enp0s3 | grep inet
We run `ip a` to check the IP address of interface `enp0s3`, which represents the VM's bridged connection on the local network.
STEP 6

Configure and Upload the ESP32 Firmware

Edit the firmware variables in the Arduino IDE to match your home Wi-Fi SSID, password, and the local IP address of your virtual machine.

Firmware Settings (Arduino IDE): 1. Locate lines 10-12 in your sketch editor. 2. Replace "YOUR_WIFI_SSID" with your actual local router name. 3. Replace "YOUR_WIFI_PASSWORD" with your actual Wi-Fi password. 4. Replace the "mqtt_server" IP string with your VM's bridged IP address (found in Step 5). 5. Click the circular Upload arrow icon in the toolbar to upload the code to the ESP32.
We configure Wi-Fi credentials and the broker IP in the firmware to ensure the ESP32 can connect to your local network and target the VM's Mosquitto broker on port 1883.
STEP 7

Run the Python Controller Script

Run the controller script inside the VM terminal to start monitoring telemetry data and commanding the actuators.

(env) ubuntu@iot-vm:~/workspace/mqtt_network$ python3 controller.py
We execute the Python controller script, starting the loop that subscribes to sensor topics and publishes "ON" or "OFF" commands to the relay topic in response to temperature changes.
3. Automated Telemetry & Control Loop Pipeline

The system operates in a continuous control loop: the ESP32 publishes sensor readings to the broker, the Python controller processes the data, and sends back commands to trigger the relay. The workflow below maps this sequence.

1. ESP32 Publishes office/sensor1/temp office/sensor1/light QoS 0 (Telemetry) 2. Mosquitto Broker Receives packets Matches topics 3. Python Logic Subscribes to sensor Checks Temperature Publishes to /relay 4. ESP32 Actuator Drive Subscribes to /relay QoS 1 (Guaranteed) digitalWrite(12, HIGH/LOW)
4. Part 2: Complete Codebases & Line-by-Line Breakdowns

Below is the complete C++ firmware code for the ESP32 node, followed by the Python script to run on your VirtualBox VM to complete the control loop.

Asset 1: ESP32 MQTT Client Firmware (`mqtt_client.ino`)

Line-by-Line Code Breakdown

// Practice Project 5: ESP32 MQTT Client Firmware Code #include <WiFi.h> #include <PubSubClient.h> // Wi-Fi and Broker Connection Constants const char* ssid = "YOUR_WIFI_SSID"; const char* password = "YOUR_WIFI_PASSWORD"; const char* mqtt_server = "192.168.1.120"; // Replace with your VM's Bridged IP Address const int RELAY_PIN = 12; // GPIO Pin connected to the actuator relay WiFiClient espClient; PubSubClient client(espClient); unsigned long lastMsgTime = 0; float dummyTemp = 24.0; // Simulated temperature sensor reading int dummyLight = 450; // Simulated LDR analog light reading void setup_wifi() { delay(10); Serial.print("Connecting to Wi-Fi SSID: "); Serial.println(ssid); WiFi.begin(ssid, password); while (WiFi.status() != WL_CONNECTED) { delay(500); Serial.print("."); } Serial.println("\nWi-Fi connection established. IP Address: "); Serial.println(WiFi.localIP()); } void callback(char* topic, byte* payload, unsigned int length) { Serial.print("Message received on topic ["); Serial.print(topic); Serial.print("] Payload: "); String message; for (int i = 0; i < length; i++) { message += (char)payload[i]; } Serial.println(message); // Trigger Relay state in response to commands if (message == "ON") { digitalWrite(RELAY_PIN, HIGH); // Turn on Relay } else if (message == "OFF") { digitalWrite(RELAY_PIN, LOW); // Turn off Relay } } void reconnect() { while (!client.connected()) { Serial.print("Connecting to local MQTT Broker..."); // Connect with Last Will and Testament configured (LWT) if (client.connect("ESP32_SensorNode", "office/sensor1/status", 0, true, "offline")) { Serial.println("Connected."); // Publish status and subscribe to relay commands client.publish("office/sensor1/status", "online", true); client.subscribe("office/actuator/relay", 1); // Subscribe with QoS level 1 } else { Serial.print("Connection failed, rc="); Serial.print(client.state()); Serial.println(" Waiting 5 seconds before retrying..."); delay(5000); } } } void setup() { pinMode(RELAY_PIN, OUTPUT); digitalWrite(RELAY_PIN, LOW); Serial.begin(115200); setup_wifi(); client.setServer(mqtt_server, 1883); client.setCallback(callback); } void loop() { if (!client.connected()) { reconnect(); } client.loop(); unsigned long now = millis(); if (now - lastMsgTime > 5000) { // Publish telemetry every 5 seconds lastMsgTime = now; // Generate simulated variations dummyTemp += ((float)random(-50, 50) / 100.0); dummyLight += random(-20, 20); String tempPayload = String(dummyTemp, 2); String lightPayload = String(dummyLight); client.publish("office/sensor1/temp", tempPayload.c_str()); client.publish("office/sensor1/light", lightPayload.c_str()); Serial.println("Telemetry data published successfully."); } }

Asset 2: Python Controller Script (`controller.py`)

Line-by-Line Code Breakdown

# Practice Project 5: Python Network Controller Script import paho.mqtt.client as mqtt import time # Connection Callback def on_connect(client, userdata, flags, rc): print(f"Connected to local broker with status code: {rc}") # Subscribe to both sensor topics using single-level wildcards client.subscribe("office/sensor1/+") print("Subscribed to topic: office/sensor1/+") # Message Processing Callback def on_message(client, userdata, msg): topic = msg.topic payload = msg.payload.decode('utf-8') print(f"[MQTT IN] {topic} : {payload}") # Check temperature threshold if "temp" in topic: try: temp_val = float(payload) # Decision logic: if temperature exceeds 26.0°C, turn relay ON if temp_val > 26.0: print(" -> Temperature high! Sending ON command to relay...") client.publish("office/actuator/relay", "ON", qos=1) else: print(" -> Temperature normal. Sending OFF command to relay...") client.publish("office/actuator/relay", "OFF", qos=1) except ValueError: print("Error: Failed to parse float value from payload.") def main(): broker_ip = "127.0.0.1" // Runs locally on the VM port = 1883 client = mqtt.Client() client.on_connect = on_connect client.on_message = on_message print(f"Attempting connection to local MQTT broker at {broker_ip}...") client.connect(broker_ip, port, 60) # Start block network handler loop client.loop_forever() if __name__ == "__main__": main()
5. Deliverables Summary

Created Artifacts

  • ESP32 firmware code with Wi-Fi/MQTT client configuration: mqtt_client.ino.
  • Python automation controller script: controller.py.
  • Topic design document outlining namespace structure and QoS selections.

Verification Proof

  • Screenshot of the VM terminal running controller.py, displaying incoming telemetry logs and generated command publish events.
  • A video clip showing the ESP32 serial console connection logging Wi-Fi connection states, broker connection states, and active-LOW relay transitions.
6. Closing Explanation: Why We Did This & What It Accomplishes

Architectural Intent & Operational Impact

Why We Did This

What This Accomplishes