About

Using low-cost consumer electronics and with the help of Arduino, node-red and MQTT, it is possible to create a fairly accurate environmental beacon that periodically registers various metrics such as temperature, pressure, humidity, air quality, etc. These notes document setting up a WeMoS D1 Mini Pro along with at DHT11 temperature and humidity sensor and displaying the readings on the node-red dashboard.

Requirements

In total, the consumables for this project tie with a very low-end temperature and humidity device for home use. Nevertheless, the flexibility of IoT and especially node-red allows displaying the data in ways that a static device would not allow. Furthermore, node-red and MQTT would allow further automation to take place based on the values collected from the wireless probe that could possibly be placed remotely.

  • WeMoS D1 Mini Pro (ESP8266) - very cheap and extremely robust programmable mico-SBC priced at around USD12

  • DHT11 sensor - the DHT11 sensor is a duplex temperature and humidity sensor with a $\pm 1^{\circ}C$ error rate on the temperature readings and a $\pm 5\%RH$ error rate on the humidity readings and comes at a cost of USD4,

  • soldering equipment and dremel (for fitting in a project box)

Wiring

Assuming that the ESP8266 will be powered via the USB port, the wiring should not require additional soldering given that the DHT11 can be supplied with $3.3V$ and that the ESP8266 provides a $3.3V$ output.

Or, illustrated with the cabling in place and the ESP being powered via the USB port:

The DHT11 uses up only a single GPIO pin such that more sensors could possibly be hooked up and more environmental data obtained. Aside from mounting the WeMoS mini board and the sensor inside a prototyping enclosure, the hardware part of the project is complete.

Arduino Sketch

Perhaps the best way to write the controlling code for the ESP8266 is to allow the temperature and humidity metrics to be polled remotely via MQTT. In other words, an external application would publish a message via the MQTT bus that the ESP8266 listens for and then, in turn, the ESP8266 publishes the metrics via MQTT. One advantage of choosing this design would mainly pertain to reducing the energy consumption of the ESP8266 as well as allowing the hardware (both the ESP and the DHT) to relax instead of constantly banging the hardware.

File: http://svn.grimore.org/arduino-sketches/arduinoDHT11PollingViaMQTT/arduinoDHT11PollingViaMQTT.ino -

/*************************************************************************/
/*    Copyright (C) 2020 Wizardry and Steamworks - License: GNU GPLv3    */
/*************************************************************************/
 
// The AP to connect to via Wifi.
#define STA_SSID ""
// The AP Wifi password.
#define STA_PSK ""
// The MQTT broker to connect to.
#define MQTT_HOST ""
// The MQTT broker username.
#define MQTT_USERNAME ""
// The MQTT broker password.
#define MQTT_PASSWORD ""
// The MQTT broker port.
#define MQTT_PORT 1883
// The default MQTT client ID is "esp-CHIPID" where CHIPID is the ESP8266
// or ESP32 chip identifier.
#define MQTT_CLIENT_ID() String("esp-" + String(GET_CHIP_ID(), HEX))
// The authentication password to use for OTA updates.
#define OTA_PASSWORD ""
// The OTA port on which updates take place.
#define OTA_PORT 8266
// The default topic that the sketch subscribes to is "esp/CHIPID" where
// CHIPID is the ESP8266 or ESP32 chip identifier.
#define MQTT_TOPIC() String("esp/" + String(GET_CHIP_ID(), HEX))
 
// Platform specific defines.
#if defined(ARDUINO_ARCH_ESP8266)
#define GET_CHIP_ID() (ESP.getChipId())
#elif defined(ARDUINO_ARCH_ESP32)
#define GET_CHIP_ID() ((uint16_t)(ESP.getEfuseMac()>>32))
#endif
 
// Miscellaneous defines.
//#define CHIP_ID_HEX (String(GET_CHIP_ID()).c_str())
#define HOSTNAME() String("esp-" + String(GET_CHIP_ID(), HEX))
 
// Platform specific libraries.
#if defined(ARDUINO_ARCH_ESP8266)
#include <ESP8266WiFi.h>
#include <ESP8266mDNS.h>
#elif defined(ARDUINO_ARCH_ESP32)
#include <WiFi.h>
#include <ESPmDNS.h>
#endif
// General libraries.
#include <WiFiUdp.h>
#include <ArduinoOTA.h>
#include <PubSubClient.h>
#include <ArduinoJson.h>
#if defined(ARDUINO_ARCH_ESP32)
#include <FS.h>
#include <SPIFFS.h>
#endif
// DHT11
#include <Adafruit_Sensor.h>
#include <DHT.h>
#include <DHT_U.h>
 
const char *sta_ssid = STA_SSID;
const char *sta_psk = STA_PSK;
const char *mqtt_host = MQTT_HOST;
const char *mqtt_username = MQTT_USERNAME;
const char *mqtt_password = MQTT_PASSWORD;
const int mqtt_port = MQTT_PORT;
const char *ota_password = OTA_PASSWORD;
const int ota_port = OTA_PORT;
 
WiFiClient espClient;
PubSubClient mqttClient(espClient);
 
// Uncomment the type of sensor in use:
#define DHTTYPE    DHT11     // DHT 11
//#define DHTTYPE    DHT22     // DHT 22 (AM2302)
//#define DHTTYPE    DHT21     // DHT 21 (AM2301)
 
// Define GPIO pins for supported architectures.
#if defined(ARDUINO_ARCH_ESP8266)
int PINS[] = { D0, D1, D2, D3, D4, D5, D6, D7, D8 };
#elif defined(ARDUINO_ARCH_ESP32)
int PINS[] = { 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11,
               12, 13, 14, 15, 16, 17, 18, 19, 21, 22,
               23, 25, 26, 27, 32, 33, 34, 35, 36, 37,
               38, 39
             };
#endif
 
const char* mqttSerialize(StaticJsonDocument<256> msg) {
  char message[256];
  serializeJson(msg, message);
  return (const char*) message;
}
 
void mqttCallback(char *topic, byte *payload, unsigned int length) {
  String msgTopic = String(topic);
  // payload is not null terminated and casting will not work
  char msgPayload[length + 1];
  snprintf(msgPayload, length + 1, "%s", payload);
  Serial.println("Message received on topic: " + String(topic) + " with payload: " + String(msgPayload));
 
  // Parse the payload sent to the MQTT topic as a JSON document.
  StaticJsonDocument<256> doc;
  Serial.println("Deserializing message....");
  DeserializationError error = deserializeJson(doc, msgPayload);
  if (error) {
    Serial.println("Failed to parse MQTT payload as JSON: " + String(error.c_str()));
    return;
  }
 
  // Ignore message with no identifier in the payload.
  if (!doc.containsKey("id")) {
    return;
  }
 
  // Do not listen to self.
  String id = (const char *)doc["id"];
  if (id == String(MQTT_CLIENT_ID().c_str())) {
    return;
  }
 
  // Reject messages that do not provide a reading pin.
  if (!doc.containsKey("pin")) {
    Serial.println("MQTT message received but no pin supplied...");
    return;
  }
 
  const int pin = (const int)doc["pin"];
 
  StaticJsonDocument<256> msg;
  msg["id"] = String(MQTT_CLIENT_ID().c_str());
  DHT_Unified dht(PINS[pin], DHTTYPE);
  dht.begin();
  // Print temperature sensor details.
  sensors_event_t event;
  dht.temperature().getEvent(&event);
  switch (!isnan(event.temperature)) {
    case true:
      msg["temperature"] = event.temperature;
      break;
    default:
      Serial.println("Error reading temperature...");
      break;
  }
 
  dht.humidity().getEvent(&event);
  switch (!isnan(event.relative_humidity)) {
    case true:
      msg["humidity"] = event.relative_humidity;
      break;
    default:
      Serial.println("Error reading humidity...");
      break;
  }
  // Eeek?
  //dht.end();
 
  // Publish the sensor data.
  mqttClient.publish(MQTT_TOPIC().c_str(), mqttSerialize(msg));
}
 
bool mqttConnect() {
  Serial.println("Attempting to connect to MQTT broker: " + String(mqtt_host));
  mqttClient.setServer(mqtt_host, mqtt_port);
 
  StaticJsonDocument<255> msg;
  if (mqttClient.connect(MQTT_CLIENT_ID().c_str(), mqtt_username, mqtt_password)) {
    Serial.println("Established connection with MQTT broker using client ID: " + MQTT_CLIENT_ID());
    mqttClient.setCallback(mqttCallback);
    msg["action"] = "connected";
    mqttClient.publish(MQTT_TOPIC().c_str(), mqttSerialize(msg));
    Serial.println("Attempting to subscribe to MQTT topic: " + MQTT_TOPIC());
    if (!mqttClient.subscribe(MQTT_TOPIC().c_str())) {
      Serial.println("Failed to subscribe to MQTT topic: " + MQTT_TOPIC());
      return false;
    }
    Serial.println("Subscribed to MQTT topic: " + MQTT_TOPIC());
    msg.clear();
    msg["action"] = "subscribed";
    mqttClient.publish(MQTT_TOPIC().c_str(), mqttSerialize(msg));
    return true;
  }
  else {
    Serial.println("Connection to MQTT broker failed with MQTT client state: " + String(mqttClient.state()));
  }
 
  return false;
}
 
bool loopWifiConnected() {
  // Process OTA loop first since emergency OTA updates might be needed.
  ArduinoOTA.handle();
 
  // Process MQTT client loop.
  if (!mqttClient.connected()) {
    // If the connection to the MQTT broker has failed then sleep before carrying on.
    if (!mqttConnect()) {
      return false;
    }
  }
  mqttClient.loop();
 
  return true;
}
 
void setup() {
  Serial.begin(115200);
  Serial.println("Booted, setting up Wifi in 10s...");
  delay(10000);
 
  WiFi.mode(WIFI_STA);
#if defined(ARDUINO_ARCH_ESP8266)
  WiFi.hostname(HOSTNAME().c_str());
#elif defined(ARDUINO_ARCH_ESP32)
  WiFi.setHostname(HOSTNAME().c_str());
#endif
  WiFi.begin(sta_ssid, sta_psk);
  uint8_t result = WiFi.waitForConnectResult();
  while (result != WL_CONNECTED) {
    Serial.print("Failed to connect to Wifi: ");
    Serial.println(result);
    Serial.println("Rebooting in 5s...");
    delay(5000);
    ESP.restart();
  }
 
  Serial.print("Connected to Wifi: ");
  Serial.println(WiFi.localIP());
 
  Serial.println("Setting up OTA in 10s...");
  delay(10000);
 
  // Port defaults to 8266
  ArduinoOTA.setPort(ota_port);
 
  // Hostname defaults to esp-[ChipID]
  ArduinoOTA.setHostname(HOSTNAME().c_str());
 
  // Set the OTA password
  ArduinoOTA.setPassword(ota_password);
 
  ArduinoOTA.onStart([]() {
    switch (ArduinoOTA.getCommand()) {
      case U_FLASH: // Sketch
        Serial.println("OTA start updating sketch.");
        break;
#if defined(ARDUINO_ARCH_ESP8266)
      case U_FS:
#elif defined(ARDUINO_ARCH_ESP32)
      case U_SPIFFS:
#endif
        Serial.println("OTA start updating filesystem.");
        SPIFFS.end();
        break;
      default:
        Serial.println("Unknown OTA update type.");
        break;
    }
  });
  ArduinoOTA.onEnd([]() {
    Serial.println("OTA update complete.");
    SPIFFS.begin();
#if defined(ARDUINO_ARCH_ESP8266)
    // For what it's worth, check the filesystem on ESP8266.
    SPIFFS.check();
#endif
    ESP.restart();
  });
  ArduinoOTA.onProgress([](unsigned int progress, unsigned int total) {
    Serial.printf("OTA update progress: %u%%\r", (progress / (total / 100)));
  });
  ArduinoOTA.onError([](ota_error_t error) {
    Serial.printf("OTA update error [%u]: ", error);
    switch (error) {
      case OTA_AUTH_ERROR:
        Serial.println("OTA authentication failed");
        break;
      case OTA_BEGIN_ERROR:
        Serial.println("OTA begin failed");
        break;
      case OTA_CONNECT_ERROR:
        Serial.println("OTA connect failed");
        break;
      case OTA_RECEIVE_ERROR:
        Serial.println("OTA receive failed");
        break;
      case OTA_END_ERROR:
        Serial.println("OTA end failed");
        break;
      default:
        Serial.println("Unknown OTA failure");
        break;
    }
    ESP.restart();
  });
  ArduinoOTA.begin();
 
  // Set up MQTT client.
  mqttClient.setServer(mqtt_host, mqtt_port);
  mqttClient.setCallback(mqttCallback);
 
  // Touchdown.
  Serial.println("Setup complete.");
}
 
void loop() {
  // Check the Wifi connection status.
  int wifiStatus = WiFi.status();
  switch (wifiStatus) {
    case WL_CONNECTED:
      if (!loopWifiConnected()) {
        delay(1000);
        break;
      }
      delay(1);
      break;
    case WL_NO_SHIELD:
      Serial.println("No Wifi shield present.");
      goto DEFAULT_CASE;
      break;
    case WL_NO_SSID_AVAIL:
      Serial.println("Configured SSID not found.");
      goto DEFAULT_CASE;
      break;
    // Temporary statuses indicating transitional states.
    case WL_IDLE_STATUS:
    case WL_SCAN_COMPLETED:
      delay(1000);
      break;
    // Fatal Wifi statuses trigger a delayed ESP restart.
    case WL_CONNECT_FAILED:
    case WL_CONNECTION_LOST:
    case WL_DISCONNECTED:
    default:
      Serial.println("Wifi connection failed with status: " + String(wifiStatus));
DEFAULT_CASE:
      delay(10000);
      ESP.restart();
      break;
  }
}
 

Node Red

The Node Red uses only the external dashboard module in order to display the temperature and humidity.

There are two parts to this flow that are connected indirectly through the MQTT bus:

  • the upper half, starting with the injection node named Poll and ending with the MQTT out node, is responsible for periodically publishing a payload via MQTT that will instruct the ESP8266 to measure and then publish the current temperature and humidity values to the MQTT bus,
  • the lower half, starting with the MQTT in node receives the message from the ESP8266 and then formats the data, pushing the temperature and humidity values separately onto two different gauge dashboard displays for each of them.

It is of course possible to use a differnet type of display, perhaps a histogram showing a history of the collected metrics in time? In any case, the WeMoS and DHT can now be placed together in a box and perhaps placed in a remote area, allowing the owner to retrieve metrics from afar.

Finishing Touches and Notes

It is perhaps interesting to observe that the Arduino sketch keeps the logic to a minimum and is closely related to the Arduino pin toggling sketch. Most of the reasoning therefore and the power consumption aside is that the ESP can easily become unstable given various bugs that may lurk within the code; microleaks or small memory corruption can build up progressively and trash the stack and the heap till the ESP will end up crashing although, fortunately, the ESP will reset itself. Other reasons could include the fact that for this project only a display of the metrics was desired however in case the ESP is supposed to gather data only occasionally then there is no need to continuously bang the DHT sensors - the Poll node illustrated in the node-red section is responsible for requesting the metrics every 1 second which matches the exact resolution of the DHT of $1Hz$. Since the ESP is so compact, it is safe to assume that the ESP could run unattended and sitting in a remote location such that granting more control over the device to Node Red is a safe bet. The Arduino sketch also implements Over-The-Air (OTA) updates allowing the owner to flash the entire code via Wifi instead of having to reach inside the project box to connect directly to the WeMoS USB port.


iot/miniature_queryable_wireless_temperature_and_humidity_probe.txt ยท Last modified: 2022/04/19 08:28 by 127.0.0.1

Access website using Tor Access website using i2p Wizardry and Steamworks PGP Key


For the contact, copyright, license, warranty and privacy terms for the usage of this website please see the contact, license, privacy, copyright.