Featured Image - Raspberry Pi BME280 Weather Station Dashboard

Posted on

by

in

,

Raspberry Pi BME280 Weather Station Using Python and Flask

Introduction

This post will show you how to interface your Raspberry Pi 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 using Python and Flask.

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 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.

Install the latest Raspberry Pi OS into your device. In my case, I have installed a headless installation on my Raspberry Pi Zero W so that I won’t need any external monitor or keyboard.

Related Content:
Setup Raspberry Pi Headless No Monitor

You should be comfortable working with the terminal/shell of your Raspberry Pi and knows how to open a file in nano/vim and traverse the directories.

As I am using a Windows PC and a headless installation on my Raspberry Pi so I am following the following development approach. First, I developed the core logic in my Windows PC using the awesome Thonny IDE and I am transferring my files into my Raspberry Pi Zero W thru WinSCP.

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

Raspberry Pi BME280 Weather Station Using Python and Flask - Wiring Schematic

The image above is the wiring diagram between our Raspberry Pi Zero W Single Board Computer(SBC) with the BMP280/BME280 sensor. We are using the hardware I2C channel of our SBC in communicating with the BMP280/BME280 sensor. I am using the BCM numbering.

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 Raspberry Pi and Python?

Update your Raspberry Pi

Login to your Raspberry Pi and perform a full update so we can get the latest patches for your device.

Related Content:
How to update Raspberry Pi

Enable the I2C options using raspi-config

We need to enable the I2C interface option in our Raspberry Pi so that we could communicate with our I2C BME280 component. If you are unsure of how this is done then please follow the link below.

Related Content:
How to enable I2C in your Raspberry Pi?

Create a Python virtual environment

Next, we create a Python virtual environment to separate the dependencies that we are going to use in this project from that of your default Python package installation folder.

To create this Python virtual environment then execute the following commands. Create a directory where we can place our program files.

pi@raspberrypi-zero-w:~/Projects $ cd /home/pi/Projects
pi@raspberrypi-zero-w:~/Projects $ pwd
/home/pi/Projects
pi@raspberrypi-zero-w:~/Projects $ mkdir -p bme280
pi@raspberrypi-zero-w:~/Projects $ cd bme280/

Create a virtual environment by using the venv module of Python. The “.venv” is the name of our virtual environment.

pi@raspberrypi-zero-w:~/Projects/bme280 $ python -m venv .venv

then activate the environment

pi@raspberrypi-zero-w:~/Projects/bme280 $ source .venv/bin/activate

Your shell should now look like this which means that you are now in a Python virtual environment named .venv

(.venv) pi@raspberrypi-zero-w:~/Projects/bme280 $

Install the RPi.bme280 library

We are going to use the RPi.bme280 library in communicating with our BME280 sensor and to install this then we could execute the following command

(.venv) pi@raspberrypi-zero-w:~/Projects/bme280 $ pip install RPi.bme280

If everything is okay then you should be seeing the following messages displayed.

Looking in indexes: https://pypi.org/simple, https://www.piwheels.org/simple
Collecting RPi.bme280
  Using cached https://www.piwheels.org/simple/rpi-bme280/RPi.bme280-0.2.4-py2.py3-none-any.whl (10 kB)
Collecting smbus2
  Using cached https://www.piwheels.org/simple/smbus2/smbus2-0.4.2-py2.py3-none-any.whl (13 kB)
Collecting pytz
  Using cached https://www.piwheels.org/simple/pytz/pytz-2022.7-py3-none-any.whl (499 kB)
Installing collected packages: smbus2, pytz, RPi.bme280
Successfully installed RPi.bme280-0.2.4 pytz-2022.7 smbus2-0.4.2

Create a sample Python test program that will communicate with our BME280 component

Create a file called bme_test.py

nano bme_test.py

then paste the following code.

import smbus2
import bme280

port = 1
address = 0x76
bus = smbus2.SMBus(port)

calibration_params = bme280.load_calibration_params(bus, address)

# the sample method will take a single reading and return a
# compensated_reading object
data = bme280.sample(bus, address, calibration_params)

# the compensated_reading class has the following attributes
print(data.id)
print(data.timestamp)
print(data.temperature)
print(data.pressure)
print(data.humidity)

# there is a handy string representation too
print(data)

Run the following program by executing the command below.

python bme_test.py

Then you should be seeing the following messages displayed on your shell. The values of the temperature, pressure, and humidity are displayed coming from our BME280 component.

(.venv) pi@raspberrypi-zero-w:~/Projects/bme280 $ python bme_test.py
e9ce6d16-f745-45d0-9925-eda9735f10f8
2022-12-27 05:35:51.911304+00:00
27.995946650300176
1009.361849501059
55.42494333834271
compensated_reading(id=e9ce6d16-f745-45d0-9925-eda9735f10f8, timestamp=2022-12-27 05:35:51.911304UTC, temp=27.996 °C, pressure=1009.36 hPa, humidity=55.42 % rH)

Let us run through what each line of our code does.

import smbus2
import bme280

Import the necessary packages and library to communicate with our BME280

port = 1
address = 0x76
bus = smbus2.SMBus(port)

Declare the port or the SM bus number that we are going to use for our I2C communication. I am using Raspberry Pi Zero W so I am using 1, if your Raspberry Pi is old then use 0. The address 0x76 is the I2C address of our BME280 and then we create an object of type SMBus passing in our port number.

calibration_params = bme280.load_calibration_params(bus, address)

# the sample method will take a single reading and return a
# compensated_reading object
data = bme280.sample(bus, address, calibration_params)

We call the load_calibration_params() method of the BME280 class passing in our configured bus and BME280 I2C address. To get a single reading then we call the sample() method of our BME280 class passing in the different parameters such as our bus, address, and our calibration_params.

# the compensated_reading class has the following attributes
print(data.id)
print(data.timestamp)
print(data.temperature)
print(data.pressure)
print(data.humidity)

# there is a handy string representation too
print(data)

We then can extract the readings by calling the following values above.

If you are able to do this then congratulations! We will try to make this program much more exciting by doing our own Raspberry Pi BME280 Weather Station Dashboard.

To exit the virtual environment then type in the following command. This will bring you back to your normal shell command.

deactivate

Raspberry Pi BMP280/BME280 Weather Station Project

Raspberry Pi BME280 Weather Station Dashboard

We are building our custom Raspberry Pi-powered BME280/BMP280 weather station project with a beautiful dashboard that was inspired by the following post.

We will be using the following software and libraries in building this project.

  • Python
  • HTML/CSS/Javascript
  • Flask library

If you are not familiar with what Flask is then please take a look at this simple Flask post that I have created to get you started.

Related Content:
Raspberry Pi and Flask Quickstart Guide

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

Raspberry Pi BME280 Weather Station Using Python and Flask - Dashboard Parts

The above image is the parts of our Raspberry Pi BMP280/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

Raspberry Pi BME280 Weather Station Using Python and Flask - Design

The image above is the overall design of our Raspberry Pi BMP280/BME280 weather station dashboard.

We have created a Flask web server inside our Raspberry Pi Zero W 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 Flask web server receives the request then it asks for sensor reading values from the BME280 sensor. The web server is written in Python code while the web application is a mixture of HTML/CSS/Javascript.

Download and setup the project

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.

cd ~/Projects/
git clone https://github.com/donskytech/raspberry-pi-bme280-weather-station-flask-python.git
cd raspberry-pi-bme280-weather-station-flask-python

Let’s create another Python virtual environment for this project by executing the below code and then activate it afterward.

python -m venv .venv
source .venv/bin/activate

Install the project dependencies by executing the below code.

pip install -r requirements.txt

This will install all the libraries and packages needed for this project. If everything went smoothly then you should be seeing the following messages displayed.

(.venv) pi@raspberrypi-zero-w:~/Projects/raspberry-pi-bme280-weather-station-flask-python $ pip install -r requirements.txt
Looking in indexes: https://pypi.org/simple, https://www.piwheels.org/simple
Collecting click==8.1.3
  Using cached https://www.piwheels.org/simple/click/click-8.1.3-py3-none-any.whl (96 kB)
Collecting Flask==2.2.2
  Using cached https://www.piwheels.org/simple/flask/Flask-2.2.2-py3-none-any.whl (101 kB)
Collecting importlib-metadata==5.2.0
  Using cached https://www.piwheels.org/simple/importlib-metadata/importlib_metadata-5.2.0-py3-none-any.whl (21 kB)
Collecting itsdangerous==2.1.2
  Using cached https://www.piwheels.org/simple/itsdangerous/itsdangerous-2.1.2-py3-none-any.whl (15 kB)
Collecting Jinja2==3.1.2
  Using cached https://www.piwheels.org/simple/jinja2/Jinja2-3.1.2-py3-none-any.whl (133 kB)
Collecting MarkupSafe==2.1.1
  Using cached https://www.piwheels.org/simple/markupsafe/MarkupSafe-2.1.1-cp39-cp39-linux_armv6l.whl (23 kB)
Collecting pytz==2022.7
  Using cached https://www.piwheels.org/simple/pytz/pytz-2022.7-py3-none-any.whl (499 kB)
Collecting RPi.bme280==0.2.4
  Using cached https://www.piwheels.org/simple/rpi-bme280/RPi.bme280-0.2.4-py2.py3-none-any.whl (10 kB)
Collecting smbus2==0.4.2
  Using cached https://www.piwheels.org/simple/smbus2/smbus2-0.4.2-py2.py3-none-any.whl (13 kB)
Collecting Werkzeug==2.2.2
  Using cached https://www.piwheels.org/simple/werkzeug/Werkzeug-2.2.2-py3-none-any.whl (232 kB)
Collecting zipp==3.11.0
  Using cached zipp-3.11.0-py3-none-any.whl (6.6 kB)
Installing collected packages: zipp, MarkupSafe, Werkzeug, smbus2, pytz, Jinja2, itsdangerous, importlib-metadata, click, RPi.bme280, Flask
Successfully installed Flask-2.2.2 Jinja2-3.1.2 MarkupSafe-2.1.1 RPi.bme280-0.2.4 Werkzeug-2.2.2 click-8.1.3 importlib-metadata-5.2.0 itsdangerous-2.1.2 pytz-2022.7 smbus2-0.4.2 zipp-3.11.0

Run the project by executing the below command on your terminal.

flask run --host=0.0.0.0

This should open up our Flask web server and show the following messages.

(.venv) pi@raspberrypi-zero-w:~/Projects/raspberry-pi-bme280-weather-station-flask-python $ flask run --host=0.0.0.0
 * Debug mode: off
WARNING: This is a development server. Do not use it in a production deployment. Use a production WSGI server instead.
 * Running on all addresses (0.0.0.0)
 * Running on http://127.0.0.1:5000
 * Running on http://192.168.100.183:5000
Press CTRL+C to quit

Open your browser and type in the following URL.

http://192.168.100.183:5000

This should open the Raspberry Pi BME280 Weather Station Dashboard for you and you should see the charts and the text getting updated every 3 seconds.

If you were able to run the project then it’s time to discuss the relevant parts of the program on how it works.

Code

Go back to your Windows/Mac or Linux workstation and clone again the project using the following command.

git clone https://github.com/donskytech/raspberry-pi-bme280-weather-station-flask-python.git
cd raspberry-pi-bme280-weather-station-flask-python

Open this with Thonny IDE when the download or cloning is finished.

Thonny Project Files

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

  • bme_test.py – this contains the sample test code that we created earlier when we tested our BME280 sensor.
  • bme_module.py – this contains the module that will communicate with our BME280 sensor.
  • app.py – contains the Flask web server code that handles the HTTP connections with our browser.
  • 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.
  • requirements.txt – contains all the dependencies and libraries needed by our project.

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:

  • RPi.bme280 – used to communicate with our I2C BME280 sensor component.
  • Flask – the library that we used to create our web server

There are other libraries and dependencies which are listed in the requirements.txt file that are transitive dependencies of the two libraries above.

bme_module.py

The bme_module.py contains the class that communicates with our BME280 sensor and below is its code.

import smbus2
import bme280
import time
import math

class BME280Module:
    SEA_LEVEL_PRESSURE_HPA = 1013.25
    PORT = 1
    ADDRESS = 0x76
    
    def __init__(self):
        self.bus = smbus2.SMBus(BME280Module.PORT)
        self.calibration_params = bme280.load_calibration_params(self.bus, BME280Module.ADDRESS)
        
        
    def get_sensor_readings(self):
        sample_reading = bme280.sample(self.bus, BME280Module.ADDRESS, self.calibration_params)
        temperature_val = sample_reading.temperature
        humidity_val = sample_reading.humidity
        pressure_val = sample_reading.pressure

        # Altitude calculation
        altitude_val = 44330 * (1.0 - math.pow(pressure_val / BME280Module.SEA_LEVEL_PRESSURE_HPA, 0.1903))
        
        return (temperature_val, pressure_val, humidity_val, altitude_val)
    

Let’s scan what each line of code does.

import smbus2
import bme280
import time
import math

Import the necessary packages in order to communicate with our BME280 using our Raspberry Pi Zero W.

class BME280Module:
    SEA_LEVEL_PRESSURE_HPA = 1013.25
    PORT = 1
    ADDRESS = 0x76

We declare a class BME280Module that would represent our interface to the BME280 sensor. Also, we defined a class constant that represents the sea level pressure, port, and the I2C address of our BME280 sensor which we will be using later to approximate our relative altitude.

    def __init__(self):
        self.bus = smbus2.SMBus(BME280Module.PORT)
        self.calibration_params = bme280.load_calibration_params(self.bus, BME280Module.ADDRESS)

This is our class constructor that initializes our object and since we are using the hardware I2C SDA and SCL pins so we do not need to declare the pin number here.

We then create an instance of the SMBusclass by passing the port assignment.

Next, we initialize a class property called calibration_params by calling the bme280.load_calibration_params(). You can review the explanation above if you want to know more.

    def get_sensor_readings(self):
        sample_reading = bme280.sample(self.bus, BME280Module.ADDRESS, self.calibration_params)
        temperature_val = sample_reading.temperature
        humidity_val = sample_reading.humidity
        pressure_val = sample_reading.pressure

        # Altitude calculation
        altitude_val = 44330 * (1.0 - math.pow(pressure_val / BME280Module.SEA_LEVEL_PRESSURE_HPA, 0.1903))
        
        return (temperature_val, pressure_val, humidity_val, altitude_val)

We declare the get_sensor_readings() method that we can call to retrieve the readings from our BME280 sensor.

We get a sample reading by calling the bme280.sample() and then extracting the values of the temperature, humidity, and pressure.

The altitude calculation is taken from the BMP180 datasheet which you can access here on page 16. Note that this is only an approximate calculation as we need to know the pressure at sea level by the time the sensor reading was taken to make it accurate.

The function get_sensor_readings() would then return a Python tuple of the temperature, humidity, pressure, and relative altitude readings to the caller.

app.py

The app.py contains our Flask web server logic and is responsible for showing our Raspberry Pi BME280 weather station dashboard.

from flask import Flask, render_template, jsonify
from bme_module import BME280Module

app = Flask(__name__)

bme280_module = BME280Module()

@app.route("/")
def hello_world():
    return render_template("index.html")


@app.route("/sensorReadings")
def get_sensor_readings():
    temperature, pressure, humidity, altitude = bme280_module.get_sensor_readings()
    return jsonify(
        {
            "status": "OK",
            "temperature": temperature,
            "pressure": pressure,
            "humidity": humidity,
            "altitude": altitude,
        }
    )

if __name__ == '__main__':
    app.run(debug=True, host='0.0.0.0')

Let’s dive in on what each line of code does.

from flask import Flask, render_template, jsonify
from bme_module import BME280Module

Import the necessary objects from Flask including our custom BME280Module class.

app = Flask(__name__)

bme280_module = BME280Module()

Declare our Flask application and our BME280Module() class.

@app.route("/")
def hello_world():
    return render_template("index.html")

This route would render our index.html page and will serve as the root route of our web server.

@app.route("/sensorReadings")
def get_sensor_readings():
    temperature, pressure, humidity, altitude = bme280_module.get_sensor_readings()
    return jsonify(
        {
            "status": "OK",
            "temperature": temperature,
            "pressure": pressure,
            "humidity": humidity,
            "altitude": altitude,
        }
    )

The get_sensor_readings() function is decorated with the “/sensorReadings” route which is what the Raspberry Pi BME280 weather Station dashboard is calling every 3 seconds to retrieve sensor readings. It retrieves the BME280 sensor readings by calling the BME280Module class member function.

It then returns a JSON response to our web application passing in the retrieved temperature, humidity, pressure, and relative altitude readings.

if __name__ == '__main__':
    app.run(debug=True, host='0.0.0.0')

This will run our application.

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>Raspberry Pi BME280 Weather Station Using Flask and Python</title>
    <link
      href="https://unpkg.com/boxicons@2.0.7/css/boxicons.min.css"
      rel="stylesheet"
    />
    <link rel="stylesheet" href="static/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">Raspberry Pi BME280 Weather Station Using Flask and Python</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="static/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>Raspberry Pi BME280 Weather Station Using Flask and Python</title>
    <link
      href="https://unpkg.com/boxicons@2.0.7/css/boxicons.min.css"
      rel="stylesheet"
    />
    <link rel="stylesheet" href="static/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">Raspberry Pi BME280 Weather Station Using Flask and Python</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.

Wrap up

We have created and learned how to interface our Raspberry Pi Zero W 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:
Using Arduino with BME280 plus a weather station project
Raspberry Pi Pico W: BME280 Weather Station Dashboard

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

4 responses to “Raspberry Pi BME280 Weather Station Using Python and Flask”

  1. How to enable I2C in your Raspberry Pi? – donskytech.com

    […] Next: Raspberry Pi BME280 Weather Station Using Python and FlaskRaspberry Pi Object Counting using an Infrared […]

  2. Raspberry Pi Object Counting using Infrared sensor –

    […] Next: Raspberry Pi BME280 Weather Station Using Python and FlaskUsing an Infrared (IR) Sensor with Raspberry […]

  3. Rich Cini Avatar
    Rich Cini

    Great project! I’m trying to extend this to add the ENS160 air quality sensor. What software did you use to code the Web page? I’ve copied the tags to add content boxes, but I can’t get them to render properly and the chart spacing is off? Any tips appreciated there. Thanks!

    1. donsky Avatar
      donsky

      Just HTML/CSS/Javascript to display everything and using Bootstrap framework.

Leave a Reply

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