ūüƶ Mini weather station with a web server on ESP32 with a BME280 sensor

Daniil Zhuk

In this guide, you will learn how to create a web server on an ESP32 microcontroller to display readings from the BME280 sensor module. The BME280 sensor measures temperature, humidity, and pressure. So you can easily create a compact weather station and track measurements through a web server on ESP32. This is what we will do in this guide.

Before you start, you need to install add-ons for the ESP32 microcontroller in the Arduino IDE development environment. Below will be described in detail how to install ESP32 on Wednesday Arduino IDE if you have not already done so.

For the Arduino IDE, there is an add-on that allows you to program the ESP32 chip through this medium and its programming language.

REQUIRED ELEMENTS FOR A MINI METE STATION ON ESP32 WITH BME280 SENSOR

For this project you will need the following elements:

  • ESP32 Developer Board;
  • BME280 module;
  • Breadboard;
  • Dummy wires mother-mother.

OVERVIEW OF BME280 SENSOR MODULE

The BME280 sensor module reads the temperature, humidity, and pressure. As pressure changes with altitude, you can also estimate altitude above sea level. There are several versions of this module, we use the one shown in the figure below.

The sensor can be connected via the SPI and I²C interfaces (there are modules that are only equipped with I²C - they come with only four leads).

The following conclusions are for the SPI interface:

  • SCK - SPI clock signal;
  • SDO - MISO, master input, slave output;
  • SDI - MOSI, master output, slave input;
  • CS - chip selection.

The following conclusions are for the I²C interface:

  • SCK is also the SCL sync output;
  • SDI is also an SDA data output.

SCHEME OF CONNECTION OF COMPONENTS

We will use the I²C interface to communicate with the BME280 sensor module. To do this, connect the sensor to the SDA and SCL terminals of the ESP32 microcontroller, as shown in the diagram.

This scheme uses the NodeMCU 32 board version with 36 general-purpose pins, if you use a different model, then recheck the pin assignment of your board.

Connect the elements as shown in the diagram below created using the Fritzing program.

INSTALLATION OF LIBRARIES REQUIRED FOR WORK
Before downloading the code, you need to check the availability of all the necessary libraries: library Adafruit_Sensor libraries for the BME280 sensor.

To install these libraries, follow these steps:

Installing the BME280 sensor library

  1. Download Adafruit_BME280 library here.
  2. Unzip the .zip file
  3. Change the folder name Adafruit-BME280-Library-master to Adafruit_BME280_Library.
  4. Move the Adafruit_BME280_Library folder to your folder with the Arduino IDE libraries installed.
  5. Restart the Arduino IDE.

Installing the Adafruit_Sensor Library

  1. Download Adafruit_Sensor library here.
  2. Unzip the .zip file
  3. Change the folder name Adafruit_Sensor-master to Adafruit_Sensor.
  4. Move the Adafruit_Sensor folder to your folder with the Arduino IDE libraries installed.
  5. Restart the Arduino IDE.

READING OF TEMPERATURE, HUMIDITY AND PRESSURE READINGS FROM SENSOR
To get acquainted with the BME280 sensor, we will use a ready-made example from the library and see how to read temperature, humidity, and pressure.

After installing the BME280 sensor library and Adafruit_Sensor library, launch the Arduino IDE and select the menu item ‚ÄúFile> Examples> Adafruit BME280 library> bme280 test‚ÄĚ (‚ÄúFile> Examples> Adafruit BME280 library> bme280 test‚ÄĚ).

#include "Wire.h"
#include "Adafruit_Sensor.h"
#include "Adafruit_BME280.h"
 
#define BME_SCK 18
#define BME_MISO 19
#define BME_MOSI 23
#define BME_CS 5*/
 
#define SEALEVELPRESSURE_HPA (1013.25)
 
Adafruit_BME280 bme; // I2C
//Adafruit_BME280 bme(BME_CS); // hardware SPI
//Adafruit_BME280 bme(BME_CS, BME_MOSI, BME_MISO, BME_SCK); // software SPI
 
unsigned long delayTime;
 
void setup() {
    Serial.begin(9600);
    Serial.println(F("BME280 test"));
 
    bool status;
    
    // default settings
    // (you can also pass in a Wire library object like &Wire2)
    status = bme.begin(0x76);  
    if (!status) {
        Serial.println("Could not find a valid BME280 sensor, check wiring!");
        while (1);
    }
    
    Serial.println("-- Default Test --");
    delayTime = 1000;
 
    Serial.println();
}
 
 
void loop() {
    printValues();
    delay(delayTime);
}
 
 
void printValues() {
    Serial.print("Temperature = ");
    Serial.print(bme.readTemperature());
    Serial.println(" *C");
 
    Serial.print("Pressure = ");
 
    Serial.print(bme.readPressure() / 100.0F);
    Serial.println(" hPa");
 
    Serial.print("Approx. Altitude = ");
    Serial.print(bme.readAltitude(SEALEVELPRESSURE_HPA));
    Serial.println(" m");
 
    Serial.print("Humidity = ");
    Serial.print(bme.readHumidity());
    Serial.println(" %");
 
    Serial.println();
}

HOW DOES IT WORK AND WHAT DOES THIS CODE MAKE?
The code starts with the inclusion of the required libraries.

#include "Wire.h"
#include "Adafruit_Sensor.h"
#include "Adafruit_BME280.h"

SPI communication
Since we will use the I²C interface, you can comment out the following lines.

Note: If you are using the SPI interface, you need to change the pin assignment in order to use the ESP32 microcontroller's general purpose pins. For SPI, you can use both the HSPI module pins and the VSPI module pins, as shown in the following table.

SPI MOSI MISO CLK CS
HSPI GPIO 13 GPIO 12 GPIO 14 GPIO 15
VSPI GPIO 23 GPIO 19 GPIO 18 GPIO 5

The pressure at sea level
The identifier SEALEVELPRESSURE_HPA is created.

#define SEALEVELPRESSURE_HPA (1013.25)

It stores atmospheric pressure at sea level in hectopascals (equivalent to millibar). This identifier is used to estimate altitude by comparing current pressure with pressure at sea level.

In this example, the default value is used, but for a more accurate result, replace this value with the current pressure at sea level in your area, which can be taken from reference books.

I²C interface

In this example, the default interface is I²C. As you can see, you just need to create an Adafruit_BME280 library object called bme.

Adafruit_BME280 bme;

If you want to use the SPI interface, you need to comment out the previous line and uncomment one of the following lines, depending on whether you are using hardware or software SPI.

//Adafruit_BME280 bme(BME_CS); // hardware SPI
//Adafruit_BME280 bme(BME_CS, BME_MOSI, BME_MISO, BME_SCK); // software SPI

 Setup () function
In the setup () function, you run a serial interface.

Serial.begin(9600);

 The sensor is also initialized:

status = bme.begin(0x76);
if (!status) {
  Serial.println("Could not find a valid BME280 sensor, check wiring!");
  while (1);
}

 Output values
In the loop () function, the printValues () function reads readings from the BME280 sensor and displays them in the serial COM port (Serial Monitor) window.

void loop() {
  printValues();
  delay(delayTime);
}

 To read the temperature, humidity and pressure and estimate the altitude, the following functions must be called:

bme.readTemperature () - reads the temperature in degrees Celsius;
bme.readHumidity () - reads absolute humidity;
bme.readPressure () - reads the pressure in hPa (hectopascal = millibar);
bme.readAltitude (SEALEVELPRESSURE_HPA) - estimates the height in meters based on pressure at sea level.

Download the code to the ESP32 microcontroller, open the window of the serial COM port (Serial Monitor) and set the baud rate to 9600 baud. You should see the readings in the serial port window.

 CREATING HTML TABLES
As you saw at the beginning of the article, we display the readings on a web page using the table processed by the ESP32 microcontroller. Therefore, we need to write the HTML code to create the table.

To create a table in HTML code, use the <table> and </ table> tags.

To create rows use the <tr> and </ tr> tags. The heading of the table is defined by the tags <th> and </ th>, and each cell is defined by the tags <td> and </ td>.

To create a table to display the readings, use the following HTML code:

<table>
  <tr>
    <th>MEASUREMENT</th>
    <th>VALUE</th>
  </tr>
  <tr>
    <td>Temp. Celsius</td>
    <td>--- *C</td>
  </tr>
  <tr>
    <td>Temp. Fahrenheit</td>
    <td>--- *F</td>
  </tr>
  <tr>
    <td>Pressure</td>
    <td>--- hPa</td>
  </tr>
  <tr>
    <td>Approx. Altitude</td>
    <td>--- meters</td></tr>
  <tr>
    <td>Humidity</td>
    <td>--- %</td>
  </tr>
</table>

Create a table header using a cell with the content ‚ÄúMEASUREMENT‚ÄĚ (Read) and a cell with the content ‚ÄúVALUE‚ÄĚ (Value).

Then, using the <tr> and </ tr> tags, we create six rows to display each of the readings.
Inside each row, we create two cells with <td> and </ td> tags: one with the name of the reading, and the other with its value. The three dashes "-" must then be replaced with the current values ‚Äč‚Äčof the BME sensor readings.

You can save this code as a file table.html, drag this file to a web browser and see what happened. The above HTML code creates the following table.

No style was applied to the table. You can use the CSS language to design the appearance of the table to your liking.

CREATING A WEB SERVER
Now that you know how to read the sensor and how to create a table to display the results, it's time to create a web server. If you have studied other ESP32 microcontroller manuals, then you should be familiar with most of the code.

Copy the following code into the Arduino IDE development environment. Do not download it yet. First, you need to insert the SSID and password.

// Load Wi-Fi library

#include <WiFi.h>
#include <Wire.h>
#include <Adafruit_BME280.h>
#include <Adafruit_Sensor.h>
 
//uncomment the following lines if you're using SPI
/*#include <SPI.h>
#define BME_SCK 18
#define BME_MISO 19
#define BME_MOSI 23
#define BME_CS 5*/
 
#define SEALEVELPRESSURE_HPA (1013.25)
 
Adafruit_BME280 bme; // I2C
//Adafruit_BME280 bme(BME_CS); // hardware SPI
//Adafruit_BME280 bme(BME_CS, BME_MOSI, BME_MISO, BME_SCK); // software SPI
 
// Replace with your network credentials
const char* ssid     = "REPLACE_WITH_YOUR_SSID";
const char* password = "REPLACE_WITH_YOUR_PASSWORD";
 
// Set web server port number to 80
WiFiServer server(80);
 
// Variable to store the HTTP request
String header;
 
void setup() {
  Serial.begin(115200);
  bool status;
 
  // default settings
  // (you can also pass in a Wire library object like &Wire2)
  //status = bme.begin();  
  if (!bme.begin(0x76)) {
    Serial.println("Could not find a valid BME280 sensor, check wiring!");
    while (1);
  }
 
  // Connect to Wi-Fi network with SSID and password
  Serial.print("Connecting to ");
  Serial.println(ssid);
  WiFi.begin(ssid, password);
  while (WiFi.status() != WL_CONNECTED) {
    delay(500);
    Serial.print(".");
  }
  // Print local IP address and start web server
  Serial.println("");
  Serial.println("WiFi connected.");
  Serial.println("IP address: ");
  Serial.println(WiFi.localIP());
  server.begin();
}
 
void loop(){
  WiFiClient client = server.available();   // Listen for incoming clients
 
  if (client) {                             // If a new client connects,
    Serial.println("New Client.");          // print a message out in the serial port
    String currentLine = "";                // make a String to hold incoming data from the client
    while (client.connected()) {            // loop while the client's connected
      if (client.available()) {             // if there's bytes to read from the client,
        char c = client.read();             // read a byte, then
        Serial.write(c);                    // print it out the serial monitor
        header += c;
        if (c == '\n') {                    // if the byte is a newline character
          // if the current line is blank, you got two newline characters in a row.
          // that's the end of the client HTTP request, so send a response:
          if (currentLine.length() == 0) {
            // HTTP headers always start with a response code (e.g. HTTP/1.1 200 OK)
            // and a content-type so the client knows what's coming, then a blank line:
            client.println("HTTP/1.1 200 OK");
            client.println("Content-type:text/html");
            client.println("Connection: close");
            client.println();
            
            // Display the HTML web page
            client.println("<!DOCTYPE html><html>");
            client.println("<head><meta name=\"viewport\" content=\"width=device-width, initial-scale=1\">");
            client.println("<link rel=\"icon\" href=\"data:,\">");
            // CSS to style the table
            client.println("<style>body { text-align: center; font-family: \"Trebuchet MS\", Arial;}");
            client.println("table { border-collapse: collapse; width:35%; margin-left:auto; margin-right:auto; }");
            client.println("th { padding: 12px; background-color: #0043af; color: white; }");
            client.println("tr { border: 1px solid #ddd; padding: 12px; }");
            client.println("tr:hover { background-color: #bcbcbc; }");
            client.println("td { border: none; padding: 12px; }");
            client.println(".sensor { color:white; font-weight: bold; background-color: #bcbcbc; padding: 1px; }");
            
            // Web Page Heading
            client.println("</style></head><body><h1>ESP32 with BME280</h1>");
            client.println("<table><tr><th>MEASUREMENT</th><th>VALUE</th></tr>");
            client.println("<tr><td>Temp. Celsius</td><td><span class=\"sensor\">");
            client.println(bme.readTemperature());
            client.println(" *C</span></td></tr>");  
            client.println("<tr><td>Temp. Fahrenheit</td><td><span class=\"sensor\">");
            client.println(1.8 * bme.readTemperature() + 32);
            client.println(" *F</span></td></tr>");      
            client.println("<tr><td>Pressure</td><td><span class=\"sensor\">");
            client.println(bme.readPressure() / 100.0F);
            client.println(" hPa</span></td></tr>");
            client.println("<tr><td>Approx. Altitude</td><td><span class=\"sensor\">");
            client.println(bme.readAltitude(SEALEVELPRESSURE_HPA));
            client.println(" m</span></td></tr>");
            client.println("<tr><td>Humidity</td><td><span class=\"sensor\">");
            client.println(bme.readHumidity());
            client.println(" %</span></td></tr>");
            client.println("</body></html>");
            
            // The HTTP response ends with another blank line
            client.println();
            // Break out of the while loop
            break;
          } else { // if you got a newline, then clear currentLine
            currentLine = "";
          }
        } else if (c != '\r') {  // if you got anything else but a carriage return character,
          currentLine += c;      // add it to the end of the currentLine
        }
      }
    }
    // Clear the header variable
    header = "";
    // Close the connection
    client.stop();
    Serial.println("Client disconnected.");
    Serial.println("");
  }
}

 Correct the following lines to insert your SSID and password between the double quotes.

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

 Then make sure the correct board and serial COM port are selected, and load the code into the ESP32 microcontroller. After downloading, open the window of the serial COM port (Serial Monitor) and set the baud rate to 115200 baud, and also copy the ESP32 microcontroller IP address.

 

 Open a web browser, insert the IP address. You will need to see the latest sensor readings. To update the readings you just need to refresh the web page.

 

HOW IT WORKS
This example is very similar to the example from the ESP32 Web Server manual (‚ÄúWeb Server on ESP32‚ÄĚ). First, we include the library for the WiFi interface, as well as the libraries necessary for reading data from the BME280 sensor.

// Load Wi-Fi library
#include "WiFi.h"
#include "Wire.h"
#include "Adafruit_BME280.h"
#include "Adafruit_Sensor.h"

The next line defines the identifier containing the pressure at sea level. For a more accurate estimate of altitude, replace the value with the current pressure at sea level in your area.

#define SEALEVELPRESSURE_HPA (1013.25)

The next line creates an Adafruit_BME280 library object called bme, which by default establishes communication with the sensor via the I²C interface.

 Adafruit_BME280 bme; // I2C

As mentioned before, you must insert your SSID and password (password) in the following lines inside double quotes.

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

Then configure the web server on port 80.

// Set web server port number to 80
WiFiServer server(80);

The next line creates a variable to hold the HTTP request header.

 String header;

 setup ()

In the setup () function, we run a serial interface at a speed of 115200 baud for debugging purposes.

 Serial.begin(115200);

 Check that the BME280 sensor is initialized successfully.

if (!bme.begin(0x76)) {
  Serial.println("Could not find a valid BME280 sensor, check wiring!");
  while (1);
}

 The following lines establish a connection via Wi-Fi via the WiFi.begin function (SSID, password), wait for a successful connection and display the IP address of the ESP microcontroller in the serial port.

// Connect to Wi-Fi network with SSID and password
Serial.print("Connecting to ");
Serial.println(ssid);
WiFi.begin(ssid, password);
while (WiFi.status() != WL_CONNECTED) {
  delay(500);
  Serial.print(".");
}
// Print local IP address and start web server
Serial.println("");
Serial.println("WiFi connected.");
Serial.println("IP address: ");
Serial.println(WiFi.localIP());
server.begin();

 loop ()

In the loop () function, the actions performed when connecting to the web server of a new client are programmed. The ESP microcontroller keeps track of the emergence of new customers all the time through the following line.

 WiFiClient client = server.available(); // Listen for incoming clients

 When we receive a request from a client, we save the incoming data. The next while loop will be executed while the client is connected. We do not recommend changing the next part of the code unless you know for sure what you are doing.

if (client) { // If a new client connects,
  Serial.println("New Client."); // print a message out in the serial port
  String currentLine = ""; // make a String to hold incoming data from the client
  while (client.connected()) { // loop while the client's connected
    if (client.available()) { // if there's bytes to read from the client,
      char c = client.read(); // read a byte, then
      Serial.write(c); // print it out the serial monitor
      header += c;
      if (c == '\n') { // if the byte is a newline character
        // if the current line is blank, you got two newline characters in a row.
        // that's the end of the client HTTP request, so send a response:
        if (currentLine.length() == 0) {
          // HTTP headers always start with a response code (e.g. HTTP/1.1 200 OK)
          // and a content-type so the client knows what's coming, then a blank line:
          client.println("HTTP/1.1 200 OK");
          client.println("Content-type:text/html");
          client.println("Connection: close");
          client.println();

 DISPLAYING A WEB PAGE IN HTML FORMAT
The next thing you need to do is to send a response to the client in the form of HTML code to create a new web page.

Note: You can read the previous parts of the code to learn the basics of HTML and CSS.

The webpage is sent to the client via the expression client.println (). You can pass functions as an argument whatever you want to send to the client.

The following code snippet sends a web page to display the sensor readings in a table.

       client.println("<!DOCTYPE html><html>");

client.println("<head><meta name=\"viewport\" content=\"width=device-width, initial-scale=1\">");
client.println("<link rel=\"icon\" href=\"data:,\">");
// CSS to style the on/off buttons
// Feel free to change the background-color and font-size attributes to fit your preferences
client.println("<style>body { text-align: center; font-family: \"Trebuchet MS\", Arial;}");
client.println("table { border-collapse: collapse; width:35%; margin-left:auto; margin-right:auto; }");
client.println("th { padding: 12px; background-color: #0043af; color: white; }");
client.println("tr { border: 1px solid #ddd; padding: 12px; }");
client.println("tr:hover { background-color: #bcbcbc; }");
client.println("td { border: none; padding: 12px; }");
client.println(".sensor { color:white; font-weight: bold; background-color: #bcbcbc; padding: 1px; }");
// Web Page Heading
client.println("</style></head><body><h1>ESP32 with BME280</h1>");
client.println("<table><tr><th>MEASUREMENT</th><th>VALUE</th></tr>");
client.println("<tr><td>Temp. Celsius</td><td><span class=\"sensor\">");
client.println(bme.readTemperature());
client.println(" *C</span></td></tr>");
client.println("<tr><td>Temp. Fahrenheit</td><td><span class=\"sensor\">");
client.println(1.8 * bme.readTemperature() + 32);
client.println(" *F</span></td></tr>");
client.println("<tr><td>Pressure</td><td><span class=\"sensor\">");
client.println(bme.readPressure() / 100.0F);
client.println(" hPa</span></td></tr>");
client.println("<tr><td>Approx. Altitude</td><td><span class=\"sensor\">");
client.println(bme.readAltitude(SEALEVELPRESSURE_HPA));
client.println(" m</span></td></tr>");
client.println("<tr><td>Humidity</td><td><span class=\"sensor\">");
client.println(bme.readHumidity());
client.println(" %</span></td></tr>");
client.println("</body></html>");

 DISPLAYING SENSOR READINGS
To display the sensor readings in the table, we just need to align them between the appropriate <td> and </ td> tags. For example, to display the temperature, use the following code.

client.println("<tr><td>Temp. Celsius</td><td><span class=\"sensor\">");
client.println(bme.readTemperature());
client.println(" *C</span></td></tr>");

¬†Note: the <span> tag is useful for customizing the appearance of a separate part of the text. In this case, we use the <span> tag to include the sensor readings in a class called ‚Äúsensor‚ÄĚ. This is useful to customize the appearance of this particular section of text through the CSS language.

By default, the table shows the temperature in degrees Celsius and Fahrenheit. You can comment on the following three lines if you want to display the temperature only in degrees Fahrenheit.

/*client.println("<tr><td>Temp. Celsius</td><td><span class=\"sensor\">");
client.println(bme.readTemperature());
client.println(" *C</span></td></tr>");*/

 COMPLETION OF THE COMMUNICATION SESSION
Finally, when the response is completed, we clear the variable with the header and close the session with the client using the client.stop () function.

// Clear the header variable
header = "";
// Close the connection
client.stop();

 START!

 In the course of this project, you learned how to read temperature, humidity, and pressure, as well as to estimate the height above sea level using the BME280 sensor module. You also learned how to create a web server that displays a table with sensor readings. You can easily change this project to display data from any other sensor.

 

Read more ‚Üí