This shows you the differences between two versions of the page.
iothings:laboratoare:2025:lab3 [2025/10/11 00:14] dan.tudose [Subscribe & control the on-board NeoPixel] |
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 287: | Line 288: | ||
</note> | </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 292: | 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 309: | 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 316: | 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 322: | 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 333: | 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 351: | Line 371: | ||
ensureWiFi(); | ensureWiFi(); | ||
ensureMQTT(); | ensureMQTT(); | ||
- | mqtt.loop(); | + | |
+ | mqtt.loop(); // process incoming/keepalive | ||
+ | delay(10); | ||
if (millis() - lastPub > 10000) { | if (millis() - lastPub > 10000) { | ||
Line 360: | 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 380: | 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 393: | 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 |
//////////////////////////////////////////// | //////////////////////////////////////////// | ||