In the first part of this tutorial we looked at each of the connections available on the board (and if using the bare bones version, soldered everything in place). Now that we have everything ready for power we can work on the software / coding side of the project. In part two of this tutorial we will boot up the Raspberry Pi, do some initial configuration of the Raspbian operating system, install Python libraries for a few of the sensors, and write some basic Python code to trigger each solenoid and read and display the data from each of the sensors. The Python code will be quite long, but we have broken it down into smaller steps. As always, if you have any questions, feel free to post at the bottom of the page.
Operating System:
Before we get started, you should have a microSD card pre-installed with Raspbian. We are using the installation image dated June 27, 2018, but all versions up to “Bullseye” in 2023 should work the same. Using a version newer than this will require additional steps during the installation process – if you are wanting to use the most current version of the Raspberry Pi OS, Pleas see our guide: Python Virtual Environments
In addition to a Raspberry Pi, this tutorial will be requiring a few additional parts:
Optionally, these items may also be required depending on how you plan to set your irrigation system up:
Going forwards there will also be tools and other components required – we will discuss these throughout the next 3 parts of this tutorial.
We are going to start by inserting the microSD card with our operating system pre-installed into the Pi. Your keyboard, mouse and monitor should also be connected at this time. Power up the Pi by plugging in the Power Supply. Once the Pi has done its initial boot of the operating system you should arrive at a desktop. A dialogue should appear for initial configuration – we will take care of this in the next step.
As of this version of Raspbian, most initial setup can be done by following the dialogue that pops up on first boot up. However, we do need to set up a few additional things once this is initial configuration is completed. Follow through the initial setup, if prompted to reboot – go ahead and do that as well.
Once all of that has been completed, we can go ahead and configure a few more things. Click the Raspberry Logo in the top left corner, scroll down to Preferences, and select “Raspberry Pi Configuration”.
On the first tab you can change your overscan settings. If you are using a computer monitor, you may want to disable the overscan setting to remove the black bars on the sides of the screen.
On the second tab we are going to want to enable the I2C and 1-Wire interfaces. These are the two interfaces our sensors are using in this project.
If prompted to reboot, click yes. Once the Pi boots back up we can proceed.
Before we can install any of the libraries or make any of the sensors work we need internet connectivity. If you are using WiFi, this was probably already set up during the initial configuration. If it isn’t working, or you skipped that step, it can be accessed by clicking the connectivity logo (located beside the Volume control in the top right corner of the screen).
Ethernet is as simple as plugging it into the Pi – if you are planning to use ethernet, go ahead and plug this in if you haven’t already.
Now that all of the basic configuration is done, we can get on with installing the software libraries needed to read each of the sensors. These libraries of code are going to take a lot of the hard work out of reading the sensors. We are going to install these using the “Terminal” , in the bar at the top of the screen, click the black square logo, and this window should pop up:
The first item to install is the Adafruit Python GPIO Library. This package is the basis for several others that we will be using. To install it we will type a series of commands into the terminal window we just opened up.
Run each of these commands (in order!) by typing them in and hitting enter:
sudo apt-get update
sudo pip3 install --upgrade setuptools
pip3 install RPI.GPIO
pip3 install adafruit-blinka
Next, we are going to set up the ADS1015 Analog to Digital chip. Adafruit has built an easy to use library for this series of Analog to Digital converters. We will use this to read the wind direction. To install the library, type the following command into the Terminal:
sudo pip3 install adafruit-circuitpython-ads1x15
Now it is time to set up the DS18B20 temperature sensor. The setup for this sensor is a bit different; the past two sensors both used the I2C bus, the DS18B20 uses 1-Wire. Enter the following commands in the terminal:
cd
sudo modprobe w1-gpio
sudo modprobe w1-therm
cd /sys/bus/w1/devices
ls
Since the temperature sensor will be listed in the devices folder, we will want to see everything in that folder. The last command “ls” will display the contents of the folder in the window. Our DS18B20 shows up with an address of 28-0316853d8fff – but each sensor has a unique ID so your number will not match the image!
Time to get a reading from the DS18B20 sensor! Enter the following command with your sensor ID in place of *sensor ID*
cd *sensor ID*
Now we can pull a temperature from it by entering the following command:
cat w1_slave
Not as easy to read as the BME280, but the information should be there. The temperature is located in the second line, take the number given and divide by 1000 to get the temperature in degrees Celsius.
Since the DS18B20 isn’t the easiest sensor to read directly in Python, we will be installing one last library to make it a little more straight forward. In the terminal type the following commands:
cd
sudo pip3 install w1thermsensor
This will make things quite a bit easier once we start writing some code.
Now we are getting somewhere! All of the complex sensors are sending back data so lets write some basic python code to poll each of the sensors and report back every 15 seconds. We aren’t going for award winning code here, just something easy to understand for those relatively new to Python.
We want to program in Python 3 so type the following command in the terminal and hit enter:
idle3
Alternatively, Python 3 can be found by clicking the Raspberry Button in the top left. Under programming you will find a shortcut. Either way, A new window should have opened for the Python 3 Shell. We are now done with the terminal window so it can be minimized or moved out of the way (but don’t close it!).
In the Python Shell window we just opened, click “File” and “New File” to start a new Python file. This will open another window that we will write all of our code in.
Programing a Raspberry Pi to read sensors and control outputs can be done any number of ways. We are going to try and keep this as easy as possible using plain Python code and a simplistic program. But before we get into that, lets write some basic code to test read the sensors and try out our solenoid drivers.
Before we can read any of the sensors, we will need to create the basic framework of the program. This means we need to import the time library, create our loop, and set the interval we want to pause the code each time the loop has completed. For those unfamiliar with Python, note that the white spaces (tabs and spaces) are very important in this language so be sure to format exactly as shown in the example.
import time
interval = 5 #How long we want to wait between loops (seconds)
while True:
time.sleep(interval)
Libraries are added to the project using the import command. On the first line we are importing the time library. On line 3 we define a variable to store the length of time in seconds that we want to sleep the program during each loop of the code. Next, we want to create the loop itself – in Python this can be done in a variety of ways; “While true:” works well. Note that when you press “Enter” the next line becomes indented. Finally, on line 7 we “sleep” or pause our program using the variable we set above.
Note: Because we are going to be adding a lot of code over the next few steps, each new line we add will be highlighted in the example code and the line number will correspond to notes below the code.
During the set up we installed a bunch of libraries to make this program a lot easier to write. We are going to use one of these libraries right now to read the DS18B20 temperature sensor. In these next bits of code we are going to import the DS18B20 library, set up the sensor, read the sensor, and print the result.
import time
from w1thermsensor import W1ThermSensor
ds18b20 = W1ThermSensor()
interval = 5 #How long we want to wait between loops (seconds)
while True:
time.sleep(interval)
#Pull Temperature from DS18B20
temperature = ds18b20.get_temperature()
#Print the results
print( ‘Temperature: ‘ , temperature)
Let’s try it out – press “F5” and it should prompt you to save it. After saving it should run, and in the Python Shell window you should see the temperature appear after a 15 second wait. It will continue to post an updated temperature every 15 seconds. This can be stopped by pressing “Ctrl + C”.
If you are using one of the SparkFun Soil Moisture Sensors (or any other analog sensor) reading the ADC will be the next step. Again, we are going to import the library, set up the sensor, and read the results. Unlike all of the past sensors, the ADC does not simply spit out exactly what we want to know. Instead, it gives us a number relative to the sensor reading that we can then interpret. For the soil moisture sensor, the higher the number we receive, the greater the moisture content. With many analog sensors, it will be up to the end user to determine how that relates to what they are measuring. This may require tweaking, and we will address this later when writing the control code. For now, we will just spit out the data and see what it says!
import time
from w1thermsensor import W1ThermSensor
import board
import busio
i2c = busio.I2C(board.SCL, board.SDA)
import adafruit_ads1x15.ads1015 as ADS
from adafruit_ads1x15.analog_in import AnalogIn
ds18b20 = W1ThermSensor()
ads = ADS.ADS1015(i2c)
ads.gain = 1
interval = 5 #How long we want to wait between loops (seconds)
while True:
time.sleep(interval)
#Pull Temperature from DS18B20
temperature = ds18b20.get_temperature()
#Measure Analog Input 0
chan = AnalogIn(ads, ADS.P0) #ADS.P1 , P2, P3 for channels 1, 2, 3
val = chan.value #Pull the raw ADC data from Channel 0
#Print the results
print( ‘Temperature: ‘ , temperature)
print( ‘Soil Moisture: ‘ , val)
print( ‘ ‘)
This sensor is a simple digital input on the Pi, this doesn’t have a fancy chip to read or anything of the sort. The flow sensor is effectively a magnet and a small magnetic switch. As the sensor spins the magnet moves past a switch, causing it to close. Each revolution represents approximately 2.25mL of fluid so we just need to count how many times this switch it is opening and closing over a period of time to figure out how much water has flowed through.
The Pi has an easy way of monitoring this sort of thing. Even though we have paused the entire program for a period of 5 seconds, we can create a background process that will still watch for changes in this pin and keep count while the main code is paused.
So lets get started: First we need to import the Raspberry Pi GPIO library, set up the pin we are using, and create the background process that monitors the pin. We will then calculate the windspeed and, finally, print the result.
import time
from w1thermsensor import W1ThermSensor
import board
import busio
i2c = busio.I2C(board.SCL, board.SDA)
import adafruit_ads1x15.ads1015 as ADS
from adafruit_ads1x15.analog_in import AnalogIn
import RPi.GPIO as GPIO
ds18b20 = W1ThermSensor()
ads = ADS.ADS1015(i2c)
ads.gain = 1
interval = 5 #How long we want to wait between loops (seconds)
waterTick = 0 #Used to count the number of times the flow input is triggered
#Set GPIO pins to use BCM pin numbers
GPIO.setmode(GPIO.BCM)
#Set digital pin 24 to an input
GPIO.setup(24, GPIO.IN)
#Event to detect flow (1 tick per revolution)
GPIO.add_event_detect(24, GPIO.FALLING)
def flowtrig(self):
global waterTick
waterTick += 1
GPIO.add_event_callback(24, flowtrig)
while True:
time.sleep(interval)
#Pull Temperature from DS18B20
temperature = ds18b20.get_temperature()
#Measure Analog Input 0
chan = AnalogIn(ads, ADS.P0) #ADS.P1 , P2, P3 for channels 1, 2, 3
val = chan.value #Pull the raw ADC data from Channel 0
waterFlow = waterTick * 2.25
waterTick = 0
#Print the results
print( ‘Temperature: ‘ , temperature)
print( ‘Soil Moisture: ‘ , val)
print( ‘Flow Rate: ‘ , waterFlow)
print( ‘ ‘)
Once again, feel free to test the code again with the “F5” key. Blowing into the liquid flow sensor will make it spin and should give you a reading!
Each of the solenoids are controlled by a MOSFET , which is in turn, controlled by a GPIO pin from the Raspberry Pi. Turning on these solenoids is as easy as turning on a digital output. We have 6 driver circuits here so, for now, we will just turn each one on for a half second to make sure everything is working.
import time
from w1thermsensor import W1ThermSensor
import board
import busio
i2c = busio.I2C(board.SCL, board.SDA)
import adafruit_ads1x15.ads1015 as ADS
from adafruit_ads1x15.analog_in import AnalogIn
import RPi.GPIO as GPIO
ds18b20 = W1ThermSensor()
ads = ADS.ADS1015(i2c)
ads.gain = 1
interval = 5 #How long we want to wait between loops (seconds)
waterTick = 0 #Used to count the number of times the flow input is triggered
#Assign channels to variables to keep track of them easier (these BCM pin numbers were listed in part 1 of the tutorial)
s1 = 13
s2 = 16
s3 = 19
s4 = 20
s5 = 26
s6 = 21
#Set GPIO pins to use BCM pin numbers
GPIO.setmode(GPIO.BCM)
#Set digital pin 24 to an input
GPIO.setup(24, GPIO.IN)
#Set solenoid driver pins to outputs:
GPIO.setup(s1, GPIO.OUT) #set Solenoid 1 output
GPIO.setup(s2, GPIO.OUT) #set Solenoid 2 output
GPIO.setup(s3, GPIO.OUT) #set Solenoid 3 output
GPIO.setup(s4, GPIO.OUT) #set Solenoid 4 output
GPIO.setup(s5, GPIO.OUT) #set Solenoid 5 output
GPIO.setup(s6, GPIO.OUT) #set Solenoid 6 output
#Event to detect flow (1 tick per revolution)
GPIO.add_event_detect(24, GPIO.FALLING)
def flowtrig(self):
global waterTick
waterTick += 1
GPIO.add_event_callback(24, flowtrig)
while True:
time.sleep(interval)
#Pull Temperature from DS18B20
temperature = ds18b20.get_temperature()
#Measure Analog Input 0
chan = AnalogIn(ads, ADS.P0) #ADS.P1 , P2, P3 for channels 1, 2, 3
val = chan.value #Pull the raw ADC data from Channel 0
waterFlow = waterTick * 2.25
waterTick = 0
#Test Solenoids by turning each on for a half second
GPIO.output(s1, GPIO.HIGH) #turn solenoid 1 on
time.sleep(0.5) #Wait for a half second
GPIO.output(s1, GPIO.LOW) #turn solenoid 1 off
time.sleep(0.5)
GPIO.output(s2, GPIO.HIGH) #turn solenoid 2 on
time.sleep(0.5) #Wait for a half second
GPIO.output(s2, GPIO.LOW) #turn solenoid 2 off
time.sleep(0.5)
GPIO.output(s3, GPIO.HIGH) #turn solenoid 3 on
time.sleep(0.5) #Wait for a half second
GPIO.output(s3, GPIO.LOW) #turn solenoid 3 off
time.sleep(0.5)
GPIO.output(s4, GPIO.HIGH) #turn solenoid 4 on
time.sleep(0.5) #Wait for a half second
GPIO.output(s4, GPIO.LOW) #turn solenoid 4 off
time.sleep(0.5)
GPIO.output(s5, GPIO.HIGH) #turn solenoid 5 on
time.sleep(0.5) #Wait for a half second
GPIO.output(s5, GPIO.LOW) #turn solenoid 5 off
time.sleep(0.5)
GPIO.output(s6, GPIO.HIGH) #turn solenoid 6 on
time.sleep(0.5) #Wait for a half second
GPIO.output(s6, GPIO.LOW) #turn solenoid 6 off
time.sleep(0.5)
#Print the results
print( ‘Temperature: ‘ , temperature)
print( ‘Soil Moisture: ‘ , val)
print( ‘Flow Rate: ‘ , waterFlow)
print( ‘ ‘)
Once again, feel free to test the code with the “F5” key. If you have your 12V solenoid power source connected to the 12V power terminal on the board, as each solenoid triggers a red LED will light up!
Alright, now we have confirmed everything is working and we have written enough code to control / read each of the different functions available from the board. In Part 3 of this tutorial series we will be making this much more useful. We will take what we learned and turning it into a program that will trigger the solenoids at pre-defined times for pre-defined durations based on a schedule. We will also send an email notification when it has started, and once again when it has completed. Ready to take that on? Part 3 is right here!
9 thoughts on “Raspberry Pi Irrigation Control – Part 2”
Len Wells
any Idea when you will drop the 3rd installment.
William @ BC Robotics
Looking like we will have it ready in the next week or so 🙂
Al Lowenstein
using the sample program I’m trying to get my soil moisture sensor to work. I’m getting the following errors
ValueError: No Hardware I2C on (scl,sda)=(3, 2)
Valid I2C ports: ((3, 3, 2), (1, 3, 2), (0, 1, 0))
I have the sensor in the second position as described in part one (I think that is Port 1, I have tried P0, P1 – P2 and P3 for kicks. Anything obvious?
Al Lowenstein
Resolved. IC2 was not enabled on the Raspberry Pi
Joe
I have not received my solenoids yet, so I’m also testing the voltage of S1 – S6 with a multimeter while turning them on. While on I keep getting ~5vdc , shouldn’t it be 12v for a 12v solenoid? What am I missing here?
Joe
So when powering the PI with a 5v adapter the solenoid connectors will receive ~ 5 v. When I plugged in the 12v power the solenoids now get the full 12v. Now my question is, can I somehow power some things as 5v and some things as 12v out of the solenoid connectors? I’m guessing no, but it would be cool.
William @ BC Robotics
Hi Joe,
It is possible to use a different voltage on different inputs but the wiring will be a little more involved. Since we are switching the ground of the solenoid you can provide power directly to each solenoid from a different power supply, or groups (rather than the “+” pin on each channel screw terminal). As long as the grounds are all tied together for the power supplies, and all those grounds are tied to the GND pin of the power input terminal on the left side of the board, you can switch several different voltages.
W Saewyc
IrrigationHAT v2 no longer has the 2pin 3.5mm screw terminal for power on the left side just south of the digital connector. There is now a Qwiic terminal labeled 3.3V STEMMA QT in this location, which is a 4-pin I2C connector using 1.0 mm pin spacing. Presumably this connector can be used to power the Raspberry Pi, although I am unsure (yet) to what it could be connected.
The 12v power 2 pin 3.5 is now located in the lower row of 2 pin 3.5mm screw terminals; it is the leftmost terminal. I am not certain which power supply is required, but I believe I saw 12v 5a somewhere in my research on this today.
It would be nice if the tutorial were updated, includied the 12v power supply in the list of required parts, as well as whatever parts and steps for the optional STEMMA QT port to power the RPi.
William @ BC Robotics
Thanks for the feedback! We are in the process of updating the tutorial. Please note that the updated version of the board is functionally identical and has the same requirements as the previous version. Updates are mainly to simplify manufacturing and simplify wiring inside an enclosure. We have also added the Qwiic sensor port as a convenience.
12V power is provided to the board via the same connector (now located at the bottom left). Current requirements will depend on what solenoids you are using / how many are on at a given time. Qwiic is an I2C connection standard for sensors and other breakout boards. We add this connection point to all new boards as a convenience. It provides 3.3V power to sensors/boards using the Qwiic bus. It is not used in the tutorial, but is available if you wish to expand your project. If you would like to learn more about Qwiic, please see this guide https://www.sparkfun.com/qwiic