MQTT Publish and Subscribe
MQTT Publish and Subscribe
Overview
Section titled “Overview”This section covers the bidirectional MQTT communication patterns between ESP32 and Node-RED. By the end of this section, you will be able to:
- Publish sensor data from ESP32 to MQTT topics
- Subscribe to control commands from Node-RED
- Implement bidirectional communication (ESP32 → Node-RED, Node-RED → ESP32)
- Design effective MQTT topic structures for your projects
Prerequisites
Section titled “Prerequisites”- MQTT client configured on ESP32 (01-10)
- Basic Sketch architecture (01-11)
- Node-RED MQTT nodes configured (or MQTT Explorer for testing)
Key Concepts
Section titled “Key Concepts”The Publish-Subscribe Pattern
Section titled “The Publish-Subscribe Pattern”MQTT communication has two directions:
ESP32 → MQTT Broker → Node-RED: "I have new sensor data" (ESP32 publishes, Node-RED subscribes)
Node-RED → MQTT Broker → ESP32: "Turn on the LED" (Node-RED publishes, ESP32 subscribes)This decouples the sender and receiver — they don’t need to know each other’s addresses or even be online simultaneously.
ESP32 as Publisher
Section titled “ESP32 as Publisher”The ESP32 publishes data about its state or sensor readings. The MQTT topic reflects the data content and follows a hierarchical structure:
| Topic | Payload | Frequency |
|---|---|---|
factory/room01/temperature | 25.5 | Every 30s |
factory/room01/humidity | 65% | Every 30s |
factory/device01/status | ”online”/“offline” | On change |
factory/device01/button | ”pressed” | On event |
ESP32 as Subscriber
Section titled “ESP32 as Subscriber”The ESP32 subscribes to topics that carry commands or configuration:
| Topic | Payload (Example) | Effect |
|---|---|---|
esp32/led/command | ”ON” / “OFF” | Control LED state |
esp32/sensor/interval | ”10000” | Change sensor interval |
esp32/config | {"threshold": 30} | Update configuration |
Implementation Steps
Section titled “Implementation Steps”Step 1: ESP32 Publishing to Node-RED
Section titled “Step 1: ESP32 Publishing to Node-RED”This is the most common pattern — ESP32 sends data periodically:
#include <WiFi.h>#include <PubSubClient.h>
WiFiClient wifiClient;PubSubClient client(wifiClient);
// MQTT topic for publishingconst char* dataTopic = "esp32/sensor/data";
void publishTemperature(float temperature) { char payload[50]; snprintf(payload, sizeof(payload), "{\"temperature\": %.1f}", temperature);
if (client.publish(dataTopic, payload)) { Serial.print("Published to "); Serial.print(dataTopic); Serial.print(": "); Serial.println(payload); } else { Serial.println("Publish failed!"); }}On the Node-RED side, add an MQTT In node configured to subscribe to esp32/sensor/data. Connect it to a Debug node to see the incoming data.
Step 2: ESP32 Subscribing to Node-RED Commands
Section titled “Step 2: ESP32 Subscribing to Node-RED Commands”The ESP32 subscribes to a command topic in setup():
void setup() { // ... Wi-Fi connection ... client.setServer(mqttServer, 1883); client.setCallback(callback); client.connect("esp32-device");
// Subscribe to command topics client.subscribe("esp32/control/led"); client.subscribe("esp32/control/config");
Serial.println("Subscribed to control topics");}
void callback(char* topic, byte* payload, unsigned int length) { String message = ""; for (int i = 0; i < length; i++) { message += (char)payload[i]; }
String topicStr = String(topic);
if (topicStr == "esp32/control/led") { if (message == "ON") { digitalWrite(2, HIGH); client.publish("esp32/status/led", "ON"); } else if (message == "OFF") { digitalWrite(2, LOW); client.publish("esp32/status/led", "OFF"); } }
if (topicStr == "esp32/control/config") { // Parse JSON configuration // See 01-13 for JSON parsing }}Step 3: Bidirectional Communication Example
Section titled “Step 3: Bidirectional Communication Example”A complete example with both directions:
#include <WiFi.h>#include <PubSubClient.h>
// Topicsconst char* topicFromESP = "esp32/sensor/data"; // ESP32 → Node-REDconst char* topicToESP = "esp32/control/led"; // Node-RED → ESP32const char* topicFeedback = "esp32/status/led"; // ESP32 → Node-RED (confirmation)
WiFiClient wifiClient;PubSubClient client(wifiClient);
// LED stateint ledPin = 2;bool ledState = false;
// Timingunsigned long lastPublish = 0;const unsigned long PUBLISH_INTERVAL = 5000;
void callback(char* topic, byte* payload, unsigned int length) { String message = ""; for (int i = 0; i < length; i++) message += (char)payload[i];
Serial.printf("Received [%s]: %s\n", topic, message.c_str());
if (String(topic) == topicToESP) { if (message == "ON") { ledState = true; digitalWrite(ledPin, HIGH); client.publish(topicFeedback, "LED_ON"); } else if (message == "OFF") { ledState = false; digitalWrite(ledPin, LOW); client.publish(topicFeedback, "LED_OFF"); } }}
void setup() { Serial.begin(115200); pinMode(ledPin, OUTPUT);
WiFi.begin("SSID", "PASSWORD"); while (WiFi.status() != WL_CONNECTED) delay(500);
client.setServer("192.168.1.100", 1883); client.setCallback(callback);
while (!client.connect("esp32-bidirectional")) delay(1000); client.subscribe(topicToESP);
Serial.println("Ready for bidirectional MQTT");}
void loop() { if (!client.connected()) { // Reconnect logic } client.loop();
// Publish sensor data periodically (ESP32 → Node-RED) if (millis() - lastPublish >= PUBLISH_INTERVAL) { lastPublish = millis();
// Simulated sensor data char json[100]; snprintf(json, sizeof(json), "{\"led\":%s,\"uptime\":%lu,\"rssi\":%ld}", ledState ? "true" : "false", millis() / 1000, WiFi.RSSI());
client.publish(topicFromESP, json); }}Step 4: Node-RED Side — Receiving Data
Section titled “Step 4: Node-RED Side — Receiving Data”In Node-RED, create a flow to receive ESP32 data:
[MQTT In: esp32/sensor/data] → [Function: parse JSON] → [Debug] → [Function: display on dashboard]The MQTT In node configuration:
- Server: Your Mosquitto broker
- Topic:
esp32/sensor/data - Output:
auto-detectorJSON
The Function node to extract values:
// msg.payload is the JSON from ESP32let data = msg.payload;node.warn(`Temperature: ${data.temperature}°C`);node.warn(`Humidity: ${data.humidity}%`);
// Forward for dashboardreturn msg;Step 5: Node-RED Side — Sending Commands
Section titled “Step 5: Node-RED Side — Sending Commands”To send commands to ESP32:
[Inject: ON] → [MQTT Out: esp32/control/led][Inject: OFF] → [MQTT Out: esp32/control/led]Or for JSON commands:
[Inject: {"command":"LED_ON","value":1}] → [MQTT Out: esp32/control/config]Step 6: Testing with MQTT Explorer
Section titled “Step 6: Testing with MQTT Explorer”Before setting up Node-RED, test the bidirectional communication with MQTT Explorer:
- Open MQTT Explorer
- Connect to the broker (same IP as ESP32)
- Watch for ESP32 published data on
esp32/sensor/data - Manually publish to
esp32/control/ledwith payloadON - Observe the ESP32 LED turning on
- Check
esp32/status/ledfor the confirmation message
Verification
Section titled “Verification”- ESP32 periodically publishes data to its MQTT topic
- MQTT Explorer shows the published messages
- Publishing “ON” to the control topic turns on the LED
- Publishing “OFF” to the control topic turns off the LED
- ESP32 publishes feedback confirmation
- Node-RED receives and processes the ESP32 data
- Node-RED commands are received and executed by ESP32
Troubleshooting
Section titled “Troubleshooting”ESP32 publishes but Node-RED does not receive
Section titled “ESP32 publishes but Node-RED does not receive”Causes:
- Topic mismatch between ESP32 publish and Node-RED subscribe
- Node-RED MQTT In node configured for wrong broker
Solutions:
- Verify the topic string is exactly the same (case-sensitive)
- Check Node-RED MQTT In node is connected to the same broker
- Use MQTT Explorer as an independent verification tool
ESP32 subscribed but does not respond to commands
Section titled “ESP32 subscribed but does not respond to commands”Causes:
client.loop()not called frequently- Callback function not registered via
setCallback() - Topic string comparison fails
Solutions:
- Ensure
client.loop()is called in everyloop()iteration (not inside a long delay) - Verify
client.setCallback(callback)is called insetup() - Print both topic strings to Serial to compare exact values
- Use
String(topic) == "your/topic"for case-sensitive comparison
LED state out of sync between ESP32 and Node-RED
Section titled “LED state out of sync between ESP32 and Node-RED”Cause: Node-RED does not know the ESP32’s initial LED state.
Solution: Publish the current state immediately on MQTT connection:
void connectToMQTT() { if (client.connect("esp32-device")) { // Publish current state client.publish("esp32/status/led", ledState ? "ON" : "OFF", true); client.subscribe("esp32/control/led"); }}The true parameter makes it a retained message.
Best Practices
Section titled “Best Practices”- Design topics hierarchically:
location/device/sensorstructure makes filtering easier - Use retained messages for state:
client.publish(topic, payload, true)— the broker keeps the last value for late-joining subscribers - Publish confirmation on state change: After executing a command, publish the new state to a status topic
- Include a status heartbeat: Publish uptime or a “still alive” message periodically
- Keep payloads small: JSON is fine, but avoid unnecessary whitespace in production
- Use unique topic structures per project: See the project chapters (02-14) for specific topic designs
Summary
Section titled “Summary”- Bidirectional MQTT involves ESP32 publishing sensor data and subscribing to commands
- Topic design follows a hierarchical structure:
location/device/data-type - ESP32 publishes periodically or on events; subscribes to command topics at startup
- Node-RED subscribes to ESP32 data topics and publishes to ESP32 command topics
- Testing with MQTT Explorer before setting up Node-RED simplifies debugging
- Always confirm state changes by publishing feedback messages