This shows you the differences between two versions of the page.
iothings:laboratoare:2025:lab3 [2025/10/11 00:09] dan.tudose [Publish Sensor Readings From BME680 as JSON] |
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 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}. |
- | + | ||
- | <code C main.cpp> | + | |
- | #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(); | + | |
- | } | + | |
- | + | ||
- | </code> | + | |
- | Test it out using [[https://dantudose.github.io/labs/lab3_2.html | this link]]. | + | |
- | + | ||
- | ===== Publish Sensor Readings From BME680 as JSON ===== | + | |
- | <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> | </note> | ||
Line 360: | Line 281: | ||
} | } | ||
+ | </code> | ||
+ | Test it out using [[https://dantudose.github.io/labs/lab3_2.html | this link]]. | ||
+ | |||
+ | ===== Publish Sensor Readings From BME680 as JSON ===== | ||
+ | <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> | ||
+ | #include <Arduino.h> | ||
+ | #include <WiFi.h> | ||
+ | #include <Wire.h> | ||
+ | #include <MQTT.h> | ||
+ | #include <ArduinoJson.h> | ||
+ | #include <Adafruit_BME680.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 | ||
+ | //////////////////////////////////////////// | ||
+ | |||
+ | // Sparrow I2C: SDA=21, SCL=22 | ||
+ | #define SDA_PIN 21 | ||
+ | #define SCL_PIN 22 | ||
+ | Adafruit_BME680 bme; // I2C | ||
+ | |||
+ | WiFiClient net; | ||
+ | MQTTClient mqtt(1024); // 1KB message buffer | ||
+ | |||
+ | 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()); | ||
+ | } | ||
+ | |||
+ | void ensureMQTT() { | ||
+ | if (mqtt.connected()) return; | ||
+ | |||
+ | // Optional Last Will so dashboards see offline state | ||
+ | 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); | ||
+ | } | ||
+ | Serial.println("\nMQTT connected"); | ||
+ | |||
+ | // Publish "online" status retained | ||
+ | mqtt.publish(willTopic, "online", true, 1); | ||
+ | } | ||
+ | |||
+ | unsigned long lastPub = 0; | ||
+ | |||
+ | void setup() { | ||
+ | Serial.begin(115200); | ||
+ | delay(200); | ||
+ | |||
+ | Wire.begin(SDA_PIN, SCL_PIN); | ||
+ | |||
+ | // MQTT broker + transport | ||
+ | mqtt.begin(MQTT_HOST, MQTT_PORT, net); | ||
+ | |||
+ | if (!bme.begin(0x76)) { // Sparrow uses 0x76 | ||
+ | Serial.println("BME688 not found!"); | ||
+ | for(;;) { delay(1000); } | ||
+ | } | ||
+ | // Reasonable oversampling; heater off for simplicity | ||
+ | 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); // off | ||
+ | } | ||
+ | |||
+ | void loop() { | ||
+ | ensureWiFi(); | ||
+ | ensureMQTT(); | ||
+ | |||
+ | mqtt.loop(); // process incoming/keepalive | ||
+ | delay(10); | ||
+ | |||
+ | if (millis() - lastPub > 10000) { | ||
+ | lastPub = millis(); | ||
+ | |||
+ | if (!bme.performReading()) { | ||
+ | Serial.println("BME read failed"); | ||
+ | return; | ||
+ | } | ||
+ | |||
+ | // Build JSON (ArduinoJson v7 style, no deprecated StaticJsonDocument) | ||
+ | JsonDocument doc; | ||
+ | doc["ts"] = (uint32_t)(millis() / 1000); | ||
+ | doc["temp_c"] = bme.temperature; | ||
+ | doc["hum_pct"] = bme.humidity; | ||
+ | doc["press_hpa"] = bme.pressure / 100.0; | ||
+ | doc["gas_ohm"] = bme.gas_resistance; | ||
+ | |||
+ | String payload; | ||
+ | serializeJson(doc, payload); | ||
+ | |||
+ | String topic = String(BASE_TOPIC) + "/bme688"; | ||
+ | bool ok = mqtt.publish(topic, payload); // QoS0, non-retained | ||
+ | Serial.printf("Pub %s => %s (%s)\n", | ||
+ | topic.c_str(), payload.c_str(), ok ? "OK" : "FAIL"); | ||
+ | } | ||
+ | } | ||
</code> | </code> | ||
Line 366: | 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 379: | 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 |
//////////////////////////////////////////// | //////////////////////////////////////////// | ||