This is an old revision of the document!


Lab 3. The MQTT Protocol

MQTT “Hello World” (publish a heartbeat)

Goal: connect to Wi-Fi, connect to MQTT, then publish a heartbeat every 5s to iot/<yourname>/heartbeat.

main.cpp
// src/main.cpp
#include <Arduino.h>
#include <WiFi.h>
#include <PubSubClient.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
////////////////////////////////////////////
 
WiFiClient espClient;
PubSubClient mqtt(espClient);
 
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\n", WiFi.localIP().toString().c_str());
}
 
void ensureMQTT() {
  if (mqtt.connected()) return;
  String clientId = String("sparrow-c6-") + String((uint32_t)ESP.getEfuseMac(), HEX);
  Serial.println("MQTT connecting...");
  Serial.println("Client ID:"); 
  Serial.print(clientId);
 
  while (!mqtt.connect(clientId.c_str())) {
    Serial.print(".");
    delay(1000);
  }
  Serial.println("\nMQTT connected");
}
 
unsigned long lastPub = 0;
 
void setup() {
  Serial.begin(115200);
  delay(1000);
  WiFi.useStaticBuffers(true);   // small memory win on C6
  mqtt.setServer(MQTT_HOST, MQTT_PORT);
}
 
void loop() {
  ensureWiFi();
  ensureMQTT();
  mqtt.loop();
 
  if (millis() - lastPub > 5000) {
    lastPub = millis();
    String topic = String(BASE_TOPIC) + "/heartbeat";
    String payload = String("{\"uptime_ms\":") + millis() + "}";
    bool ok = mqtt.publish(topic.c_str(), payload.c_str());
    Serial.printf("Publish %s => %s (%s)\n", topic.c_str(), payload.c_str(), ok ? "OK" : "FAIL");
  }
}

Test it out using this link.

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}.

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();
}

Test it out using this link.

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.

main.cpp
#include <Arduino.h>
#include <WiFi.h>
#include <Wire.h>
#include <PubSubClient.h>
#include <ArduinoJson.h>
#include <Adafruit_BME680.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
////////////////////////////////////////////
 
// Sparrow I2C: SDA=21, SCL=22
#define SDA_PIN 21
#define SCL_PIN 22
Adafruit_BME680 bme;  // I2C
 
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 ensureMQTT() {
  if (mqtt.connected()) return;
  while (!mqtt.connected()) {
    String cid = String("sparrow-c6-sense-") + String((uint32_t)ESP.getEfuseMac(), HEX);
    if (mqtt.connect(cid.c_str())) break;
    delay(1000);
  }
}
 
unsigned long lastPub = 0;
 
void setup() {
  Serial.begin(115200);
  Wire.begin(SDA_PIN, SCL_PIN);
  mqtt.setServer(MQTT_HOST, MQTT_PORT);
 
  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();
 
  if (millis() - lastPub > 10000) {
    lastPub = millis();
 
    if (!bme.performReading()) {
      Serial.println("BME read failed");
      return;
    }
    StaticJsonDocument<192> 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;
 
    char payload[192];
    size_t n = serializeJson(doc, payload);
    String topic = String(BASE_TOPIC) + "/bme688";
    bool ok = mqtt.publish(topic.c_str(), payload, n);
    Serial.printf("Pub %s => %s (%s)\n", topic.c_str(), payload, ok ? "OK" : "FAIL");
  }
}

Test it out using this link.

QoS, Retain & LWT

main.cpp
#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.

iothings/laboratoare/2025/lab3.1760128648.txt.gz · Last modified: 2025/10/10 23:37 by dan.tudose
CC Attribution-Share Alike 3.0 Unported
www.chimeric.de Valid CSS Driven by DokuWiki do yourself a favour and use a real browser - get firefox!! Recent changes RSS feed Valid XHTML 1.0