This shows you the differences between two versions of the page.
iothings:laboratoare:2025:lab3 [2025/10/11 00:00] dan.tudose |
iothings:laboratoare:2025:lab3 [2025/10/11 00:29] (current) dan.tudose [Lab 3. The MQTT Protocol] |
||
---|---|---|---|
Line 3: | Line 3: | ||
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. | 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. | ||
+ | {{ :iothings:laboratoare:2025:mqtt-pubsub-model.jpg?600 |}} | ||
==== Key Concepts ==== | ==== Key Concepts ==== | ||
Line 76: | Line 77: | ||
//////////////// EDIT THESE //////////////// | //////////////// EDIT THESE //////////////// | ||
- | const char* WIFI_SSID = "TP-Link_2A64"; | + | const char* WIFI_SSID = "YOUR_SSID"; |
- | const char* WIFI_PASSWORD = "99481100"; | + | const char* WIFI_PASSWORD = "YOUR_PASSWORD"; |
const char* MQTT_HOST = "test.mosquitto.org"; // or your lab broker | const char* MQTT_HOST = "test.mosquitto.org"; // or your lab broker | ||
const uint16_t MQTT_PORT = 1883; | const uint16_t MQTT_PORT = 1883; | ||
- | const char* BASE_TOPIC = "iot/dantudose"; // change per student | + | const char* BASE_TOPIC = "iot/studentname"; // change per student |
//////////////////////////////////////////// | //////////////////////////////////////////// | ||
Line 163: | Line 164: | ||
===== Subscribe & control the on-board NeoPixel ===== | ===== Subscribe & control the on-board NeoPixel ===== | ||
- | 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}. | + | <note tip>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}. |
+ | </note> | ||
+ | |||
+ | 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. | ||
<code C main.cpp> | <code C main.cpp> | ||
#include <Arduino.h> | #include <Arduino.h> | ||
#include <WiFi.h> | #include <WiFi.h> | ||
- | #include <PubSubClient.h> | + | #include <MQTT.h> // 256dpi MQTT library |
#include <ArduinoJson.h> | #include <ArduinoJson.h> | ||
#include <Adafruit_NeoPixel.h> | #include <Adafruit_NeoPixel.h> | ||
//////////////// EDIT THESE //////////////// | //////////////// EDIT THESE //////////////// | ||
- | const char* WIFI_SSID = "TP-Link_2A64"; | + | const char* WIFI_SSID = "YOUR_SSID"; |
- | const char* WIFI_PASSWORD = "99481100"; | + | const char* WIFI_PASSWORD = "YOUR_PASSWORD"; |
const char* MQTT_HOST = "test.mosquitto.org"; // or your lab broker | const char* MQTT_HOST = "test.mosquitto.org"; // or your lab broker | ||
const uint16_t MQTT_PORT = 1883; | const uint16_t MQTT_PORT = 1883; | ||
- | const char* BASE_TOPIC = "iot/dantudose"; // change per student | + | const char* BASE_TOPIC = "iot/studentname"; // change per student |
//////////////////////////////////////////// | //////////////////////////////////////////// | ||
Line 183: | Line 189: | ||
Adafruit_NeoPixel pixel(1, NEOPIXEL_PIN, NEO_GRB + NEO_KHZ800); | Adafruit_NeoPixel pixel(1, NEOPIXEL_PIN, NEO_GRB + NEO_KHZ800); | ||
- | WiFiClient espClient; | + | WiFiClient net; |
- | PubSubClient mqtt(espClient); | + | MQTTClient mqtt(1024); // 1KB message buffer |
+ | // ---------- Wi-Fi ---------- | ||
void ensureWiFi() { | void ensureWiFi() { | ||
if (WiFi.status() == WL_CONNECTED) return; | if (WiFi.status() == WL_CONNECTED) return; | ||
WiFi.mode(WIFI_STA); | WiFi.mode(WIFI_STA); | ||
WiFi.begin(WIFI_SSID, WIFI_PASSWORD); | WiFi.begin(WIFI_SSID, WIFI_PASSWORD); | ||
- | while (WiFi.status() != WL_CONNECTED) { delay(400); Serial.print("."); } | + | Serial.print("WiFi connecting"); |
+ | while (WiFi.status() != WL_CONNECTED) { | ||
+ | delay(400); | ||
+ | Serial.print("."); | ||
+ | } | ||
Serial.printf("\nIP: %s\n", WiFi.localIP().toString().c_str()); | Serial.printf("\nIP: %s\n", WiFi.localIP().toString().c_str()); | ||
} | } | ||
- | void onMessage(char* topic, byte* payload, unsigned int len) { | + | // ---------- MQTT message handler (256dpi signature) ---------- |
- | Serial.printf("MQTT msg on %s: %.*s\n", topic, len, (char*)payload); | + | void onMessage(String &topic, String &payload) { |
- | StaticJsonDocument<128> doc; | + | Serial.printf("MQTT msg on %s: %s\n", topic.c_str(), payload.c_str()); |
- | DeserializationError err = deserializeJson(doc, payload, len); | + | |
- | if (err) return; | + | // 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; | ||
- | int r = doc["r"] | 0, g = doc["g"] | 0, b = doc["b"] | 0; | ||
pixel.setPixelColor(0, pixel.Color(r, g, b)); | pixel.setPixelColor(0, pixel.Color(r, g, b)); | ||
- | pixel.setBrightness(doc["brightness"] | 50); // optional 0..255 | + | pixel.setBrightness(brightness); |
pixel.show(); | pixel.show(); | ||
// Acknowledge | // Acknowledge | ||
String ackTopic = String(BASE_TOPIC) + "/led/ack"; | String ackTopic = String(BASE_TOPIC) + "/led/ack"; | ||
- | StaticJsonDocument<96> ack; | + | JsonDocument ack; // also replace StaticJsonDocument here |
- | ack["ok"] = true; ack["r"] = r; ack["g"] = g; ack["b"] = b; | + | ack["ok"] = true; |
- | char buf[96]; size_t n = serializeJson(ack, buf); | + | ack["r"] = r; |
- | mqtt.publish(ackTopic.c_str(), buf, n); | + | 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() { | void ensureMQTT() { | ||
if (mqtt.connected()) return; | if (mqtt.connected()) return; | ||
- | while (!mqtt.connected()) { | + | |
- | String cid = String("sparrow-c6-led-") + String((uint32_t)ESP.getEfuseMac(), HEX); | + | String clientId = "sparrow-c6-led-" + String((uint32_t)ESP.getEfuseMac(), HEX); |
- | if (mqtt.connect(cid.c_str())) break; | + | |
+ | Serial.println("MQTT connecting..."); | ||
+ | while (!mqtt.connect(clientId.c_str())) { | ||
+ | Serial.print("."); | ||
delay(1000); | delay(1000); | ||
} | } | ||
+ | Serial.println("\nMQTT connected"); | ||
+ | |||
String sub = String(BASE_TOPIC) + "/led"; | String sub = String(BASE_TOPIC) + "/led"; | ||
- | mqtt.subscribe(sub.c_str()); | + | mqtt.subscribe(sub); |
Serial.printf("Subscribed: %s\n", sub.c_str()); | Serial.printf("Subscribed: %s\n", sub.c_str()); | ||
} | } | ||
Line 227: | Line 261: | ||
void setup() { | void setup() { | ||
Serial.begin(115200); | Serial.begin(115200); | ||
+ | delay(200); | ||
+ | |||
pixel.begin(); | pixel.begin(); | ||
- | pixel.clear(); pixel.show(); | + | pixel.clear(); |
- | mqtt.setServer(MQTT_HOST, MQTT_PORT); | + | pixel.show(); |
- | mqtt.setCallback(onMessage); | + | |
+ | // Broker + transport setup and callback | ||
+ | mqtt.begin(MQTT_HOST, MQTT_PORT, net); | ||
+ | mqtt.onMessage(onMessage); | ||
} | } | ||
Line 236: | Line 275: | ||
ensureWiFi(); | ensureWiFi(); | ||
ensureMQTT(); | ensureMQTT(); | ||
+ | |||
+ | // Process incoming packets and keepalive | ||
mqtt.loop(); | mqtt.loop(); | ||
+ | delay(10); | ||
} | } | ||
Line 243: | Line 285: | ||
===== Publish Sensor Readings From BME680 as JSON ===== | ===== Publish Sensor Readings From BME680 as JSON ===== | ||
- | Goal: read temperature, humidity, pressure (and gas resistance) from the BME688 over I²C and publish every 10s to iot/<yourname>/bme688. | + | <note tip>Goal: read temperature, humidity, pressure (and gas resistance) from the BME688 over I²C and publish every 10s to iot/<yourname>/bme688. |
+ | </note> | ||
+ | |||
+ | Every ten seconds, the ESP32 reads environmental data from the on-board BME680 sensor—including temperature, humidity, pressure, and gas resistance—and packages those readings into a JSON message. It then publishes this JSON payload to an MQTT topic dedicated to that device’s sensor data. Any other MQTT clients subscribed to that topic can instantly receive and process the latest environmental information, such as for logging, visualization, or automation. In short, this code makes the ESP32 function as a small, networked environmental node that continuously streams live sensor data to the MQTT ecosystem while maintaining reliable connection status reporting. | ||
<code C main.cpp> | <code C main.cpp> | ||
Line 249: | Line 294: | ||
#include <WiFi.h> | #include <WiFi.h> | ||
#include <Wire.h> | #include <Wire.h> | ||
- | #include <PubSubClient.h> | + | #include <MQTT.h> |
#include <ArduinoJson.h> | #include <ArduinoJson.h> | ||
#include <Adafruit_BME680.h> | #include <Adafruit_BME680.h> | ||
//////////////// EDIT THESE //////////////// | //////////////// EDIT THESE //////////////// | ||
- | const char* WIFI_SSID = "TP-Link_2A64"; | + | const char* WIFI_SSID = "YOUR_SSID"; |
- | const char* WIFI_PASSWORD = "99481100"; | + | const char* WIFI_PASSWORD = "YOUR_PASSWORD"; |
const char* MQTT_HOST = "test.mosquitto.org"; // or your lab broker | const char* MQTT_HOST = "test.mosquitto.org"; // or your lab broker | ||
const uint16_t MQTT_PORT = 1883; | const uint16_t MQTT_PORT = 1883; | ||
- | const char* BASE_TOPIC = "iot/dantudose"; // change per student | + | const char* BASE_TOPIC = "iot/studentname"; // change per student |
//////////////////////////////////////////// | //////////////////////////////////////////// | ||
Line 266: | Line 311: | ||
Adafruit_BME680 bme; // I2C | Adafruit_BME680 bme; // I2C | ||
- | WiFiClient espClient; | + | WiFiClient net; |
- | PubSubClient mqtt(espClient); | + | MQTTClient mqtt(1024); // 1KB message buffer |
void ensureWiFi() { | void ensureWiFi() { | ||
Line 273: | Line 318: | ||
WiFi.mode(WIFI_STA); | WiFi.mode(WIFI_STA); | ||
WiFi.begin(WIFI_SSID, WIFI_PASSWORD); | WiFi.begin(WIFI_SSID, WIFI_PASSWORD); | ||
- | while (WiFi.status() != WL_CONNECTED) { delay(400); Serial.print("."); } | + | Serial.print("WiFi connecting"); |
+ | while (WiFi.status() != WL_CONNECTED) { | ||
+ | delay(400); | ||
+ | Serial.print("."); | ||
+ | } | ||
Serial.printf("\nIP: %s\n", WiFi.localIP().toString().c_str()); | Serial.printf("\nIP: %s\n", WiFi.localIP().toString().c_str()); | ||
} | } | ||
Line 279: | Line 328: | ||
void ensureMQTT() { | void ensureMQTT() { | ||
if (mqtt.connected()) return; | if (mqtt.connected()) return; | ||
- | while (!mqtt.connected()) { | + | |
- | String cid = String("sparrow-c6-sense-") + String((uint32_t)ESP.getEfuseMac(), HEX); | + | // Optional Last Will so dashboards see offline state |
- | if (mqtt.connect(cid.c_str())) break; | + | String willTopic = String(BASE_TOPIC) + "/bme688/status"; |
+ | mqtt.setWill(willTopic.c_str(), "offline", true, 1); | ||
+ | |||
+ | String cid = String("sparrow-c6-sense-") + String((uint32_t)ESP.getEfuseMac(), HEX); | ||
+ | Serial.println("MQTT connecting..."); | ||
+ | while (!mqtt.connect(cid.c_str())) { | ||
+ | Serial.print("."); | ||
delay(1000); | delay(1000); | ||
} | } | ||
+ | Serial.println("\nMQTT connected"); | ||
+ | |||
+ | // Publish "online" status retained | ||
+ | mqtt.publish(willTopic, "online", true, 1); | ||
} | } | ||
Line 290: | Line 349: | ||
void setup() { | void setup() { | ||
Serial.begin(115200); | Serial.begin(115200); | ||
+ | delay(200); | ||
+ | |||
Wire.begin(SDA_PIN, SCL_PIN); | Wire.begin(SDA_PIN, SCL_PIN); | ||
- | mqtt.setServer(MQTT_HOST, MQTT_PORT); | + | |
+ | // MQTT broker + transport | ||
+ | mqtt.begin(MQTT_HOST, MQTT_PORT, net); | ||
if (!bme.begin(0x76)) { // Sparrow uses 0x76 | if (!bme.begin(0x76)) { // Sparrow uses 0x76 | ||
Line 308: | Line 371: | ||
ensureWiFi(); | ensureWiFi(); | ||
ensureMQTT(); | ensureMQTT(); | ||
- | mqtt.loop(); | + | |
+ | mqtt.loop(); // process incoming/keepalive | ||
+ | delay(10); | ||
if (millis() - lastPub > 10000) { | if (millis() - lastPub > 10000) { | ||
Line 317: | Line 382: | ||
return; | return; | ||
} | } | ||
- | StaticJsonDocument<192> doc; | + | |
- | doc["ts"] = (uint32_t)(millis()/1000); | + | // Build JSON (ArduinoJson v7 style, no deprecated StaticJsonDocument) |
- | doc["temp_c"] = bme.temperature; | + | JsonDocument doc; |
- | doc["hum_pct"] = bme.humidity; | + | doc["ts"] = (uint32_t)(millis() / 1000); |
+ | doc["temp_c"] = bme.temperature; | ||
+ | doc["hum_pct"] = bme.humidity; | ||
doc["press_hpa"] = bme.pressure / 100.0; | doc["press_hpa"] = bme.pressure / 100.0; | ||
- | doc["gas_ohm"] = bme.gas_resistance; | + | doc["gas_ohm"] = bme.gas_resistance; |
+ | |||
+ | String payload; | ||
+ | serializeJson(doc, payload); | ||
- | char payload[192]; | ||
- | size_t n = serializeJson(doc, payload); | ||
String topic = String(BASE_TOPIC) + "/bme688"; | String topic = String(BASE_TOPIC) + "/bme688"; | ||
- | bool ok = mqtt.publish(topic.c_str(), payload, n); | + | bool ok = mqtt.publish(topic, payload); // QoS0, non-retained |
- | Serial.printf("Pub %s => %s (%s)\n", topic.c_str(), payload, ok ? "OK" : "FAIL"); | + | Serial.printf("Pub %s => %s (%s)\n", |
+ | topic.c_str(), payload.c_str(), ok ? "OK" : "FAIL"); | ||
} | } | ||
} | } | ||
Line 337: | Line 406: | ||
===== QoS, Retain & LWT ===== | ===== QoS, Retain & LWT ===== | ||
- | Goal here is to add production-style robustness: MQTT session properties (LWT, retain, QoS), back-off reconnects, and how to flip to TLS. | + | <note tip>Goal: add production-style robustness: MQTT session properties (LWT, retain, QoS), back-off reconnects, and how to flip to TLS. |
+ | </note> | ||
+ | |||
+ | This example turns the ESP32 into a fully interactive MQTT client that both publishes and subscribes to topics on a broker using QoS and retain messages. When the device connects, it registers itself with a unique client ID and announces its presence by publishing an “online” status message, while also defining a “last will” message that the broker will automatically send as “offline” if the connection is lost unexpectedly. The ESP32 subscribes to a specific topic for LED control commands so that it can receive JSON payloads over MQTT, parse them, and then acknowledge each command by publishing a confirmation message back to a separate acknowledgment topic. This creates a two-way communication channel where the device can be controlled remotely and confirm successful execution of commands. | ||
+ | |||
+ | At the same time, the ESP32 regularly publishes telemetry data through MQTT. It sends heartbeat messages with uptime, signal strength, and network information, and it transmits environmental sensor readings from the BME680 to a designated topic as JSON data. All MQTT communication uses Quality of Service level 1 to ensure that messages are delivered at least once, providing reliable data exchange between the device and the broker. The code also manages automatic reconnection with exponential backoff, meaning it gracefully retries connecting to the MQTT broker when disconnected without flooding the network. In essence, from the MQTT point of view, this device behaves as a resilient, bidirectional IoT client that reports data, accepts remote commands, and maintains a persistent, reliable session with the broker. | ||
<code C main.cpp> | <code C main.cpp> | ||
Line 350: | Line 424: | ||
//////////////// EDIT THESE //////////////// | //////////////// EDIT THESE //////////////// | ||
- | const char* WIFI_SSID = "TP-Link_2A64"; | + | const char* WIFI_SSID = "YOUR_SSID"; |
- | const char* WIFI_PASSWORD = "99481100"; | + | const char* WIFI_PASSWORD = "YOUR_PASSWORD"; |
const char* MQTT_HOST = "test.mosquitto.org"; // or your lab broker | const char* MQTT_HOST = "test.mosquitto.org"; // or your lab broker | ||
const uint16_t MQTT_PORT = 1883; | const uint16_t MQTT_PORT = 1883; | ||
- | const char* BASE_TOPIC = "iot/dantudose"; // change per student | + | const char* BASE_TOPIC = "iot/studentname"; // change per student |
//////////////////////////////////////////// | //////////////////////////////////////////// | ||