Cirkit Designer Logo
Cirkit Designer
Your all-in-one circuit design IDE
Home / 
Project Documentation

ESP8266 NodeMCU-Based Environmental Monitoring System with Wi-Fi Connectivity

Image of ESP8266 NodeMCU-Based Environmental Monitoring System with Wi-Fi Connectivity

Circuit Documentation

Summary

The circuit in question is designed to monitor environmental parameters such as temperature, humidity, gas levels, and also includes health monitoring features like heart rate and blood oxygen saturation (SpO2) measurement. The central component of the circuit is an ESP8266 NodeMCU microcontroller, which interfaces with various sensors and modules to collect data and potentially alert the user through a buzzer or vibration motor. The ESP8266 NodeMCU is also equipped with Wi-Fi capabilities, allowing it to connect to the internet and use the Blynk platform for data visualization and remote monitoring.

Component List

ESP8266 NodeMCU

  • Microcontroller with Wi-Fi capability
  • Pins: D0, D1, D2, D3, D4, 3V3, GND, D5, D6, D7, D8, RX, TX, A0, RSV, SD3, SD2, SD1, CMD, SD0, CLK, EN, RST, VIN

DHT11

  • Temperature and humidity sensor
  • Pins: DATA, GND, VCC

MQ6

  • Gas sensor for LPG, butane, propane, methane, alcohol, hydrogen, and smoke
  • Pins: VCC, GND, A0, DO

Buzzer Module

  • Audio signaling device
  • Pins: GND, Vcc, I/O

PWM Vibration Motor Sensor Module Switch

  • Vibration motor for haptic feedback
  • Pins: Signal, Vcc, Ground

MAX30102 HR + SpO2 Sensor

  • Heart rate and blood oxygen saturation sensor
  • Pins: INT, IRD, RD, GND, SCL, SDA, VIN

Polymer Lithium Ion Battery - 850mAh

  • Power source for the circuit
  • Pins: GND, VCC

Wiring Details

ESP8266 NodeMCU

  • D1 -> MAX30102 HR + SpO2 Sensor (SCL)
  • D2 -> MAX30102 HR + SpO2 Sensor (SDA)
  • D5 -> DHT11 (DATA)
  • D6 -> PWM Vibration Motor Sensor Module Switch (Signal)
  • D7 -> Buzzer Module (I/O)
  • A0 -> MQ6 (A0)
  • GND -> Common ground with all components
  • VIN -> Common VCC with all components

DHT11

  • DATA -> ESP8266 NodeMCU (D5)
  • GND -> Common ground
  • VCC -> Common VCC

MQ6

  • A0 -> ESP8266 NodeMCU (A0)
  • GND -> Common ground
  • VCC -> Common VCC

Buzzer Module

  • I/O -> ESP8266 NodeMCU (D7)
  • GND -> Common ground
  • Vcc -> Common VCC

PWM Vibration Motor Sensor Module Switch

  • Signal -> ESP8266 NodeMCU (D6)
  • Vcc -> Common VCC
  • Ground -> Common ground

MAX30102 HR + SpO2 Sensor

  • SCL -> ESP8266 NodeMCU (D1)
  • SDA -> ESP8266 NodeMCU (D2)
  • GND -> Common ground
  • VIN -> Common VCC

Polymer Lithium Ion Battery - 850mAh

  • GND -> Common ground
  • VCC -> Common VCC

Documented Code

Main Sketch (sketch.ino)

// Blynk and WiFi credentials
#define BLYNK_TEMPLATE_ID "TMPL308E2KguH"
#define BLYNK_TEMPLATE_NAME "Tempruture and Humidity"
#define BLYNK_AUTH_TOKEN "Lt4kGTEKtDbmMKvszIJ2ACijS7lG3LpN"

#define BLYNK_PRINT Serial
#include <DHT.h>
#include <Wire.h>
#include <Blynk.h>
#include <ESP8266WiFi.h>
#include <BlynkSimpleEsp8266.h>

#include <MAX3010x.h>
#include "filters.h"

char auth[] = BLYNK_AUTH_TOKEN;
char ssid[] = "Ananda"; // Change your Wifi/ Hotspot Name
char pass[] = "123456789"; // Change your Wifi/ Hotspot Password

BlynkTimer timer;

// DHT sensor configuration
#define DHTPIN D5
#define DHTTYPE DHT11

int motorPin = D6; // vibration Motor
DHT dht(DHTPIN, DHTTYPE);

#define MQ5 A0
#define buzzer  D7
int MQ5_Val = 0;

WidgetLED led(V8);

// MAX30102 sensor configuration
MAX30102 sensor;
const auto kSamplingRate = sensor.SAMPLING_RATE_400SPS;
const float kSamplingFrequency = 400.0;

// Heartbeat detection parameters
const unsigned long kFingerThreshold = 10000;
const unsigned int kFingerCooldownMs = 500;
const float kEdgeThreshold = -2000.0;

// Filters configuration
const float kLowPassCutoff = 5.0;
const float kHighPassCutoff = 0.5;

// Averaging configuration
const bool kEnableAveraging = false;
const int kAveragingSamples = 5;
const int kSampleThreshold = 5;

void setup() {
  Serial.begin(9600);
  Blynk.begin(auth, ssid, pass);

  pinMode(motorPin, OUTPUT);
  pinMode(buzzer, OUTPUT);

  if(sensor.begin() && sensor.setSamplingRate(kSamplingRate)) {
    Serial.println("Sensor initialized");
  } else {
    Serial.println("Sensor not found");
    while(1);
  }

  dht.begin();
  timer.setInterval(1000L, sendSensor);
  delay(2000);
  timer.setInterval(1000L, mySensor);
}

// Filter Instances
LowPassFilter low_pass_filter_red(kLowPassCutoff, kSamplingFrequency);
LowPassFilter low_pass_filter_ir(kLowPassCutoff, kSamplingFrequency);
HighPassFilter high_pass_filter(kHighPassCutoff, kSamplingFrequency);
Differentiator differentiator(kSamplingFrequency);
MovingAverageFilter<kAveragingSamples> averager_bpm;
MovingAverageFilter<kAveragingSamples> averager_r;
MovingAverageFilter<kAveragingSamples> averager_spo2;

// Statistic for pulse oximetry
MinMaxAvgStatistic stat_red;
MinMaxAvgStatistic stat_ir;

// R value to SpO2 calibration factors
float kSpO2_A = 1.5958422;
float kSpO2_B = -34.6596622;
float kSpO2_C = 112.6898759;

// Timestamps for heartbeat and finger detection
long last_heartbeat = 0;
long finger_timestamp = 0;
bool finger_detected = false;

// Last diff to detect zero crossing
float last_diff = NAN;
bool crossed = false;
long crossed_time = 0;

void loop() {
  auto sample = sensor.readSample(1000);
  float current_value_red = sample.red;
  float current_value_ir = sample.ir;

  // Detect Finger using raw sensor value
  if(sample.red > kFingerThreshold) {
    if(millis() - finger_timestamp > kFingerCooldownMs) {
      finger_detected = true;
    }
  } else {
    // Reset values if the finger is removed
    differentiator.reset();
    averager_bpm.reset();
    averager_r.reset();
    averager_spo2.reset();
    low_pass_filter_red.reset();
    low_pass_filter_ir.reset();
    high_pass_filter.reset();
    stat_red.reset();
    stat_ir.reset();

    finger_detected = false;
    finger_timestamp = millis();
  }

  if(finger_detected) {
    current_value_red = low_pass_filter_red.process(current_value_red);
    current_value_ir = low_pass_filter_ir.process(current_value_ir);

    // Statistics for pulse oximetry
    stat_red.process(current_value_red);
    stat_ir.process(current_value_ir);

    // Heart beat detection using value for red LED
    float current_value = high_pass_filter.process(current_value_red);
    float current_diff = differentiator.process(current_value);

    // Valid values?
    if(!isnan(current_diff) && !isnan(last_diff)) {
      // Detect Heartbeat - Zero-Crossing
      if(last_diff > 0 && current_diff < 0) {
        crossed = true;
        crossed_time = millis();
      }

      if(current_diff > 0) {
        crossed = false;
      }

      // Detect Heartbeat - Falling Edge Threshold
      if(crossed && current_diff < kEdgeThreshold) {
        if(last_heartbeat != 0 && crossed_time - last_heartbeat > 300) {
          // Show Results
          int bpm = 60000/(crossed_time - last_heartbeat);
          float rred = (stat_red.maximum()-stat_red.minimum())/stat_red.average();
          float rir = (stat_ir.maximum()-stat_ir.minimum())/stat_ir.average();
          float r = rred/rir;
          float spo2 = kSpO2_A * r * r + kSpO2_B * r + kSpO2_C;

          if(bpm > 50 && bpm < 250) {
            // Average?
            if(kEnableAveraging) {
              int average_bpm = averager_bpm.process(bpm);
              int average_r = averager_r.process(r);