Build Your Own Weather Station With PHP, Python and a Raspberry Pi

March 16, 2022
Written by
Twilion
Reviewed by
Twilion

Build your own weather station with PHP, Python and a Raspberry Pi

Raspberry Pi, Arduino, BeagleBone, and similar technologies have had a revolutionary impact on so many people around the world.

Because they provide the building blocks of computing for a very low price, anyone, from a school student to a retiree, with a little bit of time and effort, can build a device that perfectly scratches whatever itch they have. They no longer need to wait for a commercial organisation to build it.

One such itch (at least one that I have) is to measure the current temperature and humidity throughout the rooms in my home. Sure, weather app's can tell you the current temperature and humidity, however, they can only do it for a wide geographical area.

So, in this tutorial, you're going to learn how to create a small weather station with a Raspberry Pi, some PHP and Python code, and a temperature and humidity sensor — for under $100.00 — accessible from any device on your home network.

How will the weather station work?

Conceptualisation of the Raspberry Pi, PHP, Python weather station

The weather station, as I've alluded to, will be compose of three parts, those being:

  1. A Raspberry PI connected to a temperature and humidity sensor, via a GPIO (General Purpose Input/Output) Breadboard Extension.
  2. A Python script to read data from the sensor and store it in an SQLite database.
  3. A web-based, Single-page Application (SPA), written in PHP. It will read the sensor data from the database and render it in a beautiful, easy-to-read way. To reduce the amount of code required, the application will be based on the Slim Framework and Twig template engine.

Here's what the web app will look like when it's completed.

DIY weather station PHP app

Prerequisites

To follow along with this tutorial, you need to have the following:

I chose the DHT11 sensor as, while it's a little slow and its accuracy isn't as high as other sensors, it doesn't cost much and is readily available. Feel free to use a sensor with greater range and accuracy, such as the DHT22 or AM2302, if you'd prefer and are more experienced.

If you're just getting started, I recommend getting a starter kit, such as the Freenove Ultimate Starter Kit. It has the sensor, GPIO Breadboard, connecting ribbon, and everything else that you'll need. Also, if you don't have a Raspberry Pi, yet, I recommend this starter kit.

Connect the DHT11 sensor to the Raspberry Pi

Now, it's time to set up the GPIO Breadboard Extension with the DHT11 sensor, so that the sensor can be read from.

To do that:

  1. If your Raspberry Pi is in a case, remove the case so that you can access the GPIO pins.
  2. Plug the T-cobbler onto the breadboard.
  3. Connect one end of the 40 pin cable to the T-cobbler.
  4. Connect the other end of the 40 pin cable to the Raspberry Pi's GPIO pins.
  5. Connect the DHT11 sensor into the breadboard with the top/front facing to the left-hand side of the breadboard.
  6. Connect the GND of the sensor to a GPIO GND pin (the white cable).
  7. Connect the VCC pin of the sensor to the 3,3V pin (the longer green wire).
  8. Connect the OUTPUT pin of the sensor to pin GPIO 17 (the yellow wire).
  9. Connect a 10K Ohm pull up resistor between the VCC and OUTPUT pins (the resistor and shorter green wire).

When wired up, it should look like the image below.

Wired up GPIO Breadboard

 

Set up the Raspberry Pi

The first thing that you need to do is to install and configure the required software on the Raspberry Pi.

This will include a number of packages (NGINX, PHP with the INTL & PDO SQLite extensions, as well as PHP-FPM, Python3, PIP3, SQLite).

To do that, run the following commands.

sudo apt-get update
sudo apt-get install -y \ 
    npm \
    php8.0-cli \
    php8.0-fpm \
    php8.0-intl \
    php8.0-sqlite3 \
    python3 pip3 \
    nginx \
    sqlite3

Then, you need to add the pi user to the gpio and www-data groups. To do that, run the following command.

sudo usermod -uG gpio www-data pi

Then, you need to change the www-data's shell, and enable it. To do that, run the following command.

sudo usermod --shell /bin/bash www-data

Following that, you need to install Pipenv, by running the following command.

pip3 install --user pipenv

Create the application's project directory

Now, it's time to write the weather station's code. To do that, you first need to create  the project directory structure and change into the top-level directory. To do that, run the following commands:

mkdir -p raspberrypi-weather-station/public/css \
    raspberrypi-weather-station/data/ \
    raspberrypi-weather-station/bin \
    raspberrypi-weather-station/templates
cd raspberrypi-weather-station

The commands create a new directory, named raspberrypi-weather-station. Inside that directory are four sub-directories:

  • bin: This will contain the Python script.
  • data: this will contain the application's SQLite database.
  • public: This will contain the PHP file that will power the PHP application, along with a directory named css, which will contain the PHP applications CSS file.
  • templates: This will contain the Twig template that the PHP application will use to render its output.

Create the application's database

The next thing to do is to create the application's SQLite database.

I recommend SQLite because it has very low overhead and it doesn't require a complicated setup or configuration process as other databases do.

The schema will be extremely minimalist, containing just one table containing three columns:

  1. humidity: This will store the humidity reading.
  2. temperature: This will store the temperature reading.
  3. timestamp: This will store the date and time of the reading. The value will be auto-inserted because the default value, if not specified, is the current timestamp (date and time).

To create it, first create the database and then connect to it, by running the command below.

sqlite3 data/weather_station.sqlite

Then, create the schema by running the following command.

create table weather_data
(
    temperature real default 0.0 not null,
    humidity real default 0.0 not null,
    timestamp DATETIME default CURRENT_TIMESTAMP
);

Create the Python script to read the DHT11 sensor

The next thing to do is to create a Python script that will read the data from the DHT11 sensor.

But before you can do that, you need to install the Adafruit_DHT package which the script depends on. From your terminal, run the following command.

pipenv install Adafruit_DHT

Now, it's time to create the Python script that will read the temperature and humidity data from the sensor. To do that in the bin directory, create a new Python file named dht11-sensor-reader.py. Then, paste the code below into the file.

import Adafruit_DHT
import os
import random
import sqlite3
import time

from sqlite3 import Error

def create_connection(path):
    db_conn = None
    try:
        db_conn = sqlite3.connect(path)
        print("Connection to SQLite DB successful")
    except Error as e:
        print(f"The error '{e}' occurred")

    return db_conn

def execute_query(db_conn, query, query_parameters):
    cursor = db_conn.cursor()
    try:
        cursor.execute(query, query_parameters)
        db_conn.commit()
        print("Query executed successfully")
    except Error as e:
        print(f"The error '{e}' occurred")

gpio_pin = 17
database_file = 'data/database/weather_station.sqlite'

humidity, temperature = Adafruit_DHT.read_retry(database_file, gpio_pin)

if humidity is not None and temperature is not None:
    connection = create_connection('./data/database/weather_station.sqlite')
    add_weather_data = f'INSERT INTO weather_data (temperature, humidity) VALUES (:temperature, :humidity);'
    parameters = {"temperature": round(temperature, 2), "humidity": round(humidity, 2)}
    execute_query(connection, add_weather_data, parameters)

The script starts by importing all of the required packages. After that, it creates two functions, one to create a connection to the SQLite database (create_connection) and another to run a SQL query against the database (execute_query).

After that, it defines two variables, gpio_pin and database_file. These store the GPIO pin to read the sensor data from, and the path to the SQLite database file, respectively.

Then, it:

  1. Attempts to read the humidity and temperature from the DHT11 sensor.
  2. Connects to the database.
  3. Writes the retrieved sensor data to the database.

Create a Crontab entry to run the Python script

Now that the Python script is ready, a Cron job needs to be created so that it will be run on a regular basis and populate the database.

To do that, as the pi user, run the following command.

crontab -e

This will open the Crontab editor for the pi user, which will allow you to set how often the script should run.

 

Do you prefer an editor such as VIM or Pico over the default Raspberry Pi terminal editor? Then run the command below before opening the crontab editor, replacing the <<editor>> placeholder with the name of your preferred editor.

export EDITOR=<<editor>>

Then, in the editor, at the bottom of the file, add the following snippet.

* * * * * cd /var/www/weather_station && python3 bin/dht11-sensor-reader.py

This script tells the pi user to cd into /var/www/weather_station and use Python3 to run bin/dht11-sensor-reader.py once every minute.

If you'd like to run the script at a different interval, use crontab guru to quickly build the cron scheduling expression (which in the example above, are the five space-separated asterisks at the start of the line).

With the Crontab entry created, the Python script will be intermittently called, storing records in the SQLite database that you'll be able to view, when you've completed the PHP frontend to the weather station.

Create the PHP web application

Now, it's time to create the PHP application. To start off, as always, you need to install the external dependencies which the application will need. Use Composer to do this, by running the following command.

composer install \
    laminas/laminas-db \
    php-di/php-di \
    slim/psr7 \
    slim/slim \
    slim/twig-view \
    twig/intl-extra

Create the core script

Then, in the public directory, create a new PHP file, named index.php, and in that file paste the following code.

<?php

declare(strict_types=1);

use DI\Container;
use Laminas\Db\Adapter\Adapter;
use Laminas\Db\Sql\Sql;
use Psr\Http\Message\{
    ResponseInterface as Response,
ServerRequestInterface as Request
};
use Slim\Factory\AppFactory;
use Slim\Views\{Twig,TwigMiddleware};
use Twig\Extra\Intl\IntlExtension;

require __DIR__ . '/../vendor/autoload.php';

$container = new Container();
$container->set('view', function(\Psr\Container\ContainerInterface $container) {
    $twig = Twig::create(__DIR__ . '/../resources/templates');
    $twig->addExtension(new IntlExtension());
    return $twig;
});
$container->set('weatherData', function() {
    $dbAdapter = new Adapter([
        'driver' => 'Pdo_Sqlite',
        'database' => __DIR__ . '/../data/database/weather_station_test_data.sqlite'
    ]);

    $sql = new Sql($dbAdapter, 'weather_data');
    $select = $sql
        ->select()
        ->columns(['temperature', 'humidity', 'timestamp']);

    return $dbAdapter->query(
        $sql->buildSqlString($select),
        $dbAdapter::QUERY_MODE_EXECUTE
    );
});

AppFactory::setContainer($container);
$app = AppFactory::create();
$app->add(TwigMiddleware::createFromContainer($app));
$app->map(['GET'], '/', function (Request $request, Response $response, array $args) {
    return $this->get('view')->render(
        $response,
        'index.html.twig',
        ['items' => $this->get('weatherData')]
    );
});

$app->run();

The first thing that the code does is to, as always, import the required classes and Composer's Autoloader. After that, it initialises the DI (Dependency Injection) Container, registering two services with it.

The first service is named view. It returns a Twig object that retrieves its templates from the resources/templates directory in the root directory of the project, and uses Twig's Intl Extension, so that it can do some nice date and time formatting (among other things).

The second, named 'weatherData', starts off by initialising a \Laminas\Db\Adapter\Adapter object, which provides the connection to the application's SQLite database.

Then, using the adapter, it programmatically generates a SQL query to select all records from the weather_data table, sorted from the most recent to the oldest records. It then runs the query against the database, and stores the results in $weatherData.

You can see the generated query below.

SELECT temperature, humidity, timestamp
    FROM weather_data
    ORDER BY timestamp DESC

After that, the Slim application is instantiated and the initialised container is passed to it. That way, it has access to both services.

Then, a GET route, with the path /, is registered with the application. When requested, this route will render and return the view template, resources/templates/index.html.twig. $weatherData is passed to the template so that it can render the retrieved sensor data.

I'm using laminas-db as it simplifies SQL query creation. If you'd like to know more about the library, check out my Pluralsight course which covers it in greater detail (using the previous version, Zend Db).

Create the view template

Now, it's time to create the view template, so that the content will be rendered and styled professionally. To do that, in the resources/templates directory, create a new file named index.html.twig. Then, in that file, paste the following code.

<!doctype html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <link href="/css/styles.css" rel="stylesheet">
    <title>DIY Weather Station</title>
</head>
<body>
<h1 class="mb-4">DIY Weather Station</h1>
<div>
    <table>
        <thead>
        <tr>
            <th>Date</th>
            <th>Time</th>
            <th>Temperature (&deg;C)</th>
            <th>Humidity (%)</th>
        </tr>
        </thead>
        <tbody>
            {% for item in items %}
            <tr>
                <td>{{ item.date|format_date() }}</td>
                <td>{{ item.time|format_time(pattern='hh:mm') }}</td>
                <td>{{ item.temperature|format_number({rounding_mode: 'floor', fraction_digit: 2}, locale='de') }}</td>
                <td>{{ item.humidity|format_number({rounding_mode: 'floor', fraction_digit: 2}, locale='de') }}</td>
            </tr>
            {% endfor %}
        </tbody>
    </table>
</div>
</body>
</html>

There's not a lot going on in the template. It imports the stylesheet in the head section and then in the body, iterates over the weather data, printing out in a nicely styled table the date, time, temperature, and humidity data for each available record.

Download the CSS file

To ensure that the application renders as professionally as expected, download the CSS file from the GitHub repository to the public/css directory, and name it styles.css.

Create an NGINX Virtual Host

At this point, the application is ready to use. But, before it can be run, a virtual host has to be added to NGINX. To do that, create a new file in /etc/nginx/sites-available named weather_station. In that file, add the following configuration.

server {
    listen 80 default_server;
    root /home/pi/raspberrypi-weather-station/public;
    index index.php;

    access_log /var/log/nginx/access.log;
    error_log /var/log/nginx/error.log;

    location / {
        try_files $uri $uri/ /index.php;
    }

    location ~ \.php$ {
        fastcgi_split_path_info ^(.+\.php)(/.+)$;
        fastcgi_pass unix:<<X>>.sock;
        fastcgi_index index.php;
        include fastcgi_params;
        fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name;
    }
}

The configuration above sets the root directory where the files will be served from /var/www/weather_station/public, the default file to call, index.php. After that, it configures logging, followed by sending all requests to /var/www/weather_station/public/index.php. Any requests ending in .php, which will be every request, except for the stylesheet.

Next, you have to enable the configuration. To do that, run the command below.

sudo rm /etc/nginx/sites-enabled/default
sudo ln -s /etc/nginx/sites-enabled/weather_station /etc/nginx/sites-enabled/default
sudo systemctl restart nginx

Test the weather station

Now that everything's complete, it's time to test the application. To do that, open http://<<Raspberry Pi IP Address>> in your browser of choice, replacing the placeholder with the IP address of your Raspberry Pi.

When loaded, you should see the page render similar to the screenshot below.

DIY weather station PHP app

That's how to build your own weather station with PHP, Python and a Raspberry Pi

Admittedly, it's pretty humble in appearance and functionality, but with time it will become a lot fuller featured. What's more, it didn't cost much to create. I hope that it whets your appetite for what you can make using a Raspberry Pi.

In the next part in the series, you'll learn how to send a daily summary via both SMS and email using Twilio and SendGrid.