1. Home
  2. Docs
  3. Device Kits
  4. Soil Moisture Sensing Using a Raspberry Pi and Avimesa.Live

Soil Moisture Sensing Using a Raspberry Pi and Avimesa.Live

In this Device Kit, we’ll be attaching a soil moisture sensor to a Raspberry Pi and sending its output to Avimesa.Live for further processing. This Device Kit will also introduce you to Avimesa Gadget and display to you how simple it can make almost any IoT project when using Avimesa as your IoT provider.

alt text

To successfully set up this Device Kit, a rudimentary knowledge of electronics, the Linux command line interface (CLI), and the Python (3) programming language is (probably) required. To connect our soil moisture sensor to the Raspberry Pi and make use of its analog output, we’ll be using a Mouser MCP3008-I/P ADC (analog-to-digital converter). We should note that the MCP3008-I/P uses SPI for serial communication to its controller (in our case a Raspberry Pi). On the Raspberry Pi, we’ll be using the Python programming language to drive (or control) Gadget as well as to read our soil moisture sensor’s output. To be more specific, we’ll make extensive use of two Python modules: the standard library subprocess module and the third party gpiozero module to, respectively, drive Gadget and deal with the Raspberry Pi’s peripherals (in our case, GPIO and SPI). The gpiozero module is the recommended Python module for controlling the Raspberry Pi’s peripherals.

Components

(1) From the CLI, run sudo raspi-config and enable SPI via Interfacing Options > P4 SPI > Yes. Next, reboot: sudo reboot. Finally, verify /dev/spidev0.0 is present in the filesystem after the reboot. If you run into any issues, please visit this page for help troubleshooting.

(2) If you need help flashing an SD card with a Raspbian image, please visit here.

(3) To install the Python module gpiozero, you’ll first want to make sure your repository list is up to date by running sudo apt update. Second, install the module for Python (3) using the following command: sudo apt install python3-gpiozero. If you run into any issues, please visit here for help troubleshooting.

Hardware Setup

First, let’s wire up our MCP3008. Following is the pinout for the MCP3008, obtained here, and the associated Raspberry Pi (PX) and analog sensor (AX) pin connections: alt text NOTE: The Raspberry Pi pin connections are from the Raspberry Pi 40 pinout header (Not the physical Broadcom connections). This link contains a great representation of the Raspberry Pi 40 pinout header and the associated Broadcom connections.

The moisture sensor’s connections are simple: connect the Raspberry Pi pins P0 (3V3) and P6 (GND) to the specified (+) and (-) pins respectively and connect the analog output to A0 of the MCP3008.

Sensor Calibration

Most analog sensors require calibration and the SparkFun Electronics soil moisture sensor is definitely one of those sensors. Our goal here is to determine what outputs from the soil moisture sensor signal wet (saturated) dirt and dry (unsaturated) dirt.

First, we’ll need something to read our sensor’s output. We can use the following Python script to obtain the values described previously:

from time import sleep
from gpiozero import MCP3008, DigitalOutputDevice

def get_soil_moisture():
    adc = MCP3008(channel=0) # SPI wrapper for ADC chip
    r_val = []
    for i in range(10):      # 10 readings 5 milliseconds apart
        sleep(.005)
        r_val.append(adc.value)
    adc.close()
    return r_val[4]          # median reading

sensor_power = DigitalOutputDevice(17)

while True:
    try:
        sensor_power.on()
        raw_sense_read = get_soil_moisture()
        print('Calibration reading: {0}'.format(raw_sense_read),
              flush=True)
        sensor_power.off()
        sleep(5)

    except KeyboardInterrupt:
        break

sensor_power.close()

You’ll want to copy this into something like calibrtion.py on the Pi and run it using python3 calibration.py. You’ll want to have this script running in a terminal connected to your Pi whenever collecting calibration values and, whenever finished, you can kill it with ctrl + c. NOTE: This step will also test our wiring of the sensor and ADC – if you’re not getting values similar to mine, your setup may be incorrect and will require some investigation.

If your setup is correct, let’s get a maximum output for our sensor by steadily holding the soil moisture sensor in some water to get some readings. alt text

This sensor forms an electrical connection whenever there is damp soil, and definitely water, in between its two leads (meant for insertion into some dirt). You may wonder why I’m not using dirt: heavily saturated (wet) dirt and water give almost, if not exactly, the same output values. Whenever the sensor is in (highly conductive) water, we should see something like Calibration reading: 0.809380 coming from our running Python script. It should be stated here that the gpiozero module’s MCP3008 class, a SPI wrapper tailored for an MCP3008 chip, returns ADC readings via its member value that range from 0.0 to 1.0 (lowest to highest). We now know what to expect from our soil moisture sensor in terms of maximum output values.

Now for the tedious portion of this sensor’s calibration: determining its minimum output (output whenever inserted into dry dirt – think almost dust). To determine this value, I took some of the soil my plant resides in and let it sit in the sun for most of the sunny hours of a day. After this, I put my dried dirt into a container and inserted the soil moisture sensor for some calibration readings via python3 calibration.py: alt text

Script output: Calibration reading: 0.533799.

Now let’s use our two calibration values to determine what we’re truly after: the equation of the line for our linear sensor. From algebra:

y = m * x + b

We’re going to calculate m and b; x is our sensor output and y is our soil moisture saturation. We have two data points: (0.53, 0.0) and (0.81, 100.0) which are respectively (x1, y1) and (x2, y2). Now, let’s calculate m:

m = (y2 - y1) / (x2 - x1)

And now b using the data point (x1, y1):

b = y1 - m * x1

My values are m = 357.0 (rounding) and b = -189.0. So, my equation is:

y = 357.0 * x - 189.0

Yours will most likely be a little different; note it as we’ll be using it in a later section.

Avimesa.Live Setup

First, you’ll need an active Avimesa.Live account.

Second, you’ll need to obtain a valid set of Avimesa device credentials:

Software

The script:

#!/usr/bin/python3

import subprocess

from time import sleep
from gpiozero import MCP3008, DigitalOutputDevice

def get_soil_moisture():
    # SPI wrapper for ADC chip
    adc = MCP3008(channel=0)

    # 10 readings 5 milliseconds apart
    r_val = []
    for i in range(10):
        sleep(.005)
        r_val.append(adc.value)

    adc.close()

    # median reading
    r_val.sort()
    r_val = r_val[4]

    # equation of the line for our sensor
    r_val = 357.0 * r_val - 189.0 # CHANGE ME

    return r_val

# Use your own Avimesa credentials here
gdgt_args = ['/usr/local/bin/avmsagadget',
             '-i',
             'deadbeefdeadbeefdeadbeefdeadbeef', # DEVICE ID
             'deadbeefdeadbeefdeadbeefdeadbeef'] # DEVICE AUTHENTICATION KEY
gdgt_timeout = 5

# GPIO17 for momentary sensor power
sensor_power = DigitalOutputDevice(17)

try:
    sensor_power.on()

    # Allow sensor to electrically settle
    sleep(2)

    sense_read = get_soil_moisture()
    if sense_read > 100.0:
        sense_read = 100.0
    elif sense_read < 0.0:
        sense_read = 0.0 

    gdgt_input = ('{{"ch_idx":0,"ch_data":[{{"data_idx":0,'
                  '"units":1,"val":{0}}}]}}\nrun\nexit\n').format(sense_read)
    gdgt_input = gdgt_input.encode()

    gdgt_complete = subprocess.run(args=gdgt_args,
                                   input=gdgt_input,
                                   stdout=subprocess.PIPE,
                                   stderr=subprocess.PIPE,
                                   timeout=gdgt_timeout)

    print("STDOUT:\n" + gdgt_complete.stdout.decode())
    print("STDERR:\n" + gdgt_complete.stderr.decode())

except subprocess.TimeoutExpired:
    print('Gadget timed out after {0} seconds!'.format(gdgt_timeout))

except subprocess.CalledProcessError as E:
    print('Gadget exited with non-zero return code {0}'.format(E.returncode))
    print('STDERR:\n' + E.stderr.decode())

finally:
    sensor_power.close()

Copy this bad boy into something like avmsa_soil_moisture_sensing, give your user executable permissions using chmod u+x avmsa_soil_moisture_sensing, and then run it a few times with ./avmsa_soil_moisture_sensing. Make sure you updated your equation of the line for your sensor and inserted your device’s credentials into the script! And for automated messages every minute, you can copy avmsa_soil_moisture_sensing to /usr/local/bin using sudo cp avmsa_soil_moisture_sensing /usr/local/bin and then add it as a cron job to run every minute by adding * * * * * /usr/local/bin/avmsa_soil_moisture_sensing when editing your user’s crontab using crontab -e.

Avimesa.Live Integration

All we have to do here is add our sensor in Avimesa.Live:

Watering: alt text