Skip to content

MQTT Publish and Subscribe

MQTT Publish and Subscribe

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
  • MQTT client configured on ESP32 (01-10)
  • Basic Sketch architecture (01-11)
  • Node-RED MQTT nodes configured (or MQTT Explorer for testing)

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.

The ESP32 publishes data about its state or sensor readings. The MQTT topic reflects the data content and follows a hierarchical structure:

TopicPayloadFrequency
factory/room01/temperature25.5Every 30s
factory/room01/humidity65%Every 30s
factory/device01/status”online”/“offline”On change
factory/device01/button”pressed”On event

The ESP32 subscribes to topics that carry commands or configuration:

TopicPayload (Example)Effect
esp32/led/command”ON” / “OFF”Control LED state
esp32/sensor/interval”10000”Change sensor interval
esp32/config{"threshold": 30}Update configuration

This is the most common pattern — ESP32 sends data periodically:

#include <WiFi.h>
#include <PubSubClient.h>
WiFiClient wifiClient;
PubSubClient client(wifiClient);
// MQTT topic for publishing
const 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>
// Topics
const char* topicFromESP = "esp32/sensor/data"; // ESP32 → Node-RED
const char* topicToESP = "esp32/control/led"; // Node-RED → ESP32
const char* topicFeedback = "esp32/status/led"; // ESP32 → Node-RED (confirmation)
WiFiClient wifiClient;
PubSubClient client(wifiClient);
// LED state
int ledPin = 2;
bool ledState = false;
// Timing
unsigned 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);
}
}

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-detect or JSON

The Function node to extract values:

// msg.payload is the JSON from ESP32
let data = msg.payload;
node.warn(`Temperature: ${data.temperature}°C`);
node.warn(`Humidity: ${data.humidity}%`);
// Forward for dashboard
return 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]

Before setting up Node-RED, test the bidirectional communication with MQTT Explorer:

  1. Open MQTT Explorer
  2. Connect to the broker (same IP as ESP32)
  3. Watch for ESP32 published data on esp32/sensor/data
  4. Manually publish to esp32/control/led with payload ON
  5. Observe the ESP32 LED turning on
  6. Check esp32/status/led for the confirmation message
  • 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

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:

  1. Verify the topic string is exactly the same (case-sensitive)
  2. Check Node-RED MQTT In node is connected to the same broker
  3. 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:

  1. Ensure client.loop() is called in every loop() iteration (not inside a long delay)
  2. Verify client.setCallback(callback) is called in setup()
  3. Print both topic strings to Serial to compare exact values
  4. 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.

  • Design topics hierarchically: location/device/sensor structure 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
  1. Bidirectional MQTT involves ESP32 publishing sensor data and subscribing to commands
  2. Topic design follows a hierarchical structure: location/device/data-type
  3. ESP32 publishes periodically or on events; subscribes to command topics at startup
  4. Node-RED subscribes to ESP32 data topics and publishes to ESP32 command topics
  5. Testing with MQTT Explorer before setting up Node-RED simplifies debugging
  6. Always confirm state changes by publishing feedback messages