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.
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.
- Raspberry Pi 4B Model 8GB – Amazon | AliExpress
- or Raspberry Pi Zero 2 W – Amazon | AliExpress
- or Raspberry Pi Zero W – Amazon | AliExpress
- DHT22 (AM2302) – Amazon | AliExpress
- or DHT11 – Amazon | AliExpress | Bangood
- Breadboard – Amazon | AliExpress | Bangood
- Connecting Wires – Amazon | AliExpress | Bangood
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.
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
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">©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.
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">©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
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!
Leave a Reply