MQTT Message Transmission
MQTT Message Transmission
Overview
Section titled “Overview”This section covers the MQTT message transmission from the IoT button — publishing a button press event to the MQTT broker and receiving acknowledgments. By the end of this section, you will be able to:
- Configure the PubSubClient library for MQTT on ESP32
- Publish button press events with appropriate payload structure
- Handle MQTT connection failures gracefully
- Ensure message delivery with appropriate QoS settings
Prerequisites
Section titled “Prerequisites”Before starting this section, please ensure:
- Completed 04-07. WiFi Connection on Button Press
- Understanding of MQTT topics and QoS (see Chapter 06)
- MQTT broker running (Mosquitto or EMQX)
Key Concepts
Section titled “Key Concepts”Button Message Architecture
Section titled “Button Message Architecture”The IoT button publishes a simple message when pressed:
Publisher (Button) Broker Subscribers │ │ │ │ ──► Topic: "factory/button/01" │ │ │ Payload: {"button":"01", │ │ │ "action":"toggle", │ │ │ "battery":3.85} │ │ │ │──► Subscribe ──────► Node-RED │ │ │ │ │ │ │ ◄── (QoS 1 acknowledgment) │ (Toggle action)Topic Structure
Section titled “Topic Structure”For the work order button system, use a consistent topic hierarchy:
factory/button/{button_id}/{action}
Examples:- factory/button/01/press — Button 01 pressed- factory/button/02/press — Button 02 pressed- factory/button/status/01 — Button 01 status (battery level, online)Topic design considerations:
- Unique per button: Each button has a unique ID in the topic
- Action-based: The last level indicates the action type
- Wildcard subscribable: Node-RED can subscribe to
factory/button/#
Payload Structure
Section titled “Payload Structure”{ "button_id": "BTN-001", "action": "toggle", "timestamp": 1715901234, "battery_voltage": 3.85, "battery_percent": 85, "rssi": -65, "firmware": "v1.0"}Implementation Steps
Section titled “Implementation Steps”Step 1: Install and Configure PubSubClient
Section titled “Step 1: Install and Configure PubSubClient”#include <WiFi.h>#include <PubSubClient.h>
// MQTT Broker configurationconst char* MQTT_BROKER = "192.168.1.100"; // Mosquitto server IPconst int MQTT_PORT = 1883;const char* MQTT_USER = ""; // Leave empty if no authenticationconst char* MQTT_PASS = "";const char* MQTT_TOPIC = "factory/button/01/press";
// Button identificationconst char* BUTTON_ID = "BTN-001";
WiFiClient wifiClient;PubSubClient mqttClient(wifiClient);Step 2: Connect to MQTT Broker
Section titled “Step 2: Connect to MQTT Broker”bool connectMQTT() { mqttClient.setServer(MQTT_BROKER, MQTT_PORT);
// Generate unique client ID String clientId = "iot_button_"; clientId += String((uint32_t)ESP.getEfuseMac(), HEX);
Serial.print("Connecting to MQTT broker: "); Serial.println(MQTT_BROKER);
if (mqttClient.connect(clientId.c_str(), MQTT_USER, MQTT_PASS)) { Serial.println("MQTT connected"); return true; } else { Serial.print("MQTT connection failed, state: "); Serial.println(mqttClient.state()); return false; }}Step 3: Publish Button Press with Battery Status
Section titled “Step 3: Publish Button Press with Battery Status”#include <ArduinoJson.h>
float readBatteryVoltage() { int adcValue = analogRead(BATTERY_ADC_PIN); return (adcValue / 4095.0) * 3.3 * 2; // Voltage divider ×2}
bool publishButtonPress() { // Create JSON payload StaticJsonDocument<256> doc; doc["button_id"] = BUTTON_ID; doc["action"] = "toggle";
// Add battery status float voltage = readBatteryVoltage(); doc["battery_voltage"] = voltage; doc["battery_percent"] = (int)((voltage - 3.3) / (4.2 - 3.3) * 100); doc["rssi"] = WiFi.RSSI(); doc["timestamp"] = time(nullptr);
// Serialize to string char payload[256]; serializeJson(doc, payload); Serial.print("Publishing: "); Serial.println(payload);
// Publish with QoS 1 (at least once delivery) bool success = mqttClient.publish(MQTT_TOPIC, payload, true);
if (success) { Serial.println("Published successfully"); } else { Serial.println("Publish failed"); }
return success;}Step 4: Complete Press-and-Sleep Flow
Section titled “Step 4: Complete Press-and-Sleep Flow”void performButtonAction() { // Step 1: Connect WiFi (from Section 04-07) if (!connectWiFiWithRetry(2)) { Serial.println("WiFi failed - cannot publish"); return; }
// Step 2: Connect MQTT if (!connectMQTT()) { Serial.println("MQTT failed - sleeping"); WiFi.disconnect(true); return; }
// Step 3: Publish message publishButtonPress();
// Step 4: Allow MQTT to flush mqttClient.loop(); delay(100);
// Step 5: Disconnect cleanly mqttClient.disconnect(); WiFi.disconnect(true);
Serial.println("Button action complete");}
void setup() { Serial.begin(115200); delay(100);
// Button was pressed (wake from deep sleep) performButtonAction();
// Go back to sleep Serial.println("Entering deep sleep..."); Serial.flush(); esp_deep_sleep_start();}Step 5: MQTT Message Verification
Section titled “Step 5: MQTT Message Verification”To verify the button messages are being received, use MQTT Explorer or mosquitto_sub:
# Subscribe to all button topicsmosquitto_sub -h 192.168.1.100 -t "factory/button/#" -v
# Expected output:# factory/button/01/press {"button_id":"BTN-001","action":"toggle","battery_voltage":3.85,...}Verification
Section titled “Verification”- Button publishes MQTT message when pressed
- Message is received by the MQTT broker
- Payload contains correct JSON structure
- Battery voltage is included in the message
- Device disconnects MQTT cleanly before sleep
Troubleshooting
Section titled “Troubleshooting”Issue 1: MQTT Connection Failed
Section titled “Issue 1: MQTT Connection Failed”Symptom: mqttClient.connect() returns false
MQTT State Codes:
| State Code | Meaning | Solution |
|---|---|---|
| -4 | Connection timeout | Check broker IP and network |
| -3 | Connection lost | Broker not running |
| -2 | Connect failed | Wrong broker address |
| -1 | Disconnected | Normal state before connect |
| 1 | Protocol error | Check MQTT version compatibility |
| 2 | Client ID rejected | Check for duplicates |
| 3 | Server unavailable | Broker overloaded or not running |
| 4 | Bad username/password | Check credentials |
| 5 | Not authorized | Check broker ACL |
Solution:
void debugMQTTState() { int state = mqttClient.state(); switch (state) { case MQTT_CONNECT_UNAUTHORIZED: Serial.println("MQTT: Unauthorized - check credentials"); break; case MQTT_CONNECT_TIMEOUT: Serial.println("MQTT: Timeout - check broker IP and port"); break; case MQTT_CONNECT_FAILED: Serial.println("MQTT: Failed - is broker running?"); break; default: Serial.print("MQTT state: "); Serial.println(state); }}Issue 2: Message Not Received by Node-RED
Section titled “Issue 2: Message Not Received by Node-RED”Symptom: Button reports successful publish, but Node-RED doesn’t trigger
Possible Causes:
- Topic mismatch between button and Node-RED MQTT In node
- MQTT In node subscribed to wrong topic
- QoS mismatch (button publishes QoS 1, Node-RED subscribes QoS 0)
Solution: Verify with MQTT Explorer or mosquitto_sub before checking Node-RED.
Best Practices
Section titled “Best Practices”- ✅ Use QoS 1 for button messages — ensures delivery without excessive overhead
- ✅ Include battery voltage in every message for health monitoring
- ✅ Use unique client IDs based on MAC address to avoid conflicts
- ✅ Disconnect MQTT cleanly before deep sleep
- ✅ Include firmware version for update tracking
- ❌ Do not publish large payloads — keep under 512 bytes for reliability
- ❌ Avoid retained messages for button presses (each press is a new event)
Summary
Section titled “Summary”- MQTT publish after WiFi connect — sequential, not parallel
- JSON payload includes button ID, action, battery level, RSSI, and timestamp
- QoS 1 ensures delivery without the overhead of QoS 2
- Total active time: ~4-5 seconds (WiFi 2-3s + MQTT 1s + cleanup)
- Battery voltage monitoring is critical for proactive maintenance
References
Section titled “References”Target Audience: Alibaba.com IoT Pre-sales Engineers
Status: ✅ Completed