Dabbling in Arduino with MQTT

A bit of a detour from the other self hosted stuff, but still related. On a whim I bought an electronics kit from a well known Chinese online retailer.  The aim being to share the environmental status of an outbuilding (without having to leave the house). It was cheap & it does work, but the instructions were a little sparse.

Well, when I say sparse, I mean non-existent! That's OK, because we have the internet & people who are willing to share their knowledge & create tutorials which they no longer need.

I wanted to merge a few of those tutorials into a single "all singing" version, so I thought that I might as well put it "out there". The aim was to have the sensors read, shared to the wifi & ultimately presented on an integrated dashboard.

I'm not affiliated, or otherwise linked, but if you want to just order the kits & build them then these are the ones that I bought. (I think that you can get them elsewhere too).

Geekcreit WiFi ESP8266 Starter Kit IoT NodeMCU Wireless I2C OLED Display DHT11 Temperature Humidity Sensor Module
Only US$12.95, buy best geekcreit wifi esp8266 starter kit iot nodemcu wireless i2c oled display dht11 temperature humidity sensor module sale online store at wholesale price.
AOQDQDQD® ESP8266 Weather Station Kit with Temperature Humidity Atmosphetic Pressure Light Sensor 0.96 Display for Arduino IDE IoT Starter
Only US$12.99, buy best aoqdqdqd® esp8266 weather station kit with temperature humidity atmosphetic pressure light sensor 0.96 display for arduino ide iot starter sale online store at wholesale price.

You'll need a couple of reference points (& at this stage it's also well worth me thinking randomnerdtutorials for a lot of the basic info & sketches involved in the rest of this.

First of all you will need to know which pins on the board map to the GPIO numbers that get used in the sketches.

You'll also need an MQTT broker set up & ready to receive the sensor data, I'm not going to talk you through that or the dashboard, there are lots of resources/pre-built set ups out there to help you if you need it. Again, randomnerstutorials has done most of the work for me with the DHT11 sensor sketch also providing a built in web server that lets you see the data with nothing more than your WiFi & a web browser.

Wiring

Use the jumper cables to connect pins on the ESP8266 to the DHT11 as follows
* Connect the VCC (or "+") pin on the DHT11 to a pin on the ESP8266 board that says 3V
* Connect the GND (or "-") pin on the DHT11 to a pin on the ESP8266 board that says G
* Connect the data (or "out") pin on the DHT11 to pin D3 on the ESP8266 board

You need to connect the OLED screen to the board too:
* Connect the VCC pin on the screen to a pin on the ESP8266 board that says 3V
* Connect the GND pin on the screen to a pin on the ESP8266 board that says G
* Connect the SCL pin on the screen to the D1 pin on the ESP8266 board (see the pin reference graphic above)
* Connect the SDA on the screen to the D2 pin on the ESP8266 board (see the pin reference graphic above)

That's all the wiring apart from the microUSB!

Programming

You'll need the arduino IDE, & will need to install the support for the ESP8266 board that you're using. I needed to go to File/Preferences & add "http://arduino.esp8266.com/stable/package_esp8266com_index.json" to the additional boards manager within the Arduino IDE.

There are several libraries which will be needed (Aduino IDE, tools menu, manage libraries). These are the extra ones that I have installed
* Adafruit BMP085 & BMP085 Unified (Needed for the temperature & pressure sensor)
* Adafruit GFX library & Adafruit SSD1306 (both for the 1306 screen)
* Adafruit Unified Sensor
* Adafruit DHT sensor library
* BH1750 by Christopher Laws
* hp_BH1750
* ESP Async Web Server library (requires the ESP Async TCP library)
* ESP8266 & ESP32 OLED driver for SSD1306 displays (thingpulse version of the Adafruit drivers)

Let's start with the simplest sketch. This one doesn't use the screen, but does broadcast to the MQTT broker & runs a web server that lets you see the readings. You'll find that by connecting to the IP address of the ESP8266 (which is probably easiest to find by checking your router). There are plently of examples of just using the screen & the DHT11, so I'm not bothering with that here. (You could use the second sketch & either ignore or remove the MQTT parts if you want)

/*********
  Based on  https://randomnerdtutorials.com/esp8266-dht11dht22-temperature-and-humidity-web-server-with-arduino-ide/
*********/

// Import required libraries
#include <Arduino.h>
#include <ESP8266WiFi.h>
#include <Hash.h>
#include <ESPAsyncTCP.h>
#include <ESPAsyncWebServer.h>
#include <Adafruit_Sensor.h>
#include <DHT.h>
#include <Ticker.h>
#include <AsyncMqttClient.h>

// Replace with your network credentials
const char* ssid = "MY_WIFI_SSID";
const char* password = "MY_WIFI_PASSWORD";

// TOPIC_BASE is changed to identify different sensor units (locations)
#define TOPIC_BASE "8266"

#define DHTPIN 0     // Digital pin connected to the DHT sensor (marked D3 on the board!)

// Uncomment the type of sensor in use:
#define DHTTYPE    DHT11     // DHT 11
//#define DHTTYPE    DHT22     // DHT 22 (AM2302)
//#define DHTTYPE    DHT21     // DHT 21 (AM2301)

//  Mosquitto MQTT Broker
// Replace xxx & yyy with the numbers that match the IP address of your MQTT broker
#define MQTT_HOST IPAddress(192, 168, xxx, yyy)
// For a cloud MQTT broker, type the domain name
//#define MQTT_HOST "example.com"
#define MQTT_PORT 1883

// Temperature MQTT Topics

#define MQTT_PUB_TEMP TOPIC_BASE "/dht/temperature"
#define MQTT_PUB_HUM TOPIC_BASE "/dht/humidity"

// End
// Of
// Definitions

// Initialize DHT sensor
DHT dht(DHTPIN, DHTTYPE);

// Variables to hold sensor readings
// current temperature & humidity, updated in loop()
float t = 0.0;
float h = 0.0;

AsyncMqttClient mqttClient;
Ticker mqttReconnectTimer;

WiFiEventHandler wifiConnectHandler;
WiFiEventHandler wifiDisconnectHandler;
Ticker wifiReconnectTimer;

// Create AsyncWebServer object on port 80
AsyncWebServer server(80);

// Generally, you should use "unsigned long" for variables that hold time
// The value will quickly become too large for an int to store
unsigned long previousMillis = 0;    // will store last time DHT was updated

// Updates DHT readings every 10 seconds
const long interval = 10000;

void connectToWifi() {
  Serial.println("Connecting to Wi-Fi...");
  WiFi.begin(ssid, password);
}

void onWifiConnect(const WiFiEventStationModeGotIP& event) {
  Serial.println("Connected to Wi-Fi.");
  connectToMqtt();
}

void onWifiDisconnect(const WiFiEventStationModeDisconnected& event) {
  Serial.println("Disconnected from Wi-Fi.");
  mqttReconnectTimer.detach(); // ensure we don't reconnect to MQTT while reconnecting to Wi-Fi
  wifiReconnectTimer.once(2, connectToWifi);
}

void connectToMqtt() {
  Serial.println("Connecting to MQTT...");
  mqttClient.connect();
}

void onMqttConnect(bool sessionPresent) {
  Serial.println("Connected to MQTT.");
  Serial.print("Session present: ");
  Serial.println(sessionPresent);
}

void onMqttDisconnect(AsyncMqttClientDisconnectReason reason) {
  Serial.println("Disconnected from MQTT.");

  if (WiFi.isConnected()) {
    mqttReconnectTimer.once(2, connectToMqtt);
  }
}

void onMqttPublish(uint16_t packetId) {
  Serial.print("Publish acknowledged.");
  Serial.print("  packetId: ");
  Serial.println(packetId);
}

const char index_html[] PROGMEM = R"rawliteral(
<!DOCTYPE HTML><html>
<head>
  <meta name="viewport" content="width=device-width, initial-scale=1">
  <link rel="stylesheet" href="https://use.fontawesome.com/releases/v5.7.2/css/all.css" integrity="sha384-fnmOCqbTlWIlj8LyTjo7mOUStjsKC4pOpQbqyi7RrhN7udi9RwhKkMHpvLbHG9Sr" crossorigin="anonymous">
  <style>
    html {
     font-family: Arial;
     display: inline-block;
     margin: 0px auto;
     text-align: center;
    }
    h2 { font-size: 3.0rem; }
    p { font-size: 3.0rem; }
    .units { font-size: 1.2rem; }
    .dht-labels{
      font-size: 1.5rem;
      vertical-align:middle;
      padding-bottom: 15px;
    }
  </style>
</head>
<body>
  <h2>ESP8266 DHT Server</h2>
  <p>
    <i class="fas fa-thermometer-half" style="color:#059e8a;"></i> 
    <span class="dht-labels">Temperature</span> 
    <span id="temperature">%TEMPERATURE%</span>
    <sup class="units">&deg;C</sup>
  </p>
  <p>
    <i class="fas fa-tint" style="color:#00add6;"></i> 
    <span class="dht-labels">Humidity</span>
    <span id="humidity">%HUMIDITY%</span>
    <sup class="units">%</sup>
  </p>
</body>
<script>
setInterval(function ( ) {
  var xhttp = new XMLHttpRequest();
  xhttp.onreadystatechange = function() {
    if (this.readyState == 4 && this.status == 200) {
      document.getElementById("temperature").innerHTML = this.responseText;
    }
  };
  xhttp.open("GET", "/temperature", true);
  xhttp.send();
}, 10000 ) ;

setInterval(function ( ) {
  var xhttp = new XMLHttpRequest();
  xhttp.onreadystatechange = function() {
    if (this.readyState == 4 && this.status == 200) {
      document.getElementById("humidity").innerHTML = this.responseText;
    }
  };
  xhttp.open("GET", "/humidity", true);
  xhttp.send();
}, 10000 ) ;
</script>
</html>)rawliteral";

// Replaces placeholder with DHT values
String processor(const String& var) {
  //Serial.println(var);
  if (var == "TEMPERATURE") {
    return String(t);
  }
  else if (var == "HUMIDITY") {
    return String(h);
  }
  return String();
}

void setup() {
  // Serial port for debugging purposes
  Serial.begin(9600);
  Serial.println();
  dht.begin();

  wifiConnectHandler = WiFi.onStationModeGotIP(onWifiConnect);
  wifiDisconnectHandler = WiFi.onStationModeDisconnected(onWifiDisconnect);

  mqttClient.onConnect(onMqttConnect);
  mqttClient.onDisconnect(onMqttDisconnect);
  mqttClient.onPublish(onMqttPublish);
  mqttClient.setServer(MQTT_HOST, MQTT_PORT);
  // If your broker requires authentication (username and password), set them below
  //mqttClient.setCredentials("REPlACE_WITH_YOUR_USER", "REPLACE_WITH_YOUR_PASSWORD");

  connectToWifi();

  // Print ESP8266 Local IP Address
  Serial.println(WiFi.localIP());

  // Route for root / web page
  server.on("/", HTTP_GET, [](AsyncWebServerRequest * request) {
    request->send_P(200, "text/html", index_html, processor);
  });
  server.on("/temperature", HTTP_GET, [](AsyncWebServerRequest * request) {
    request->send_P(200, "text/plain", String(t).c_str());
  });
  server.on("/humidity", HTTP_GET, [](AsyncWebServerRequest * request) {
    request->send_P(200, "text/plain", String(h).c_str());
  });

  // Start server
  server.begin();
}

void loop() {
  unsigned long currentMillis = millis();
  if (currentMillis - previousMillis >= interval) {
    // save the last time you updated the DHT values
    previousMillis = currentMillis;
    // Read temperature as Celsius (the default)
    float newT = dht.readTemperature();
    // Read temperature as Fahrenheit (isFahrenheit = true)
    //float newT = dht.readTemperature(true);
    // if temperature read failed, don't change t value
    if (isnan(newT)) {
      Serial.println("Failed to read from DHT sensor!");
    }
    else {
      t = newT;
      Serial.println(t);
      // Publish an MQTT message on topic MQTT_PUB_TEMP
      uint16_t packetIdPub1 = mqttClient.publish(MQTT_PUB_TEMP, 1, true, String(t).c_str());
      Serial.printf("Publishing on topic %s at QoS 1, packetId: %i ", MQTT_PUB_TEMP, packetIdPub1);
      Serial.printf("Message: %.2f \n", t);
    }
    // Read Humidity
    float newH = dht.readHumidity();
    // if humidity read failed, don't change h value
    if (isnan(newH)) {
      Serial.println("Failed to read from DHT sensor!");
    }
    else {
      h = newH;
      Serial.println(h);
      // Publish an MQTT message on topic MQTT_PUB_HUM
      uint16_t packetIdPub2 = mqttClient.publish(MQTT_PUB_HUM, 1, true, String(h).c_str());
      Serial.printf("Publishing on topic %s at QoS 1, packetId %i: ", MQTT_PUB_HUM, packetIdPub2);
      Serial.printf("Message: %.2f \n", h);
    }
  }
}

The second version of the sketch is pretty much the same thing, but as well as sharing the readings via MQTT & the web page this time we also send the results to the OLED. A chunk of the code came from this page (with some corrections/edits). You'll note that you need to add some libraries to the Arduino IDE (Adafruit_GFX & Adafruit_SSD1306) in order to get the screen running.

/*********
  Based on  https://randomnerdtutorials.com/esp8266-dht11dht22-temperature-and-humidity-web-server-with-arduino-ide/
*********/

// Import required libraries
#include <Arduino.h>
#include <ESP8266WiFi.h>
#include <Hash.h>
#include <ESPAsyncTCP.h>
#include <ESPAsyncWebServer.h>
#include <Adafruit_Sensor.h>
#include <DHT.h>
#include <Ticker.h>
#include <AsyncMqttClient.h>
// Extra for display
#include <Wire.h>
#include <Adafruit_GFX.h>
#include <Adafruit_SSD1306.h>

// Replace with your network credentials
const char* ssid = "MY_WIFI_SSID";
const char* password = "MY_WIFI_PASSWORD";

// TOPIC_BASE is changed to identify different sensor units (locations)
#define TOPIC_BASE "office"

#define DHTPIN 0         // Digital pin connected to the DHT sensor (marked D3 on the board!)
#define SCREEN_WIDTH 128 // OLED display width, in pixels
#define SCREEN_HEIGHT 64 // OLED display height, in pixels
#define SCREEN_ADDRESS 0x3C // For the Geekcreit 1306 displays
#define OLED_RESET -1

// Uncomment the type of sensor in use:
#define DHTTYPE    DHT11     // DHT 11
//#define DHTTYPE    DHT22     // DHT 22 (AM2302)
//#define DHTTYPE    DHT21     // DHT 21 (AM2301)

//  Mosquitto MQTT Broker
// Replace xxx & yyy with the numbers that match the IP address of your MQTT broker
#define MQTT_HOST IPAddress(192, 168, xxx, yyy)
// For a cloud MQTT broker, type the domain name
//#define MQTT_HOST "example.com"
#define MQTT_PORT 1883

// Temperature MQTT Topics
#define MQTT_PUB_TEMP TOPIC_BASE "/dht/temperature"
#define MQTT_PUB_HUM TOPIC_BASE "/dht/humidity"

// Initialize DHT sensor
DHT dht(DHTPIN, DHTTYPE);

//Initialise display
// SDA = pin D2
// SCL = pin D1
Adafruit_SSD1306 display(SCREEN_WIDTH, SCREEN_HEIGHT, &Wire, OLED_RESET);

// Variables to hold sensor readings
// current temperature & humidity, updated in loop()
float t = 0.0;
float h = 0.0;
//bool d = true;

AsyncMqttClient mqttClient;
Ticker mqttReconnectTimer;

WiFiEventHandler wifiConnectHandler;
WiFiEventHandler wifiDisconnectHandler;
Ticker wifiReconnectTimer;

// Create AsyncWebServer object on port 80
AsyncWebServer server(80);

// Generally, you should use "unsigned long" for variables that hold time
// The value will quickly become too large for an int to store
unsigned long previousMillis = 0;    // will store last time DHT was updated

// Updates DHT readings every 10 seconds
const long interval = 10000;

void connectToWifi() {
  Serial.println("Connecting to Wi-Fi...");
  WiFi.begin(ssid, password);
}

void onWifiConnect(const WiFiEventStationModeGotIP& event) {
  Serial.println("Connected to Wi-Fi.");
  connectToMqtt();
}

void onWifiDisconnect(const WiFiEventStationModeDisconnected& event) {
  Serial.println("Disconnected from Wi-Fi.");
  mqttReconnectTimer.detach(); // ensure we don't reconnect to MQTT while reconnecting to Wi-Fi
  wifiReconnectTimer.once(2, connectToWifi);
}

void connectToMqtt() {
  Serial.println("Connecting to MQTT...");
  mqttClient.connect();
}

void onMqttConnect(bool sessionPresent) {
  Serial.println("Connected to MQTT.");
  Serial.print("Session present: ");
  Serial.println(sessionPresent);
}

void onMqttDisconnect(AsyncMqttClientDisconnectReason reason) {
  Serial.println("Disconnected from MQTT.");

  if (WiFi.isConnected()) {
    mqttReconnectTimer.once(2, connectToMqtt);
  }
}

void onMqttPublish(uint16_t packetId) {
  Serial.print("Publish acknowledged.");
  Serial.print("  packetId: ");
  Serial.println(packetId);
}

const char index_html[] PROGMEM = R"rawliteral(
<!DOCTYPE HTML><html>
<head>
  <meta name="viewport" content="width=device-width, initial-scale=1">
  <link rel="stylesheet" href="https://use.fontawesome.com/releases/v5.7.2/css/all.css" integrity="sha384-fnmOCqbTlWIlj8LyTjo7mOUStjsKC4pOpQbqyi7RrhN7udi9RwhKkMHpvLbHG9Sr" crossorigin="anonymous">
  <style>
    html {
     font-family: Arial;
     display: inline-block;
     margin: 0px auto;
     text-align: center;
    }
    h2 { font-size: 3.0rem; }
    p { font-size: 3.0rem; }
    .units { font-size: 1.2rem; }
    .dht-labels{
      font-size: 1.5rem;
      vertical-align:middle;
      padding-bottom: 15px;
    }
  </style>
</head>
<body>
  <h2>ESP8266 DHT Server</h2>
  <p>
    <i class="fas fa-thermometer-half" style="color:#059e8a;"></i> 
    <span class="dht-labels">Temperature</span> 
    <span id="temperature">%TEMPERATURE%</span>
    <sup class="units">&deg;C</sup>
  </p>
  <p>
    <i class="fas fa-tint" style="color:#00add6;"></i> 
    <span class="dht-labels">Humidity</span>
    <span id="humidity">%HUMIDITY%</span>
    <sup class="units">%</sup>
  </p>
</body>
<script>
setInterval(function ( ) {
  var xhttp = new XMLHttpRequest();
  xhttp.onreadystatechange = function() {
    if (this.readyState == 4 && this.status == 200) {
      document.getElementById("temperature").innerHTML = this.responseText;
    }
  };
  xhttp.open("GET", "/temperature", true);
  xhttp.send();
}, 10000 ) ;

setInterval(function ( ) {
  var xhttp = new XMLHttpRequest();
  xhttp.onreadystatechange = function() {
    if (this.readyState == 4 && this.status == 200) {
      document.getElementById("humidity").innerHTML = this.responseText;
    }
  };
  xhttp.open("GET", "/humidity", true);
  xhttp.send();
}, 10000 ) ;
</script>
</html>)rawliteral";

// Replaces placeholder with DHT values
String processor(const String& var) {
  //Serial.println(var);
  if (var == "TEMPERATURE") {
    return String(t);
  }
  else if (var == "HUMIDITY") {
    return String(h);
  }
  return String();
}

void showTemp(float temp,float hud) {
  //display.drawBitmap(0, 5,  img, 48, 50, 1)
//  if (d = true) {
    display.setTextSize(1);
    display.setCursor(3,0);
    display.println("Temperature/Humidity");
    display.setTextSize(2);
    display.setTextColor(WHITE);
    display.setCursor(52,10);
    display.print(temp);
    display.println("C");
    display.setCursor(52,30);
    display.print(hud);
    display.println("%");
    display.setTextSize(1);
    display.setCursor(52,50);
    display.println("Macklin.co");
    display.display();
    display.clearDisplay();
//  }
}

void setup() {
  // Serial port for debugging purposes
  Serial.begin(9600);
  Serial.println("Starting up...");
  // Start the DHT11 sensor readings
  dht.begin();
  // Start up the display
  if(!display.begin(SSD1306_SWITCHCAPVCC, SCREEN_ADDRESS)) {
    Serial.println(F("SSD1306 allocation failed"));
    //d = false;
    // for(;;); // Don't proceed, loop forever
  }  else {
    // Show initial display buffer contents on the screen --
    // the library initializes this with an Adafruit splash screen.
    display.display();
    delay(2000); // Pause for 2 seconds
  
    // Clear the buffer
    display.clearDisplay();
  }
  

  wifiConnectHandler = WiFi.onStationModeGotIP(onWifiConnect);
  wifiDisconnectHandler = WiFi.onStationModeDisconnected(onWifiDisconnect);

  mqttClient.onConnect(onMqttConnect);
  mqttClient.onDisconnect(onMqttDisconnect);
  //mqttClient.onSubscribe(onMqttSubscribe);
  //mqttClient.onUnsubscribe(onMqttUnsubscribe);
  mqttClient.onPublish(onMqttPublish);
  mqttClient.setServer(MQTT_HOST, MQTT_PORT);
  // If your broker requires authentication (username and password), set them below
  //mqttClient.setCredentials("REPlACE_WITH_YOUR_USER", "REPLACE_WITH_YOUR_PASSWORD");

  connectToWifi();

  // Print ESP8266 Local IP Address
  Serial.println(WiFi.localIP());

  // Route for root / web page
  server.on("/", HTTP_GET, [](AsyncWebServerRequest * request) {
    request->send_P(200, "text/html", index_html, processor);
  });
  server.on("/temperature", HTTP_GET, [](AsyncWebServerRequest * request) {
    request->send_P(200, "text/plain", String(t).c_str());
  });
  server.on("/humidity", HTTP_GET, [](AsyncWebServerRequest * request) {
    request->send_P(200, "text/plain", String(h).c_str());
  });

  // Start server
  server.begin();
}

void loop() {
  unsigned long currentMillis = millis();
  if (currentMillis - previousMillis >= interval) {
    // save the last time you updated the DHT values
    
//    showTemp(t,h);   // show temp on screen
    
    previousMillis = currentMillis;
    // Read temperature as Celsius (the default)
    float newT = dht.readTemperature();
    // Read temperature as Fahrenheit (isFahrenheit = true)
    //float newT = dht.readTemperature(true);
    // if temperature read failed, don't change t value
    if (isnan(newT)) {
      Serial.println("Failed to read from DHT sensor!");
    }
    else {
      t = newT;
      Serial.println(t);
      // Publish an MQTT message on topic MQTT_PUB_TEMP
      uint16_t packetIdPub1 = mqttClient.publish(MQTT_PUB_TEMP, 1, true, String(t).c_str());
      Serial.printf("Publishing on topic %s at QoS 1, packetId: %i ", MQTT_PUB_TEMP, packetIdPub1);
      Serial.printf("Message: %.2f \n", t);
    }
    // Read Humidity
    float newH = dht.readHumidity();
    // if humidity read failed, don't change h value
    if (isnan(newH)) {
      Serial.println("Failed to read from DHT sensor!");
    }
    else {
      h = newH;
      Serial.println(h);
      // Publish an MQTT message on topic MQTT_PUB_HUM
      uint16_t packetIdPub2 = mqttClient.publish(MQTT_PUB_HUM, 1, true, String(h).c_str());
      Serial.printf("Publishing on topic %s at QoS 1, packetId %i: ", MQTT_PUB_HUM, packetIdPub2);
      Serial.printf("Message: %.2f \n", h);
    }
    showTemp(t,h);   // show temp on screen  
  }
}

Lastly & by far the longest is the script for the full sensor kit, which includes the light sensor & the pressure sensor. I'll put that on another page!