Build a greenhouse monitor with a Raspberry Pi

An illustration of a greenhouse with Raspberry Pi logos on the door and hanging from vines inside
(Image credit: Shutterstock)

The Raspberry Pi Sense Hat is a versatile add-on, with sensors for temperature, humidity, pressure, orientation and direction, and there are numerous examples of how it can be used online. The European Space Agency has even sent two of them to the International Space Station for conducting experiments in weightless environments.

Here at IT Pro, we're a little more down to earth. To showcase the Sense Hat's capabilities, we'll focus on three of those sensors – temperature, humidity and pressure – to build a greenhouse-monitoring system. With winter on the way and gardeners moving vulnerable plants under cover, it makes sense to have a way of keeping an eye on conditions without having to go out and check a thermometer.

We'll therefore take a reading every 15 minutes and upload the data to a web server so it can be checked from the comfort of a centrally heated home. We'll also make use of the LED matrix mounted on the Sense Hat's top surface to give visual feedback should temperatures stray outside of a defined range. That way, if you mount the Pi so it's visible from the house, you can see at a glance if you need to go out and turn on a heater or open a window, even if you haven't checked the server.

Naturally, you need a power source wherever you position your Pi plus Sense Hat. If you're going to use it in an outbuilding, you should also make sure it isn't going to get wet. You don't need Wi-Fi if you only want to record your measurements onto the SD card, but you will if you want to upload them to the server, so make sure your network signal carries far enough, or use a booster. If you don't have a greenhouse, you could use the same technique to monitor the temperature of, say, a nursery, a vivarium housing reptiles or even just your office.

Install the Hat and set up your Pi

The Sense Hat works with Raspberry Pi 2 Model B and later, so if you've upgraded and have an old Pi hanging around, this is a great way to give it a useful second life. For this project, we're using a Raspberry Pi Zero WH, with built-in Bluetooth and Wi-Fi, and a pre-soldered GPIO header. We won't be using Bluetooth, but Wi-Fi and GPIO are essential; if you're using a Zero without pins, either buy a header and solder it on or, if you don't have a soldering iron, check out Pimoroni's solderless header.

You'll need the "Male + Female + Installation Jig", which, at £7, is pricier than the male pins alone (to which the Sense Hat attaches), but is still cheaper than an iron, flux and just the pins you need. We've used it ourselves and, although the first few taps with a hammer are a breath-holding experience, it's easier than you might imagine.

Sense Hat ships with four standoffs. Having unplugged your Pi from the mains, screw the standoffs into the holes on the corners of your Pi (if you're using a Pi Zero, hold your Pi so the side with all of the chips on it is facing you and the GPIO pins are at the top. Screw the standoffs to the top left and both of the right holes, leaving the bottom-left hole vacant). Now mount the Sense Hat by pressing it onto the GPIO pins, with the LED matrix facing you and the female GPIO sockets again at the top.

When it's properly seated, use the remaining four screws (two on the Zero) to secure the Sense Hat to the standoffs. On the Zero, you'll notice that only the top two standoffs align with holes on the Sense Hat, so screw these into place. The third standoff you attached to the Pi will sit beneath the Hat, supporting it without being physically attached.

Download the Pi imager from this link, then insert a microSD card into your PC and click "Choose OS". Select "Raspberry Pi OS (32-bit)", click "Choose SD Card" and select the card you've inserted. Finally, click "Write". This wipes the microSD card and installs the OS. When it's finished, open Windows Notepad and enter the following, replacing the parts in angled brackets as appropriate. Note that the country code for the United Kingdom is GB, not UK.

Swipe to scroll horizontally
ctrl_interface=DIR=/var/run/wpa_supplicant GROUP=netdev
update_config=1
country=<2 letter ISO 3166-1 country code>network={
ssid="<your Wi-Fi network name>"
psk="<your Wi-Fi network password>"
}

Save this file in the root of the microSD card as wpa_supplicant.conf (be careful: unless you change the "Save as file type" dropdown to "All files (*.*)" on the Notepad save dialog, it will add .txt to the filename and your Pi won't be able to connect to your network. Close the file, create a text file with no contents and save it as ssh. Make sure it has no extension by again choosing "All files (*.*)" from the "Save as file type" dropdown.

Eject your microSD card, insert it into the Pi, connect to power and wait for it to boot. Its first boot may take a little longer than subsequent boots, so leave it a couple of minutes, then check your router's DHCP table to see when it's connected to the network. Make a note of its IP address, then open a Windows command prompt and type:

Swipe to scroll horizontally
ssh pi@192.168.1.88

If your Pi's IP address isn't 192.168.1.88, adapt the command as appropriate. When asked if you want to authorise the connection, type yes, press Enter and, when asked for a password, type raspberry and press Enter again.

Your first job is to change the default password, so type passwd and press Enter, then type in the existing password and your new password when requested. Make sure your Pi is running the most up-to-date software by entering these two commands, pressing Enter after each one.

Swipe to scroll horizontally
sudo apt-get updatesudo apt-get upgrade

Finally, it's time to install the Sense Hat software with:

Swipe to scroll horizontally
sudo apt-get install sense-hat

Next, to simplify the process of logging in remotely, set a meaningful hostname, which you can use in place of the IP address, so it won't matter if your router issues a different address in the future. Type the following and press Enter:

Swipe to scroll horizontally
sudo raspi-config

Key down to option 2 and press Enter, then select Hostname. Choose any name you like. We're running nine Pis on our network, each of which we've given a name beginning pi to differentiate them from PCs and Macs, so we've used pisense for this device. In future, we can connect to it from Windows command prompt with:

Swipe to scroll horizontally
ssh pi@pisense.local

You don't need to add .local when setting it up, but you'll need to use it with the ssh command.

Finally, let's enable VNC so we can interact with the GUI remotely, rather than working solely in the text-based environment. Return to the config homescreen and this time key down to Interfacing Options (the fifth entry). Press Enter, key down to VNC and press Enter again. Confirm that you want to enable it. When it finishes installing the VNC extensions, quit the config routine and let the Pi reboot.

While it's doing so, download VNC Viewer on your PC. When the Pi has finished booting, launch VNC Viewer and, in the address bar, type "pisense.local" (swapping "pisense" for whatever hostname you specified) and press Enter. Enter "pi" as the username, and whatever password you specified during the setup process to log in.

Interacting with Sense Hat

We're going to control the Sense Hat and read its data using Python, and we'll use cron to automate the collection at regular intervals and upload it to the server.

Click the raspberry on the taskbar and launch Geany Programmer's Editor from the Programming submenu. Enter the following code, which we'll explain in detail below:

Swipe to scroll horizontally
#!/usr/bin/env python3import timefrom datetime import datetimefrom sense_hat import SenseHat
sense = SenseHat()
sense.clear()now = datetime.now()
thedate = now.strftime("%Y-%m-%d")
thetime = now.strftime("%H:%M:%S")temp = sense.get_temperature()
pressure = sense.get_pressure()
humidity = sense.get_humidity()print(temp)
print(pressure)
print(humidity)if temp < 15:
	r = 0
	g = 0
	b = 255
	sense.clear((r, g, b))
elif temp > 30:
	r = 255
	g = 0
	b = 0
	sense.clear((r, g, b))import csv
thefile = "/home/pi/sense.csv"
thefields = [thedate, thetime, temp, pressure, humidity]
with open(thefile, "a") as fp:
	wr = csv.writer(fp, dialect="excel")
	wr.writerow(thefields)

It's important that you maintain left margin indents in the code as Python uses these to define subroutines. For example, the indented section between if temp and elif temp runs only if the condition specified on the if temp line is true, and Python knows that the point where the indent stops, at the elif line, is the end of that subroutine.

The code explained

The first few lines of code set up the operating environment. We import the time and date, which we'll write out with the data later on so that we know when each measurement was taken, we then connect to the Sense Hat itself with the following lines:

Swipe to scroll horizontally
from sense_hat import SenseHatsense = SenseHat()

The line that immediately follows this clears the matrix display so that if it's currently showing anything (later in the code, we'll turn it red for high temperatures and blue for low), it will be switched off until we can verify that the conditions remain true.

We then write the date and time to the variables thedate and thetime. Notice that we've formatted each variable in the brackets at the end of the line, and we've chosen to use year-month-date for the date as this makes the data easier to sort should we choose to import it into a spreadsheet. If you want to format the date differently, you can choose from a wide range of variables:

Swipe to scroll horizontally
%atruncated week dayMon, Tue, Wed
%Afull week dayMonday, Tuesday, Wednesday
%ddate to two digits01, 02, 03… 29, 30, 31
%mmonth to two digits01, 02, 03… 10, 11, 12
%btruncated monthJan, Feb, Mar
%Bfull monthJanuary, February, March
%yyear to two digits20, 21, 22
%Yyear to four digits2020, 2021, 2022

The same is true of recording the time. We have chosen to use the 24-hour clock, but you could swap our %H for %I to use the 12 hour clock, and append %p at the end of the string to add AM or PM as appropriate. For a full list of variables, check out W3 Schools' comprehensive explanation.

In the next section, we start interacting with the Sense Hat by reading data from its sensors. We're interested in temperature, pressure and humidity, each of which we will write to an appropriately named variable. The format in each case is very similar:

Swipe to scroll horizontally
temp = sense.get_temperature()
pressure = sense.get_pressure()
humidity = sense.get_humidity()

We'll then print the variables onscreen. After all, there's not much point gathering them if we're not going to display them.

Swipe to scroll horizontally
print(temp)print(pressure)print(humidity)

Now that the recordings are stored in variables, we can perform tests against them. What we're most interested in is the temperature value, so in the next section we check, first, whether the temperature is below 15˚C and, in the section below that, if it's above 3o˚C.

Swipe to scroll horizontally
if temp < 15:
	r = 0
	g = 0
	b = 255
	sense.clear((r, g, b))
elif temp > 30:
	r = 255
	g = 0
	b = 0
	sense.clear((r, g, b))

If either case is found to be true, we again set some variables. In the case of temperatures below 15˚C, we set the variable for b to 255 and zero out the variables r and g. If the temperature is above 30˚C, we set r to 255, and zero out g and b. It doesn't take a genius to work out that we're setting values for red, green and blue here, and that 255 relates to the intensity of each tone. Thus, if the temperature is below our minimum (15°C) we set full-on blue and, if it's above our maximum (30°C), we pick full red.

Now that we've specified our colours, we'll return to a command that we've already employed once in the code: sense.clear() . When we included it at the start of the code, we hadn't specified any colours within the brackets, so the effect was to turn off the LED array. Yet, by including the variables for r, g and b within the braces when we call it at this point, we'll instead illuminate the array with full blue if it's cold, and full red if it's hot. If the temperature sits between our specified minimum and maximum, neither of our test conditions will have been met, so no change will be made to the LED array.

As we turned it off at the start of the code, therefore, it will remain off if neither of the temperature tests results in a positive response. In this way, if the temperature was cold enough to have turned the array blue and we remedied it by turning on a heater in the greenhouse, we'd be able to see from inside the house that the temperature had risen sufficiently to protect our plants when the array extinguished itself.

Recording your observations

If all you want is a temperature-based traffic light system, you could stop at that point, but isn't it more interesting to keep a record of your readings so you can refer back? At present, the file we want to write to doesn't exist, so click the raspberry icon and pick Text Editor from the Accessories submenu. Without adding anything to the new file this creates, press Ctrl+S for the Save dialog, click Home in the sidebar and save the file as sense.csv. Close the sense.csv file and return to Geany Programmer's Editor.

The next section of code writes them to a comma-separated values (CSV) file, which we'll upload to a server using a separate routine.

Swipe to scroll horizontally
import csv
thefile = "/home/pi/sense.csv"
thefields = [thedate, thetime, temp, pressure, humidity]
with open(thefile, "a") as fp:
	wr = csv.writer(fp, dialect="excel")
	wr.writerow(thefields)

We start by telling Python that we're going to work with a CSV file, and specify that the file in question is called sense.csv, which is stored in our home directory. Having specified the name and location of the file we want to write to, we list the variables we want to write. As you can see, these are the date, time, temperature, pressure and humidity readings. Each variable will be separated by a comma, resulting in a file that can be opened and read just as easily in a plain text editor as it can in Excel.

The fourth line opens the file whose name is stored in the variable thetime, and the a switch tells Python that we want to append our data to the end of what already exists. The next two lines are indented as they tell the Pi what to do with the open file.

That's the end of the code. Press Ctrl+S to save it and call it sense.py. To run it manually, open a Terminal window by clicking the Terminal icon on the Pi's taskbar (or select Terminal from the Accessories menu), type python3 sense.py and hit Enter. You'll see the current temperature, pressure and humidity and, if either of the tests for low or high temperatures is satisfied, the LED array will light up blue or red. Behind the scenes, the measurements, plus the current date and time, will be added to the sense.csv file. You can check that this has happened by right-clicking the file and picking Text Editor from the menu to open it.

Upload and automation

The next step is to upload the CSV file to a web server so you can more easily interpret the readings. Luckily, this requires only two lines of code, so long as you have the FTP login details for your server. In Geany Programmer's Editor, create a new file and enter the following, replacing the sections in angled brackets as appropriate, while keeping everything that follows curl on a single line:

Swipe to scroll horizontally
#!/bin/bashcurl -T /home/pi/sense.csv ftp://<ftp server address>/sense.csv --user <ftp username>:<ftp password>

Save the file as upload.sh, again in the Home folder where it will sit alongside your existing sense.py and sense.csv files. If you want to check that you've got your FTP server address, username and password correct before relying on the code, type the curl line at the command prompt first and make sure the transfer completes successfully.

Of course, you don't want to have to manually invoke these two scripts every time you take a reading. Doing so would require you to log in to the Pi through the day and night using either ssh or VNC and, unless you accurately anticipated an extreme temperature, the traffic light system would be useless. So we'll use cron to automate the process. Shut Geany Programmer's Editor and return to the Terminal. Type the following and press Enter:

Swipe to scroll horizontally
sudo crontab -e

When asked which editor you'd like to use, press 1 for Nano. Key down to the bottom of the file, press Enter to move to a new line and type the following two lines of code:

Swipe to scroll horizontally
*/15 * * * * python3 /home/pi/sense.py2-59/15 * * * * sudo /home/pi/upload.sh

Press Ctrl+O to save the file (the "O" stands for "write out", not open), then Ctrl+X to exit Nano.

The first of these two lines runs your sense.py Python routine every 15 minutes – on the hour, at quarter past, half past and quarter to. The four stars that follow the */15 mean every hour, every day, every month and every day of the week. If you only wanted to run it every 15 minutes in December, you would instead use */15 * * 12 *. If you only wanted to run it on the first of the month, it would be */15 * 1 * *. If you wanted to run it every minute of every hour, regardless of the date, use * * * * *.

On the second line, we're again invoking something every 15 minutes. In this case, it's the script that uploads the CSV file to the server. However, you'll notice that the first part of the equation is more complicated. While we ran sense.py every 15 minutes starting from minute zero of each hour, we're running upload.sh at the same interval, but offset by two minutes – so, we're telling cron to start counting at two minutes past the hour and upload at two, 17, 32 and 47 minutes past the hour. Why? That way we can be sure the data collection routine has finished running before the upload routine kicks in.

At the server end

When you upload a CSV file to a web server and try to open it in a browser, most browsers will download the file rather than displaying it, which isn't ideal as you then need to open it in a spreadsheet. It's more convenient to render the results in a browser by incorporating them into a web page. Here, there's no point reinventing the wheel – and doing so would require several further pages of explanation – so credit to the Stack Overflow contributors who have already solved this problem.

The answer here reads a specified CSV file and drops the contents into a table. Ensure you change the filename on the second line, which begins $file, to sense.csv and, if necessary, include its path relative to the domain root. You could also edit the code to use

tags rather than table cells, and perhaps import Bootstrap within your header or footer to make it mobile-responsive.

Over time, your CSV will grow considerably (at present, we're adding four new lines every hour so it will grow by almost 100 lines a day). You might, therefore, want to periodically open the file in the Pi text editor and delete the contents, or use PHP to only display, say, the five most recent entries when parsing it on the server. In the latter instance, another Stack Overflow post is likely to provide the help you need.

Nik Rawlinson is a journalist with over 20 years of experience writing for and editing some of the UK’s biggest technology magazines. He spent seven years as editor of MacUser magazine and has written for titles as diverse as Good Housekeeping, Men's Fitness, and PC Pro.

Over the years Nik has written numerous reviews and guides for ITPro, particularly on Linux distros, Windows, and other operating systems. His expertise also includes best practices for cloud apps, communications systems, and migrating between software and services.