ESP32 MAX30100 Pulse Oximeter Web Server

In this ESP32 guide, we will show you how to build a pulse oximeter web server using MAX30100 pulse oximeter and heart rate sensor. This sensor is used to measure heartbeat/pulse rate in BPM and blood oxygen concentration (SpO2) in percentage. The web server will display the current beat per minute (BPM) and blood oxygen saturation (SpO2) readings.

ESP32 MAX30100 Pulse Oximeter Web Server

MAX30100 Pulse Oximeter Sensor Introduction

The MAX30100 sensor is used as both heart rate monitor and a pulse oximeter. These features are enabled by the construction of this sensor which consists of two LEDs, a photodetector, optimized optics, and low noise signal processing components. It is easily used with microcontrollers such as Arduino, ESP32, NodeMCU, etc. to build an efficient heartbeat and oxygen saturation device.

This sensor is mostly available in two different versions: GY-MAX30100 and RCWL-0530.

GY-MAX30100

In this guide we will focus on RCWL-0530 as it is a little tricky to work with due to a design issue that we will later see how to resolve. The GY-MAX30100 sensor on the other hand is a very easy to use sensor with no additional problems.

Let us look at the overview of this sensor.

Below you can view the diagram of RCWL-0530.

MAX30100 module overview

As you may notice the MAX30100 IC lies at the centre of the module. It consists of two different types of LEDs on the right hand side. There is one Red LED and one IR LED. On the left hand side you can view the photodetector. Blood oxygen saturation and heart rate are found using these these two key features. We will later learn how the sensor actually works to obtain the BPM and SPO2.

Another important feature you may notice is that the MAX30100 sensor module consists of two LDO regulators. This is because the MAX30100 IC requires 1.8V and the LEDs require 3.3V to function properly. With the addition of the voltage regulators, we can safely use microcontrollers that use 5/3.3/1.8V level input/outputs.

Key Features

  • The MAX30100 sensor module has an ultra low power operation, uses 600μA (measurement mode and 0.7μA(standby mode). Therefore a great choice to use in wearable devices such as smart watches etc.
  • It has a high sample rate capability along with fast data output capability.
  • Additionally, the sensor features integrated ambient light cancellation as well.
  • One additional feature that the MAX30100 sensor module possess is the inclusion of an on-chip temperature sensor. This gives us the die temperature (-40˚C to +85˚C) which is ± 1˚C accurate.
  • For communicating with microcontrollers, the sensor uses the I2C pins SCL and SDA.
  • Another feature of this sensor is that it uses a 16 sample FIFO buffer to store data. In other words, it further reduces power consumption as it already holds maximum of sixteen heart rate and SPO2 values.
  • The MAX30100 can also be used with interrupts which can be enabled for several sources such as power ready, SPO2 data ready, heart rate data ready, temperature ready and FIFO almost full. With the generation of interrupts, the microcontroller can perform other events which do not happen during the sequential execution of a program while the sensor keeps obtaining new data samples.

The table below shows the specifications of this sensor:

Operating Voltage1.8V to 3.3V
Input Current 20mA
Temperature Range-40˚C to +85˚C
Temperature Accuracy±1˚C
ADC Resolution14 bits
IR LED peak wavelength870-900nm
Red LED peak wavelength650-670nm

For more information about the specifications, refer to the MAX30100 Datasheet.

Recommended Reading: MAX30100 Pulse Oximeter and Heart Rate Sensor with ESP32

Interfacing MAX30100 Pulse Oximeter with ESP32

In this section, we will learn how to interface MAX30100 sensor module with ESP32 board.

Design Problem with RCWL-0530

Before we move forward, you may remember that we mentioned before that the RCWL-0530 version has a serious design issue. Let us see how to resolve that first.

You can view the schematic diagram of the module below:

MAX30100 module schematic
MAX30100 module schematic

MAX30100 sensor module consists of two LDO regulators. This is because the MAX30100 IC requires 1.8V and the LEDs require 3.3V to function properly. Now refer to the schematic diagram of the module and notice that the SDA, SCL and INT lines are connected to 1.8V via 4.7k ohm pull up resistors. This is a serious design issue because now the sensor will not properly work with microcontrollers with higher logic levels e.g. ESP32 in our case. The I2C device will not be recognized.

There are two ways to resolve this problem.

Solution 1: Remove the 4.7k pull up resistors from the module and connect external resistors instead

One way to resolve the issue stated above is to remove the three pull-up resistors shown in the diagram below and use external ones instead.

MAX30100 module remove resistors

You can easily remove them using a soldering iron. After you have removed the resistors, you are good to go and can connect your module with ESP32 as shown below:

MAX30100 with ESP32 (external resistors) connection diagram

Here we are connecting SDA and SCL pins to 3.3V from ESP32 via external 4.7k ohm resistors. The rest of the connections are shown in the table below:

MAX30100 ModuleESP32
VCC3.3V
SCLGPIO22
SDAGPIO21
GNDGND
ESP32 with MAX30100 sensor

Solution 2: Cut the path and make a new path

The second solution is to disconnect the 4.7k ohm pull-up resistors from 1.8V by cutting that path and making a path such that the pull-up resistors connect to 3.3V instead.

Max30100 module cut path

Cut the path between the 4.7k ohm resistor and the 1.8V LDO regulator. Then connect a jumper wire or solder a path between the 3.3V LDO regulator and the 4.7k ohm resistor as shown in the picture above.

After you have made the changes, you can connect your module with ESP32 as shown below:

MAX30100 with ESP32 (cut and connect) connection diagram

The connections between the sensor module and ESP32 are as follows:

MAX30100 ModuleESP32
VCC3.3V
SCLGPIO22
SDAGPIO21
GNDGND

Installing MAX3100 Arduino Library

We will use Arduino IDE to program our ESP32 development board. Thus, you should have the latest version of Arduino IDE. Additionally, you also need to install the ESP32 plugin.

If your IDE does not have the plugin installed you can visit the link below: Installing ESP32 library in Arduino IDE and upload code.

Open Arduino IDE and click on Sketch > Library > Manage Libraries

MPU-6050 Install library

The following window will open up.

search library Arduino IDE

Type ‘ max30100’ in the search bar and press enter. Install the latest version of the library: MAX30100lib Library by OXullo Intersecans.

Max30100lib library install

After the installation of the library, restart your IDE.

ESP32 Pulse Oximeter Web Server Sketch

Open your Arduino IDE and go to File > New to open a new file. Copy the code given below and save it.

Remember to replace the Wi-Fi credentials.

#include <WiFi.h>
#include <WebServer.h>
#include <Wire.h>
#include "MAX30100_PulseOximeter.h"

#define REPORTING_PERIOD_MS     1000

float BPM, SpO2;

const char* ssid = "WRITE_YOUR_SSID";  
const char* password = "WRITE_YOUR_PASSWORD";  

PulseOximeter pox;
uint32_t tsLastReport = 0;

WebServer server(80);

void onBeatDetected()
{
  Serial.println("Beat Detected!");
}

void setup() {
  Serial.begin(115200);

  Serial.print("Connecting");
  WiFi.begin(ssid, password);
  while (WiFi.status() != WL_CONNECTED) {
    delay(1000);
    Serial.print(".");
  }
  Serial.println("");
  Serial.println("WiFi Connected");
  Serial.print("Got IP: ");  
  Serial.println(WiFi.localIP());
  
  server.on("/", handle_OnConnect);
  server.onNotFound(handle_NotFound);

  server.begin();
  Serial.println("HTTP server started");
  
  Serial.print("Initializing pulse oximeter..");

  if (!pox.begin()) {
    Serial.println("FAILED");
    for (;;);
  } else {
    Serial.println("SUCCESS");

    pox.setOnBeatDetectedCallback(onBeatDetected);
  }

  pox.setIRLedCurrent(MAX30100_LED_CURR_7_6MA);
}
void loop() {
  server.handleClient();
  pox.update();
  BPM = pox.getHeartRate();
  SpO2 = pox.getSpO2();

  if (millis() - tsLastReport > REPORTING_PERIOD_MS)
  {
    Serial.print("BPM: ");
    Serial.println(BPM);

    Serial.print("SpO2: ");
    Serial.print(SpO2);
    Serial.println("%");
    Serial.println();

    tsLastReport = millis();
  }

}

void handle_OnConnect() {

  server.send(200, "text/html", HTML(BPM, SpO2));
}

void handle_NotFound() {
  server.send(404, "text/plain", "Not found");
}

String HTML(float BPM, float SpO2) {
  String str = "<!DOCTYPE html>";
  str += "<html>";
  str += "<head>";
  str += "<title>ESP32 MAX30100 Web Server</title>";
  str += "<meta name='viewport' content='width=device-width, initial-scale=1.0'>";
  str += "<link rel='stylesheet' href='https://cdnjs.cloudflare.com/ajax/libs/font-awesome/5.7.2/css/all.min.css'>";
  str += "<link rel='stylesheet' type='text/css' href='styles.css'>";
  str += "<style>";
  str += "body { background-color: #fff; font-family: sans-serif; color: #0f0d0d; font: 14px Helvetica, sans-serif box-sizing: border-box;}";
  str += "#page { margin: 20px; background-color: #fff;}";
  str += ".container { height: inherit; padding-bottom: 20px;}";
  str += ".header { padding: 20px;}";
  str += ".header h1 { padding-bottom: 0.3em; color: #0f0d0d; font-size: 40px; font-weight: bold; font-family: Garmond, 'sans-serif'; text-align: center;}";
  //str += "h2 { padding-bottom: 0.2em; border-bottom: 1px solid #eee; margin: 2px; text-align: left;}";
 // str += ".header h3 { font-weight: bold; font-family: Arial, 'sans-serif'; font-size: 17px; color: #b6b6b6; text-align: center;}";
  str += ".box-full { padding: 20px; border 1px solid #ddd; border-radius: 1em 1em 1em 1em; box-shadow: 1px 7px 7px 1px rgba(0,0,0,0.4); background: #fff; margin: 20px; width: 300px;}";
  str += "@media (max-width: 494px) { #page { width: inherit; margin: 5px auto; } #content { padding: 1px;} .box-full { margin: 8px 8px 12px 8px; padding: 10px; width: inherit;; float: none; } }";
  str += "@media (min-width: 494px) and (max-width: 980px) { #page { width: 465px; margin 0 auto; } .box-full { width: 380px; } }";
  str += "@media (min-width: 980px) { #page { width: 930px; margin: auto; } }";
  str += ".sensor { margin: 12px 0px; font-size: 2.5rem;}";
  str += ".sensor-labels { font-size: 1rem; vertical-align: middle; padding-bottom: 15px;}";
  str += ".units { font-size: 1.2rem;}";
  str += "hr { height: 1px; color: #eee; background-color: #eee; border: none;}";
  str += "</style>";

  //Ajax Code Start
  str += "<script>\n";
  str += "setInterval(loadDoc,1000);\n";
  str += "function loadDoc() {\n";
  str += "var xhttp = new XMLHttpRequest();\n";
  str += "xhttp.onreadystatechange = function() {\n";
  str += "if (this.readyState == 4 && this.status == 200) {\n";
  str += "document.body.innerHTML =this.responseText}\n";
  str += "};\n";
  str += "xhttp.open(\"GET\", \"/\", true);\n";
  str += "xhttp.send();\n";
  str += "}\n";
  str += "</script>\n";
  //Ajax Code END

  str += "</head>";
  str += "<body>";
  str += "<div id='page'>";
  str += "<div class='header'>";
  str += "<h1>ESP32 Pulse Oximeter</h1>";
  str += "</div>";
  str += "<div id='content' align='center'>";
  str += "<div class='box-full' align='left'>";
  str += "<div class='sensors-container'>";

  //For Heart Rate
  str += "<p class='sensor'>";
  str += "<i class='fas fa-heartbeat' style='color:#ff4000'></i>";
  str += "<span class='sensor-labels'> Heart Rate </span>";
  str += (int)BPM;
  str += "<sup class='units'>BPM</sup>";
  str += "</p>";
  str += "<hr>";

  //For Sp02
  str += "<p class='sensor'>";
  str += "<i class='fas fa-tint' style='color:#dceffc'></i>";
  str += "<span class='sensor-labels'> SpO2 </span>";
  str += (int)SpO2;
  str += "<sup class='units'>%</sup>";
  str += "</p>";

  str += "</div>";
  str += "</div>";
  str += "</div>";
  str += "</div>";
  str += "</div>";
  str += "</body>";
  str += "</html>";
  return str;
}

How the Code Works?

Firstly, we will include the necessary libraries. As we have to connect our ESP32 to a wireless network, we need WiFi.h library for that purpose. The WebServer.h will be required to build the web server. Moreover, Wire.h and MAX30100_PulseOximeter.h will be required for the MAX30100 sensor’s functionality.

#include <WiFi.h>
#include <WebServer.h>
#include <Wire.h>
#include "MAX30100_PulseOximeter.h"

Next we define the reporting time in milliseconds between the samples.


#define REPORTING_PERIOD_MS     1000

Declare two variables (BPM and SpO2) of type float to hold the heart rate and SPO2 readings respectively.

float BPM, SpO2;

Next, we will create two global variables, one for the SSID and another for the password. These will hold our network credentials which will be used to connect to our wireless router. Replace both of them with your credentials to ensure a successful connection.

const char* ssid = "WRITE_YOUR_SSID";  
const char* password = "WRITE_YOUR_PASSWORD"; 

Here we are creating an object ‘pox’ of PulseOximeter.

PulseOximeter pox;

The following variable holds the time when the last beat occurred.

uint32_t tsLastReport = 0;

The WebServer object will be used to set up the ESP32 web server. We will pass the default HTTP port which is 80, as the input to the constructor. This will be the port where the server will listen to the requests.

WebServer server(80);

The following callback function will display “Beat Detected!” in the serial monitor when a heart beat occurs.

void onBeatDetected()
{
  Serial.println("Beat Detected!");
}

setup()

Inside the setup() function, we first open the serial communication at a baud rate of 115200.

Serial.begin(115200);

The following section of code will connect our ESP32 board with the local network whose network credentials we already specified above. After the connection will be established, the IP address of the ESP32 board will get printed on the serial monitor. This will help us to make a request to the server.

Serial.print("Connecting");
  WiFi.begin(ssid, password);
  while (WiFi.status() != WL_CONNECTED) {
    delay(1000);
    Serial.print(".");
  }
  Serial.println("");
  Serial.println("WiFi Connected");
  Serial.print("Got IP: ");  
  Serial.println(WiFi.localIP());

To start the server, we will call begin() on our server object.

  server.on("/", handle_OnConnect);
  server.onNotFound(handle_NotFound);

  server.begin();
  Serial.println("HTTP server started");

Then we initialize the MAX30100 sensor module. A relevant message will be displayed in the serial monitor. The callback function onBeatDetected will be registered as well.

  Serial.print("Initializing pulse oximeter..");

  if (!pox.begin()) {
    Serial.println("FAILED");
    for (;;);
  } else {
    Serial.println("SUCCESS");

    pox.setOnBeatDetectedCallback(onBeatDetected);
  }

Then we set the current value for LED drive by using the PulseOximeter object on setIRLedCurrent() method. Here we have configured the sensor to use 7.6mA for the IR LED.

By default, the current is set to 50mA which may cause problems in initialization so we set a lower value instead.

You can use the following values inside the function:

  • MAX30100_LED_CURR_0MA
  • MAX30100_LED_CURR_4_4MA
  • MAX30100_LED_CURR_7_6MA
  • MAX30100_LED_CURR_11MA
  • MAX30100_LED_CURR_14_2MA
  • MAX30100_LED_CURR_17_4MA
  • MAX30100_LED_CURR_20_8MA
  • MAX30100_LED_CURR_24MA
  • MAX30100_LED_CURR_27_1MA
  • MAX30100_LED_CURR_30_6MA
  • MAX30100_LED_CURR_33_8MA
  • MAX30100_LED_CURR_37MA
  • MAX30100_LED_CURR_40_2MA
  • MAX30100_LED_CURR_43_6MA
  • MAX30100_LED_CURR_46_8MA
  • MAX30100_LED_CURR_50MA
pox.setIRLedCurrent(MAX30100_LED_CURR_7_6MA);

loop()

Inside the loop() function, we will first read from the sensor using pox.update(). Then we will obtain the readings for the heart rate (saved in BPM) and the blood oxygen concentration (saved in SpO2) and print them in the serial monitor after every second. To obtain the heart rate reading, use the PulseOximeter object on the getHeartRate() method. Similarly, to obtain the blood oxygen concentration, use the PulseOximeter object on the getSpO2() method.

Moreover, we will call handleClient() on the server object so that the server can listen to the HTTP requests continuously.

void loop() {
  server.handleClient();
  pox.update();
  BPM = pox.getHeartRate();
  SpO2 = pox.getSpO2();

  if (millis() - tsLastReport > REPORTING_PERIOD_MS)
  {
    Serial.print("BPM: ");
    Serial.println(BPM);

    Serial.print("SpO2: ");
    Serial.print(SpO2);
    Serial.println("%");
    Serial.println();

    tsLastReport = millis();
  }

}

handle_onConnect()

The handle_onConnect() function takes in no parameters. It is responsible for ESP32 handling the /root URL. We will configure the root / URL where our server will listen to HTTP GET requests.
It will be responsible for building the webpage by sending the BPM and SpO2 readings to the HTML() function.

void handle_OnConnect() {

  server.send(200, "text/html", HTML(BPM, SpO2));
}

HTML()

The following HTML() function builds the web server. It takes in two parameters. The first is the BPM reading and the second is the SpO2 reading.

String HTML(float BPM, float SpO2) {
  String str = "<!DOCTYPE html>";
  str += "<html>";
  str += "<head>";
  str += "<title>ESP32 MAX30100 Web Server</title>";
  str += "<meta name='viewport' content='width=device-width, initial-scale=1.0'>";
  str += "<link rel='stylesheet' href='https://cdnjs.cloudflare.com/ajax/libs/font-awesome/5.7.2/css/all.min.css'>";
  str += "<link rel='stylesheet' type='text/css' href='styles.css'>";
  str += "<style>";
  str += "body { background-color: #fff; font-family: sans-serif; color: #0f0d0d; font: 14px Helvetica, sans-serif box-sizing: border-box;}";
  str += "#page { margin: 20px; background-color: #fff;}";
  str += ".container { height: inherit; padding-bottom: 20px;}";
  str += ".header { padding: 20px;}";
  str += ".header h1 { padding-bottom: 0.3em; color: #0f0d0d; font-size: 40px; font-weight: bold; font-family: Garmond, 'sans-serif'; text-align: center;}";
  str += ".box-full { padding: 20px; border 1px solid #ddd; border-radius: 1em 1em 1em 1em; box-shadow: 1px 7px 7px 1px rgba(0,0,0,0.4); background: #fff; margin: 20px; width: 300px;}";
  str += "@media (max-width: 494px) { #page { width: inherit; margin: 5px auto; } #content { padding: 1px;} .box-full { margin: 8px 8px 12px 8px; padding: 10px; width: inherit;; float: none; } }";
  str += "@media (min-width: 494px) and (max-width: 980px) { #page { width: 465px; margin 0 auto; } .box-full { width: 380px; } }";
  str += "@media (min-width: 980px) { #page { width: 930px; margin: auto; } }";
  str += ".sensor { margin: 12px 0px; font-size: 2.5rem;}";
  str += ".sensor-labels { font-size: 1rem; vertical-align: middle; padding-bottom: 15px;}";
  str += ".units { font-size: 1.2rem;}";
  str += "hr { height: 1px; color: #eee; background-color: #eee; border: none;}";
  str += "</style>";


  str += "<script>\n";
  str += "setInterval(loadDoc,1000);\n";
  str += "function loadDoc() {\n";
  str += "var xhttp = new XMLHttpRequest();\n";
  str += "xhttp.onreadystatechange = function() {\n";
  str += "if (this.readyState == 4 && this.status == 200) {\n";
  str += "document.body.innerHTML =this.responseText}\n";
  str += "};\n";
  str += "xhttp.open(\"GET\", \"/\", true);\n";
  str += "xhttp.send();\n";
  str += "}\n";
  str += "</script>\n";

  str += "</head>";
  str += "<body>";
  str += "<div id='page'>";
  str += "<div class='header'>";
  str += "<h1>ESP32 Pulse Oximeter</h1>";
  str += "</div>";
  str += "<div id='content' align='center'>";
  str += "<div class='box-full' align='left'>";
  str += "<div class='sensors-container'>";

  //For Heart Rate
  str += "<p class='sensor'>";
  str += "<i class='fas fa-heartbeat' style='color:#ff4000'></i>";
  str += "<span class='sensor-labels'> Heart Rate </span>";
  str += (int)BPM;
  str += "<sup class='units'>BPM</sup>";
  str += "</p>";
  str += "<hr>";

  //For Sp02
  str += "<p class='sensor'>";
  str += "<i class='fas fa-tint' style='color:#dceffc'></i>";
  str += "<span class='sensor-labels'> SpO2 </span>";
  str += (int)SpO2;
  str += "<sup class='units'>%</sup>";
  str += "</p>";

  str += "</div>";
  str += "</div>";
  str += "</div>";
  str += "</div>";
  str += "</div>";
  str += "</body>";
  str += "</html>";
  return str;
}

Building the Web Server

HTML is a hypertext markup language which is used to build web pages. All web browsers understand this language and can read web pages which are based on HTML language. 

In HTML, we place all the content of a web page between <html> and </html> tags. The <html> tag shows the beginning of a web page and the  </html> indicates the end of a web page. 

HTML code mainly includes two parts such as head and body. The head part contains CSS, scripts, meta tags, links of external resources, and styling codes. It is placed between <head> and </head> tags. 

Note: Whatever code or data, we place inside the head tags will not be visible to the user. It is only used to enhance the functionality of a web page such as scripts and styles, etc. 

We will start with the title of the web page. The <title> tag will indicate the beginning of the title and the </tile> tag will indicate the ending. In between these tags, we will specify “ESP32 MAX30100 Web Server” which will be displayed in the browser’s title bar.

str += "<title>ESP32 MAX30100 Web Server</title>";

Inside head tags, create a <meta> tag to make the web page responsive in any web browser. 

str += "<meta name='viewport' content='width=device-width, initial-scale=1.0'>";

In our web page, we also use icons with the heart rate and SpO2 readings. This link tag loads the icons used in the webpage from fontawesome.

str += "<link rel='stylesheet' href='https://cdnjs.cloudflare.com/ajax/libs/font-awesome/5.7.2/css/all.min.css'>";
Styling

CSS is used to give styles to a web page. To add CSS files in head tags, we use <style></style> tags. The font size, color and positioning of the heading, paragraphs and the box will also be set.

str += "<style>";
  str += "body { background-color: #fff; font-family: sans-serif; color: #0f0d0d; font: 14px Helvetica, sans-serif box-sizing: border-box;}";
  str += "#page { margin: 20px; background-color: #fff;}";
  str += ".container { height: inherit; padding-bottom: 20px;}";
  str += ".header { padding: 20px;}";
  str += ".header h1 { padding-bottom: 0.3em; color: #0f0d0d; font-size: 40px; font-weight: bold; font-family: Garmond, 'sans-serif'; text-align: center;}";
  str += ".box-full { padding: 20px; border 1px solid #ddd; border-radius: 1em 1em 1em 1em; box-shadow: 1px 7px 7px 1px rgba(0,0,0,0.4); background: #fff; margin: 20px; width: 300px;}";
  str += "@media (max-width: 494px) { #page { width: inherit; margin: 5px auto; } #content { padding: 1px;} .box-full { margin: 8px 8px 12px 8px; padding: 10px; width: inherit;; float: none; } }";
  str += "@media (min-width: 494px) and (max-width: 980px) { #page { width: 465px; margin 0 auto; } .box-full { width: 380px; } }";
  str += "@media (min-width: 980px) { #page { width: 930px; margin: auto; } }";
  str += ".sensor { margin: 12px 0px; font-size: 2.5rem;}";
  str += ".sensor-labels { font-size: 1rem; vertical-align: middle; padding-bottom: 15px;}";
  str += ".units { font-size: 1.2rem;}";
  str += "hr { height: 1px; color: #eee; background-color: #eee; border: none;}";
  str += "</style>";
HTML Web Page Body

The second most important part of an HTML document is the body which goes inside the tags <body> and </body>.

str += "<body>";
  str += "<div id='page'>";
  str += "<div class='header'>";
  str += "<h1>ESP32 Pulse Oximeter</h1>";
  str += "</div>";
  str += "<div id='content' align='center'>";
  str += "<div class='box-full' align='left'>";
  str += "<div class='sensors-container'>";

  //For Heart Rate
  str += "<p class='sensor'>";
  str += "<i class='fas fa-heartbeat' style='color:#ff4000'></i>";
  str += "<span class='sensor-labels'> Heart Rate </span>";
  str += (int)BPM;
  str += "<sup class='units'>BPM</sup>";
  str += "</p>";
  str += "<hr>";

  //For Sp02
  str += "<p class='sensor'>";
  str += "<i class='fas fa-tint' style='color:#dceffc'></i>";
  str += "<span class='sensor-labels'> SpO2 </span>";
  str += (int)SpO2;
  str += "<sup class='units'>%</sup>";
  str += "</p>";

  str += "</div>";
  str += "</div>";
  str += "</div>";
  str += "</div>";
  str += "</div>";
  str += "</body>";

The body part includes the main content of the web page such as headings, images, buttons, icons, tables, charts, etc. For example, in this web server, the body part includes icons, heading, and readings. 

We will include the heading of our webpage inside the <h1></h1> tags and it will be “ESP32 Pulse Oximeter”.

str += "<h1>ESP32 Pulse Oximeter</h1>";

Next, the following lines of code will display icons and readings for heart rate and SpO2. We will specify the name, icon, value and unit for both the readings. The <p> and </p> tags are used to insert paragraphs in the HTML body. 

  //For Heart Rate
  str += "<p class='sensor'>";
  str += "<i class='fas fa-heartbeat' style='color:#ff4000'></i>";
  str += "<span class='sensor-labels'> Heart Rate </span>";
  str += (int)BPM;
  str += "<sup class='units'>BPM</sup>";
  str += "</p>";
  str += "<hr>";

  //For Sp02
  str += "<p class='sensor'>";
  str += "<i class='fas fa-tint' style='color:#dceffc'></i>";
  str += "<span class='sensor-labels'> SpO2 </span>";
  str += (int)SpO2;
  str += "<sup class='units'>%</sup>";
  str += "</p>";
JavaScript

Also, JavaScript will be in charge of handling the received data so that the readings will be updated correctly. 

  str += "<script>\n";
  str += "setInterval(loadDoc,1000);\n";
  str += "function loadDoc() {\n";
  str += "var xhttp = new XMLHttpRequest();\n";
  str += "xhttp.onreadystatechange = function() {\n";
  str += "if (this.readyState == 4 && this.status == 200) {\n";
  str += "document.body.innerHTML =this.responseText}\n";
  str += "};\n";
  str += "xhttp.open(\"GET\", \"/\", true);\n";
  str += "xhttp.send();\n";
  str += "}\n";
  str += "</script>\n";

The loadDoc() function takes in no parameters. It is responsible to update the web server every second. Inside this function we use the XMLHttpRequest. This will allow us to make an HTTP request in JavaScript. This will make sure that the web server updates automatically with out the need to reload it.

We will initialize the request by using the xhttp.open() method. Inside it we will pass on three arguments. The first argument specifies the type of HTTP method which is GET in our case. The second argument is the URL. The last argument is true which specifies that the request is asynchronous.

str += "xhttp.open(\"GET\", \"/\", true);\n";

Lastly, we will use xhttp.send() to open the connection. This will send the request to the server.

 str += "xhttp.send();\n";

Demonstration

Make sure you choose the correct board and COM port before uploading your code to the board. Go to Tools > Board and select ESP32 Dev Module.

select esp32 board

Next, go to Tools > Port and select the appropriate port through which your board is connected.

Selecting COM PORT ESP32

Click on the upload button to upload the code into the ESP32 development board.
After you have uploaded your code to the development board, press its ENABLE button.

ESP32 enable reset button

In your Arduino IDE, open up the serial monitor and you will be able to see the IP address of your ESP32 module.

ESP32 MAX30100 Pulse Oximeter Web Server IP Address

Now hold the sensor in between your fingers. Do not hold it very tightly or very lightly. Make sure you apply normal pressure while holding the sensor. This will result in a better clean signal.

The BPM and SpO2 readings will update after every second. Additionally, you can also view when a heart beat is detected.

ESP32 MAX30100 Pulse Oximeter Web Server Readings serial monitor

Type the IP address in a new web browser and press enter. The Pulse Oximeter web server will open.

ESP32 MAX30100 Pulse Oximeter Web Server Laptop view

You can open the web server in your cell phone as well.

ESP32 MAX30100 Pulse Oximeter Web Server mobile view

You may also like to read:

Leave a Comment