Arduino BME280 Weather Station Featured Image

Posted on

by

in

, ,

Using Arduino with BME280 plus a weather station project

Introduction

This post will show you how to interface your Arduino code with the BMP280/BME280 sensor to retrieve Temperature, Humidity, Pressure, and Relative Altitude. At the end of this post, we will create a beautiful weather station dashboard that will display the sensor readings in colorful text and charts.

If you want to see this project in a video format then please see below. You can also watch this on my YouTube channel.

Prerequisites

The component list below shows the components needed in order to follow along with this post.

Disclosure: These are affiliate links and I will earn small commissions to support my site when you buy through these links.

I am using the awesome PlatformIO IDE in developing this project as this will help you greatly in the development process rather than using the Arduino IDE. If you are unfamiliar with how to use PlatformIO, please take a look at my earlier post about how to get started using this IDE.

Related Content:
PlatformIO Tutorial for Arduino Development

What is BME280/BMP280?

Bosch BME280 temperature sensor

The BME280 is a humidity sensor especially developed for mobile applications and wearables where size and low power consumption are key design parameters. The unit combines high linearity and high accuracy sensors and is perfectly feasible for low current consumption, long-term stability and high EMC robustness. The humidity sensor offers an extremely fast response time and therefore supports performance requirements for emerging applications such as context awareness, and high accuracy over a wide temperature range.

bosch

Bosch developed the BME280 sensor which is capable of providing the following information with high accuracy.

  • Temperature
  • Humidity
  • Pressure
  • Altitude

The complete datasheet can be found here but the important specifications are listed below.

Temperature-40 to +85 °Celcius (+/- 1°C)
Humidity0% to 100% RH (+/- 3%)
Pressure300Pa to 1100hPa (+/- 1 hPa)
Altitude0 to 30,000 ft (+/- 1 m)
Operating Voltage3-5 V

Note: The BMP280/BME280 sensor can also function as an altimeter as the pressure changes with altitude but for the altitude to be accurate then you need to know the pressure at sea level so you can use this as a baseline.

Pinout

BME280 I2C Pinout

I am using a BME280 module that uses the I2C as its communication protocol so the standard SDA/SCL pins are available together with the power pins VCC/GND.

Schematic/Wiring

Arduino ESP8266 ESP32 BME280 Wiring

The image above is the wiring diagram between our ESP8266 microcontroller with the BMP280/BME280 sensor. For some ESP8266 modules, the SCL and SDA pins used for I2C communication are labeled as D1 and D2 respectively.

If your module is using the SPI pins then that would be a different wiring with a different set of pins.

How to program the BMP280/BME280 sensor with Arduino?

Install the Adafruit BME280 library

Install the Adafruit BME280 Library as we will use this library in driving our BME280 sensor.

If you are using the Arduino 2.0 IDE then click Tools then Manage Libraries. Locate the Adafruit BME280 library in the search box and click install.

Arduino IDE Adafrtuit BME280 library install

If you are using Visual Studio Code with PlatformIO IDE extensions then you can open the Libraries tab and Search the Adafruit BME280 library then click the Add to Project button.

Platform IO Add Adafruit BME280 Library

Example Arduino Code to read BME280 sensor

The bmetest.ino file from the Adafruit GitHub repository can be used as a way to read the BME280 sensor.

#include <Wire.h>
#include <SPI.h>
#include <Adafruit_Sensor.h>
#include <Adafruit_BME280.h>

#define BME_SCK 13
#define BME_MISO 12
#define BME_MOSI 11
#define BME_CS 10

#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);
    while(!Serial);    // time to get serial running
    Serial.println(F("BME280 test"));

    unsigned status;
    
    // default settings
    status = bme.begin();  
    // You can also pass in a Wire library object like &Wire2
    // status = bme.begin(0x76, &Wire2)
    if (!status) {
        Serial.println("Could not find a valid BME280 sensor, check wiring, address, sensor ID!");
        Serial.print("SensorID was: 0x"); Serial.println(bme.sensorID(),16);
        Serial.print("        ID of 0xFF probably means a bad address, a BMP 180 or BMP 085\n");
        Serial.print("   ID of 0x56-0x58 represents a BMP 280,\n");
        Serial.print("        ID of 0x60 represents a BME 280.\n");
        Serial.print("        ID of 0x61 represents a BME 680.\n");
        while (1) delay(10);
    }
    
    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();
}

Let us go through what each line of code does.

#include <Wire.h>
#include <SPI.h>
#include <Adafruit_Sensor.h>
#include <Adafruit_BME280.h>

#define BME_SCK 13
#define BME_MISO 12
#define BME_MOSI 11
#define BME_CS 10

Import the necessary header files to drive the BME280 temperature sensor. Also, if you have the SPI module of a BME280 then you can define the pins that will participate in the SPI communication.

#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;

As I have mentioned earlier, the Altitude can be measured if you know the pressure at sea level so, for testing purposes, we define the pressure at sea level here. Next, we create an instance of the class Adafruit_BME280 when using I2C or if you are using SPI then you can uncomment the last two lines. A delay before the next read is defined here as well.

void setup() {
    Serial.begin(9600);
    while(!Serial);    // time to get serial running
    Serial.println(F("BME280 test"));

We set up our serial port in here and wait for it to be available.

    unsigned status;
    
    // default settings
    status = bme.begin();  
    // You can also pass in a Wire library object like &Wire2
    // status = bme.begin(0x76, &Wire2)
    if (!status) {
        Serial.println("Could not find a valid BME280 sensor, check wiring, address, sensor ID!");
        Serial.print("SensorID was: 0x"); Serial.println(bme.sensorID(),16);
        Serial.print("        ID of 0xFF probably means a bad address, a BMP 180 or BMP 085\n");
        Serial.print("   ID of 0x56-0x58 represents a BMP 280,\n");
        Serial.print("        ID of 0x60 represents a BME 280.\n");
        Serial.print("        ID of 0x61 represents a BME 680.\n");
        while (1) delay(10);
    }
    
    Serial.println("-- Default Test --");
    delayTime = 1000;

    Serial.println();
}

Begin the communication with the bme.begin() method and check if there is no error by looking at the return status variable.

If you encounter the message “Could not find a valid BME280 sensor check wiring, address, sensor ID!” then the first thing to check is your wiring if it is correct.

Next, your bme280 default I2C address might not be the default 0x77 so you can replace the code above with

//status = bme.begin();
status = bme.begin(0x76);

In the loop section, print the readings of your BME280 by executing the below code

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();
}

A sample output from running this program would look like this.

Temperature = 29.02 °C
Pressure = 1005.23 hPa
Approx. Altitude = 66.95 m
Humidity = 61.67 %

If you were able to retrieve these values from your BMP/BME280 sensor then congratulations! We can now proceed to display these values in a more colorful and efficient manner by making our own Arduino-powered BMP/BME280 weather station with a beautiful dashboard.

BME280/BMP280 Weather Station Project

Arduino BME280-BMP280 Weather Station

We are building our custom Arduino-powered BME280/BMP280 weather station project using an ESP8266/ESP32 board with a beautiful dashboard that was inspired by the following post.

Note: I have developed this application on a wide-screen monitor with a resolution of 1366 x 768 so the user interface might vary for smaller resolutions. I did not bother to test this in a mobile environment like your phone. The code would become bigger to compensate for mobile environments so I just simplified it.

BME280 Weather Station Dashboard Parts

Arduino with BME280 Weather Station Dashboard Parts

The above image is the parts of our Arduino BMP/BME280 Weather station dashboard and it includes the following:

  • Sidebar – displays the dashboard link and my project name
  • Boxes – displays the BME280 sensor readings in text format
  • Line Chart – displays the historical sensor reading records
  • Gauge Chart – displays the sensor readings in a beautiful gauge chart format

All parts above are referenced in the code so I have listed them here.

Design

Arduino with BME280 Weather Station Design

The image above is the overall design of our Arduino BMP/BME280 weather station dashboard.

We have created a web server inside our ESP8266/ESP32 board that will open up our web application and they both communicate thru HTTP. The Weather station dashboard is requesting for sensor readings every three seconds and it asynchronously updates its user interface.

When the web server receives the request then it asks for sensor reading values from the BME280 sensor. The web server is written in Arduino code while the web application is a mixture of HTML/CSS/Javascript.

If you are unsure what a web server is and how to create a web server using an ESP8266 and Arduino then please see the link below.

Related Content:
ESP8266 Webserver Using LittleFS

Code

The complete code for this project is in my GitHub account and you can either download it as a zip file or if you know Git then you can clone it using this command.

git clone https://github.com/donskytech/arduino-bme280-weather-station

Open this with Visual Studio Code when the download or cloning is finished.

Project Files

The files encircled above are the important parts of this project and the following are its role.

  • main.cpp – this is where we have created our Arduino web server and is the one that communicates with our BME/BMP280 sensor
  • index.html – contains the HTML markup of our weather station project
  • style.css – stylizes the content of our HTML page
  • main.js – handles the logic of updating the values read from the sensor and displays the readings in graphical charts.

We will discuss each file in the next section and I will try my best to explain to you what each line of code does.

Libraries used in this project

I am using the following libraries in building this project:

  • ESPAsyncWebServer-esphome – used to create the web server of our application that will handle HTTP requests.
  • Adafruit BME280 Library – used to communicate our Arduino code with the BME280 sensor.
  • ArduinoJson – used to return JSON (Javascript Object Notation) responses to our web applications.

The platform.ini file is listing all of the dependencies and configurations that we have assigned to this project. As we are using the PlatformIO IDE extension so there is no need to manually download or configure anything compare to using Arduino IDE.

; PlatformIO Project Configuration File
;
;   Build options: build flags, source filter
;   Upload options: custom upload port, speed and extra flags
;   Library options: dependencies, extra library storages
;   Advanced options: extra scripting
;
; Please visit documentation for the other options and examples
; https://docs.platformio.org/page/projectconf.html

[env:esp12e]
platform = espressif8266
board = esp12e
framework = arduino
board_build.filesystem = littlefs
monitor_speed = 115200
lib_deps = 
	https://github.com/adafruit/Adafruit_BME280_Library.git
	ottowinter/ESPAsyncWebServer-esphome@^3.0.0
	bblanchon/ArduinoJson@^6.19.4

main.cpp

/*
  Title:  Arduino BME/BMP280 Weather Station Dashboard
  Author: donsky
  For:    www.donskytech.com
  Date:   Dec 20, 2022
*/
#include <Arduino.h>
#ifdef ESP32
#include <WiFi.h>
#include <AsyncTCP.h>
#elif defined(ESP8266)
#include <ESP8266WiFi.h>
#include <ESPAsyncTCP.h>
#endif
#include <ESPAsyncWebServer.h>
#include <LittleFS.h>
#include "AsyncJson.h"
#include "ArduinoJson.h"
#include <Wire.h>
#include <SPI.h>
#include <Adafruit_Sensor.h>
#include <Adafruit_BME280.h>

#define BME_SCK 13
#define BME_MISO 12
#define BME_MOSI 11
#define BME_CS 10
#define SEALEVELPRESSURE_HPA (1013.25)


/*
  Replace the SSID and Password according to your wifi
*/
const char *ssid = "<REPLACE_WITH_YOUR_WIFI_SSID>";
const char *password = "<REPLACE_WITH_YOUR_WIFI_PASSWORD";

// Webserver setup
AsyncWebServer server(80);

void notFound(AsyncWebServerRequest *request)
{
  request->send(404, "text/plain", "Not found");
}

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

void setup()
{
  Serial.begin(115200);
  while (!Serial)
    ; // time to get serial running

  // Initialize Filesystem LittleFS
  if (!LittleFS.begin())
  {
    Serial.println("Cannot load LittleFS Filesystem!");
    return;
  }

  // Connect to WIFI
  WiFi.mode(WIFI_STA);
  WiFi.begin(ssid, password);
  if (WiFi.waitForConnectResult() != WL_CONNECTED)
  {
    Serial.printf("WiFi Failed!\n");
    return;
  }
  String localIPAddress = WiFi.localIP().toString();
  Serial.print("IP Address: ");
  Serial.println(localIPAddress);

  // Route for root index.html
  server.on("/", HTTP_GET, [](AsyncWebServerRequest *request)
            { request->send(LittleFS, "/index.html", String(), false); });

  // Route for root style.css
  server.on("/style.css", HTTP_GET, [](AsyncWebServerRequest *request)
            { request->send(LittleFS, "/style.css", "text/css"); });

  // Route for root main.js
  server.on("/main.js", HTTP_GET, [](AsyncWebServerRequest *request)
            { request->send(LittleFS, "/main.js", "text/javascript"); });

  // Respond to /sensorReadings event
  server.on("/sensorReadings", HTTP_GET, [](AsyncWebServerRequest *request) {
    AsyncResponseStream *response = request->beginResponseStream("application/json");
    DynamicJsonDocument json(1024);
    json["status"] = "ok";
    json["temperature"] = bme.readTemperature();
    json["pressure"] = bme.readPressure() / 100.0F;
    json["altitude"] = bme.readAltitude(SEALEVELPRESSURE_HPA);
    json["humidity"] = bme.readHumidity();
    serializeJson(json, *response);
    request->send(response);
  });

  server.onNotFound(notFound);

  server.begin();

  unsigned status;

  // default settings
  status = bme.begin(0x76);
  // You can also pass in a Wire library object like &Wire2
  // status = bme.begin(0x76, &Wire2)
  if (!status)
  {
    Serial.println("Could not find a valid BME280 sensor, check wiring, address, sensor ID!");
    Serial.print("SensorID was: 0x");
    Serial.println(bme.sensorID(), 16);
    Serial.print("        ID of 0xFF probably means a bad address, a BMP 180 or BMP 085\n");
    Serial.print("   ID of 0x56-0x58 represents a BMP 280,\n");
    Serial.print("        ID of 0x60 represents a BME 280.\n");
    Serial.print("        ID of 0x61 represents a BME 680.\n");
    while (1)
      delay(10);
  }
}

void loop()
{
  // Nothing in here
}

The above code is our Arduino code that runs a web server and communicates with our BMP/BME280 sensor.

/*
  Title:  Arduino BME/BMP280 Weather Station Dashboard
  Author: donsky
  For:    www.donskytech.com
  Date:   Dec 20, 2022
*/
#include <Arduino.h>
#ifdef ESP32
#include <WiFi.h>
#include <AsyncTCP.h>
#elif defined(ESP8266)
#include <ESP8266WiFi.h>
#include <ESPAsyncTCP.h>
#endif
#include <ESPAsyncWebServer.h>
#include <LittleFS.h>
#include "AsyncJson.h"
#include "ArduinoJson.h"
#include <Wire.h>
#include <SPI.h>
#include <Adafruit_Sensor.h>
#include <Adafruit_BME280.h>

Import necessary C++ header files to create our web server and communicate with our BMP/BME280 sensor.

#define BME_SCK 13
#define BME_MISO 12
#define BME_MOSI 11
#define BME_CS 10
#define SEALEVELPRESSURE_HPA (1013.25)

If your BMP/BME280 sensor comes with an SPI package then you can define here the pin assignment to be used for the communication. The SEALEVELPRESSURE_HPA is used to calculate the relative altitude.

/*
  Replace the SSID and Password according to your wifi
*/
const char *ssid = "<REPLACE_WITH_YOUR_WIFI_SSID>";
const char *password = "<REPLACE_WITH_YOUR_WIFI_PASSWORD";

Replace the following lines with your Wifi network configuration.

// Webserver setup
AsyncWebServer server(80);

void notFound(AsyncWebServerRequest *request)
{
  request->send(404, "text/plain", "Not found");
}

We declare our web server here and define our notFound function that will handle resources not present in our web server.

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

The Adafruit_BME280 class is described here and we are using I2C as our communication protocol. However, if your sensor is using SPI then we can use the commented line below to define the SPI pins.

void setup()
{
  Serial.begin(115200);
  while (!Serial)
    ; // time to get serial running

The Serial baud rate is assigned and we wait for the Serial to be available.

  // Initialize Filesystem LittleFS
  if (!LittleFS.begin())
  {
    Serial.println("Cannot load LittleFS Filesystem!");
    return;
  }

We initialize our LittleFS file system here.

Related Content:
ESP8266 LittleFS Tutorial Series

  // Connect to WIFI
  WiFi.mode(WIFI_STA);
  WiFi.begin(ssid, password);
  if (WiFi.waitForConnectResult() != WL_CONNECTED)
  {
    Serial.printf("WiFi Failed!\n");
    return;
  }
  String localIPAddress = WiFi.localIP().toString();
  Serial.print("IP Address: ");
  Serial.println(localIPAddress);

Connect to our wifi network using the code above.

  // Route for root index.html
  server.on("/", HTTP_GET, [](AsyncWebServerRequest *request)
            { request->send(LittleFS, "/index.html", String(), false); });

  // Route for root style.css
  server.on("/style.css", HTTP_GET, [](AsyncWebServerRequest *request)
            { request->send(LittleFS, "/style.css", "text/css"); });

  // Route for root main.js
  server.on("/main.js", HTTP_GET, [](AsyncWebServerRequest *request)
            { request->send(LittleFS, "/main.js", "text/javascript"); });

  // Respond to /sensorReadings event
  server.on("/sensorReadings", HTTP_GET, [](AsyncWebServerRequest *request) {
    AsyncResponseStream *response = request->beginResponseStream("application/json");
    DynamicJsonDocument json(1024);
    json["status"] = "ok";
    json["temperature"] = bme.readTemperature();
    json["pressure"] = bme.readPressure() / 100.0F;
    json["altitude"] = bme.readAltitude(SEALEVELPRESSURE_HPA);
    json["humidity"] = bme.readHumidity();
    serializeJson(json, *response);
    request->send(response);
  });

  server.onNotFound(notFound);

  server.begin();

The routes for our web server are defined wherein our root (“/”), index.html, style.css, and main.js are served. The “/sensorReadings” routes will return JSON content to our web application. Furthermore, this is the route that communicates with our BME/BMP280 sensor to retrieve sensor readings.

The server.begin(); line will start our web server.

 unsigned status;

  // default settings
  status = bme.begin(0x76);
  // You can also pass in a Wire library object like &Wire2
  // status = bme.begin(0x76, &Wire2)
  if (!status)
  {
    Serial.println("Could not find a valid BME280 sensor, check wiring, address, sensor ID!");
    Serial.print("SensorID was: 0x");
    Serial.println(bme.sensorID(), 16);
    Serial.print("        ID of 0xFF probably means a bad address, a BMP 180 or BMP 085\n");
    Serial.print("   ID of 0x56-0x58 represents a BMP 280,\n");
    Serial.print("        ID of 0x60 represents a BME 280.\n");
    Serial.print("        ID of 0x61 represents a BME 680.\n");
    while (1)
      delay(10);
  }

The code above starts the communication with our BME280 sensor.

void loop()
{
  // Nothing in here
}

The loop() code does not contain any logic as we are using the asynchronous web server.

index.html

Below is the code for our index.html which will display the sensor readings in both text and chart format.

<!DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="UTF-8" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
    <meta http-equiv="X-UA-Compatible" content="ie=edge" />
    <title>Arduino BME280 Weather Station</title>
    <link
      href="https://unpkg.com/boxicons@2.0.7/css/boxicons.min.css"
      rel="stylesheet"
    />
    <link rel="stylesheet" href="./style.css" />
    <script src="https://cdn.plot.ly/plotly-2.16.1.min.js"></script>
  </head>
  <body>
    <div class="sidebar">
      <div class="logo-details">
        <i class='bx bx-microchip'></i>
        <span class="logo_name">donskytech</span>
      </div>
      <ul class="nav-links">
        <li>
          <a href="#" class="active">
            <i class="bx bx-grid-alt"></i>
            <span class="links_name">Dashboard</span>
          </a>
        </li>
      </ul>
    </div>
    <section class="home-section">
      <nav>
        <div class="sidebar-button">
          <i class="bx bx-menu sidebarBtn"></i>
          <span class="dashboard">Arduino BME280 Weather Station Dashboard</span>
        </div>
      </nav>
      <div class="home-content">
        <div class="overview-boxes">
          <div class="box">
            <div class="right-side">
              <div class="box-topic">Temperature</div>
              <div class="number" id="temperature"></div>
            </div>
            <i class="bx bxs-thermometer readings"></i>
          </div>
          <div class="box">
            <div class="right-side">
              <div class="box-topic">Humidity</div>
              <div class="number" id="humidity"></div>
            </div>
            <i class="bx bxs-droplet-half readings two"></i>
          </div>
          <div class="box">
            <div class="right-side">
              <div class="box-topic">Pressure</div>
              <div class="number" id="pressure"></div>
            </div>
            <i class="bx bxs-tachometer readings three"></i>
          </div>
          <div class="box">
            <div class="right-side">
              <div class="box-topic">Approx Altitude</div>
              <div class="number" id="altitude"></div>
            </div>
            <i class="bx bxs-building readings four"></i>
          </div>
        </div>
        <div class="graph-box">
          <div class="history-charts">
            <div class="title">History Charts</div>
            <div class="history-details">
              <div class="history-col-item">
                <div id="temperature-history" class="history-divs"></div>
                <div id="humidity-history" class="history-divs"></div>
              </div>
              <div class="history-col-item">
                <div id="pressure-history" class="history-divs"></div>
                <div id="altitude-history" class="history-divs"></div>
              </div>
            </div>
          </div>
          <div class="gaugeCharts">
            <div class="title">Gauge Charts</div>
            <div class="gauge-list">
              <div class="gauge-box"><div id="temperature-gauge"></div></div>
              <div class="gauge-box"><div id="humidity-gauge"></div></div>
              <div class="gauge-box"><div id="pressure-gauge"></div></div>
              <div class="gauge-box"><div id="altitude-gauge"></div></div>
            </div>
          </div>
        </div>
      </div>
    </section>
    <script src="main.js"></script>
  </body>
</html>
<!DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="UTF-8" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
    <meta http-equiv="X-UA-Compatible" content="ie=edge" />
    <title>Arduino BME280 Weather Station</title>
    <link
      href="https://unpkg.com/boxicons@2.0.7/css/boxicons.min.css"
      rel="stylesheet"
    />
    <link rel="stylesheet" href="./style.css" />
    <script src="https://cdn.plot.ly/plotly-2.16.1.min.js"></script>
  </head>

We set the meta tags for our page and import the necessary CSS and javascript files. In addition, we are using boxicons to display the different icons used in our project.

  <body>
    <div class="sidebar">
      <div class="logo-details">
        <i class='bx bx-microchip'></i>
        <span class="logo_name">donskytech</span>
      </div>
      <ul class="nav-links">
        <li>
          <a href="#" class="active">
            <i class="bx bx-grid-alt"></i>
            <span class="links_name">Dashboard</span>
          </a>
        </li>
      </ul>
    </div>

The sidebar is where we display my soon-to-be popular name (donskytech hahaha! :))

    <section class="home-section">
      <nav>
        <div class="sidebar-button">
          <i class="bx bx-menu sidebarBtn"></i>
          <span class="dashboard">Arduino BME280 Weather Station Dashboard</span>
        </div>
      </nav>
      <div class="home-content">
        <div class="overview-boxes">
          <div class="box">
            <div class="right-side">
              <div class="box-topic">Temperature</div>
              <div class="number" id="temperature"></div>
            </div>
            <i class="bx bxs-thermometer readings"></i>
          </div>
          <div class="box">
            <div class="right-side">
              <div class="box-topic">Humidity</div>
              <div class="number" id="humidity"></div>
            </div>
            <i class="bx bxs-droplet-half readings two"></i>
          </div>
          <div class="box">
            <div class="right-side">
              <div class="box-topic">Pressure</div>
              <div class="number" id="pressure"></div>
            </div>
            <i class="bx bxs-tachometer readings three"></i>
          </div>
          <div class="box">
            <div class="right-side">
              <div class="box-topic">Approx Altitude</div>
              <div class="number" id="altitude"></div>
            </div>
            <i class="bx bxs-building readings four"></i>
          </div>
        </div>

The “home-section” class is where we create the HTML elements for displaying the sensor readings in both text and graph format. The above HTML code will display the top section of our dashboard page.

Arduino BME280-BMP280 Weather Station Sensor Values
<div class="graph-box">
          <div class="history-charts">
            <div class="title">History Charts</div>
            <div class="history-details">
              <div class="history-col-item">
                <div id="temperature-history" class="history-divs"></div>
                <div id="humidity-history" class="history-divs"></div>
              </div>
              <div class="history-col-item">
                <div id="pressure-history" class="history-divs"></div>
                <div id="altitude-history" class="history-divs"></div>
              </div>
            </div>
          </div>
          <div class="gaugeCharts">
            <div class="title">Gauge Charts</div>
            <div class="gauge-list">
              <div class="gauge-box"><div id="temperature-gauge"></div></div>
              <div class="gauge-box"><div id="humidity-gauge"></div></div>
              <div class="gauge-box"><div id="pressure-gauge"></div></div>
              <div class="gauge-box"><div id="altitude-gauge"></div></div>
            </div>
          </div>
        </div>
      </div>
    </section>
    <script src="main.js"></script>
  </body>
</html>

The above code will display the graph charts of our sensor readings. It only contains div elements as this will be populated by the Plotly.js javascript charting library. The HTML elements above will display the following user interface.

Arduino BME280/BMP280 Weather Station Chart Values

style.css

The style.css will stylize or beautify our index.html thru Cascading Style Sheets classes. I won’t be discussing so much about how these CSS style classes work but I would suggest that you comment on each line one by one and see how it impacts our index.html page.

/* Googlefont Poppins CDN Link */
@import url('https://fonts.googleapis.com/css2?family=Poppins:wght@200;300;400;500;600;700&display=swap');

*{
  margin: 0;
  padding: 0;
  box-sizing: border-box;
  font-family: 'Poppins', sans-serif;
}

.sidebar{
  position: fixed;
  height: 100%;
  width: 240px;
  background: #f63044;
  transition: all 0.5s ease;
}

.sidebar.active{
  width: 60px;
}

.sidebar .logo-details{
  height: 80px;
  display: flex;
  align-items: center;
}

.sidebar .logo-details i{
  font-size: 28px;
  font-weight: 500;
  color: #fff;
  min-width: 60px;
  text-align: center
}
.sidebar .logo-details .logo_name{
  color: #fff;
  font-size: 24px;
  font-weight: 500;
}
.sidebar .nav-links{
  margin-top: 10px;
}

.sidebar .nav-links li{
  position: relative;
  list-style: none;
  height: 50px;
}
.sidebar .nav-links li a{
  height: 100%;
  width: 100%;
  display: flex;
  align-items: center;
  text-decoration: none;
  transition: all 0.4s ease;
}
.sidebar .nav-links li a.active{
  background: #7e0109;
}
.sidebar .nav-links li a:hover{
  background: #eff1f3;
}
.sidebar .nav-links li i{
  min-width: 60px;
  text-align: center;
  font-size: 18px;
  color: #fff;
}
.sidebar .nav-links li a .links_name{
  color: #fff;
  font-size: 15px;
  font-weight: 400;
  white-space: nowrap;
}

.home-section{
  position: relative;
  background: #f5f5f5;
  min-height: 100vh;
  width: calc(100% - 240px);
  left: 240px;
  transition: all 0.5s ease;
}
.sidebar.active ~ .home-section{
  width: calc(100% - 60px);
  left: 60px;
}
.home-section nav{
  display: flex;
  justify-content: space-between;
  height: 80px;
  background: #fff;
  display: flex;
  align-items: center;
  position: fixed;
  width: calc(100% - 240px);
  left: 240px;
  z-index: 100;
  padding: 0 20px;
  box-shadow: 0 1px 1px rgba(0, 0, 0, 0.1);
  transition: all 0.5s ease;
}
.sidebar.active ~ .home-section nav{
  left: 60px;
  width: calc(100% - 60px);
}
.home-section nav .sidebar-button{
  display: flex;
  align-items: center;
  font-size: 24px;
  font-weight: 700;
}
nav .sidebar-button i{
  font-size: 35px;
  margin-right: 10px;
}
.home-section .home-content{
  position: relative;
  padding-top: 104px;
}
.home-content .overview-boxes{
  display: flex;
  align-items: center;
  justify-content: space-between;
  flex-wrap: wrap;
  padding: 0 20px;
  margin-bottom: 26px;
}
.overview-boxes .box{
  display: flex;
  align-items: center;
  justify-content: center;
  width: calc(100% / 4 - 15px);
  background: #fff;
  padding: 15px 14px;
  border-radius: 12px;
  box-shadow: 0 5px 10px rgba(0,0,0,0.1);
}
.overview-boxes .box-topic{
  font-size: 20px;
  font-weight: 500;
}
.home-content .box .number{
  display: inline-block;
  font-size: 35px;
  margin-top: -6px;
  font-weight: 500;
}
.home-content .box .indicator{
  display: flex;
  align-items: center;
}
.home-content .box .indicator i{
  height: 20px;
  width: 20px;
  background: #8FDACB;
  line-height: 20px;
  text-align: center;
  border-radius: 50%;
  color: #fff;
  font-size: 20px;
  margin-right: 5px;
}
.box .indicator i.down{
  background: #e87d88;
}
.home-content .box .indicator .text{
  font-size: 12px;
}
.home-content .box .readings{
  display: inline-block;
  font-size: 32px;
  height: 50px;
  width: 50px;
  line-height: 50px;
  text-align: center;
  color: #ff6666;
  border-radius: 12px;
  margin: -15px 0 0 6px;
}
.home-content .box .cart.two{
   color: #2BD47D;
   background: #C0F2D8;
 }
.home-content .box .cart.three{
   color: #ffc233;
   background: #ffe8b3;
 }
.home-content .box .cart.four{
   color: #e05260;
   background: #f7d4d7;
 }
.home-content .total-order{
  font-size: 20px;
  font-weight: 500;
}
.home-content .graph-box{
  display: flex;
  justify-content: space-between;
}
/* left box */
.home-content .graph-box .history-charts{
  width: 70%;
  background: #fff;
  padding: 20px 30px;
  margin: 0 20px;
  border-radius: 12px;
  box-shadow: 0 5px 10px rgba(0, 0, 0, 0.1);
}
.home-content .graph-box .history-charts{
  display: flex;
  flex-flow: column wrap;
}
.home-content .graph-box .history-details{
  display: flex;
  justify-content: space-between; 
}

.home-content .graph-box .history-details .history-col-item{
  width: 50%;
  display: flex;
  flex-flow: column nowrap;
  align-items: center;
  justify-content: center;
}

.graph-box .title{
  font-size: 24px;
  font-weight: 500;
}

.home-content .graph-box .history-details .history-divs{
  margin-top: 20px;
  border-radius: 12px;
  box-shadow: 0 5px 10px rgba(0, 0, 0, 0.1);
}

/* Right box */
.home-content .graph-box .gaugeCharts{
  width: 30%;
  background: #fff;
  padding: 20px 30px;
  margin: 0 20px 0 0;
  border-radius: 12px;
  box-shadow: 0 5px 10px rgba(0, 0, 0, 0.1);
  
}

.home-content .graph-box .gaugeCharts .gauge-list{
  display: flex;
  align-items: center;
  flex-flow: column nowrap;
}

main.js

This file is the heart and soul of our web application as it does several things for us. First, it initializes all of our graphs and charts. Next, it periodically communicates with our web server to retrieve the latest BMP280/BME280 sensor readings. And lastly, it updates our sensor readings text and records the history of our last 12 readings to be displayed as line charts.

var temperatureHistoryDiv = document.getElementById("temperature-history");
var humidityHistoryDiv = document.getElementById("humidity-history");
var pressureHistoryDiv = document.getElementById("pressure-history");
var altitudeHistoryDiv = document.getElementById("altitude-history");

var temperatureGaugeDiv = document.getElementById("temperature-gauge");
var humidityGaugeDiv = document.getElementById("humidity-gauge");
var pressureGaugeDiv = document.getElementById("pressure-gauge");
var altitudeGaugeDiv = document.getElementById("altitude-gauge");

// History Data
var temperatureTrace = {
  x: [],
  y: [],
  name: "Temperature",
  mode: "lines+markers",
  type: "line",
};
var humidityTrace = {
  x: [],
  y: [],
  name: "Humidity",
  mode: "lines+markers",
  type: "line",
};
var pressureTrace = {
  x: [],
  y: [],
  name: "Pressure",
  mode: "lines+markers",
  type: "line",
};
var altitudeTrace = {
  x: [],
  y: [],
  name: "Altitude",
  mode: "lines+markers",
  type: "line",
};

var temperatureLayout = {
  autosize: false,
  title: {
    text: "Temperature",
  },
  font: {
    size: 14,
    color: "#7f7f7f",
  },
  colorway: ["#B22222"],
  width: 450,
  height: 260,
  margin: { t: 30, b: 20, pad: 5 },
};
var humidityLayout = {
  autosize: false,
  title: {
    text: "Humidity",
  },
  font: {
    size: 14,
    color: "#7f7f7f",
  },
  colorway: ["#00008B"],
  width: 450,
  height: 260,
  margin: { t: 30, b: 20, pad: 5 },
};
var pressureLayout = {
  autosize: false,
  title: {
    text: "Pressure",
  },
  font: {
    size: 14,
    color: "#7f7f7f",
  },
  colorway: ["#FF4500"],
  width: 450,
  height: 260,
  margin: { t: 30, b: 20, pad: 5 },
};
var altitudeLayout = {
  autosize: false,
  title: {
    text: "Altitude",
  },
  font: {
    size: 14,
    color: "#7f7f7f",
  },
  colorway: ["#008080"],
  width: 450,
  height: 260,
  margin: { t: 30, b: 20, pad: 5 },
};

Plotly.newPlot(temperatureHistoryDiv, [temperatureTrace], temperatureLayout);
Plotly.newPlot(humidityHistoryDiv, [humidityTrace], humidityLayout);
Plotly.newPlot(pressureHistoryDiv, [pressureTrace], pressureLayout);
Plotly.newPlot(altitudeHistoryDiv, [altitudeTrace], altitudeLayout);

// Gauge Data
var temperatureData = [
  {
    domain: { x: [0, 1], y: [0, 1] },
    value: 0,
    title: { text: "Temperature" },
    type: "indicator",
    mode: "gauge+number+delta",
    delta: { reference: 30 },
    gauge: {
      axis: { range: [null, 50] },
      steps: [
        { range: [0, 20], color: "lightgray" },
        { range: [20, 30], color: "gray" },
      ],
      threshold: {
        line: { color: "red", width: 4 },
        thickness: 0.75,
        value: 30,
      },
    },
  },
];

var humidityData = [
  {
    domain: { x: [0, 1], y: [0, 1] },
    value: 0,
    title: { text: "Humidity" },
    type: "indicator",
    mode: "gauge+number+delta",
    delta: { reference: 50 },
    gauge: {
      axis: { range: [null, 100] },
      steps: [
        { range: [0, 20], color: "lightgray" },
        { range: [20, 30], color: "gray" },
      ],
      threshold: {
        line: { color: "red", width: 4 },
        thickness: 0.75,
        value: 30,
      },
    },
  },
];

var pressureData = [
  {
    domain: { x: [0, 1], y: [0, 1] },
    value: 0,
    title: { text: "Pressure" },
    type: "indicator",
    mode: "gauge+number+delta",
    delta: { reference: 750 },
    gauge: {
      axis: { range: [null, 1100] },
      steps: [
        { range: [0, 300], color: "lightgray" },
        { range: [300, 700], color: "gray" },
      ],
      threshold: {
        line: { color: "red", width: 4 },
        thickness: 0.75,
        value: 30,
      },
    },
  },
];

var altitudeData = [
  {
    domain: { x: [0, 1], y: [0, 1] },
    value: 0,
    title: { text: "Altitude" },
    type: "indicator",
    mode: "gauge+number+delta",
    delta: { reference: 60 },
    gauge: {
      axis: { range: [null, 150] },
      steps: [
        { range: [0, 50], color: "lightgray" },
        { range: [50, 100], color: "gray" },
      ],
      threshold: {
        line: { color: "red", width: 4 },
        thickness: 0.75,
        value: 30,
      },
    },
  },
];

var layout = { width: 300, height: 250, margin: { t: 0, b: 0, l: 0, r: 0 } };

Plotly.newPlot(temperatureGaugeDiv, temperatureData, layout);
Plotly.newPlot(humidityGaugeDiv, humidityData, layout);
Plotly.newPlot(pressureGaugeDiv, pressureData, layout);
Plotly.newPlot(altitudeGaugeDiv, altitudeData, layout);

// Will hold the arrays we receive from our BME280 sensor
// Temperature
let newTempXArray = [];
let newTempYArray = [];
// Humidity
let newHumidityXArray = [];
let newHumidityYArray = [];
// Pressure
let newPressureXArray = [];
let newPressureYArray = [];
// Altitude
let newAltitudeXArray = [];
let newAltitudeYArray = [];

// The maximum number of data points displayed on our scatter/line graph
let MAX_GRAPH_POINTS = 12;
let ctr = 0;

// Callback function that will retrieve our latest sensor readings and redraw our Gauge with the latest readings
function updateSensorReadings() {
  fetch(`/sensorReadings`)
    .then((response) => response.json())
    .then((jsonResponse) => {
      let temperature = jsonResponse.temperature.toFixed(2);
      let humidity = jsonResponse.humidity.toFixed(2);
      let pressure = jsonResponse.pressure.toFixed(2);
      let altitude = jsonResponse.altitude.toFixed(2);

      updateBoxes(temperature, humidity, pressure, altitude);

      updateGauge(temperature, humidity, pressure, altitude);

      // Update Temperature Line Chart
      updateCharts(
        temperatureHistoryDiv,
        newTempXArray,
        newTempYArray,
        temperature
      );
      // Update Humidity Line Chart
      updateCharts(
        humidityHistoryDiv,
        newHumidityXArray,
        newHumidityYArray,
        humidity
      );
      // Update Pressure Line Chart
      updateCharts(
        pressureHistoryDiv,
        newPressureXArray,
        newPressureYArray,
        pressure
      );

      // Update Altitude Line Chart
      updateCharts(
        altitudeHistoryDiv,
        newAltitudeXArray,
        newAltitudeYArray,
        altitude
      );
    });
}

function updateBoxes(temperature, humidity, pressure, altitude) {
  let temperatureDiv = document.getElementById("temperature");
  let humidityDiv = document.getElementById("humidity");
  let pressureDiv = document.getElementById("pressure");
  let altitudeDiv = document.getElementById("altitude");

  temperatureDiv.innerHTML = temperature + " C";
  humidityDiv.innerHTML = humidity + " %";
  pressureDiv.innerHTML = pressure + " hPa";
  altitudeDiv.innerHTML = altitude + " m";
}

function updateGauge(temperature, humidity, pressure, altitude) {
  var temperature_update = {
    value: temperature,
  };
  var humidity_update = {
    value: humidity,
  };
  var pressure_update = {
    value: pressure,
  };
  var altitude_update = {
    value: altitude,
  };
  Plotly.update(temperatureGaugeDiv, temperature_update);
  Plotly.update(humidityGaugeDiv, humidity_update);
  Plotly.update(pressureGaugeDiv, pressure_update);
  Plotly.update(altitudeGaugeDiv, altitude_update);
}

function updateCharts(lineChartDiv, xArray, yArray, sensorRead) {
  if (xArray.length >= MAX_GRAPH_POINTS) {
    xArray.shift();
  }
  if (yArray.length >= MAX_GRAPH_POINTS) {
    yArray.shift();
  }
  xArray.push(ctr++);
  yArray.push(sensorRead);

  var data_update = {
    x: [xArray],
    y: [yArray],
  };

  Plotly.update(lineChartDiv, data_update);
}

// Continuos loop that runs evry 3 seconds to update our web page with the latest sensor readings
(function loop() {
  setTimeout(() => {
    updateSensorReadings();
    loop();
  }, 3000);
})();

Let us run through what each line does.

var temperatureHistoryDiv = document.getElementById("temperature-history");
var humidityHistoryDiv = document.getElementById("humidity-history");
var pressureHistoryDiv = document.getElementById("pressure-history");
var altitudeHistoryDiv = document.getElementById("altitude-history");

var temperatureGaugeDiv = document.getElementById("temperature-gauge");
var humidityGaugeDiv = document.getElementById("humidity-gauge");
var pressureGaugeDiv = document.getElementById("pressure-gauge");
var altitudeGaugeDiv = document.getElementById("altitude-gauge");

Declare all the HTML div elements that will display our charts wherein Plotly.js would auto-populate its content.

// History Data
var temperatureTrace = {
  x: [],
  y: [],
  name: "Temperature",
  mode: "lines+markers",
  type: "line",
};
var humidityTrace = {
  x: [],
  y: [],
  name: "Humidity",
  mode: "lines+markers",
  type: "line",
};
var pressureTrace = {
  x: [],
  y: [],
  name: "Pressure",
  mode: "lines+markers",
  type: "line",
};
var altitudeTrace = {
  x: [],
  y: [],
  name: "Altitude",
  mode: "lines+markers",
  type: "line",
};

var temperatureLayout = {
  autosize: false,
  title: {
    text: "Temperature",
  },
  font: {
    size: 14,
    color: "#7f7f7f",
  },
  colorway: ["#B22222"],
  width: 450,
  height: 260,
  margin: { t: 30, b: 20, pad: 5 },
};
var humidityLayout = {
  autosize: false,
  title: {
    text: "Humidity",
  },
  font: {
    size: 14,
    color: "#7f7f7f",
  },
  colorway: ["#00008B"],
  width: 450,
  height: 260,
  margin: { t: 30, b: 20, pad: 5 },
};
var pressureLayout = {
  autosize: false,
  title: {
    text: "Pressure",
  },
  font: {
    size: 14,
    color: "#7f7f7f",
  },
  colorway: ["#FF4500"],
  width: 450,
  height: 260,
  margin: { t: 30, b: 20, pad: 5 },
};
var altitudeLayout = {
  autosize: false,
  title: {
    text: "Altitude",
  },
  font: {
    size: 14,
    color: "#7f7f7f",
  },
  colorway: ["#008080"],
  width: 450,
  height: 260,
  margin: { t: 30, b: 20, pad: 5 },
};

Plotly.newPlot(temperatureHistoryDiv, [temperatureTrace], temperatureLayout);
Plotly.newPlot(humidityHistoryDiv, [humidityTrace], humidityLayout);
Plotly.newPlot(pressureHistoryDiv, [pressureTrace], pressureLayout);
Plotly.newPlot(altitudeHistoryDiv, [altitudeTrace], altitudeLayout);

We initialize all of our line charts with configuration data (e.g temperatureTrace etc.) and configure the layout of each graph. The code Plotly.newPlot() will create a blank chart for us.


// Gauge Data
var temperatureData = [
  {
    domain: { x: [0, 1], y: [0, 1] },
    value: 0,
    title: { text: "Temperature" },
    type: "indicator",
    mode: "gauge+number+delta",
    delta: { reference: 30 },
    gauge: {
      axis: { range: [null, 50] },
      steps: [
        { range: [0, 20], color: "lightgray" },
        { range: [20, 30], color: "gray" },
      ],
      threshold: {
        line: { color: "red", width: 4 },
        thickness: 0.75,
        value: 30,
      },
    },
  },
];

var humidityData = [
  {
    domain: { x: [0, 1], y: [0, 1] },
    value: 0,
    title: { text: "Humidity" },
    type: "indicator",
    mode: "gauge+number+delta",
    delta: { reference: 50 },
    gauge: {
      axis: { range: [null, 100] },
      steps: [
        { range: [0, 20], color: "lightgray" },
        { range: [20, 30], color: "gray" },
      ],
      threshold: {
        line: { color: "red", width: 4 },
        thickness: 0.75,
        value: 30,
      },
    },
  },
];

var pressureData = [
  {
    domain: { x: [0, 1], y: [0, 1] },
    value: 0,
    title: { text: "Pressure" },
    type: "indicator",
    mode: "gauge+number+delta",
    delta: { reference: 750 },
    gauge: {
      axis: { range: [null, 1100] },
      steps: [
        { range: [0, 300], color: "lightgray" },
        { range: [300, 700], color: "gray" },
      ],
      threshold: {
        line: { color: "red", width: 4 },
        thickness: 0.75,
        value: 30,
      },
    },
  },
];

var altitudeData = [
  {
    domain: { x: [0, 1], y: [0, 1] },
    value: 0,
    title: { text: "Altitude" },
    type: "indicator",
    mode: "gauge+number+delta",
    delta: { reference: 60 },
    gauge: {
      axis: { range: [null, 150] },
      steps: [
        { range: [0, 50], color: "lightgray" },
        { range: [50, 100], color: "gray" },
      ],
      threshold: {
        line: { color: "red", width: 4 },
        thickness: 0.75,
        value: 30,
      },
    },
  },
];

var layout = { width: 300, height: 250, margin: { t: 0, b: 0, l: 0, r: 0 } };

Plotly.newPlot(temperatureGaugeDiv, temperatureData, layout);
Plotly.newPlot(humidityGaugeDiv, humidityData, layout);
Plotly.newPlot(pressureGaugeDiv, pressureData, layout);
Plotly.newPlot(altitudeGaugeDiv, altitudeData, layout);

The code above, on the other hand, will display the gauge charts that are displayed at the right-hand part of our home section.

// Will hold the arrays we receive from our BME280 sensor
// Temperature
let newTempXArray = [];
let newTempYArray = [];
// Humidity
let newHumidityXArray = [];
let newHumidityYArray = [];
// Pressure
let newPressureXArray = [];
let newPressureYArray = [];
// Altitude
let newAltitudeXArray = [];
let newAltitudeYArray = [];

// The maximum number of data points displayed on our scatter/line graph
let MAX_GRAPH_POINTS = 12;
let ctr = 0;

All the array variables above will hold the last 12 historical readings in our line charts.

Each sensor reading is assigned a unique number by using the ctr a variable that gets increased for each request.

// Callback function that will retrieve our latest sensor readings and redraw our Gauge with the latest readings
function updateSensorReadings() {
  fetch(`/sensorReadings`)
    .then((response) => response.json())
    .then((jsonResponse) => {
      let temperature = jsonResponse.temperature.toFixed(2);
      let humidity = jsonResponse.humidity.toFixed(2);
      let pressure = jsonResponse.pressure.toFixed(2);
      let altitude = jsonResponse.altitude.toFixed(2);

      updateBoxes(temperature, humidity, pressure, altitude);

      updateGauge(temperature, humidity, pressure, altitude);

      // Update Temperature Line Chart
      updateCharts(
        temperatureHistoryDiv,
        newTempXArray,
        newTempYArray,
        temperature
      );
      // Update Humidity Line Chart
      updateCharts(
        humidityHistoryDiv,
        newHumidityXArray,
        newHumidityYArray,
        humidity
      );
      // Update Pressure Line Chart
      updateCharts(
        pressureHistoryDiv,
        newPressureXArray,
        newPressureYArray,
        pressure
      );

      // Update Altitude Line Chart
      updateCharts(
        altitudeHistoryDiv,
        newAltitudeXArray,
        newAltitudeYArray,
        altitude
      );
    });
}

The function updateSensorReadings() will call our “/sensorReadings” route from our web server. Its job is to handle the response and convert it to a JSON format and then it will extract the result and assign it to local variables.

Shown below is a sample JSON response when calling the “/sensorReadings” endpoint called.

JSON Response from sensor readings

Functions updateBoxes(), updateGauge(), and updateCharts() are then called to update the line graph and gauge content.

function updateBoxes(temperature, humidity, pressure, altitude) {
  let temperatureDiv = document.getElementById("temperature");
  let humidityDiv = document.getElementById("humidity");
  let pressureDiv = document.getElementById("pressure");
  let altitudeDiv = document.getElementById("altitude");

  temperatureDiv.innerHTML = temperature + " C";
  humidityDiv.innerHTML = humidity + " %";
  pressureDiv.innerHTML = pressure + " hPa";
  altitudeDiv.innerHTML = altitude + " m";
}

The function updateBoxes() will update the values that are displayed at the top of our page.

function updateGauge(temperature, humidity, pressure, altitude) {
  var temperature_update = {
    value: temperature,
  };
  var humidity_update = {
    value: humidity,
  };
  var pressure_update = {
    value: pressure,
  };
  var altitude_update = {
    value: altitude,
  };
  Plotly.update(temperatureGaugeDiv, temperature_update);
  Plotly.update(humidityGaugeDiv, humidity_update);
  Plotly.update(pressureGaugeDiv, pressure_update);
  Plotly.update(altitudeGaugeDiv, altitude_update);
}

updateGauge() will update the display of our gauge charts by calling the Plotly.update() javascript function.

function updateCharts(lineChartDiv, xArray, yArray, sensorRead) {
  if (xArray.length >= MAX_GRAPH_POINTS) {
    xArray.shift();
  }
  if (yArray.length >= MAX_GRAPH_POINTS) {
    yArray.shift();
  }
  xArray.push(ctr++);
  yArray.push(sensorRead);

  var data_update = {
    x: [xArray],
    y: [yArray],
  };

  Plotly.update(lineChartDiv, data_update);
}

updateCharts() will update all of our line charts.

// Continuos loop that runs evry 3 seconds to update our web page with the latest sensor readings
(function loop() {
  setTimeout(() => {
    updateSensorReadings();
    loop();
  }, 3000);
})();

The function updateSensorReadings() is then called to get the latest reading every 3 seconds.

How to run the Arduino BMP280/BME280 Weather Station Project?

Once you understood how the code works then we can now start deploying this to our microcontroller unit.

First, we need to upload our HTML/CSS/javascript files inside our data folder into the file system of our ESP8266/ESP32 microcontroller. If you do not know how this is done then you can follow this Uploading files to the Microcontroller Flash Memory in PlatformIO.

Next, you can now click the Upload and Monitor link in the PlatformIO extensions. An example image of how this is done is shown below.

PlatformIo - Upload and Monitor

Wait for the upload to finish and look at the serial monitor.

PlatformIO Serial Monitor IP Address

Open your browser and type in the following in the IP in the address bar then you should be seeing our web application displayed.

Wrap up

We have created and learned how to interface our Arduino code with the BMP280/BME280 sensors in this post. Also, we were able to create a weather station dashboard that automatically displays the sensor readings in big text and colorful charts that automatically updates themselves asynchronously.

Read Next:
Creating an ESP32 RFID with a Web Server Application
ESP8266 Webserver Conveyor Counter

If you like my post then please consider sharing this. Thanks!

7 responses to “Using Arduino with BME280 plus a weather station project”

  1. How to enable Websockets in Mosquitto? – donskytech.com

    […] Related Next: Building a MicroPython Wifi Robot CarUsing Arduino with BME280 plus a weather station project […]

  2. How to interface DHT11 with MicroPython? – donskytech.com

    […] Read Next: Raspberry Pi Pico W: BME280 Weather Station DashboardUsing Arduino with BME280 plus a weather station project […]

  3. MicroPython Development Using Thonny IDE – donskytech.com

    […] Next: Using Arduino with BME280 plus a weather station projectRaspberry Pi Pico W: BME280 Weather Station […]

  4. Arduino MQTT Example Project – BMP/BME 280 Weather Station

    […] you have been an avid reader of my blog post then you might be familiar with my earlier post about Using Arduino with BME280 plus a weather station project wherein we used the same ESP32/ESP8266 MCU board as a web server that will be the source of our […]

  5. Develop Custom IoT Web Application with MongoDB Database

    […] Content: Using Arduino with BME280 plus a weather station projectRaspberry Pi Pico W: BME280 Weather Station […]

  6. Interface ESP32 with the PCF8575 I/O Port Expander

    […] Read Next: Arduino Fingerprint Door Lock using ESP32 with a Web AppUsing Arduino with BME280 plus a weather station project […]

  7. How to create a MicroPython Web Server the easy way!

    […] Read Next: Raspberry Pi Pico W: BME280 Weather Station DashboardUsing Arduino with BME280 plus a weather station project […]

Leave a Reply

Your email address will not be published. Required fields are marked *