Kutluhan Aktar
Published © CC BY

IoT Heart Rate (BPM) Monitor and Tracker w/ Tuya Smart

With ESP8266 and Arduino, observe the heart rate (BPM) generated by MAX30102 on Tuya Cloud compatible w/ Android and iOS.

ExpertFull instructions provided5 hours3,453
IoT Heart Rate (BPM) Monitor and Tracker w/ Tuya Smart

Things used in this project

Hardware components

PCBWay Custom PCB
PCBWay Custom PCB
×1
Arduino Nano R3
Arduino Nano R3
×1
NodeMCU V3 LoLin ESP8266
×1
MAX30102 High-Sensitivity Pulse Oximeter and Heart-Rate Sensor for Wearable Health
Maxim Integrated MAX30102 High-Sensitivity Pulse Oximeter and Heart-Rate Sensor for Wearable Health
×1
MAX30100 Pulse Oximeter and Heart Rate Sensor
(Optional)
×1
SSD1306 OLED 128x64
×1
5mm Common Anode RGB LED
×1
SparkFun Button (6x6)
×1
Resistor 220 ohm
Resistor 220 ohm
×3
DC Power Connector, Jack
DC Power Connector, Jack
×1
External Battery
×1

Software apps and online services

Arduino IDE
Arduino IDE
KiCad
KiCad
Tuya IoT Platform

Hand tools and fabrication machines

Soldering iron (generic)
Soldering iron (generic)
Solder Wire, Lead Free
Solder Wire, Lead Free

Story

Read more

Custom parts and enclosures

Gerber Files

Fabrication Files

Tuya Cloud Module V2.6.3

Schematics

PCB_1

PCB_2

PCB_3

PCB_4

PCB_5

PCB_6

Code

IoT_Heart_Rate_Monitor_w_Tuya.ino

Arduino
         /////////////////////////////////////////////  
        //     IoT Heart Rate (BPM) Monitor        //
       //        and Tracker w/ Tuya Smart        //
      //             ---------------             //
     //         (Arduino Nano & ESP8266)        //           
    //             by Kutluhan Aktar           // 
   //                                         //
  /////////////////////////////////////////////

//
// With ESP8266 and Arduino, observe the heart rate (BPM) generated by MAX30102 on Tuya Cloud compatible w/ Android and iOs.
//
// For more information:
// https://www.theamplituhedron.com/projects/IoT-Heart-Rate-(BPM)-Monitor-and-Tracker-w-Tuya-Smart/
//
//
// Connections
// Arduino Nano :  
//                                NodeMCU V3 LoLin ESP8266
// D8  --------------------------- TX 
// D9  --------------------------- RX
//                                MAX30102 Pulse Oximeter and Heart Rate Sensor
// A4  --------------------------- SDA
// A5  --------------------------- SCL 
//                                SSD1306 OLED 128x64
// A4  --------------------------- SDA
// A5  --------------------------- SCL          
//                                5mm Common Anode RGB LED
// D3  --------------------------- R
// D5  --------------------------- G
// D6  --------------------------- B
//                                Network Connection Button
// D7  --------------------------- S


// Include the required libraries.
#include <TuyaWifi.h>
#include <SoftwareSerial.h>
#include <Adafruit_GFX.h>
#include <Adafruit_SSD1306.h>
#include <Wire.h>
#include "MAX30105.h"
#include "heartRate.h" 

// Define the Tuya Device:
SoftwareSerial conn(8, 9); // RX, TX
TuyaWifi my_device(&conn);

// Define Tuya Device connection status settings:
unsigned char led_state = 0;
int wifi_key_pin = 7;

// Define data points (DPs) of the Tuya Device:
#define DPID_Blood_Oxygen_Data 101
#define DPID_Respiratory_Rate 102
#define DPID_Blood_Oxygen 103
#define DPID_Finger_In 104

// Stores all DPs and their types. PS: array[][0]:dpid, dp type(TuyaDefs.h) : DP_TYPE_RAW, DP_TYPE_BOOL, DP_TYPE_VALUE, DP_TYPE_STRING, DP_TYPE_ENUM, DP_TYPE_BITMAP
unsigned char dp_array[][2] =
{
  {DPID_Blood_Oxygen_Data, DP_TYPE_RAW},
  {DPID_Respiratory_Rate, DP_TYPE_VALUE},
  {DPID_Blood_Oxygen, DP_TYPE_VALUE},
  {DPID_Finger_In, DP_TYPE_BOOL},
};

// Define the Tuya Device Information:
unsigned char pid[] = {"Enter_PID"};
unsigned char mcu_ver[] = {"3.1.4"};

// Define the MAX3010x sensor:
MAX30105 particleSensor;

// MAX3010x sensor settings:
const byte RATE_SIZE = 4;
byte rates[RATE_SIZE];
byte rateSpot = 0;
long lastBeat = 0;

// Define the SSD1306 screen settings:
#define SCREEN_WIDTH 128 // OLED display width, in pixels
#define SCREEN_HEIGHT 32 // OLED display height, in pixels
#define OLED_RESET    -1 // Reset pin # (or -1 if sharing Arduino reset pin)

Adafruit_SSD1306 display(SCREEN_WIDTH, SCREEN_HEIGHT, &Wire, OLED_RESET);

// Define monochrome graphics:
static const unsigned char PROGMEM touch [] = {
0x00, 0x00, 0x00, 0x00, 0xF0, 0x00, 0x03, 0x08, 0x00, 0x06, 0x64, 0x00, 0x05, 0x92, 0x00, 0x09,
0x0A, 0x00, 0x0A, 0xEA, 0x00, 0x0A, 0x9A, 0x00, 0x08, 0x9A, 0x00, 0x04, 0x92, 0x00, 0x06, 0x94,
0x00, 0x02, 0x98, 0x00, 0x00, 0x90, 0x00, 0x00, 0x9C, 0x00, 0x00, 0x97, 0x80, 0x00, 0x96, 0xE0,
0x00, 0x96, 0xD0, 0x04, 0x96, 0x90, 0x0E, 0x80, 0x90, 0x09, 0x80, 0x10, 0x09, 0x80, 0x10, 0x09,
0x80, 0x10, 0x04, 0x80, 0x10, 0x04, 0x00, 0x10, 0x04, 0x00, 0x10, 0x02, 0x00, 0x10, 0x02, 0x00,
0x20, 0x03, 0x00, 0x20, 0x01, 0x80, 0x40, 0x00, 0xE1, 0x80, 0x00, 0x3E, 0x00, 0x00, 0x00, 0x00
};
static const unsigned char PROGMEM heartrate [] = {
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x03, 0xF8, 0x1F, 0xC0, 0x06, 0x0E, 0x70, 0x60, 0x08, 0x03, 0xC0, 0x10, 0x10, 0x01, 0x80, 0x18,
0x30, 0x00, 0x00, 0x0C, 0x20, 0x30, 0x00, 0x04, 0x20, 0x30, 0x00, 0x04, 0x20, 0x30, 0x00, 0x04,
0x20, 0x30, 0x80, 0x04, 0x22, 0x79, 0x80, 0x04, 0x23, 0x69, 0x80, 0x04, 0x33, 0x49, 0x8E, 0x0C,
0x1F, 0xCB, 0xFA, 0x08, 0x18, 0xCE, 0x0E, 0x18, 0x0C, 0xC6, 0x04, 0x30, 0x06, 0x06, 0x00, 0x60,
0x03, 0x04, 0x00, 0xC0, 0x01, 0x80, 0x01, 0x80, 0x00, 0xC0, 0x03, 0x00, 0x00, 0x30, 0x0C, 0x00,
0x00, 0x18, 0x18, 0x00, 0x00, 0x0C, 0x30, 0x00, 0x00, 0x06, 0x60, 0x00, 0x00, 0x01, 0x80, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00
};

// Define RGB pins:
#define redPin 3
#define greenPin 5
#define bluePin 6

// Define the data holders:
unsigned long last_time = 0;
float beatsPerMinute;
int beatAvg, _value;
volatile boolean finger_in = false;

void setup() {
  Serial.begin(9600);
  conn.begin(9600);
  
  // RGB:
  pinMode(redPin, OUTPUT);
  pinMode(greenPin, OUTPUT);
  pinMode(bluePin, OUTPUT);
  adjustColor(0,0,0);
    
  // Initialize networking keys.
  pinMode(wifi_key_pin, INPUT_PULLUP);
  // Enter the PID and MCU software version:
  my_device.init(pid, mcu_ver);
  // Incoming all DPs and their types array, DP numbers:
  my_device.set_dp_cmd_total(dp_array, 4);
  // Register DP download processing callback function:
  my_device.dp_process_func_register(dp_process);
  // Register upload all DP callback function:
  my_device.dp_update_all_func_register(dp_update_all);
  // Define the last time:
  last_time = millis();

  // Initialize the MAX3010x sensor:
  particleSensor.begin(Wire, I2C_SPEED_FAST);
  // Configure sensor with default settings:
  particleSensor.setup();
  particleSensor.setPulseAmplitudeRed(0x0A);

  // Initialize the SSD1306 screen:
  display.begin(SSD1306_SWITCHCAPVCC, 0x3C);
  display.display();
  delay(3000);
}

void loop() {
  // Start the serial communication with the Tuya Device:
  my_device.uart_service();
  // Activate the network connection mode when the WiFi Key Pin is pressed.
  if (digitalRead(wifi_key_pin) == LOW) {
    delay(80);
    if (digitalRead(wifi_key_pin) == LOW) {
      my_device.mcu_set_wifi_mode(SMART_CONFIG);
    }
  }
  // RGB LED blinks green when the Tuya Device is attempting to connect to the network:
  if((my_device.mcu_get_wifi_work_state() != WIFI_LOW_POWER) && (my_device.mcu_get_wifi_work_state() != WIFI_CONN_CLOUD) && (my_device.mcu_get_wifi_work_state() != WIFI_SATE_UNKNOW)) {
    if(millis()- last_time >= 500){
      last_time = millis();
      // Adjust:
      if(led_state == LOW){ led_state = HIGH; adjustColor(0,255,0); } else{ led_state = LOW; adjustColor(0,0,0); }
    }
  }
  // If the Tuya Device (ESP8266) is connected to the cloud server:
  if(my_device.mcu_get_wifi_work_state() == WIFI_CONN_CLOUD){
    adjustColor(0,0,255);
  }
  delay(10);

  long irValue = particleSensor.getIR();
  // If a finger is detected:                                         
  if(irValue > 10000){
    // Update the Finger_In data point:
    if(finger_in == false){
      my_device.mcu_dp_update(DPID_Finger_In, true, 1);
      finger_in = true;
    }
    // Print:
    display.clearDisplay();
    display.drawBitmap(0, 0, heartrate, 32, 32, SSD1306_WHITE);                                  
    display.setTextSize(1);
    display.setCursor(50,0);                              
    display.setTextColor(SSD1306_BLACK, SSD1306_WHITE);      
    display.println("BPM:");
    display.setTextSize(3);            
    display.setCursor(80,10);
    display.setTextColor(SSD1306_WHITE);                 
    display.println(beatAvg); 
    display.display();
    // If a heart beat is detected:
    if (checkForBeat(irValue) == true){
      long delta = millis() - lastBeat;
      lastBeat = millis();
      // Calculate the average heart beat:
      beatsPerMinute = 60 / (delta / 1000.0);
      if(beatsPerMinute < 255 && beatsPerMinute > 20){
        // Store the recent reading in the rates array:
        rates[rateSpot++] = (byte)beatsPerMinute; 
        rateSpot %= RATE_SIZE; // Wrap variable.
        // Take the average of readings:
        beatAvg = 0;
        for (byte x = 0 ; x < RATE_SIZE ; x++)
          beatAvg += rates[x];
        beatAvg /= RATE_SIZE;
      }
      // Update the Respiratory_Rate data point:
      if(beatAvg > 0){
        my_device.mcu_dp_update(DPID_Respiratory_Rate, beatAvg, 1);
        // Get the SPo2 or red light measurement value:
        Spo2_or_Red_Light(1);
        // Update the Blood_Oxygen data point:
        my_device.mcu_dp_update(DPID_Blood_Oxygen, _value, 1);
      } 
    } 
  }
  // If there is no finger on the sensor:
  if(irValue < 10000){
    if(finger_in == true){
      my_device.mcu_dp_update(DPID_Finger_In, false, 1);
      finger_in = false;
    }
    beatAvg=0;
    display.clearDisplay();
    display.drawBitmap(0, 0, touch, 24, 32, SSD1306_WHITE);
    display.setTextSize(1);                   
    display.setTextColor(SSD1306_WHITE);             
    display.setCursor(40,0);                
    display.println("Place your"); 
    display.setCursor(40,15);
    display.setTextColor(SSD1306_BLACK, SSD1306_WHITE);
    display.println("index");
    display.setCursor(80,15);
    display.setTextColor(SSD1306_WHITE);
    display.println("finger!");     
    display.display();
  }
}

void Spo2_or_Red_Light(int option){
  switch(option){
    case 1:
      _value = map(particleSensor.getRed(), 0, 130000, 0, 100);
      break;
    case 2:
      // Use the example code in the Example8_SPO2.ino file in the Examples.
      /*     */
      break;
    default:
      _value = 1;
      break;
  }
}

unsigned char dp_process(unsigned char dpid,const unsigned char value[], unsigned short length){
  /* all DP only report */
  return SUCCESS;
}

void dp_update_all(void){
  // Update all DPs with default values:
  // my_device.mcu_dp_update(DPID_Blood_Oxygen_Data, 0xff, 1);
  my_device.mcu_dp_update(DPID_Respiratory_Rate, 20, 1);
  my_device.mcu_dp_update(DPID_Blood_Oxygen, 20, 1);
  my_device.mcu_dp_update(DPID_Finger_In, false, 1);
}

void adjustColor(int r, int g, int b){
  analogWrite(redPin, (255-r));
  analogWrite(greenPin, (255-g));
  analogWrite(bluePin, (255-b));
}

Credits

Kutluhan Aktar

Kutluhan Aktar

79 projects • 291 followers
Self-Taught Full-Stack Developer | @EdgeImpulse Ambassador | Maker | Independent Researcher

Comments