This is an old revision of the document!
MQTT (Message Queuing Telemetry Transport) is a lightweight, publish-subscribe network protocol designed for efficient communication in constrained environments. Originally developed by IBM in the late 1990s, MQTT has become a standard for IoT (Internet of Things) systems due to its low bandwidth requirements and minimal overhead. It is mostly used in Home Automation systems, Industrial IoT applications and Mobile Messaging and Telemetry.
sensors/temperature/room1
).sensors/+/room1
) to match multiple topics.In this lab, you'll interact with an MQTT broker, publish sensor data from your Sparrow sensor node, and visualize real-time telemetry. Familiarity with topics and QoS settings will be essential to design robust IoT communication systems.
You will need to add this to yout Platformio project:
; PlatformIO Project Configuration File ; ; Build options: build flags, source filter ; Upload options: custom upload port, speed and extra flags ; Library options: dependencies, extra library storages ; Advanced options: extra scripting ; ; Please visit documentation for the other options and examples ; https://docs.platformio.org/page/projectconf.html [env:esp32-c6-sparrow] platform = https://github.com/pioarduino/platform-espressif32/releases/download/54.03.20/platform-espressif32.zip board = esp32-c6-devkitm-1 framework = arduino build_flags = -D ARDUINO_USB_MODE=1 -D ARDUINO_USB_CDC_ON_BOOT=1 -D ESP32_C6_env monitor_speed = 115200 lib_deps = adafruit/Adafruit NeoPixel@^1.11.0 adafruit/Adafruit BME680 Library bblanchon/ArduinoJson @ ^7 256dpi/MQTT @ ^2.5.2
This example connects an ESP32 to Wi-Fi and establishes an MQTT connection to a public broker (test.mosquitto.org). It sets up a unique client ID based on the ESP32’s MAC address, registers a “Last Will” message so that if the device disconnects unexpectedly, the broker automatically marks it as offline, and then publishes an “online” status when connected. It also subscribes to all topics under a user-defined base topic (like iot/studentname/#), so it can receive messages addressed to that namespace, and logs any received messages to the serial monitor.
Once connected, the ESP32 enters a loop where it continuously maintains the MQTT session, reconnects if needed, and publishes a heartbeat message every five seconds containing its uptime in milliseconds. This heartbeat acts as a regular signal that the device is alive. The code’s use of non-blocking MQTT functions keeps the system responsive, while the combination of retained status messages and the Last Will feature ensures other clients always know the ESP32’s current state — whether it’s online, offline, or actively sending updates.
#include <Arduino.h> #include <WiFi.h> #include <MQTT.h> // 256dpi MQTT library //////////////// EDIT THESE //////////////// const char* WIFI_SSID = "YOUR_SSID"; const char* WIFI_PASSWORD = "YOUR_PASSWORD"; const char* MQTT_HOST = "test.mosquitto.org"; // or your lab broker const uint16_t MQTT_PORT = 1883; const char* BASE_TOPIC = "iot/studentname"; // change per student //////////////////////////////////////////// WiFiClient net; MQTTClient mqtt(1024); // 1KB message buffer unsigned long lastPub = 0; void connectWiFi() { if (WiFi.status() == WL_CONNECTED) return; WiFi.mode(WIFI_STA); WiFi.begin(WIFI_SSID, WIFI_PASSWORD); Serial.print("WiFi connecting"); while (WiFi.status() != WL_CONNECTED) { delay(500); Serial.print("."); } Serial.printf("\nWiFi OK, IP: %s\n", WiFi.localIP().toString().c_str()); } void messageReceived(String &topic, String &payload) { Serial.printf("Message on %s: %s\n", topic.c_str(), payload.c_str()); } void connectMQTT() { // Build a real String, then pass c_str() String clientId = "esp32-client-" + String((uint32_t)ESP.getEfuseMac(), HEX); Serial.println("MQTT connecting..."); while (!mqtt.connect(clientId.c_str())) { Serial.print("."); delay(1000); } Serial.println("\nMQTT connected!"); // Optional subscription String subTopic = String(BASE_TOPIC) + "/#"; mqtt.subscribe(subTopic); Serial.printf("Subscribed to %s\n", subTopic.c_str()); } void setup() { Serial.begin(115200); delay(500); connectWiFi(); mqtt.begin(MQTT_HOST, MQTT_PORT, net); mqtt.onMessage(messageReceived); // Optional: last will so brokers/clients know if we drop String willTopic = String(BASE_TOPIC) + "/status"; mqtt.setWill(willTopic.c_str(), "offline", true, 1); connectMQTT(); // Publish "online" once connected mqtt.publish(willTopic, "online", true, 1); } void loop() { connectWiFi(); // ensure WiFi if (!mqtt.connected()) connectMQTT(); mqtt.loop(); // non-blocking delay(10); // Publish every 5 seconds if (millis() - lastPub > 5000) { lastPub = millis(); String topic = String(BASE_TOPIC) + "/heartbeat"; String payload = String("{\"uptime_ms\":") + millis() + "}"; bool ok = mqtt.publish(topic, payload); Serial.printf("Publish %s => %s (%s)\n", topic.c_str(), payload.c_str(), ok ? "OK" : "FAIL"); } }
Test it out using this link.
Goal: subscribe to iot/<yourname>/led and set the Sparrow’s single WS2812 LED color from JSON payloads like {“r”:255,”g”:0,”b”:64}.
#include <Arduino.h> #include <WiFi.h> #include <PubSubClient.h> #include <ArduinoJson.h> #include <Adafruit_NeoPixel.h> //////////////// EDIT THESE //////////////// const char* WIFI_SSID = "TP-Link_2A64"; const char* WIFI_PASSWORD = "99481100"; const char* MQTT_HOST = "test.mosquitto.org"; // or your lab broker const uint16_t MQTT_PORT = 1883; const char* BASE_TOPIC = "iot/dantudose"; // change per student //////////////////////////////////////////// #define NEOPIXEL_PIN 3 Adafruit_NeoPixel pixel(1, NEOPIXEL_PIN, NEO_GRB + NEO_KHZ800); WiFiClient espClient; PubSubClient mqtt(espClient); void ensureWiFi() { if (WiFi.status() == WL_CONNECTED) return; WiFi.mode(WIFI_STA); WiFi.begin(WIFI_SSID, WIFI_PASSWORD); while (WiFi.status() != WL_CONNECTED) { delay(400); Serial.print("."); } Serial.printf("\nIP: %s\n", WiFi.localIP().toString().c_str()); } void onMessage(char* topic, byte* payload, unsigned int len) { Serial.printf("MQTT msg on %s: %.*s\n", topic, len, (char*)payload); StaticJsonDocument<128> doc; DeserializationError err = deserializeJson(doc, payload, len); if (err) return; int r = doc["r"] | 0, g = doc["g"] | 0, b = doc["b"] | 0; pixel.setPixelColor(0, pixel.Color(r, g, b)); pixel.setBrightness(doc["brightness"] | 50); // optional 0..255 pixel.show(); // Acknowledge String ackTopic = String(BASE_TOPIC) + "/led/ack"; StaticJsonDocument<96> ack; ack["ok"] = true; ack["r"] = r; ack["g"] = g; ack["b"] = b; char buf[96]; size_t n = serializeJson(ack, buf); mqtt.publish(ackTopic.c_str(), buf, n); } void ensureMQTT() { if (mqtt.connected()) return; while (!mqtt.connected()) { String cid = String("sparrow-c6-led-") + String((uint32_t)ESP.getEfuseMac(), HEX); if (mqtt.connect(cid.c_str())) break; delay(1000); } String sub = String(BASE_TOPIC) + "/led"; mqtt.subscribe(sub.c_str()); Serial.printf("Subscribed: %s\n", sub.c_str()); } void setup() { Serial.begin(115200); pixel.begin(); pixel.clear(); pixel.show(); mqtt.setServer(MQTT_HOST, MQTT_PORT); mqtt.setCallback(onMessage); } void loop() { ensureWiFi(); ensureMQTT(); mqtt.loop(); }
Test it out using this link.
This example, once connected, subscribes to a topic that controls an RGB LED connected to the board. Whenever a new MQTT message arrives on that topic, the ESP32 reads the JSON payload, extracts the red, green, blue, and brightness values, and updates the LED accordingly.
After setting the LED color, the device publishes an acknowledgment message to a separate MQTT topic, confirming the values it received and applied. This acknowledgment lets any remote controller or dashboard know that the LED update was successful. The whole process uses the non-blocking, event-driven design of the 256dpi library, meaning it can handle MQTT communication efficiently without freezing the microcontroller’s main loop. In essence, this sketch turns the ESP32 into a small, networked RGB controller that listens for MQTT commands and reports its actions back to the broker.
#include <Arduino.h> #include <WiFi.h> #include <MQTT.h> // 256dpi MQTT library #include <ArduinoJson.h> #include <Adafruit_NeoPixel.h> //////////////// EDIT THESE //////////////// const char* WIFI_SSID = "YOUR_SSID"; const char* WIFI_PASSWORD = "YOUR_PASSWORD"; const char* MQTT_HOST = "test.mosquitto.org"; // or your lab broker const uint16_t MQTT_PORT = 1883; const char* BASE_TOPIC = "iot/studentname"; // change per student //////////////////////////////////////////// #define NEOPIXEL_PIN 3 Adafruit_NeoPixel pixel(1, NEOPIXEL_PIN, NEO_GRB + NEO_KHZ800); WiFiClient net; MQTTClient mqtt(1024); // 1KB message buffer // ---------- Wi-Fi ---------- void ensureWiFi() { if (WiFi.status() == WL_CONNECTED) return; WiFi.mode(WIFI_STA); WiFi.begin(WIFI_SSID, WIFI_PASSWORD); Serial.print("WiFi connecting"); while (WiFi.status() != WL_CONNECTED) { delay(400); Serial.print("."); } Serial.printf("\nIP: %s\n", WiFi.localIP().toString().c_str()); } // ---------- MQTT message handler (256dpi signature) ---------- void onMessage(String &topic, String &payload) { Serial.printf("MQTT msg on %s: %s\n", topic.c_str(), payload.c_str()); // Create a dynamic document on the stack with a defined capacity JsonDocument doc; DeserializationError err = deserializeJson(doc, payload); if (err) { Serial.printf("JSON parse error: %s\n", err.c_str()); return; } int r = doc["r"] | 0; int g = doc["g"] | 0; int b = doc["b"] | 0; int brightness = doc["brightness"] | 50; pixel.setPixelColor(0, pixel.Color(r, g, b)); pixel.setBrightness(brightness); pixel.show(); // Acknowledge String ackTopic = String(BASE_TOPIC) + "/led/ack"; JsonDocument ack; // also replace StaticJsonDocument here ack["ok"] = true; ack["r"] = r; ack["g"] = g; ack["b"] = b; ack["brightness"] = brightness; char buf[96]; size_t n = serializeJson(ack, buf, sizeof(buf)); mqtt.publish(ackTopic, String(buf, n)); } // ---------- MQTT connect / subscribe ---------- void ensureMQTT() { if (mqtt.connected()) return; String clientId = "sparrow-c6-led-" + String((uint32_t)ESP.getEfuseMac(), HEX); Serial.println("MQTT connecting..."); while (!mqtt.connect(clientId.c_str())) { Serial.print("."); delay(1000); } Serial.println("\nMQTT connected"); String sub = String(BASE_TOPIC) + "/led"; mqtt.subscribe(sub); Serial.printf("Subscribed: %s\n", sub.c_str()); } void setup() { Serial.begin(115200); delay(200); pixel.begin(); pixel.clear(); pixel.show(); // Broker + transport setup and callback mqtt.begin(MQTT_HOST, MQTT_PORT, net); mqtt.onMessage(onMessage); } void loop() { ensureWiFi(); ensureMQTT(); // Process incoming packets and keepalive mqtt.loop(); delay(10); }
Test it out using this link.
Goal here is to add production-style robustness: MQTT session properties (LWT, retain, QoS), back-off reconnects, and how to flip to TLS.
#include <Arduino.h> #include <WiFi.h> #include <WiFiClientSecure.h> #include <MQTT.h> #include <Wire.h> #include <Adafruit_BME680.h> #include <Adafruit_NeoPixel.h> #include <ArduinoJson.h> //////////////// EDIT THESE //////////////// const char* WIFI_SSID = "TP-Link_2A64"; const char* WIFI_PASSWORD = "99481100"; const char* MQTT_HOST = "test.mosquitto.org"; // or your lab broker const uint16_t MQTT_PORT = 1883; const char* BASE_TOPIC = "iot/dantudose"; // change per student //////////////////////////////////////////// #define SDA_PIN 21 #define SCL_PIN 22 #define NEOPIXEL_PIN 3 WiFiClient net; MQTTClient mqtt(2048); Adafruit_BME680 bme; Adafruit_NeoPixel pixel(1, NEOPIXEL_PIN, NEO_GRB + NEO_KHZ800); unsigned long nextConnTry = 0; uint32_t backoffMs = 1000; unsigned long lastHeartbeat = 0; unsigned long lastSensorPub = 0; void ensureWiFi() { if (WiFi.status() == WL_CONNECTED) return; WiFi.mode(WIFI_STA); WiFi.begin(WIFI_SSID, WIFI_PASSWORD); Serial.print("WiFi connecting"); while (WiFi.status() != WL_CONNECTED) { delay(500); Serial.print("."); } Serial.printf("\nWiFi OK, IP: %s, RSSI: %d dBm\n", WiFi.localIP().toString().c_str(), WiFi.RSSI()); } void setLED(uint8_t r, uint8_t g, uint8_t b, uint8_t brightness) { pixel.setBrightness(brightness); pixel.setPixelColor(0, pixel.Color(r, g, b)); pixel.show(); } void handleLEDJson(const String& payload) { JsonDocument doc; DeserializationError err = deserializeJson(doc, payload); if (err) { Serial.printf("LED JSON parse error: %s\n", err.c_str()); return; } uint8_t r = doc["r"].isNull() ? 0 : doc["r"].as<uint8_t>(); uint8_t g = doc["g"].isNull() ? 0 : doc["g"].as<uint8_t>(); uint8_t b = doc["b"].isNull() ? 0 : doc["b"].as<uint8_t>(); uint8_t br = doc["brightness"].isNull() ? 100 : doc["brightness"].as<uint8_t>(); setLED(r,g,b,br); JsonDocument ack; ack["ok"] = true; ack["r"] = r; ack["g"] = g; ack["b"] = b; ack["brightness"] = br; String out; serializeJson(ack, out); mqtt.publish((String(BASE_TOPIC)+"/led/ack").c_str(), out.c_str(), false, 1); } void messageReceived(String &topic, String &payload) { Serial.printf("MSG [%s]: %s\n", topic.c_str(), payload.c_str()); if (topic == String(BASE_TOPIC) + "/led") handleLEDJson(payload); } bool connectMQTT() { String cid = String("sparrow-c6-qos1-") + String((uint32_t)ESP.getEfuseMac(), HEX); mqtt.setCleanSession(true); mqtt.setKeepAlive(30); String willTopic = String(BASE_TOPIC) + "/status"; mqtt.setWill(willTopic.c_str(), "{\"status\":\"offline\"}", true, 1); Serial.printf("MQTT connecting to %s:%u as %s ...\n", MQTT_HOST, MQTT_PORT, cid.c_str()); bool ok = mqtt.connect(cid.c_str(), nullptr, nullptr); if (!ok) { Serial.printf("MQTT connect failed, error=%d\n", mqtt.lastError()); return false; } mqtt.subscribe((String(BASE_TOPIC)+"/led").c_str(), 1); mqtt.publish(willTopic.c_str(), "{\"status\":\"online\"}", true, 1); Serial.println("MQTT connected"); return true; } void ensureMQTT() { if (mqtt.connected()) return; if (millis() < nextConnTry) return; if (!connectMQTT()) { backoffMs = min<uint32_t>(backoffMs * 2, 30000); nextConnTry = millis() + backoffMs; Serial.printf("Retry in %lu ms\n", (unsigned long)backoffMs); } else { backoffMs = 1000; } } bool readBME(float& tC, float& hPct, float& pHpa, float& gas) { if (!bme.performReading()) return false; tC = bme.temperature; hPct = bme.humidity; pHpa = bme.pressure / 100.0; gas = bme.gas_resistance; return true; } void setup() { Serial.begin(115200); pixel.begin(); setLED(0,0,0,10); Wire.begin(SDA_PIN, SCL_PIN); if (!bme.begin(0x76)) { Serial.println("BME688/BME680 not found at 0x76!"); } else { bme.setTemperatureOversampling(BME680_OS_8X); bme.setHumidityOversampling(BME680_OS_2X); bme.setPressureOversampling(BME680_OS_4X); bme.setIIRFilterSize(BME680_FILTER_SIZE_3); bme.setGasHeater(0, 0); } mqtt.begin(MQTT_HOST, MQTT_PORT, net); mqtt.onMessage(messageReceived); } void loop() { ensureWiFi(); ensureMQTT(); mqtt.loop(); if (mqtt.connected() && millis() - lastHeartbeat > 15000) { lastHeartbeat = millis(); JsonDocument hb; hb["uptime_s"] = (uint32_t)(millis()/1000); hb["ip"] = WiFi.localIP().toString(); hb["rssi"] = WiFi.RSSI(); String out; serializeJson(hb, out); mqtt.publish((String(BASE_TOPIC)+"/heartbeat").c_str(), out.c_str(), true, 1); Serial.printf("Heartbeat -> %s\n", out.c_str()); } if (mqtt.connected() && millis() - lastSensorPub > 10000) { lastSensorPub = millis(); float tC, hPct, pHpa, gas; if (readBME(tC, hPct, pHpa, gas)) { JsonDocument s; s["ts"] = (uint32_t)(millis()/1000); s["temp_c"] = tC; s["hum_pct"] = hPct; s["press_hpa"] = pHpa; s["gas_ohm"] = gas; String out; serializeJson(s, out); mqtt.publish((String(BASE_TOPIC)+"/bme688").c_str(), out.c_str(), false, 1); Serial.printf("Sensor -> %s\n", out.c_str()); } else { Serial.println("BME read failed"); } } }
Test it out using this link.