DEV Community

Cover image for Building A Weather Station with a Raspberry Pi and Python - Part One
dev_neil_a
dev_neil_a

Posted on • Edited on

Building A Weather Station with a Raspberry Pi and Python - Part One

Building a Raspberry Pi Environment Sensor

YouTube Video

If you would prefer to watch a video of this article, there is a video version available on YouTube below:

Introduction

I recently decided I wanted to have a play around with a Raspberry Pi and came up with a few project ideas that I could put together with a Pi.

For this particular project, I’ll be putting together a Raspberry Pi with an environmental sensor board, along with an LCD Screen. The aim is to have the values that the sensor collects show up on the LCD screen and on the monitor as well. I will be demonstrating two LCD screen options, one being a 16 column by two rows, and another with 20 columns and 4 rows.

This project will be split up over three articles, with this part covering the initial “getting it working” part. The second will look at tidying up the code and adding logging to the program, along with using some environment variables for config settings.

The last part will include setting up a SQL database to store the readings and adding a basic web server to show the last 10 readings and an option to download all the readings to a CSV file.

With that said, let’s move on and look at what hardware and software that will be used for this project.

Required Hardware

For this project, I used the following hardware:

  • 1 x Raspberry Pi 3 B+ 1GB. You can use either a Pi 4 or 5 instead
  • 1 x 128GB SanDisk MicroSD Card. Again, you can use any, but I would recommend a card of at least 64GB or higher
  • 1 x Power Adapter
  • 1 x Adafruit BME280 Sensor which collects temperature, humidity and pressure
  • 1 x LCD Screen. Both of the following will be used, one at a time:
    • 1 x 16x2 LCD Screen with a PCF8574 expander board
    • 1 x 20x4 LCD Screen with a PCF8574 expander board
  • Wires
    • 10 x Male to female jumper / Dupont wires
    • 2 x red (for 5v power)
    • 1 x orange (for 3v power)
    • 2 x black (for ground)
    • 2 x yellow (for i2c SDA)
    • 2 x blue (for i2c SCL)
    • 1 x STEMMA QT / Qwiic JST SH 4-pin to 4-pin male jumper / Dupont wires
  • 1 x Breadboard

Here is a photo of the hardware:

Required Software

On the software side, the project will make use of:

  • The Raspberry Pi Imager tool
  • Raspberry Pi OS Lite (on the MicroSD card). This is a version of Raspberry Pi OS that doesn’t have a desktop environment as I needed to free up memory
  • A terminal emulator – iTerm2 is what I use on macOS, but you can use whichever you like
  • Microsoft Visual Studio Code
  • Python 3.11 (newer versions may work as well)

Wiring Up The Equipment

The wiring for this project is simple. Use the below diagram to cable up the Pi, sensor and the LCD screen to the breadboard.

After the wiring is done, install the microSD card into the Pi.

Updating Raspberry Pi OS

With the hardware all setup, the first thing to do is to check for any updates for Pi OS. As this is a new install, there will be some to install.

Open a terminal SSH to the Pi by running:

ssh username@ip-address
Enter fullscreen mode Exit fullscreen mode

Once logged in, update the list of repositories by running:

sudo apt update
Enter fullscreen mode Exit fullscreen mode

After that, install any updates by running:

sudo apt upgrade -y
Enter fullscreen mode Exit fullscreen mode

With all the updates installed, some additional packages need to be installed. To install them, run:

sudo apt install i2c-tools virtualenv python3-virtualenv -y
Enter fullscreen mode Exit fullscreen mode

To go over what the packages are, the first one is i2c-tools, which is a collection of tools that are used for interacting with the i2c interface that the sensor and LCD screen are connected to.

The last two, virtualenv and python3-virtualenv, allow for easy creation of virtual environments for Python. I had issues with another method, so I opted to use virtualenv instead.

After those have been installed, reboot the Pi using:

sudo reboot
Enter fullscreen mode Exit fullscreen mode

Rebooting isn’t really needed but I would recommend doing it, especially with a fresh Pi OS install.

The system will end the SSH session and restart the Pi. Wait for a minute or two and then run SSH again.

Detecting The Sensor and LCD Screen

Now that Pi OS has been updated, the next step is to check to make sure the sensor module and LCD screen have been detected on the i2c interface.

First, the i2c interface on the Pi needs to be enabled as by default, it’s disabled on Pi OS. To enable it, run:

sudo raspi-config
Enter fullscreen mode Exit fullscreen mode

Once the menu appears, go to Interface Options > I2C > Yes.

Exit out of raspi-config.

Next, to see if the board and LCD screen have been detected, run:

i2cdetect -y 1
Enter fullscreen mode Exit fullscreen mode

The output should look like the below:

     0  1  2  3  4  5  6  7  8  9  a  b  c  d  e  f
00:                         -- -- -- -- -- -- -- --
10: -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- --
20: -- -- -- -- -- -- -- 27 -- -- -- -- -- -- -- --
30: -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- --
40: -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- --
50: -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- --
60: -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- --
70: -- -- -- -- -- -- -- 77
Enter fullscreen mode Exit fullscreen mode

There are two numbers showing, 27 and 77. 27 is the i2c address for the LCD screen and 77 is the i2c address for the sensor board, which you can check by looking at the back of the sensor board as it’s printed on there.

This indicates that both are detected and good to go.

If one or both don’t appear, double check the cabling and that the i2c interface is enabled.

Setup VS Code

Now that the sensor and LCD screen are working, it’s time to start putting together the code.

First, if you haven’t already, download and install VS Code for your operating system.

Next, click on open a remote window using the icon in the very bottom left of the status bar in VS Code. If you don’t see it, check that the Remote Development extension is installed.

It will ask you how to connect. Select ‘Connect to host’ and then select ‘Add new SSH host’. Enter your username and ip address of the pi as yourusername@ip-address-of-the-pi.

Save the settings to the top file when asked.

Next, click connect and enter the password for the user on the Pi.

If it asks you to install VS Code remote server, do so.

Now that VS Code server is installed, install the Python extensions from Microsoft to make working with Python easier. You can do this by going to Extensions, and then typing Python. Select the option that is from Microsoft, which is usually at the top and install it to the SSH system.

Next, open a new terminal in VS Code (Terminal > New Window in the menu) if one is not already present.

In the terminal, create a new folder called ‘project’ by running:

mkdir project
Enter fullscreen mode Exit fullscreen mode

Next, run cd project to go into that folder.

With that done, click on Open Folder in the Explorer tab and select the project folder.

Next, a Python virtual environment will need to be created so that the projects packages can be isolated from the global Python environment. To do this, run:

virtualenv -p /usr/bin/python3 venv
Enter fullscreen mode Exit fullscreen mode

This will create a virtual environment called venv.

Next, the virtual environment needs to be activated, which is done by running:

source ./venv/bin/activate.
Enter fullscreen mode Exit fullscreen mode

With the virtual environment now active, several Python libraries need to be installed using the pip or pip3 command to allow Python to work with the sensor board and LCD screen. To install these, run:

pip3 install adafruit-circuitpython-bme280 RPLCD smbus2
Enter fullscreen mode Exit fullscreen mode

Using Python with The Sensor and a 16x2 LCD Screen

First, create a new file called main.py in the project folder. That file will automatically open.

Next, copy and paste the following code into this file:

# --- Import the required libraries / modules:
from adafruit_bme280 import basic as adafruit_bme280
from datetime import datetime as dt
from RPLCD.i2c import CharLCD
from time import sleep

import board


# ======== Part 1: Get the sensor working: ========

# --- Attempt to create a board object, using the board's default I2C interface:
try:
    i2c = board.I2C()  # --- uses board.SCL and board.SDA
except OSError as error:
    raise Exception(f"Critical: {error}")
except ValueError as error:
    raise Exception(f"Critical: {error}")


# --- Attempt to initialise the sensor on the i2c interface using the default values:
try:
    bme280 = adafruit_bme280.Adafruit_BME280_I2C(i2c)
except OSError as error:
    raise Exception(f"Critical: {error}")
except ValueError as error:
    raise Exception(f"Critical: {error}")


# --- change this to match the location's pressure (hPa) at sea level.
# --- See references section for a website to get your sea level:
bme280.sea_level_pressure = 1013.25


# ======== Part 2: Get the LCD scree output working: ========
try:
    lcd = CharLCD(i2c_expander="PCF8574", 
                  address=0x27,
                  port=1, 
                  cols=16, 
                  rows=2, 
                  dotsize=8)

except OSError:
    raise Exception("Display could not be detected. Please check the display is connected and the I2C port and / or address are correct.")

except NotImplementedError:
    raise Exception("The display expander is not supported. Please check the display configuration.")


# --- Show the results on the console and the LCD screen:
update_output = True


while update_output == True:
    # --- Set variables for all four measurements:
    temperature = bme280.temperature
    humidity = bme280.relative_humidity
    pressure = bme280.pressure
    altitude = bme280.altitude

    # --- Show the values on the console:
    print(dt.now().strftime("%d/%m/%Y, %H:%M:%S\n"))
    print("Temperature: %0.2f°C" % temperature)
    print("Humidity: %0.2f %%" % humidity)
    print("Pressure: %0.2f hPa" % pressure)
    print("Altitude: %0.2f meters" % altitude)       

    # --- Clear the current contents of the LCD screen:
    lcd.clear()

    # --- Output values to the LCD screen:
    lcd.write_string(f"Temp: %0.2f{chr(223)}C\r\n" % temperature)
    lcd.write_string(f"Humi: %0.2f%%" % humidity)

    # --- Sleep for three seconds
    sleep(3)
Enter fullscreen mode Exit fullscreen mode

The \r\n that is at the end of a lcd.write_string line indicates a new line (\n) and return to the beginning of that line (\r).

Also, if you are wondering what chr(223) means, it is the ASCII code for the degreesymbol .

Right-click in a blank space around the code and go to Run Python -> Run Python File in Terminal.

The five lines for the date & time, temperature, humidity, pressure and altitude will show up in the VS Code terminal and the temp and humi lines on the LCD screen. Both will update every three seconds with the latest reading.

To stop the program, use ctrl-c in the console as there is no way to stop the while loop with the way the program is built.

NOTE: Change the value of bme280.sea_level_pressure to match where you are. There is a link in the references section to help you get the right sea level number.

Using Python with the 20 x 4 Screen

To use a 20 x 4 LCD screen instead of a 16 x 2 LCD screen, the code is the same, but there are some changes that need to be made to it.

First, the wiring on the display is the same as the 16 x 2 so there is nothing different there, at least for the two screens I used.

Next, before making any changes, run i2cdetect -y 1. The response will be the same (27 for the address) as the screens use the same expander board.

Moving onto the code changes. First, change the lcd arguments to cols=20 and rows=4 from 16 and 2 respectively. It should now look like the below:

lcd = CharLCD(i2c_expander="PCF8574", 
                  address=0x27,
                  port=1, 
                  cols=20, 
                  rows=4, 
                  dotsize=8)
Enter fullscreen mode Exit fullscreen mode

Lastly, replace the lcd.write_string lines with the below to show the pressure and altitude on the LCD screen, along with the temperature and humidity:

lcd.write_string(f"Temp: %0.2f{chr(223)}C\r\n" % temperature)
lcd.write_string(f"Humi: %0.2f%%\r\n" % humidity)
lcd.write_string(f"Pres: %0.2fhpa\r\n" % pressure)
lcd.write_string(f"Alti: %0.2fm" % altitude)
Enter fullscreen mode Exit fullscreen mode

The complete code now looks like this:

# --- Import the required libraries / modules:
from adafruit_bme280 import basic as adafruit_bme280
from datetime import datetime as dt
from RPLCD.i2c import CharLCD
from time import sleep

import board


# ======== Part 1: Get the sensor working: ========

# --- Attempt to create a board object, using the board's default I2C interface:
try:
    i2c = board.I2C()  # --- uses board.SCL and board.SDA
except OSError as error:
    raise Exception(f"Critical: {error}")
except ValueError as error:
    raise Exception(f"Critical: {error}")


# --- Attempt to initialise the sensor on the i2c interface using the default values:
try:
    bme280 = adafruit_bme280.Adafruit_BME280_I2C(i2c)
except OSError as error:
    raise Exception(f"Critical: {error}")
except ValueError as error:
    raise Exception(f"Critical: {error}")


# --- change this to match the location's pressure (hPa) at sea level.
# --- See references section for a website to get your sea level:
bme280.sea_level_pressure = 1013.25


# ======== Part 2: Get the LCD scree output working: ========
try:
    lcd = CharLCD(i2c_expander="PCF8574", 
                  address=0x27,
                  port=1, 
                  cols=20, 
                  rows=4, 
                  dotsize=8)

except OSError:
    raise Exception("Display could not be detected. Please check the display is connected and the I2C port and / or address are correct.")

except NotImplementedError:
    raise Exception("The display expander is not supported. Please check the display configuration.")


# --- Show the results on the console and the LCD screen:
update_output = True


while update_output == True:
    # --- Set variables for all four measurements:
    temperature = bme280.temperature
    humidity = bme280.relative_humidity
    pressure = bme280.pressure
    altitude = bme280.altitude

    # --- Show the values on the console:
    print(dt.now().strftime("%d/%m/%Y, %H:%M:%S\n"))
    print("Temperature: %0.2f°C" % temperature)
    print("Humidity: %0.2f %%" % humidity)
    print("Pressure: %0.2f hPa" % pressure)
    print("Altitude: %0.2f meters" % altitude)       

    # --- Clear the current contents of the LCD screen:
    lcd.clear()

    # --- Output values to the LCD screen:
    lcd.write_string(f"Temp: %0.2f{chr(223)}C\r\n" % temperature)
    lcd.write_string(f"Humi: %0.2f%%\r\n" % humidity)
    lcd.write_string(f"Pres: %0.2fhpa\r\n" % pressure)
    lcd.write_string(f"Alti: %0.2fm" % altitude)

    # --- Sleep for three seconds
    sleep(3)
Enter fullscreen mode Exit fullscreen mode

Run the program again. There will be five lines shown in the console (print) and four lines on the LCD screen (lcd.write_string).

Again, to close the program, press ctrl+c in the console window.

Conclusion

That completes this part of the project. In the next part, I’m going to split up the code into modules for the LCD screen and the sensor, along with adding some logging to help store any errors that are encountered along the way.

I will also be adding some environment variables to the program to make it more configurable. For example, include a file that indicates if an LCD screen is connected and if so, what type it is. For example, is it a 16x2 or a 20x4 screen.

References

BME280 Tutorial:

https://learn.adafruit.com/adafruit-bme280-humidity-barometric-pressure-temperature-sensor-breakout/pinouts

Determine your see level:

https://meteologix.com/uk/observations/united-kingdom/pressure-qnh/20240823-1000z.html

Top comments (0)