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:
| HTTP | MQTT | |
|---|---|---|
| Pattern | Request-response (client asks, server answers) | Publish-subscribe (messages flow both ways) |
| Overhead | Large headers on every request | Minimal overhead (as little as 2 bytes) |
| Real-time | Must poll repeatedly for updates | Receives messages instantly when published |
| Connection | Opens and closes for each request | Persistent connection, stays open |
| Power use | Higher (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/statusTopics 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 Level | Name | Guarantee | Use Case |
|---|---|---|---|
| 0 | At most once | Fire and forget -- message may be lost | Frequent sensor readings (a lost one does not matter) |
| 1 | At least once | Message delivered, but may arrive twice | Important data where duplicates are acceptable |
| 2 | Exactly once | Message delivered exactly once | Critical 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:
| Broker | Address | Port | Notes |
|---|---|---|---|
| Mosquitto | test.mosquitto.org | 1883 | Run by the Eclipse Foundation, widely used for testing |
| HiveMQ | broker.hivemq.com | 1883 | Has a web-based MQTT client for testing |
| EMQX | broker.emqx.io | 1883 | Large 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
- In the Arduino IDE, go to Sketch > Include Library > Manage Libraries.
- Search for PubSubClient.
- 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
- Connects to your WiFi network.
- Connects to the public Mosquitto test broker.
- Subscribes to
iotwithesp/demo/command-- it will receive any messages published to this topic. - Every 5 seconds, publishes an incrementing counter to
iotwithesp/demo/counter. - If someone publishes "reset" to the command topic, the counter resets to 0.
Important: The
mqtt.loop()call inloop()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.
- Download and install MQTT Explorer.
- Connect to
test.mosquitto.orgon port 1883. - Navigate to
iotwithesp/demo/counterto see your ESP32's messages arriving. - 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" -vPublish 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 intuitiveUse consistent naming:
home/livingroom/temperature
home/livingroom/humidity
home/kitchen/temperature
home/kitchen/humidityWildcard subscriptions: MQTT supports two wildcards:
+matches one level:home/+/temperaturematcheshome/livingroom/temperatureandhome/kitchen/temperature#matches everything below:home/#matches all topics starting withhome/
These are used only when subscribing, not when publishing.
🔗Understanding PubSubClient Return Codes
When mqtt.connect() fails, mqtt.state() returns an error code:
| Code | Meaning |
|---|---|
| -4 | Connection timeout |
| -3 | Connection lost |
| -2 | Connect failed |
| -1 | Disconnected |
| 0 | Connected |
| 1 | Bad protocol version |
| 2 | Bad client ID |
| 3 | Server unavailable |
| 4 | Bad credentials |
| 5 | Not 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| AThe 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.