Introduction to MQTT

Learn the MQTT protocol for lightweight IoT messaging with ESP32

If WiFi and HTTP are how your ESP32 connects to the internet, MQTT is the language it should speak once it gets there. MQTT (Message Queuing Telemetry Transport) is a lightweight publish-subscribe messaging protocol designed specifically for IoT devices -- small, low-power gadgets that need to send and receive data efficiently.

In this article, you will learn what MQTT is, how it works, and how to set up your ESP32 to publish sensor data and receive commands over MQTT.

🔗Why MQTT Instead of HTTP?

You already learned how to make HTTP requests in the previous article. HTTP works, but it has drawbacks for IoT:

HTTPMQTT
PatternRequest-response (client asks, server answers)Publish-subscribe (messages flow both ways)
OverheadLarge headers on every requestMinimal overhead (as little as 2 bytes)
Real-timeMust poll repeatedly for updatesReceives messages instantly when published
ConnectionOpens and closes for each requestPersistent connection, stays open
Power useHigher (more data, more handshakes)Lower (less data, fewer handshakes)

For a sensor that sends a temperature reading every 30 seconds, MQTT uses significantly less bandwidth and battery than repeatedly making HTTP POST requests.

🔗How MQTT Works

MQTT has three core concepts:

🔗1. The Broker

The broker is a server that sits in the middle of all communication. Devices do not talk to each other directly -- they all talk to the broker, and the broker routes messages to the right recipients.

Think of it like a post office: you send a letter (message) to a topic (address), and the post office delivers it to everyone who has subscribed to that address.

🔗2. Topics

Topics are strings that organize messages into channels. They use a forward-slash hierarchy, similar to file paths:

home/livingroom/temperature
home/livingroom/humidity
home/garden/soil-moisture
devices/esp32-01/status

Topics are case-sensitive. Home/Temperature and home/temperature are different topics.

🔗3. Publish and Subscribe

  • Publish: A device sends a message to a specific topic. "The temperature is 23.5."
  • Subscribe: A device tells the broker it wants to receive all messages on a specific topic. "Tell me whenever someone publishes to home/livingroom/temperature."

A device can be both a publisher and a subscriber. Your ESP32 might publish sensor readings and subscribe to a command topic to receive instructions.

🔗Quality of Service (QoS)

MQTT defines three levels of delivery guarantee:

QoS LevelNameGuaranteeUse Case
0At most onceFire and forget -- message may be lostFrequent sensor readings (a lost one does not matter)
1At least onceMessage delivered, but may arrive twiceImportant data where duplicates are acceptable
2Exactly onceMessage delivered exactly onceCritical commands (rarely needed for ESP32)

For most ESP32 projects, QoS 0 or 1 is sufficient.

🔗Free MQTT Brokers for Testing

You do not need to set up your own broker to get started. These public brokers are free and available for testing:

BrokerAddressPortNotes
Mosquittotest.mosquitto.org1883Run by the Eclipse Foundation, widely used for testing
HiveMQbroker.hivemq.com1883Has a web-based MQTT client for testing
EMQXbroker.emqx.io1883Large public broker

Warning: Public brokers are for testing only. Anyone can subscribe to any topic, so do not send sensitive data. For real projects, set up a private broker (Mosquitto runs well on a Raspberry Pi) or use a cloud service with authentication.

🔗Setting Up the PubSubClient Library

The most popular MQTT library for Arduino / ESP32 is PubSubClient by Nick O'Leary.

🔗Installation

  1. In the Arduino IDE, go to Sketch > Include Library > Manage Libraries.
  2. Search for PubSubClient.
  3. Install the library by Nick O'Leary.

🔗Working Example: Publish and Subscribe

This complete example connects to WiFi, connects to an MQTT broker, publishes a counter value every 5 seconds, and listens for incoming messages on a command topic.

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

// WiFi credentials
const char* ssid = "YourNetworkName";
const char* password = "YourPassword";

// MQTT broker settings
const char* mqtt_server = "test.mosquitto.org";
const int mqtt_port = 1883;

// Use a unique client ID to avoid collisions on public brokers
// Change "esp32-abc123" to something unique to you
const char* mqtt_client_id = "esp32-abc123";

// Topics
const char* topic_publish = "iotwithesp/demo/counter";
const char* topic_subscribe = "iotwithesp/demo/command";

WiFiClient espClient;
PubSubClient mqtt(espClient);

unsigned long lastPublishTime = 0;
const unsigned long publishInterval = 5000;  // Publish every 5 seconds
int counter = 0;

// Called when a message arrives on a subscribed topic
void mqttCallback(char* topic, byte* payload, unsigned int length) {
  // Convert payload to a string
  String message;
  for (unsigned int i = 0; i < length; i++) {
    message += (char)payload[i];
  }

  Serial.printf("Message received on [%s]: %s\n", topic, message.c_str());

  // React to commands
  if (message == "reset") {
    counter = 0;
    Serial.println("Counter reset to 0.");
  }
}

void connectToWiFi() {
  Serial.printf("Connecting to WiFi: %s", ssid);
  WiFi.mode(WIFI_STA);
  WiFi.begin(ssid, password);

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

  Serial.printf("\nWiFi connected. IP: %s\n",
                WiFi.localIP().toString().c_str());
}

void connectToMQTT() {
  while (!mqtt.connected()) {
    Serial.printf("Connecting to MQTT broker at %s...", mqtt_server);

    if (mqtt.connect(mqtt_client_id)) {
      Serial.println(" connected!");

      // Subscribe to the command topic
      mqtt.subscribe(topic_subscribe);
      Serial.printf("Subscribed to: %s\n", topic_subscribe);
    } else {
      Serial.printf(" failed (rc=%d). Retrying in 5 seconds.\n",
                     mqtt.state());
      delay(5000);
    }
  }
}

void setup() {
  Serial.begin(115200);
  delay(1000);

  connectToWiFi();

  mqtt.setServer(mqtt_server, mqtt_port);
  mqtt.setCallback(mqttCallback);

  connectToMQTT();
}

void loop() {
  // Maintain the MQTT connection
  if (!mqtt.connected()) {
    connectToMQTT();
  }
  mqtt.loop();  // Process incoming messages and maintain the connection

  // Publish at regular intervals
  unsigned long now = millis();
  if (now - lastPublishTime >= publishInterval) {
    lastPublishTime = now;

    counter++;
    String payload = String(counter);

    mqtt.publish(topic_publish, payload.c_str());
    Serial.printf("Published to [%s]: %s\n",
                  topic_publish, payload.c_str());
  }
}

🔗What This Code Does

  1. Connects to your WiFi network.
  2. Connects to the public Mosquitto test broker.
  3. Subscribes to iotwithesp/demo/command -- it will receive any messages published to this topic.
  4. Every 5 seconds, publishes an incrementing counter to iotwithesp/demo/counter.
  5. If someone publishes "reset" to the command topic, the counter resets to 0.

Important: The mqtt.loop() call in loop() is essential. It processes incoming messages and sends keep-alive packets to the broker. Without it, your ESP32 will disconnect after about 15 seconds and will not receive any messages.

🔗Testing with an MQTT Client

To see your ESP32's messages and send commands back, you need an MQTT client on your computer or phone.

🔗Option 1: MQTT Explorer (Desktop App)

MQTT Explorer is a free, cross-platform desktop app that shows all topics on a broker in a tree view. It is the easiest way to visualize what is happening.

  1. Download and install MQTT Explorer.
  2. Connect to test.mosquitto.org on port 1883.
  3. Navigate to iotwithesp/demo/counter to see your ESP32's messages arriving.
  4. To send a command, publish "reset" to iotwithesp/demo/command.

🔗Option 2: Mosquitto Command-Line Tools

If you have Mosquitto installed on your computer (available via apt, brew, or the Mosquitto website), you can use the command-line tools:

Subscribe to see messages (in one terminal):

mosquitto_sub -h test.mosquitto.org -t "iotwithesp/demo/counter" -v

Publish a command (in another terminal):

mosquitto_pub -h test.mosquitto.org -t "iotwithesp/demo/command" -m "reset"

The -v flag shows the topic name alongside each message, which is helpful when monitoring multiple topics.

🔗Option 3: HiveMQ Web Client

HiveMQ provides a browser-based MQTT client at hivemq.com/demos/websocket-client. You can connect to a broker, subscribe to topics, and publish messages without installing anything.

🔗Publishing Sensor Data

In a real project, you would publish actual sensor readings instead of a counter. Here is how you might publish temperature data:

void publishTemperature(float temperature) {
  if (!mqtt.connected()) return;

  // Option 1: Simple string
  char payload[16];
  snprintf(payload, sizeof(payload), "%.1f", temperature);
  mqtt.publish("home/livingroom/temperature", payload);

  // Option 2: JSON format (more useful for dashboards)
  char json[64];
  snprintf(json, sizeof(json),
           "{\"temperature\":%.1f,\"unit\":\"C\"}", temperature);
  mqtt.publish("home/livingroom/sensor", json);
}

JSON is commonly used for MQTT payloads because it is easy to parse on the receiving end, whether that is a Node-RED flow, a Python script, or a web dashboard.

🔗MQTT Topic Design

Good topic design makes your system easier to manage as it grows. Here are some guidelines:

Use a hierarchy that goes from general to specific:

home/livingroom/temperature    -- good
temperature/home/livingroom    -- less intuitive

Use consistent naming:

home/livingroom/temperature
home/livingroom/humidity
home/kitchen/temperature
home/kitchen/humidity

Wildcard subscriptions: MQTT supports two wildcards:

  • + matches one level: home/+/temperature matches home/livingroom/temperature and home/kitchen/temperature
  • # matches everything below: home/# matches all topics starting with home/

These are used only when subscribing, not when publishing.

🔗Understanding PubSubClient Return Codes

When mqtt.connect() fails, mqtt.state() returns an error code:

CodeMeaning
-4Connection timeout
-3Connection lost
-2Connect failed
-1Disconnected
0Connected
1Bad protocol version
2Bad client ID
3Server unavailable
4Bad credentials
5Not authorized

These codes help you diagnose connection problems. The most common issues are network problems (codes -4 to -2), duplicate client IDs on public brokers (code 2), and authentication failures on private brokers (codes 4 and 5).

🔗Putting It All Together

Here is what a complete MQTT-based sensor system looks like:

graph LR
    A[ESP32 + Sensor] -->|publish| B[MQTT Broker]
    B -->|subscribe| C[Dashboard / App]
    C -->|publish command| B
    B -->|subscribe| A

The ESP32 publishes sensor readings to the broker. A dashboard subscribes to those readings and displays them. The dashboard can also publish commands (like "turn on the heater"), and the ESP32 subscribes to those commands and acts on them.

This pattern -- sensors publish, controllers subscribe and command -- is the foundation of most IoT systems.

🔗What's Next?

You now have all the building blocks for a complete IoT project: you can read sensors, communicate over wires and wirelessly, and exchange data using MQTT. From here, you can explore the sensor guides to learn about specific sensors, or jump into the projects section for complete, end-to-end builds that put all of these skills together.