Build a Smart, Automated IoT Plant Irrigation System with Raspberry Pi and PubNub

As our technology becomes increasingly connected through the rise of IoT and cloud computing, we have not only benefited our lives but our planet as well. Smart farming has taken the world by storm as farmers have learned to embrace the world of IoT, not reject it. These farmers have been able to precisely monitor the conditions of their produce as well as control every milliliter of their resources.

Agri-Tech companies such as Illuminum Greenhouses have been able to solve the problem of a rapidly growing population’s need for sustainable food and farming:

“We construct affordable modern greenhouses and install automated drip irrigation kits for small holder farmers by using locally available materials and solar powered sensors.”

-Illuminum Greenhouses Kenya

As our world population is soon to reach 10 billion, we need to more greatly adopt technological, conservative methods for feeding the masses and IoT is the way to go.

We at PubNub have worked with numerous Agri-tech companies such as smart tractors, climate monitoring and many more. We have even written articles demonstrating how to utilize the power of cloud computing into smart agriculture.

As for inspiration for this project, we have created this demo project that demonstrates how our real-time data infrastructure can be used to create self-sustaining botanical systems.

What You’ll Need

Setup

Wiring

First and foremost, if you don’t have a dedicated 5V power supply, you can easily make one out of an old phone charging cable. Simply, snip the head off of the phone adapter and strip about 5 inches of the rubber casing to expose the wires.

The wires will be flimsy, so soldering some female header wires to the exposed phone cable wires will be a good idea.

It’d be a good idea to do this for the pump’s exposed wires as well.

If you’re worried about the silicon tubing not being an air-tight fit on the pump, you can always hot glue around the nosel to create a perfect seal.

Now you’re ready to start wiring up your components! Follow this diagram and make sure you plug in extra male-male wires to the female headers we soldered earlier to connect everything together.

Notice that we used a relay in our circuit as the Raspberry Pi can only safely tolerate input voltages under 3.3V. Therefore, we need to isolate our 5V power supply to avoid any damage to the Pi.

Hardware

Pump

The pump is a submersible water pump, meaning that it must be completely submerged in a water reservoir in order to pump water. Therefore, for this project, you will need to leave out a bowl (or any method for a reservoir) next to the plant at all times.

NOTE: It’s okay to get the wire tips wet as long as none of the water gets on the RPI!

DHT Sensor

The DHT11 temp/hum sensory is a standard sensor that does not need to be in any particular location, besides being in the same environment as your plant. Since temperature and humidity do not vary greatly within a 5 ft radius, it’s okay to just leave the sensor plugged into the RPI and not right next to the plant.

Soil Moisture Sensor

The soil moisture sensor is a standard moisture sensor that outputs a voltage when wet, and none when dry. You can adjust the sensitivity of the sensor with the potentiometer located on the sensor.

Let’s Get Coding!

Before you do anything, make sure you sign up for a free PubNub account so we don’t run into any problems down the road.

Step 1: Enable SSH

Secure Shell protocol (SSH) allows users to remotely access their Raspberry Pi terminals from their computers over wifi. If supplemented with GitHub, users can push code from their computers to a remote repository, SSH into the Pi terminal, and pull the code wirelessly. This allows for a quicker and smoother development process.

To enable SSH on your RPI, follow the simple and quick instructions.

Step 2: Download Device Libraries and Dependencies

While we may have 3 devices, we only need to read analog voltages from 2 of them. The pump only needs to operate on a GPIO switch. Therefore, we only need to pull dedicated device libraries for the Temp/Hum sensor and soil moisture sensor.

NOTE: Remember that these libraries must be installed onto the RPI by SSH-ing into your device and running the commands below.

DHT Temperature Humidity Sensor

In your project’s directory, clone and enter the sensor’s code repository.

git clone https://github.com/adafruit/Adafruit_Python_DHT.git
cd Adafruit_Python_DHT

Install some Python dependencies.

sudo apt-get upgrade  
sudo apt-get install build-essential python-dev

Now, install the sensor’s library.

sudo python setup.py install

This should compile the code for the library and install it on your device so any Python program can access the Adafruit_DHT python module.

If you want to quickly test the sensor to see if the sensor works enter these commands:

cd examples
sudo ./AdafruitDHT.py 2302 4

You should see a reading similar to this:

Raspberry Pi GPIO Library

For our submersible pump, we need to operate an output voltage of 1 or 0 from the RPI to turn it on. Conversely, the soil moisture sensor gives the RPI an input voltage 1 or 0, depending on whether it is wet or dry. Both of these functions require the use of the RPI’s GPIO pins.

Since we’ll be coding in Python, it will be useful to install the GPIO Zero library, which will allow us to make simple function calls to activate the pins.

sudo apt install python-gpiozero

Step 3: Build Your App with PubNub Functions

The full code for this project is located in this repository for reference: https://github.com/Cakhavan/IoT_Plant

First, let’s import our libraries and other dependencies to enable the devices and PubNub Functionality

#Device Libraries
import sys
import Adafruit_DHT
from gpiozero import LED, Button
from time import sleep

#PubNub 
import pubnub
from pubnub.pnconfiguration import PNConfiguration
from pubnub.pubnub import PubNub
from pubnub.callbacks import SubscribeCallback
from pubnub.enums import PNOperationType, PNStatusCategory

Next, we’ll need to initialize our sensors and pumps to their corresponding RPI pin numbers

#Pump is connected to GPIO4 as an LED
pump = LED(4)

#DHT Sensor (model type 22) is connected to GPIO17
sensor = 22
pin = 17

#Soil Moisture sensor is connected to GPIO14 as a button
soil = Button(14)

NOTICE: Since the pump will need to be sent an output voltage of on or off (like we would typically do with LEDs) we initialize it as an LED instance. Additionally, we initialized the soil moisture sensor as a button for it will send out a continuous input voltage of a 1 (like when holding down a button) when wet.

It will also be a good idea to set some initial conditions for when the program first boots up: a flag variable to toggle the state of the program to Auto mode or Manual mode and turning the pump off when the program boots up.

#flag variable to toggle between Auto and Manual mode
flag = 1

#always make sure the program starts with the pump turned off (conventions are backwards for the pump i.e. .on()=='off' and .off()=='on')
pump.on()

In order to make sure that our IoT Plant can communicate with a client, we need to enable PubNub’s 2-way pub-sub capability. To make sure we can receive messages we need to add a listener to handle the incoming messages and declare a subscription to listen in on a particular channel.

class MySubscribeCallback(SubscribeCallback):
    def status(self, pubnub, status):
        pass
        # The status object returned is always related to subscribe but could contain
        # information about subscribe, heartbeat, or errors
        # use the operationType to switch on different options
        if status.operation == PNOperationType.PNSubscribeOperation \
                or status.operation == PNOperationType.PNUnsubscribeOperation:
            if status.category == PNStatusCategory.PNConnectedCategory:
                pass
                # This is expected for a subscribe, this means there is no error or issue whatsoever
            elif status.category == PNStatusCategory.PNReconnectedCategory:
                pass
                # This usually occurs if subscribe temporarily fails but reconnects. This means
                # there was an error but there is no longer any issue
            elif status.category == PNStatusCategory.PNDisconnectedCategory:
                pass
                # This is the expected category for an unsubscribe. This means there
                # was no error in unsubscribing from everything
            elif status.category == PNStatusCategory.PNUnexpectedDisconnectCategory:
                pass
                # This is usually an issue with the internet connection, this is an error, handle
                # appropriately retry will be called automatically
            elif status.category == PNStatusCategory.PNAccessDeniedCategory:
                pass
                # This means that PAM does allow this client to subscribe to this
                # channel and channel group configuration. This is another explicit error
            else:
                pass
                # This is usually an issue with the internet connection, this is an error, handle appropriately
                # retry will be called automatically
        elif status.operation == PNOperationType.PNSubscribeOperation:
            # Heartbeat operations can in fact have errors, so it is important to check first for an error.
            # For more information on how to configure heartbeat notifications through the status
            # PNObjectEventListener callback, consult <link to the PNCONFIGURATION heartbeart config>
            if status.is_error():
                pass
                # There was an error with the heartbeat operation, handle here
            else:
                pass
                # Heartbeat operation was successful
        else:
            pass
            # Encountered unknown status type
 
    def presence(self, pubnub, presence):
        pass  # handle incoming presence data
 
    def message(self, pubnub, message):
        if message.message == 'ON':
        global flag
        flag = 1
        elif message.message == 'OFF':
      global flag
      flag = 0
        elif message.message == 'WATER':
        pump.off()
        sleep(5)
        pump.on()
 
 
pubnub.add_listener(MySubscribeCallback())
pubnub.subscribe().channels('ch1').execute()

Then, to enable sending out messages, we declare a publisher callback

def publish_callback(result, status):
  pass

Now, we need a method to check whether our sensor is wet or dry. Since we declared our soil sensor as a button, the function call “is_held” just means that we should check to see when the soil sensor is outputting a continuous “1” (is dry).

def get_status():
  #Soil sensor acts as button. is_held == sensor outputting a 1 for dryness
  if soil.is_held: 
    print("dry")
    return True
  else:
    print("wet")
    return False

Next, let’s implement the meat of our program. We’re going to need the RPI continuously polling to check sensor data, so let’s implement a continuous while loop:

while True:

We now need to set state conditions that check whether or not the flag variable is a 1 or a 0 (aka in auto mode or manual mode)

if flag == 1:
     #to be implemented
elif flag == 0:
     #to be implemented

Just like we did before, let’s grab data from the DHT sensor and also print it to the terminal

if flag == 1:    
     # Try to grab a sensor reading.  Use the read_retry method which will retry up
     # to 15 times to get a sensor reading (waiting 2 seconds between each retry).
     humidity, temperature = Adafruit_DHT.read_retry(sensor, pin)
     DHT_Read = ('Temp={0:0.1f}*  Humidity={1:0.1f}%'.format(temperature, humidity))
     print(DHT_Read)

Now we can publish that data to PubNub for future IoT use. We’re going to publish on a specific channel so let’s publish to something called “ch2” since “ch1” is reserved for listening for the flag variable.

pubnub.publish().channel('ch2').message([DHT_Read]).async(publish_callback)

Then check the status of the soil sensor and turn the pump on or off accordingly

wet = get_status()

if wet == True:
    print("turning on")
    pump.off()
    sleep(5)
    print("pump turning off")
    pump.on()
    sleep(1)
else:
    pump.on()

sleep(1)

NOTE: Remember the backwards convention of the .on() and .off() method call

Lastly, let’s implement the manual mode elif condition, which is just making sure the pump is always off

elif flag == 0:
  pump.on()
  sleep(3)

Step 4: How to Upload Your Code

Like we discussed in the previous section on the use of Git, you should always commit your code to your repository and then pull it to your Raspberry Pi. The process always follows this order

1.) Save your code on your computer

2.) Commit it to the cloud using these commands

git add .
git commit -m "updates"
git push

3.) SSH into your RPI and cd into the proper directory

ssh pi@<YOUR IP ADDRESS>
cd <your directory>

4.) Pull the code from the cloud and run it

git pull
python Plant.py

Step 5: Create a Client

We’re now going to create a front-end HTML page to interact with our IoT plant and later visualize its data in step 6. We will build something that looks like this.

First, let’s start with the 3 buttons that will put the Plant in either the auto state, manual mode state, or water plant state. This will be done by having each button publish a message of which the plant will change its flag variable accordingly.

Let’s first give our page a title

<!DOCTYPE html>
<html>
  <body>

            <h1>IoT Plant</h1>

  </body>
</html>

In the body of your HTML file, insert 3 buttons that publish messages with the PubNub SDK like so

<button type="button" onclick="publish('ON')">Auto Mode ON</button>
<button type="button" onclick="publish('OFF')">Auto Mode OFF</button>
<button type="button" onclick="publish('WATER')">Water Plant</button>

Below those buttons, let’s have our website be continuously updating the screen with the exact temperature humidity data published by the “ch2” channel declared in step 4. In order to do this, we give an id named “sensorData” to a <p> tag so that the program can later inject the content of the <p> tag, using the “getElementByID” and “changeInnerElement” function calls. You’ll see what I’m talking about in a moment.

<p id="sensorData">Temperature and Humidity Data</p>

Now let’s import the PubNub JavaScript SDK in order to actually use that “publish” method and other methods.

<script src="https://cdn.pubnub.com/sdk/javascript/pubnub.4.20.2.js"></script>

Then instantiate a PubNub instance with your API keys

var pubnub = new PubNub({
  publishKey : 'YOUR PUB KEY',
  subscribeKey : 'YOUR SUB KEY',
  });

Then create a publishing call back to publish the button data to the RPI. Notice how we reserved “ch1” for button data and “ch2” for RPI data.

function publish(a){
  var publishConfig = 
    {
      channel : "ch1",
      message : a
    };

    pubnub.publish(publishConfig, function(status, response){
      console.log(status, response);
    });
      
  }

Next, we instantiate a listener and subscriber to get the RPI sensor data. Notice the line that uses the “getElementById” and “innerHTML” function calls introduced earlier. This allows us to take any PubNub message and update any variable on the screen with its respective id tags. This is great for real-time IoT dashboards!

pubnub.addListener({
    message: function(m) {
        // handle message
        var channelName = m.channel; // The channel for which the message belongs
        var channelGroup = m.subscription; // The channel group or wildcard subscription match (if exists)
        var pubTT = m.timetoken; // Publish timetoken
        var msg = m.message; // The Payload
        var publisher = m.publisher; //The Publisher

        //inject the sensor data msg into the sensorData tag declared earlier for real-time updates
        document.getElementById("sensorData").innerHTML = msg;
        console.log(msg)
    },
    presence: function(p) {
        // handle presence
        var action = p.action; // Can be join, leave, state-change or timeout
        var channelName = p.channel; // The channel for which the message belongs
        var occupancy = p.occupancy; // No. of users connected with the channel
        var state = p.state; // User State
        var channelGroup = p.subscription; //  The channel group or wildcard subscription match (if exists)
        var publishTime = p.timestamp; // Publish timetoken
        var timetoken = p.timetoken;  // Current timetoken
        var uuid = p.uuid; // UUIDs of users who are connected with the channel
    },
    status: function(s) {
        var affectedChannelGroups = s.affectedChannelGroups;
        var affectedChannels = s.affectedChannels;
        var category = s.category;
        var operation = s.operation;
    }
});

pubnub.subscribe({
    channels: ['ch2'],
    
});

Step 6: Graph Your Incoming Data with PubNub EON

Now comes the flashiest and coolest part of our project: EON! PubNub EON allows users to easily render in a real-time data graph with the drop of a script tag.

By incorporating EON into our project, we’re going to get something that looks like this

HTML Setup

Since at this point you most likely have your HTML page opened up, let’s start implementing EON there first.

To add PubNub EON to any HTML page, simply add this div tag in your body, which will inject the actual chart

<div id="chart"></div>

Then import the script tag SDK

<script src="https://pubnub.github.io/eon/v/eon/1.0.0/eon.js"></script>
<link rel="stylesheet" href="https://pubnub.github.io/eon/v/eon/1.0.0/eon.css"/>

Then subscribe to the eon-chart channel that you will be posting data to

eon.chart({
        channels: ['eon-chart'],
        history: true,
        flow: true,
        pubnub: pubnub,
        generate: {
          bindto: '#chart',
          data: {
            labels: false
          }
        }
      });

And that’s it! It’s as simple as that!

Now, let’s implement the last step, which is formatting the sensor’s publisher so EON knows how to graph the data.

Python Setup

This part is even easier. It’s just two lines!

Go back to your Plant.py python code. Now, to publish correctly in python to the eon-chart API correctly, you need to create a dictionary of this type

dictionary = {"eon": {"DATA NAME": DATA, "DATA 2 NAME": DATA 2 etc...}}

So in our case, our dictionary would look like this

dictionary = {"eon": {"Temperature": temperature, "Humidity": humidity}}

Paste this in right above the line that publishes our sensor data to ch2.

Now we need to publish this dictionary to a channel that our graph is going to read from. In our case, the eon-chart channel.

pubnub.publish().channel("eon-chart").message(dictionary).async(publish_callback)

And there you go! You have successfully created a self-sustaining system for preserving life FOREVER…..until you need to refill the reservoir.

Try PubNub Today