Prefer to watch?See the full build, wiring, and testing in the video.
Watch the full video ↓

Introduction


If you've ever wanted to build your own smart home or IoT system, you've probably noticed one major dependency: the cloud.

Many commercial IoT devices send their data through third-party cloud servers. That can work well, but it also means your devices depend on an internet connection and an external service.

What if you could keep your IoT communication completely inside your own network?

Introduction

In this project, we'll turn a Raspberry Pi into a local MQTT server using the Mosquitto MQTT Broker. Then we'll connect an ESP32 to the broker and use MQTT to send data and control an LED.

We'll also test the system from both a computer using MQTTX and an Android smartphone using MQTT Bhai.

The result is a simple but powerful local IoT architecture:

IoT architecture

By the end of this tutorial, you'll have a working local MQTT communication system that can be expanded into a complete home automation or IoT platform.

 

🎥 Watch the Full Video

I've also covered the complete setup step-by-step in the video, including installing Mosquitto, configuring the Raspberry Pi, connecting the ESP32, testing MQTTX, and controlling the ESP32 using MQTT Bhai.

🚀 What We Are Building

The Raspberry Pi will act as our MQTT Broker.

The ESP32 will connect to the Raspberry Pi over Wi-Fi and act as an MQTT client.

The ESP32 will:

  • Connect to the local Wi-Fi network
  • Connect to the Mosquitto MQTT broker
  • Publish telemetry data
  • Subscribe to an MQTT command topic
  • Receive on and off commands
  • Control its onboard LED

We can then use either:

  • MQTTX from a computer
  • MQTT Bhai from an Android smartphone
  • MQTT command-line tools from the Raspberry Pi

to communicate with the ESP32.

This creates a simple two-way IoT communication system without requiring an external cloud service.

💡 How MQTT Works

Before building the project, let's quickly understand the basic MQTT architecture.

⭐ Project Features

With this project, you'll learn how to:

  • Set up a local MQTT broker on Raspberry Pi
  • Install and configure Mosquitto
  • Understand MQTT Publisher and Subscriber concepts
  • Connect ESP32 to a local MQTT broker
  • Publish JSON telemetry from ESP32
  • Subscribe to MQTT command topics
  • Control an ESP32 LED remotely
  • Test MQTT communication from an Android phone using MQTT Bhai
  • Build an IoT system without relying on cloud services

🛠️ What You'll Need

Hardware

ComponentQuantityPurchase Link
Raspberry Pi 3 / 4 / 51Amazon · AliExpress
ESP32 Development Board1Amazon · AliExpress
LED1Amazon
330 Ω Resistor1Amazon
Jumper WiresAs requiredAmazon
Computer / Laptop1
Android SmartphoneOptional

💻 Software Requirements

You'll need the following software:

Raspberry Pi

  • Raspberry Pi OS
  • Mosquitto MQTT Broker
  • Mosquitto MQTT Clients

Computer

  • Arduino IDE

Android

  • MQTT Bhai

📱 MQTT Bhai — Control Your IoT Devices from Android

For this project, we're also going to use MQTT Bhai, an Android MQTT client developed by IoT Bhai.

MQTT Bhai allows you to connect to MQTT brokers, manage multiple broker profiles, subscribe to topics, publish messages, and monitor MQTT traffic directly from your smartphone. It also supports standard TCP and secure SSL/TLS MQTT connections and QoS settings.

Download MQTT Bhai App from Google Play

🔗 Prerequisites

Before starting this tutorial, you should have:

Step 1: Install Mosquitto on Raspberry Pi

The first step is to install the Mosquitto MQTT broker on the Raspberry Pi.

Open a terminal on the Raspberry Pi or connect to it using SSH.

First, update the package list:

sudo apt update

Now install Mosquitto and the MQTT command-line clients:

sudo apt install mosquitto mosquitto-clients -y

After installation, check the Mosquitto service:

sudo systemctl status mosquitto.service

If everything is working correctly, you should see that the service is running.

Step 2: Configure the MQTT Broker

Now we need to configure Mosquitto so devices on our local network can connect to it.

Open the Mosquitto configuration file:

sudo nano /etc/mosq
uitto/mosquitto.conf

Depending on your Mosquitto installation, you can also create a configuration file inside:

/etc/mosquitto/conf.d/

For example:

sudo nano /etc/mosquitto/conf.d/default.conf

Add:

listener 1883
allow_anonymous true

Let's understand these settings.

listener 1883

Port 1883 is the standard TCP port commonly used for MQTT connections without TLS.

This allows devices on your local network to connect to the broker.

allow_anonymous true

This allows clients to connect without providing a username and password.

This is convenient for a simple local learning project.

However, do not use anonymous access on an MQTT broker exposed directly to the internet.

For a production system, you should configure authentication, access control, and preferably TLS encryption.

Save the configuration and restart Mosquitto:

sudo systemctl restart mosquitto.service

Step 3: Enable Mosquitto at Boot

We want Mosquitto to start automatically whenever the Raspberry Pi boots.

Run:

sudo systemctl enable mosquitto.service

You can verify the service again:

sudo systemctl status mosquitto.service

Now your Raspberry Pi is ready to act as an MQTT broker.

Step 4: Find Your Raspberry Pi IP Address

The ESP32 needs to know where the MQTT broker is located.

We therefore need the Raspberry Pi's local IP address.

Run:

ifconfig

Look for the IP address assigned to your network interface.

For example:

192.168.1.50

Your IP address will probably be different.

We'll use:

192.168.1.50

as an example throughout this tutorial.

Important: Replace 192.168.1.50 with the actual IP address of your Raspberry Pi.

Step 5: Connect MQTT Bhai to the Raspberry Pi

Now let's perform the same MQTT testing from an Android phone.

Install MQTT Bhai from Google Play:

MQTT Bhai — Google Play

Open the application and create a new broker connection.

Enter your Raspberry Pi's local network details.

For example:

Broker:
192.168.1.50

Port:
1883

Because we're using an unauthenticated local broker for this tutorial, you don't need to enter a username or password.

Save the connection and connect.

Your phone must be connected to the same local network as the Raspberry Pi.

Once connected, subscribe to:

test/topic

MQTT Bhai is now waiting for messages published to this topic.

Let's send a message from the Raspberry Pi.

Publish a Message from the Raspberry Pi

Open a terminal on the Raspberry Pi and run:

mosquitto_pub -h localhost -t test/topic -m "Hello from Raspberry Pi"

Here:

  • -h localhost tells the MQTT client to connect to the Mosquitto broker running on the Raspberry Pi.
  • -t test/topic specifies the MQTT topic.
  • -m "Hello from Raspberry Pi" is the message we want to publish.

As soon as you execute the command, MQTT Bhai should receive the message:

Hello from Raspberry Pi

This is a simple but important MQTT test. The Raspberry Pi is acting as the publisher, Mosquitto is acting as the broker, and MQTT Bhai is acting as the subscriber.

Once this works, we know that MQTT Bhai can successfully communicate with the Mosquitto broker over the local network.

Step 6: Install the PubSubClient Library

Open Arduino IDE.

Go to:

Tools → Manage Libraries

Search for:

PubSubClient

Install the PubSubClient library.

This library provides the MQTT client functionality we'll use on the ESP32.

 

Step 7: Program the ESP32

/*
 * PROFESSIONAL MQTT EXPERIMENT - ESP32
 * * Features:
 * - Non-blocking Architecture (No delay())
 * - Automatic Reconnection (WiFi & MQTT)
 * - LWT (Last Will & Testament) for State Monitoring
 * - JSON Data Serialization
 * - Remote Command Handling
 */

#include <WiFi.h>
#include <PubSubClient.h>
#include <ArduinoJson.h>

// ==========================================
// 1. CONFIGURATION (Edit these)
// ==========================================
const char* ssid = "";
const char* password = "";

// MQTT Broker Settings (Using public HiveMQ for demo, change for production)
const char* mqtt_server = "mqtt.iotbhai.io";
const int mqtt_port = 1883; 
const char* mqtt_user = ""; // Leave blank for public brokers
const char* mqtt_pass = "";

// Unique Device ID (Must be unique on the broker)
const char* device_id = "ESP32_Pro_Unit_01"; 

// Topics (Structure: device_type/device_id/function)
const char* topic_telemetry = "esp32/unit01/data";   // Where we send sensor data
const char* topic_command   = "esp32/unit01/cmd";    // Where we listen for commands
const char* topic_status    = "esp32/unit01/status"; // LWT (Online/Offline)

// ==========================================
// 2. GLOBAL OBJECTS & VARIABLES
// ==========================================
WiFiClient espClient;
PubSubClient client(espClient);

// Timers for non-blocking delays
unsigned long lastMsgTime = 0;
const long interval = 5000; // Send data every 5 seconds

#define LED_PIN 2 // Onboard LED

// ==========================================
// 3. SETUP WIFI
// ==========================================
void setup_wifi() {
  delay(10);
  Serial.println();
  Serial.print("Connecting to WiFi: ");
  Serial.println(ssid);

  WiFi.mode(WIFI_STA);
  WiFi.begin(ssid, password);

  while (WiFi.status() != WL_CONNECTED) {
    delay(500);
    Serial.print(".");
  }

  Serial.println("");
  Serial.println("WiFi connected");
  Serial.print("IP address: ");
  Serial.println(WiFi.localIP());
}

// ==========================================
// 4. CALLBACK (Handle Incoming Messages)
// ==========================================
void callback(char* topic, byte* payload, unsigned int length) {
  Serial.print("Message arrived [");
  Serial.print(topic);
  Serial.print("] ");

  // Convert payload to string for easier handling
  String message;
  for (int i = 0; i < length; i++) {
    message += (char)payload[i];
  }
  Serial.println(message);

  // -- Command Logic --
  // Example: If we receive "ON", turn on LED
  if (String(topic) == topic_command) {
    if (message == "ON") {
      digitalWrite(LED_PIN, HIGH);
      // Feedback: Publish new state immediately
      client.publish(topic_telemetry, "{\"led\": \"ON\"}"); 
    } else if (message == "OFF") {
      digitalWrite(LED_PIN, LOW);
      client.publish(topic_telemetry, "{\"led\": \"OFF\"}");
    }
  }
}

// ==========================================
// 5. RECONNECT (The Engine Room)
// ==========================================
void reconnect() {
  // Loop until we're reconnected
  while (!client.connected()) {
    Serial.print("Attempting MQTT connection...");
    
    // --- LWT CONFIGURATION ---
    // define Last Will: Topic, QoS, Retain, Message
    // If this ESP32 dies, the Broker will post "offline" to the status topic automatically.
    
    if (client.connect(device_id, mqtt_user, mqtt_pass, topic_status, 1, true, "offline")) {
      Serial.println("connected");
      
      // Once connected, publish an announcement that we are alive (Retained = true)
      client.publish(topic_status, "online", true);
      
      // Resubscribe to command topics
      client.subscribe(topic_command);
    } else {
      Serial.print("failed, rc=");
      Serial.print(client.state());
      Serial.println(" try again in 5 seconds");
      delay(5000); // Blocking delay here is acceptable as we can't operate without connection
    }
  }
}

// ==========================================
// 6. MAIN SETUP
// ==========================================
void setup() {
  Serial.begin(115200);
  pinMode(LED_PIN, OUTPUT);
  
  setup_wifi();
  
  client.setServer(mqtt_server, mqtt_port);
  client.setCallback(callback);
}

// ==========================================
// 7. MAIN LOOP
// ==========================================
void loop() {
  // Ensure we stay connected
  if (!client.connected()) {
    reconnect();
  }
  client.loop(); // Keep MQTT alive

  // --- Non-Blocking Timer for Telemetry ---
  unsigned long now = millis();
  if (now - lastMsgTime > interval) {
    lastMsgTime = now;
    
    // Create a JSON Document
    JsonDocument doc; // ArduinoJson v7
    doc["device"] = device_id;
    doc["uptime"] = millis() / 1000;
    doc["wifi_rssi"] = WiFi.RSSI();
    
    // Add dynamic data (simulated sensor)
    doc["temp"] = random(20, 30); 

    // Serialize JSON to String
    char buffer[256];
    serializeJson(doc, buffer);

    // Publish to MQTT
    Serial.print("Publishing data: ");
    Serial.println(buffer);
    client.publish(topic_telemetry, buffer);
  }
}

Step 8: Configure the ESP32 Code

Before uploading the code, change:

const char* ssid = "YOUR_WIFI_SSID";
const char* password = "YOUR_WIFI_PASSWORD";

to your actual Wi-Fi credentials.

For example:

const char* ssid = "MyWiFi";
const char* password = "MyPassword";

Then change:

const char* mqtt_server = "192.168.1.50";

to the IP address of your Raspberry Pi.

For example:

const char* mqtt_server = "192.168.1.50";

Make sure the ESP32 and Raspberry Pi are connected to the same local network.

 

Step 9: Upload the ESP32 Code

Select your ESP32 board from:

Tools → Board

Then select the appropriate COM port.

Upload the sketch.

After uploading, open the Serial Monitor.

Set the baud rate to:

115200

You should see the ESP32 connecting to Wi-Fi.

Then it will attempt to connect to the MQTT broker.

If everything is configured correctly, you'll see:

WiFi connected!
Attempting MQTT connection...connected

The ESP32 will then publish telemetry every five seconds.

For example:

Published: {"temperature": 24}

Step 10: Receive ESP32 Data Using MQTT Bhai

Now let's do the same thing from your Android phone.

Open MQTT Bhai and connect to the Raspberry Pi broker.

Subscribe to:

esp32/unit01/data

You should now see the ESP32 telemetry arriving directly on your smartphone.

📱 Control the ESP32 from MQTT Bhai

This is where MQTT Bhai becomes particularly useful.

From your smartphone:

Topic

esp32/unit01/cmd

Payload

on

The ESP32 LED should turn ON.

Then publish:

off

The LED should turn OFF.

You can now control physical hardware from your smartphone through your own Raspberry Pi MQTT server.

🛠️ Troubleshooting

ESP32 Cannot Connect to MQTT

Check the following:

  • Raspberry Pi and ESP32 are on the same network
  • Raspberry Pi IP address is correct
  • Mosquitto is running
  • MQTT port is 1883
  • The Raspberry Pi firewall isn't blocking port 1883
  • Your Mosquitto configuration is correct

Check the broker:

sudo systemctl status mosquitto

MQTT Bhai Cannot Connect

Make sure:

  1. Your Android phone is connected to the same Wi-Fi network as the Raspberry Pi.
  2. The Raspberry Pi IP address is correct.
  3. The MQTT port is 1883.
  4. Mosquitto is running.
  5. The broker allows the connection.

For this tutorial, the connection should look like:

Host: 192.168.1.50
Port: 1883

🔐 A Note About MQTT Security

In this tutorial, we used:

allow_anonymous true

because we're building a simple local learning environment.

This is convenient, but it should not be considered a production security configuration.

 

🎯 Conclusion

In this project, we turned a Raspberry Pi into a local MQTT server using the Mosquitto MQTT Broker and connected an ESP32 to it over Wi-Fi.