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

Kit 1: 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 Microchip Technology MCP3002 ADC (analog-to-digital converter). We should note that the MCP3002 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

Initial Setup

(1) Once you download a desirable Raspbian image, you can use Etcher to quickly copy it to your microSD.

(2) 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.

(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

Following is a diagram and a table that, together, should help in wiring up the soil moisture sensor, MCP3002, and Raspberry Pi. Here is the datasheet for the MCP3002 and here is a great representation of the Raspberry Pi 40 pinout header and the associated Broadcom connections. dev-kit-1-fritz.png

NOTE: XXX denotes no connection in the table.

Raspberry Pi MCP3002 Soil Moisture Sensor
P1 – 3V3 P8 – VDD/VREF XXX
P6 – Ground P4 – VSS GND
P11 – GPIO17 XXX VCC
P19 – SPI MOSI P5 – DIN XXX
P21 – SPI MISO P6 – DOUT XXX
P23 – SPI SCLK P7 – CLK XXX
P24 – SPI CE0 P1 – nCS/SHDN XXX
XXX P1 – CH0 SIG

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 MCP3002, DigitalOutputDevice

def get_soil_moisture():
    adc = MCP3002(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 calibration.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 MCP3002 class, a SPI wrapper tailored for an MCP3002 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 MCP3002, DigitalOutputDevice

def get_soil_moisture():
    # SPI wrapper for ADC chip
    adc = MCP3002(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