Featured Image - DHT22 Weather Station With Python, Flask, Flask-SocketIO

Posted on

by

in

Raspberry Pi DHT22 Weather Station Project

Introduction

In the previous Display Real-Time Updates Using Python, Flask, and WebSocket post, we talked about how we can use Python, Flask, and Flask-SocketIO to create a web application that has the ability to display real-time messages thru WebSocket. We only generated dummy data to serve as sensor readings in that post. However, in this post, we will take it further by creating an actual Internet of Things (IoT) project that involves a real sensor which is the DHT22 temperature and humidity sensor.

If you want to see this project in an actual demo then please see the below video or watch it on my YouTube channel.

Raspberry Pi DHT22 Weather Station Project

We are going to build our own DHT22 weather station project using the Raspberry Pi that will display real-time sensor readings thru WebSocket. It will contain a dashboard page that will show the current DHT22 temperature and humidity readings.

We will display it in both text and graphical chart format so as to show the real-time movement of the temperature readings. Please see the sample web application that we are going to build in this project.

Web - DHT22 Weather Station Project
Mobile - Web - DHT22 Weather Station Project
Mobile - Web - DHT22 Weather Station Project -2

Design

Raspberry Pi DHT22 Weather Station Project - Design

The image above will show you the overall design of our project. We are going to create a Web Server inside our Raspberry Pi that will serve our web application. At the same time, it would communicate with our DHT22 sensor to retrieve the latest sensor readings.

We are going to use the following tools/libraries in building the software side of this project.

We will be using Python in programming our backend web server application with our Raspberry Pi. In order to create our web application then we will be using the Flask microweb framework. The Flask-SocketIO is used to exchange messages between our web application and our DHT22 (AM2302) sensor. I have used the Bootstrap CSS framework in drawing the web application for this project.

The post below is a must-read as I won’t be discussing so much about how I used Python, Flask, and Flask-SocketIO for the WebSocket exchange between our web application and our Python Flask web server.

Must Read:
Display Real-Time Updates Using Python, Flask, and Websocket

I have used the Bootstrap HTML/CSS framework in developing the web application part of this project. We will make our web application responsive so that it looks good even on mobile devices. Using Bootstrap would help us not deal with the nitty-gritty details of developing web applications in HTML/CSS/Javascript. To get started with Bootstrap then please see this.

Prerequisites

As this is an IoT project so we need to have some knowledge of how to communicate with our DHT22 sensor from our web application. The following post will show you how we would communicate with our DHT22 sensor from our Raspberry Pi using Python, Flask, and Flask-SocketIO. I am using the adafruit-circuitpython-dht library in connecting with my DHT22 sensor in this post.

Must Read:
Raspberry Pi – How to Interface with a DHT22 sensor

Parts/Components

The followings are the parts/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.

Wiring/Schematic

The below image shows the wiring and schematic for our Raspberry Pi DHT22 weather station project. I am using a Raspberry Pi 4B here but this project is also applicable to other variants such as Raspberry Pi Zero 2 W or Raspberry Pi Zero W.

Raspberry Pi DHT22 Weather Station Project - Wiring Schematic

Code

Setup

The code for our Raspberry Pi-powered DHT22 Weather Station Project is available on my GitHub repository and you can either download it as a zip file or clone it using Git. Connect to your Raspberry Pi by using ssh or Putty and execute the below command.

git clone https://github.com/donskytech/dht22-weather-station-python-flask-socketio
cd dht22-weather-station-python-flask-socketio

Create a new Python virtual environment on the same terminal.

# Windows or PC
py -m venv .venv
# Linux or Mac
python -m venv .venv

After the virtual environment is created then we can activate it by executing the below commands.

# Windows PC
.venv\Scripts\activate.bat
# Linux or Mac
source .venv/bin/activate

Install the required dependencies for this project.

pip install -r requirements.txt

The following are the required Python, Flask, and Flask-SocketIO dependencies needed to create our web application.

bidict==0.22.0
cachelib==0.9.0
click==8.1.3
Flask==2.2.1
Flask-Login==0.6.2
Flask-Session==0.4.0
Flask-SocketIO
importlib-metadata==4.12.0
itsdangerous==2.1.2
Jinja2==3.1.2
MarkupSafe==2.1.1
python-engineio
python-socketio
six==1.11.0
Werkzeug==2.2.3
zipp==3.8.1

Take note that it does not include the DHT22 libraries as this would need to be installed separately with the sudo requirement. Please see my earlier post on how to install this as it is mandatory so that we can retrieve our DHT22 sensor readings.

To run the project then you execute the following commands

flask run --host=0.0.0.0

Access the web application by using the following URL in your web browser.

http://<IP-Address>:5000

# Sample Output
(.venv) pi@raspberrypi4:~/Projects/dht22-weather-station-python-flask-socketio $ flask run --host=0.0.0.0
WARNING: This is a development server. Do not use it in a production deployment. Use a production WSGI server instead.
 * 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.121:5000
Press CTRL+C to quit

DHT22 Weather Station Project Files

Raspberry Pi DHT22 Weather Station Project Files

The image above shows the different parts of our Raspberry Pi DHT22 Weather Station project when you open it in Visual Studio Code and what are their roles.

  • app.py – contains our Flask and Flask-SocketIO web server and our WebSocket server
  • dht22_module.py – class that will read our DHT22 sensor
  • templates/index.html – our web application that will display the current DHT22 sensor readings
  • static/index.css – custom Cascading Stylesheet (CSS) to beautify our web page
  • static/index.js – custom Javascript file that will open a WebSocket connection to our Flask-SocketIO server.
  • requirements.txt – contains the libraries and dependencies for this project

Let us discuss what each file does one by one for us to understand how it works.

dht22_module.py

import adafruit_dht
import time


class DHT22Module:
    def __init__(self, pin):
        self.dht_device = adafruit_dht.DHT22(pin)

    def get_sensor_readings(self):
        while True:
            try:
                # Print the values to the serial port
                temperature_c = self.dht_device.temperature
                temperature_f = temperature_c * (9 / 5) + 32
                humidity = self.dht_device.humidity
                print(
                    "Temp: {:.1f} F / {:.1f} C    Humidity: {}% ".format(
                        temperature_f, temperature_c, humidity
                    )
                )
                return temperature_c, humidity

            except RuntimeError as error:
                # Errors happen fairly often, DHT's are hard to read, just keep going
                print(error.args[0])
                time.sleep(2.0)
                continue
            except Exception as error:
                self.dht_device.exit()
                raise error

The class DHT22Module is our main interface to our DHT22 sensor. In the constructor of the class, we passed in the GPIO pin that we will use to communicate with our sensor.

The function get_sensor_readings() is what we will call to retrieve the sensor readings from our DHT22 sensor. We are using the adafruit_dht library to retrieve the sensor readings and it would return a Python tuple of both the temperature and humidity readings.

app.py

import json
from flask import Flask, render_template, request
from flask_socketio import SocketIO
from random import random
from threading import Lock
from datetime import datetime
from dht22_module import DHT22Module
import board

dht22_module = DHT22Module(board.D18)

thread = None
thread_lock = Lock()

app = Flask(__name__)
app.config["SECRET_KEY"] = "donsky!"
socketio = SocketIO(app, cors_allowed_origins="*")

"""
Background Thread
"""


def background_thread():
    while True:
        temperature, humidity = dht22_module.get_sensor_readings()
        sensor_readings = {
            "temperature": temperature,
            "humidity": humidity,
        }
        sensor_json = json.dumps(sensor_readings)

        socketio.emit("updateSensorData", sensor_json)
        socketio.sleep(2)


"""
Serve root index file
"""


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


"""
Decorator for connect
"""


@socketio.on("connect")
def connect():
    global thread
    print("Client connected")

    with thread_lock:
        if thread is None:
            thread = socketio.start_background_task(background_thread)


"""
Decorator for disconnect
"""


@socketio.on("disconnect")
def disconnect():
    print("Client disconnected", request.sid)


# if __name__ == "__main__":
#     socketio.run(app, port=5000, host="0.0.0.0", debug=True)

The code above is our Python, Flask, and Flask-SocketIO program that will create a web server to display our web application and open a WebSocket server where we will send back the DHT22 sensor readings.

Let us go thru what each line of code does.

import json
from flask import Flask, render_template, request
from flask_socketio import SocketIO
from random import random
from threading import Lock
from datetime import datetime
from dht22_module import DHT22Module
import board

We declare the needed Flask and FlaskSocket-IO libraries for us to create the web server and WebSocket application. Also, we import our class DHT22Module so that we could get our DHT22 sensor readings.

dht22_module = DHT22Module(board.D18)

thread = None
thread_lock = Lock()

app = Flask(__name__)
app.config["SECRET_KEY"] = "donsky!"
socketio = SocketIO(app, cors_allowed_origins="*")

An instance of the class DHT22Module is created by supplying the GPIO18 pin of our Raspberry Pi. We also created an instance of our Flask application and the class SocketIO.

"""
Background Thread
"""
def background_thread():
    while True:
        temperature, humidity = dht22_module.get_sensor_readings()
        sensor_readings = {
            "temperature": temperature,
            "humidity": humidity,
        }
        sensor_json = json.dumps(sensor_readings)

        socketio.emit("updateSensorData", sensor_json)
        socketio.sleep(2)

This is our background thread that will periodically read our DHT22 sensor readings by using our class DHT22Module. We then convert the sensor readings and convert it to JSON format before sending a WebSocket message to all WebSocket clients connected to our server.

Take note that we are using the event “updateSensorData” where we send our sensor readings in JSON format. This is important as the web application will listen to this particular event at the client side.

"""
Serve root index file
"""


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

We define a flask route that will serve our index.html template that will show a dashboard page. This dashboard page will display all the sensor readings retrieved by our Raspberry Pi from our DHT22 sensor.

"""
Decorator for connect
"""


@socketio.on("connect")
def connect():
    global thread
    print("Client connected")

    with thread_lock:
        if thread is None:
            thread = socketio.start_background_task(background_thread)


"""
Decorator for disconnect
"""


@socketio.on("disconnect")
def disconnect():
    print("Client disconnected", request.sid)

When a WebSocket client connects to our WebSocket server then the background thread is initiated for that client. If it disconnects then it will just print that a client has disconnected.

The two functions connect() and disconnect() is decorated with Flask-SocketIO decorator functions.

templates/index.html

<!DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="utf-8" />
    <meta name="viewport" content="width=device-width, initial-scale=1" />
    <title>DHT22 Weather Station Project</title>
    <link
      href="https://cdn.jsdelivr.net/npm/bootstrap@5.2.3/dist/css/bootstrap.min.css"
      rel="stylesheet"
      integrity="sha384-rbsA2VBKQhggwzxH7pPCaAqO46MgnOM80zW1RWuH61DGLwZJEdK2Kadq2F9CUG65"
      crossorigin="anonymous"
    />
    <link
      href="https://fonts.googleapis.com/css2?family=Material+Symbols+Outlined"
      rel="stylesheet"
    />
    <script
      src="https://cdn.plot.ly/plotly-2.20.0.min.js"
      charset="utf-8"
    ></script>
    <link
      href="{{url_for('static', filename = 'index.css')}}"
      rel="stylesheet"
    />
    <script
      src="https://cdnjs.cloudflare.com/ajax/libs/socket.io/3.0.4/socket.io.js"
      integrity="sha512-aMGMvNYu8Ue4G+fHa359jcPb1u+ytAF+P2SCb+PxrjCdO3n3ZTxJ30zuH39rimUggmTwmh2u7wvQsDTHESnmfQ=="
      crossorigin="anonymous"
    ></script>
  </head>
  <body>
    <header>
      <!-- Fixed navbar -->
      <nav class="navbar navbar-expand-md navbar-dark fixed-top bg-dark">
        <div class="container-fluid">
          <a class="navbar-brand" href="#">DonskyTech</a>
          <button
            class="navbar-toggler"
            type="button"
            data-bs-toggle="collapse"
            data-bs-target="#navbarCollapse"
            aria-controls="navbarCollapse"
            aria-expanded="false"
            aria-label="Toggle navigation"
          >
            <span class="navbar-toggler-icon"></span>
          </button>
          <div class="collapse navbar-collapse" id="navbarCollapse">
            <ul class="navbar-nav me-auto mb-2 mb-md-0">
              <li class="nav-item">
                <a class="nav-link active" aria-current="page" href="#">Home</a>
              </li>
              <li class="nav-item">
                <a class="nav-link" href="#">About</a>
              </li>
            </ul>
          </div>
        </div>
      </nav>
    </header>
    <div class="container-fluid content mt-5">
      <div class="row">
        <nav
          id="sidebarMenu"
          class="col-md-3 col-lg-2 d-md-block sidebar collapse"
        >
          <div class="sidebar position-sticky pt-3 sidebar-sticky">
            <ul class="nav flex-column">
              <li class="nav-item">
                <a
                  class="nav-link active link-danger"
                  aria-current="page"
                  href="#"
                >
                  <span class="material-symbols-outlined"> dashboard </span>
                  Dashboard
                </a>
              </li>
            </ul>
          </div>
        </nav>
        <div class="col-md-9 ms-sm-auto col-lg-10 px-md-4">
          <div class="row">
            <div
              class="d-flex justify-content-between flex-wrap flex-md-nowrap align-items-center pt-3 pb-2 mb-3 border-bottom border-danger"
            >
              <h1 class="h2 text-danger">DHT22 Weather Station Dashboard</h1>
              <div class="row"></div>
            </div>
          </div>
          <div class="row">
            <div
              class="d-flex p-2 justify-content-evenly overview-boxes justify-content-center"
            >
              <div
                class="box d-flex align-items-center justify-content-center rounded-3 p-1 shadow"
              >
                <div class="right-side">
                  <div class="box-topic">Temperature</div>
                  <div class="number" id="temperature">35 C</div>
                </div>
                <span class="indicator material-symbols-outlined"
                  >device_thermostat</span
                >
              </div>
              <div
                class="box d-flex align-items-center justify-content-center rounded-3 p-1 shadow"
              >
                <div class="right-side">
                  <div class="box-topic">Humidity</div>
                  <div class="number" id="humidity">40%</div>
                </div>
                <span class="indicator material-symbols-outlined">
                  humidity_percentage
                </span>
              </div>
            </div>
          </div>
          <div class="row">
            <div class="col-md-12 col-lg-8 bg-white">
              <div class="p-3 border-top border-3 border-danger">
                <div class="row mt-3 gx-5">
                  <div class="col-md-12 col-lg-6 history-divs">
                    <div id="temperature-history"></div>
                  </div>
                  <div class="col-md-12 col-lg-6 history-divs">
                    <div id="humidity-history"></div>
                  </div>
                </div>
              </div>
            </div>
            <div
              class="col-md-12 col-lg-4 d-flex flex-column align-items-center"
            >
              <div
                class="row p-3 bg-light border-top border-3 border-danger bg-white"
              >
                <div id="temperature-gauge"></div>
              </div>
              <div
                class="row p-3 bg-light border-top border-3 border-danger mt-4 bg-white"
              >
                <div id="humidity-gauge" class=""></div>
              </div>
            </div>
          </div>
        </div>
      </div>
    </div>
    <footer class="footer mt-auto py-3 bg-dark">
      <div class="container text-center">
        <span class="text-white">&copy;2023 www.donskytech.com</span>
      </div>
    </footer>
    <script
      src="https://cdn.jsdelivr.net/npm/bootstrap@5.2.3/dist/js/bootstrap.bundle.min.js"
      integrity="sha384-kenU1KFdBIe4zVF0s0G1M5b4hcpxyD9F7jL+jjXkk+Q2h455rYXK/7HAuoJl+0I4"
      crossorigin="anonymous"
    ></script>
    <script
      type="text/javascript"
      src="{{ url_for('static', filename = 'index.js') }}"
    ></script>
  </body>
</html>

The following code above will represent our web application that will act as a dashboard page for our DHT22 sensor readings.

Below is how the HTML page is structured and the parts of the web applications. I have used Plotly.js in developing the graphical charts of this project. The top part is called boxes which display the current values. The historical chart displays the last 12 readings while the gauge charts show the current values when compared to a range.

Dashboard Page Parts
Mobile - Web - DHT22 Weather Station Project -2

As mentioned above, I have used the Bootstrap HTML/CSS framework in developing this project so that it is mobile responsive. Let us try walking thru the code.

<!DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="utf-8" />
    <meta name="viewport" content="width=device-width, initial-scale=1" />
    <title>DHT22 Weather Station Project</title>
    <link
      href="https://cdn.jsdelivr.net/npm/bootstrap@5.2.3/dist/css/bootstrap.min.css"
      rel="stylesheet"
      integrity="sha384-rbsA2VBKQhggwzxH7pPCaAqO46MgnOM80zW1RWuH61DGLwZJEdK2Kadq2F9CUG65"
      crossorigin="anonymous"
    />
    <link
      href="https://fonts.googleapis.com/css2?family=Material+Symbols+Outlined"
      rel="stylesheet"
    />
    <script
      src="https://cdn.plot.ly/plotly-2.20.0.min.js"
      charset="utf-8"
    ></script>
    <link
      href="{{url_for('static', filename = 'index.css')}}"
      rel="stylesheet"
    />
    <script
      src="https://cdnjs.cloudflare.com/ajax/libs/socket.io/3.0.4/socket.io.js"
      integrity="sha512-aMGMvNYu8Ue4G+fHa359jcPb1u+ytAF+P2SCb+PxrjCdO3n3ZTxJ30zuH39rimUggmTwmh2u7wvQsDTHESnmfQ=="
      crossorigin="anonymous"
    ></script>
  </head>

The HEAD section is where we set the title and other meta elements for our Raspberry Pi DHT22 Weather Station Project. This is where we have imported several CSS and Javascript files like Bootstrap, Plotly, SocketIO, and custom project files.

  <body>
    <header>
      <!-- Fixed navbar -->
      <nav class="navbar navbar-expand-md navbar-dark fixed-top bg-dark">
        <div class="container-fluid">
          <a class="navbar-brand" href="#">DonskyTech</a>
          <button
            class="navbar-toggler"
            type="button"
            data-bs-toggle="collapse"
            data-bs-target="#navbarCollapse"
            aria-controls="navbarCollapse"
            aria-expanded="false"
            aria-label="Toggle navigation"
          >
            <span class="navbar-toggler-icon"></span>
          </button>
          <div class="collapse navbar-collapse" id="navbarCollapse">
            <ul class="navbar-nav me-auto mb-2 mb-md-0">
              <li class="nav-item">
                <a class="nav-link active" aria-current="page" href="#">Home</a>
              </li>
              <li class="nav-item">
                <a class="nav-link" href="#">About</a>
              </li>
            </ul>
          </div>
        </div>
      </nav>
    </header>

The code above is what will show the header part of our web application and some of the visible menus at the top.

    <div class="container-fluid content mt-5">
      <div class="row">
        <nav
          id="sidebarMenu"
          class="col-md-3 col-lg-2 d-md-block sidebar collapse"
        >
          <div class="sidebar position-sticky pt-3 sidebar-sticky">
            <ul class="nav flex-column">
              <li class="nav-item">
                <a
                  class="nav-link active link-danger"
                  aria-current="page"
                  href="#"
                >
                  <span class="material-symbols-outlined"> dashboard </span>
                  Dashboard
                </a>
              </li>
            </ul>
          </div>
        </nav>

This is the sidebar part of our web application. Currently, there is only one link which is the dashboard page.

        <div class="col-md-9 ms-sm-auto col-lg-10 px-md-4">
          <div class="row">
            <div
              class="d-flex justify-content-between flex-wrap flex-md-nowrap align-items-center pt-3 pb-2 mb-3 border-bottom border-danger"
            >
              <h1 class="h2 text-danger">DHT22 Weather Station Dashboard</h1>
              <div class="row"></div>
            </div>
          </div>
          <div class="row">
            <div
              class="d-flex p-2 justify-content-evenly overview-boxes justify-content-center"
            >
              <div
                class="box d-flex align-items-center justify-content-center rounded-3 p-1 shadow"
              >
                <div class="right-side">
                  <div class="box-topic">Temperature</div>
                  <div class="number" id="temperature">35 C</div>
                </div>
                <span class="indicator material-symbols-outlined"
                  >device_thermostat</span
                >
              </div>
              <div
                class="box d-flex align-items-center justify-content-center rounded-3 p-1 shadow"
              >
                <div class="right-side">
                  <div class="box-topic">Humidity</div>
                  <div class="number" id="humidity">40%</div>
                </div>
                <span class="indicator material-symbols-outlined">
                  humidity_percentage
                </span>
              </div>
            </div>
          </div>

To display the title and the boxes that show the humidity and temperature readings then we need the code above.

<div class="row">
            <div class="col-md-12 col-lg-8 bg-white">
              <div class="p-3 border-top border-3 border-danger">
                <div class="row mt-3 gx-5">
                  <div class="col-md-12 col-lg-6 history-divs">
                    <div id="temperature-history"></div>
                  </div>
                  <div class="col-md-12 col-lg-6 history-divs">
                    <div id="humidity-history"></div>
                  </div>
                </div>
              </div>
            </div>
            <div
              class="col-md-12 col-lg-4 d-flex flex-column align-items-center"
            >
              <div
                class="row p-3 bg-light border-top border-3 border-danger bg-white"
              >
                <div id="temperature-gauge"></div>
              </div>
              <div
                class="row p-3 bg-light border-top border-3 border-danger mt-4 bg-white"
              >
                <div id="humidity-gauge" class=""></div>
              </div>
            </div>
          </div>
        </div>
      </div>
    </div>

This is the code for our Plotly historical line and gauge charts.

<footer class="footer mt-auto py-3 bg-dark">
      <div class="container text-center">
        <span class="text-white">&copy;2023 www.donskytech.com</span>
      </div>
    </footer>
    <script
      src="https://cdn.jsdelivr.net/npm/bootstrap@5.2.3/dist/js/bootstrap.bundle.min.js"
      integrity="sha384-kenU1KFdBIe4zVF0s0G1M5b4hcpxyD9F7jL+jjXkk+Q2h455rYXK/7HAuoJl+0I4"
      crossorigin="anonymous"
    ></script>
    <script
      type="text/javascript"
      src="{{ url_for('static', filename = 'index.js') }}"
    ></script>
  </body>
</html>

The footer section will just display my name which is donskytech and import our Bootstrap javascript and local index.js file.

static/index.js

var temperatureHistoryDiv = document.getElementById("temperature-history");
var humidityHistoryDiv = document.getElementById("humidity-history");

var temperatureGaugeDiv = document.getElementById("temperature-gauge");
var humidityGaugeDiv = document.getElementById("humidity-gauge");

var graphConfig = {
  displayModeBar: false,
  responsive: true,
};

// 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 temperatureLayout = {
  autosize: true,
  title: {
    text: "Temperature",
  },
  font: {
    size: 14,
    color: "#7f7f7f",
  },
  colorway: ["#B22222"],
  //   width: 450,
  //   height: 260,
  margin: { t: 30, b: 20, l: 30, r: 20, pad: 0 },
};
var humidityLayout = {
  autosize: true,
  title: {
    text: "Humidity",
  },
  font: {
    size: 14,
    color: "#7f7f7f",
  },
  colorway: ["#00008B"],
  //   width: 450,
  //   height: 260,
  margin: { t: 30, b: 20, l: 30, r: 20, pad: 0 },
};
var config = { responsive: true };

Plotly.newPlot(
  temperatureHistoryDiv,
  [temperatureTrace],
  temperatureLayout,
  graphConfig
);
Plotly.newPlot(
  humidityHistoryDiv,
  [humidityTrace],
  humidityLayout,
  graphConfig
);

// 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 layout = { width: 350, height: 250, margin: { t: 0, b: 0, l: 0, r: 0 } };

Plotly.newPlot(temperatureGaugeDiv, temperatureData, layout, graphConfig);
Plotly.newPlot(humidityGaugeDiv, humidityData, layout, graphConfig);

// Temperature
let newTempXArray = [];
let newTempYArray = [];
// Humidity
let newHumidityXArray = [];
let newHumidityYArray = [];

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

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

  temperatureDiv.innerHTML = temperature + " C";
  humidityDiv.innerHTML = humidity + " %";
}

function updateGauge(temperature, humidity) {
  var temperature_update = {
    value: temperature,
  };
  var humidity_update = {
    value: humidity,
  };

  Plotly.update(temperatureGaugeDiv, temperature_update);
  Plotly.update(humidityGaugeDiv, humidity_update);
}

function updateCharts(lineChartDiv, xArray, yArray, sensorRead) {
  var today = new Date();
  var time =
    today.getHours() + ":" + today.getMinutes() + ":" + today.getSeconds();
  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);
}

function updateSensorReadings(jsonResponse) {
  let temperature = jsonResponse.temperature.toFixed(2);
  let humidity = jsonResponse.humidity.toFixed(2);

  updateBoxes(temperature, humidity);

  updateGauge(temperature, humidity);

  // Update Temperature Line Chart
  updateCharts(
    temperatureHistoryDiv,
    newTempXArray,
    newTempYArray,
    temperature
  );
  // Update Humidity Line Chart
  updateCharts(
    humidityHistoryDiv,
    newHumidityXArray,
    newHumidityYArray,
    humidity
  );
}
/*
  SocketIO Code
*/
//   var socket = io.connect("http://" + document.domain + ":" + location.port);
var socket = io.connect();

//receive details from server
socket.on("updateSensorData", function (msg) {
  var sensorReadings = JSON.parse(msg);
  updateSensorReadings(sensorReadings);
});

This file serves two main purposes. It draws our Plotly graphical charts and handles the WebSocket message exchange between the web application and our WebSocket server running on Flask-SocketIO.

var temperatureHistoryDiv = document.getElementById("temperature-history");
var humidityHistoryDiv = document.getElementById("humidity-history");

var temperatureGaugeDiv = document.getElementById("temperature-gauge");
var humidityGaugeDiv = document.getElementById("humidity-gauge");

We declare the different HTML div elements where we will draw our Plotly historical and gauge charts.

var graphConfig = {
  displayModeBar: false,
  responsive: true,
};

// 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 temperatureLayout = {
  autosize: true,
  title: {
    text: "Temperature",
  },
  font: {
    size: 14,
    color: "#7f7f7f",
  },
  colorway: ["#B22222"],
  //   width: 450,
  //   height: 260,
  margin: { t: 30, b: 20, l: 30, r: 20, pad: 0 },
};
var humidityLayout = {
  autosize: true,
  title: {
    text: "Humidity",
  },
  font: {
    size: 14,
    color: "#7f7f7f",
  },
  colorway: ["#00008B"],
  //   width: 450,
  //   height: 260,
  margin: { t: 30, b: 20, l: 30, r: 20, pad: 0 },
};
var config = { responsive: true };

Plotly.newPlot(
  temperatureHistoryDiv,
  [temperatureTrace],
  temperatureLayout,
  graphConfig
);
Plotly.newPlot(
  humidityHistoryDiv,
  [humidityTrace],
  humidityLayout,
  graphConfig
);

These are the Plotly configuration and layout objects needed to draw our historical graph charts.

// 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 layout = { width: 350, height: 250, margin: { t: 0, b: 0, l: 0, r: 0 } };

Plotly.newPlot(temperatureGaugeDiv, temperatureData, layout, graphConfig);
Plotly.newPlot(humidityGaugeDiv, humidityData, layout, graphConfig);

The following are the configurations needed to draw our gauge charts.

// Temperature
let newTempXArray = [];
let newTempYArray = [];
// Humidity
let newHumidityXArray = [];
let newHumidityYArray = [];

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

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

  temperatureDiv.innerHTML = temperature + " C";
  humidityDiv.innerHTML = humidity + " %";
}

function updateGauge(temperature, humidity) {
  var temperature_update = {
    value: temperature,
  };
  var humidity_update = {
    value: humidity,
  };

  Plotly.update(temperatureGaugeDiv, temperature_update);
  Plotly.update(humidityGaugeDiv, humidity_update);
}

function updateCharts(lineChartDiv, xArray, yArray, sensorRead) {
  var today = new Date();
  var time =
    today.getHours() + ":" + today.getMinutes() + ":" + today.getSeconds();
  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);
}

These are utility functions that we need to update our web applications whenever we receive a WebSocket message from our web server. We are only saving the last 12 records for our historical charts of both temperature and humidity.

function updateSensorReadings(jsonResponse) {
  let temperature = jsonResponse.temperature.toFixed(2);
  let humidity = jsonResponse.humidity.toFixed(2);

  updateBoxes(temperature, humidity);

  updateGauge(temperature, humidity);

  // Update Temperature Line Chart
  updateCharts(
    temperatureHistoryDiv,
    newTempXArray,
    newTempYArray,
    temperature
  );
  // Update Humidity Line Chart
  updateCharts(
    humidityHistoryDiv,
    newHumidityXArray,
    newHumidityYArray,
    humidity
  );
}
/*
  SocketIO Code
*/
//   var socket = io.connect("http://" + document.domain + ":" + location.port);
var socket = io.connect();

//receive details from server
socket.on("updateSensorData", function (msg) {
  var sensorReadings = JSON.parse(msg);
  updateSensorReadings(sensorReadings);
});

These are our SocketIO code programming where we handle the WebSocket message exchange between our Flask web server plus the Flask-SocketIO WebSocket server and our web application.

We are listening to the event named updateSensorData and if a message comes into this event then we parse it and call the function updateSensorReadings(). This function would then call the different utility function that we created earlier that will update our web application.

static/index.css

body {
  background-color: #f5f5f5;
}
.bg-white {
  background-color: #fff;
}
.sidebar {
  background-color: #fff;
}
.box {
  background: #cf1b03;
  width: calc(100% / 2 - 20px);
  color: #fff;
}
.box .box-topic {
  font-size: 20px;
  font-weight: 500;
}
.box .number {
  display: inline-block;
  font-size: 35px;
  margin-top: -6px;
  font-weight: 500;
}

.box .indicator {
  font-size: 48px;
}

These are our custom CSS styling that will just add some little effects to our overall web application.

That is basically how the code works and how it contributes to our Raspberry Pi DHT22 Weather Station project.

How about displaying multiple DHT22/DHT11 sensors dynamically?

If you want to display multiple DHT22/DHT11 sensors on the same dashboard then it is also possible. Please take a look at my Raspberry Pi IoT Weather Station

Raspberry Pi Weather Station - Multiple DHt22/DHt11
Raspberry Pi Weather Station – Multiple DHt22/DHt11

How to auto-start the Flask web application when your Raspberry Pi starts or boots?

If you wanted to automatically run this project when your Raspberry Pi starts or boots then please follow the steps outlined here.

Related Content:
Set up your Raspberry Pi to Start Python Script on Boot/Startup

Wrap Up

I have shown you how easy it is to create your own Raspberry Pi DHT22 Weather Station project using Python, Flask, and Flask-SocketIO. By leveraging the WebSocket feature of Flask-SocketIO then we were able to display the real-time sensor readings of our DHT22 sensor.

I hope you learned something. Happy exploring!

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

18 responses to “Raspberry Pi DHT22 Weather Station Project”

  1. Display Real-Time Updates Using Python, Flask, and Websocket

    […] For more details about this project then please see the following Raspberry Pi DHT22 Weather Station Project. […]

  2. Carter Cherry (quaternion) Avatar
    Carter Cherry (quaternion)

    Thanks for all these excellent, well explained , educational projects. I have learned quite a bit with your projects,

    1. donsky Avatar
      donsky

      Thanks a lot for the support!

  3. Bob Andrews Avatar
    Bob Andrews

    Thanks for all these excellent, well explained

    Please can you help the noob

    I have an issue with the Web Server
    i issue the following commands

    # cd /home/pi/Projects/dht22_test/
    # flask run –host=0.0.0.0
    it says:

    Environment: production
    WARNING: This is a development server. Do not use it in a production deployment.
    Use a production WSGI server instead.
    * Debug mode: off
    Temp: 72.7 F / 22.6 C Humidity: 63.4%
    Temp: 72.7 F / 22.6 C Humidity: 63.4%
    Temp: 72.7 F / 22.6 C Humidity: 63.4%
    Temp: 72.7 F / 22.6 C Humidity: 63.4%

    When i try to browse
    http://myraspberryip:5000

    i get an error on my Chrome Browser

    This site can’t be reached
    raspberryip refused to connect.

  4. Bob Andrews Avatar
    Bob Andrews

    Thank you very much for an Excellent Product
    Very Impressive

    I have a few questions
    Q1
    I need to have an Alert via Email for the system. Is this possible?
    If so how do i implement it?

    Q2
    I need to Create a Daily/Weekly/Monthly Report
    How can this be done?

    Q3
    I need to Add an Additional 5 x DHT22 Sensor
    How do i do that?

    any help greatly appreciated

    1. donsky Avatar
      donsky

      Q1
      I need to have an Alert via Email for the system. Is this possible?
      If so how do i implement it?

      There are quite a few things you can try that I can think of:
      Answer:
      1. Use Node-Red (if you know how)
      2. Try notify.sh

      Q2
      I need to Create a Daily/Weekly/Monthly Report
      How can this be done?
      Answer: Then you need to save the data in the time-series database and query it according to your needs. Try MongoDB.
      Or you could use an IoT cloud framework to do this for you such as Amazon, Azure, or Google Cloud. Also some of the other smaller companies like ThingSpeak.

      Q3
      I need to Add an Additional 5 x DHT22 Sensor
      How do i do that?
      Answer:
      Add it into the app.py and handle the code. You might need to change the HTML/CSS/Javascript if you want to display all of it.

  5. Adding Touch Screen Display to your Raspberry Pi Projects %

    […] and you need a way to display your user interface to your users. An example project is what I did Raspberry Pi DHT22 Weather Station Project where I displayed real-time sensor readings of my temperature and humidity. Attaching a keyboard […]

    1. Steven Avatar
      Steven

      Great project

      How do add an additional DHT22 Sensor to Display Two DHT22 Sensors on the webpage

      1. donsky Avatar
        donsky

        Hey,
        You can connect multiple DHT22 to your Raspberry Pi and read it from the app.py but you need to update the HTML/Javascript of the program to handle the display.
        Currently, the code is only handling one DHT22 sensor.
        Happy Exploring!
        Donsky!

  6. Raspberry Pi Weather Station Uses One Sensor, Displays Data via Web Based Dashboard – Mist Vista

    […] things in between. Maker Donsky, the mastermind behind Donskytech, has used one to create a lovely Raspberry Pi weather station project. It uses just one sensor so you don’t need much hardware to get off the ground and he […]

  7. Selective: Raspberry Pi Weather Station Uses One Sensor, Displays Data via Web Based Dashboard – Daily News Circuit – Daily News Circuit

    […] things in between. Maker Donsky, the mastermind behind Donskytech, has used one to create a lovely Raspberry Pi weather station project. It uses just one sensor so you don’t need much hardware to get off the ground and he […]

  8. Steven Avatar
    Steven

    Thank you for great application

    How can i read Multiple DHT22 Sensors on the dht22_module.py

    Any help greatly appreciated

    1. donsky Avatar
      donsky

      Hey,
      You can connect multiple DHT22 to your Raspberry Pi and read it from the app.py but you need to update the HTML/Javascript of the program to handle the display.
      Currently, the code is only handling one DHT22 sensor.
      Happy Exploring!
      Donsky!

  9. Donsky's Tutorial Makes use of Python, WebSockets to Flip a Raspberry Pi Into an IoT Environmental Monitor – Slsolutech.Best IT Related Website

    […] complete tutorial is offered on Donsky’s web site, with supply code accessible on GitHub underneath the permissive Apache 2.0 […]

  10. Raspberry Pi IoT Weather Station – donskytech.com

    […] Pi because you can even create your own mini Internet of Things (IoT) platform with it. On my last Raspberry Pi DHT22 Weather Station Project, I created an application that displays the real-time reading of my DHT22 sensors using Python, […]

  11. Set up your Raspberry Pi to Start Python Script on Boot/Startup

    […] this section, we will deploy our DHT22 Weather Station dashboard Python web application that displays a real-time display of sensor readings which uses the Flask […]

  12. David Arthur Avatar
    David Arthur

    Thanks for this great project. I received a Pi4 and a Pi5 for Christmas and I started this project in the 4. I know a little about Python programming so I was able to convert the py, js and html files to Fahrenheit for use in the US. The interface works great on my Pixel 6 Pro, so I can access the readings from anywhere

    1. donsky Avatar
      donsky

      Great! Good to hear I was able to help! Happy Exploring!

Leave a Reply

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