Raspberry pi controlled greenhouse — Circuit and code

In the previous article(https://responsiblebusiness.co/raspberry-pi-controlled-greenhouse-ideas-to-lessons-1e03438688b2) I had talked about our raspberry pi controlled greenhouse. This greenhouse has now been in full production for over 8 months and has produced multiple crops of green leafy vegetables, bitter gourd (we have been eating bitter gourd for six months now), cucumber, pepper and even a helping of grass for goats more as an experiment to help some friends figure out the fastest way to grow them. With 100s of kilos in production the crops have been more enough for 2 families with 4 members.

Greenhouse, making the greenhouse and add the rpi based circuitry

In all this time I, or anyone for that matter, have visited the greenhouse for actual work (we have visited quite often to gather the harvest) including pruning, digging, planting, etc… not more than on an average of 10 days intervals. We have watered the greenhouse about once every 2–3 days. During the monsoon season we didn’t water for spells lasting even a month. A paragraph on why this happened can be found at the end of this article.

Bitter Gourd and other vegetables growing. Getting ready to plant more. Rpi is all cover by vines

Circuit Diagram

Block level circuit diagram for rpi based controller

The circuit diagram for the raspberry pi based greenhouse controller is given above. This circuit uses sensors as input and controls to pump, fan and lights as output. 
The raspberry pi is powered through mainlines and has been running constantly without any failure from the time it came on 8 months ago. We had an incident where creepers went into the raspberry pi casing and wrapped around the pi, but it kept working. 
The soil moisture sensor initially was one bought off amazon made by Seeed Studio, but it lost is metal coating, probably due to soil and moisture actions. We have now changed it to a 2 metal rod based sensor and it has been giving us pretty accurate data for last 5 months. In that time we have not have to replace anything on it and total cost for it was less than a dollar.

Codes
There are 2 code files we are using. One is for the greenhouse that takes data from various sensors and provides controls to pump, fans and lights. Another is for a weather station that is running about a kilometer from the greenhouse. This we use as reference to see if our expectation of weather conditions inside the greenhouse will be as expected against the outside weather. We could have used global weather data from weather sites, but decided that we wanted something very close by that helped us see micro-weather rather than something for the city scale. Below are the core portions of the code with explanations that could be used by someone else attempting similar greenhouses. The full codes are given at the end of this article.

Quick thoughts — the codes are hacks to get the devices working as quickly as possible. So too much thought has not been given on cleanliness, scaling, etc… The code has been running for the last months with minor hitches and upgrades along the way.

Tools used
Influxdb — all data is stored as time series data on influxdb. This allows us to handle data better as we are more interested in time series data rather than spot data. So features of influxdb make it simple for us to store as well as retrieve data in interesting ways to analyze them.

Python — all programming is done in Python using the GPIO on the raspberry pi in the Greenhouse and rtl-sdr on the weather station side.

Firebase — data is sent to firebase to make data available to an android app that allows us to monitor whether the devices are working.

Core Code for Greenhouse

#Import of modules
import grovepi #grovepi module as we are using grovepi sensors and grovepi breakout board
import RPi.GPIO as GPIO #to access the GPIO pins
import subprocess #mainly to reset rpi. Quick hack, talked about below
from influxdb import InfluxDBClient #to send data to influxDB database
import requests #to send data to an external server
import firebase #to send data to firebase for good mobile refresh on android
#to load the gpio and temperature sensor modules for the kernel
os.system(‘modprobe w1-gpio’)
os.system(‘modprobe w1-therm’)

The reference to this issue can be seen here : https://www.raspberrypi.org/forums/viewtopic.php?p=300363
These modules are not loaded at start in Raspbian. So we were repeatedly facing issue of not being able to find the sensors. The above lines solve that. These modules can be called in /etc/modules to clean up.

There are 2 seperate temperature sensors connected to the raspberry pi. One is DHT22 pro from seeed studio and another is soil moisture sensor similar to https://www.adafruit.com/product/381, but we bought cheaper ones from Amazon, which we cannot find now.

#code is get data from soil moisture sensor
base_dir = ‘/sys/bus/w1/devices/’
device_folder = glob.glob(base_dir + ‘28*’)[0]
device_file = device_folder + ‘/w1_slave’

def read_temp_raw():
f = open(device_file, ‘r’)
lines = f.readlines()
f.close()
return lines

def read_temp():
lines = read_temp_raw()
while lines[0].strip()[-3:] != ‘YES’:
lines = read_temp_raw()
equals_pos = lines[1].find(‘t=’)
if equals_pos != -1:
temp_string = lines[1][equals_pos+2:]
temp_c = float(temp_string) / 1000.0
temp_f = temp_c * 9.0 / 5.0 + 32.0
return temp_c, temp_f

The above code reads raw data directly off the system bus, converts to Celsius and Fahrenheit units. This code is used for the soil moisture sensor.

#Code to get data from soil moisture sensor
moisture = grovepi.analogRead(moisture_sensor)
if moisture<100:
moisture=0
else:
moisture=int(((1023-moisture)/10.23))

Our soil moisture sensor is home grown, it uses a couple of galvanized steel rods (2 mm thick) and about 30 cm long. We measure the resistivity between the 2 rods. The rods are connected to the grovepi analog input. The received analog input is converted to cubic meters. This allows us to use reference values for how much water is required in the soil based on plant type.

#Control code based on data from sensors
if light_sensor_value<100 and now.hour>2 and now.hour<22:
lights=1
GPIO.output(relay_light,GPIO.LOW)
else:
GPIO.output(relay_light,GPIO.HIGH)
lights=0
if (time.time()-fans_last)>30:
if temp>32 or now.hour>23:
fans_last=time.time()
fans=1
GPIO.output(relay_fans,GPIO.LOW)
else:
GPIO.output(relay_fans,GPIO.HIGH)
fans=0
if (time.time()-pump_last)>30:
if moisture>0 and moisture<50:
pump_last=time.time()
pump=1
GPIO.output(relay_pump,GPIO.LOW)
else:
GPIO.output(relay_pump,GPIO.HIGH)
pump=0

We have added a light sensor about 3 months back. In the above code we measure if light level and what time of day or night we are working in then set the lights on/off. This helps us to put on LED lights only to extend day light hours as the greenhouse is in a very well lighted location. We have not seen impact of lighting and realized we had possibly put in lights that were not useful. So we are now designing LED arrays to do tests in upcoming winter months. The schematics for the LED array is got from the OpenAgri group’s website.

We allow the fans to run once the temperature of the greenhouse crosses 32 deg Celsius in 30 minute intervals. If temperature does not reduce below 32, then we continue to operate the fan.

Currently the water sprinklers go on when moisture level in the soil is below 50m3. This has to be associated with the temperature data and fan operation. We are working on a prediction model described in the Next Steps section.

#call avrdude to reset the rpi
if counter == 10:
subprocess.call([“avrdude”, “-c”, “gpio”, “-p”, “m328p”])
counter = 0

The above code is used to reset the data ports on the grovepi (as far as we have understood). We could get data from sensors for around 2–3 times and when we had to get the data again, only previous data would be shown. Going through the Google search pointed us to the m328p chip being used on the grovepi and the avrdude instruction gets normal data.

Weather Station code
The only portion to the weather is taking data through a rtl-sdr.

cmd = “/usr/local/bin/rtl_433 -R 9 -d 0 &” #rtl_433 call
#using Pexpect to spawn a thread for the rtl_433 command
child = pexpect.spawn(cmd)
#getting data from the spawned process
while True:
try:
child.expect(‘\n’)
print child.before
if “sensorId” in child.before:
test = ast.literal_eval(child.before)
print test
temperature = (float(test[‘temperature’][‘T’])-32)*5/9
humidity = test[‘humidity’][‘H’]
rain = float(test[‘rainCounter’][‘RC’])/7.65
print “rain counter data : “, rain
windSpeed = test[‘windSpeed’][‘WS’]
windDirection = test[‘windDirection’][‘WD’]
currentTime = datetime.datetime.now()

This is pretty straight forward expect for rain where the currently got data is divided by 7.65 This is done to calibrate the rainfall sensor to 0.
All above data is then stored and send to firebase.

Next Steps
We have been collecting data every few seconds on the Greenhouse as well as on the weather station for nearly 8 months now. Currently working on creating a simple predictive application that will predict the weather conditions of greenhouse and based on the conditions from weather station and greenhouse make predictions as to what the data is going to be in the future and take necessary control actions as appropriate. This, we hope, will allow lesser monitoring of the greenhouse and weather station data from our side. Secondly it will allow the control actions to be automated as well as more intelligent.

Why no watering during Monsoon
Since the greenhouse is located with vegetable garden surrounding it from 3 sides, there was moisture content difference in the soil between outside and inside. Also there was temperature variation. Water started coming in to the greenhouse. Though water could not be seen on the surface, digging just few centimeters exposed the moisture in soil. This was more than enough to water the plants at the root. So no access watering was required during the monsoons. Though this moisture difference based watering was beyond our control, this gave us valuable lessons and ideas on potentially different way of watering to the roots that could be a cheap alternative to drip irrigation. Also it could be a way to avoid leaching. Further studies need to be done to see if this method actually works well enough.

Full Code of the Greenhouse system

import os
import glob
import datetime
import time
import grovepi
import RPi.GPIO as GPIO
import subprocess
from influxdb import InfluxDBClient
import requests
import firebase
#from time import time
from grove_rgb_lcd import * # Connect Grove-LCD RGB Backlight to I2C port of Grove-Pi
os.system('modprobe w1-gpio')
os.system('modprobe w1-therm')

base_dir = '/sys/bus/w1/devices/'
device_folder = glob.glob(base_dir + '28*')[0]
device_file = device_folder + '/w1_slave'

def read_temp_raw():
f = open(device_file, 'r')
lines = f.readlines()
f.close()
return lines

def read_temp():
lines = read_temp_raw()
while lines[0].strip()[-3:] != 'YES':
lines = read_temp_raw()
equals_pos = lines[1].find('t=')
if equals_pos != -1:
temp_string = lines[1][equals_pos+2:]
temp_c = float(temp_string) / 1000.0
temp_f = temp_c * 9.0 / 5.0 + 32.0
return temp_c, temp_f
temp_c=read_temp()
temp_humidity=4
#gas_sensor = 2
moisture_sensor=1
Vcc=16
light_sensor=0
light_threshold=10
relay_pump=23
relay_fans=18
relay_light=24
#relay_led=24
grovepi.pinMode(temp_humidity,"INPUT")
#grovepi.pinMode(gas_sensor,"INPUT")
grovepi.pinMode(moisture_sensor,"INPUT")
grovepi.pinMode(Vcc,"OUTPUT")
grovepi.pinMode(light_sensor,"INPUT")
GPIO.setmode(GPIO.BCM)
GPIO.setwarnings(False)
GPIO.setup(relay_pump,GPIO.OUT)
GPIO.setup(relay_fans,GPIO.OUT)
GPIO.setup(relay_light,GPIO.OUT)
#GPIO.setup(relay_led,GPIO.OUT)
# Generates the necessary payload to post
# temperature data into the InfluxDB
capture_interval = 25.0 # Every 25 seconds
#light_threshold=10;
client = InfluxDBClient('localhost','8086','root','root','testdb')
counter = 0
pump_last=0
fans_last=0
pump=0
fans=0
lights=0
while True:
try:
# setRGB(255,255,255) # white
# strg = "Greenhouse Data"
# print strg
# for i in range(0,16):
# setText(strg[:i])
# time.sleep(.2)
time.sleep(1) # Get temperature and humidity sensor value. First read might give previous residual value
light_sensor_value = grovepi.analogRead(light_sensor)
if light_sensor_value==0:
light_sensor_value=1
resistance = (float)(1023 - light_sensor_value) * 10 / light_sensor_value
now=datetime.datetime.now()
# Vcc Timing first 5 minutes of every hour
if now.minute<15:# and now.minute<46:
grovepi.digitalWrite(Vcc,1)
else:
grovepi.digitalWrite(Vcc,0)
# Read sensor value from moisture sensor
moisture = grovepi.analogRead(moisture_sensor)
time.sleep(1)
moisture = grovepi.analogRead(moisture_sensor)
if moisture<100:
moisture=0
else:
moisture=int(((1023-moisture)/10.23))
time.sleep(1) #2 second delay for sensor stability. Total delay is capture_interval + 2
[temp,humidity] = grovepi.dht(temp_humidity,1)
# gas = grovepi.analogRead(gas_sensor)
time.sleep(1)
#second read done to get fresh value
[temp,humidity] = grovepi.dht(temp_humidity,1)
# gas = grovepi.analogRead(gas_sensor)
# light_sensor_value = grovepi.analogRead(light_sensor)
# resistance = (float)(1023 - sensor_value) * 10 / sensor_value
time.sleep(1)
# print("Temp.: {0}".format(temp))
# print 'Temperature (Digital) = %.2f Deg C' % temp_c
# print("Humidity: {0}".format(humidity))
# print("Gas = %d" %gas)
# print("Light sensor value = %d resistance =%.2f" %(light_sensor_value, resistance))
# print("Moisture: {0}".format(moisture))
now = datetime.datetime.now()
if light_sensor_value<100 and now.hour>2 and now.hour<22:
lights=1
GPIO.output(relay_light,GPIO.LOW)
else:
GPIO.output(relay_light,GPIO.HIGH)
lights=0
if (time.time()-fans_last)>30:
if temp>32 or now.hour>23:
fans_last=time.time()
fans=1
GPIO.output(relay_fans,GPIO.LOW)
else:
GPIO.output(relay_fans,GPIO.HIGH)
fans=0
if (time.time()-pump_last)>30:
if moisture>0 and moisture<50:
pump_last=time.time()
pump=1
GPIO.output(relay_pump,GPIO.LOW)
else:
GPIO.output(relay_pump,GPIO.HIGH)
pump=0
relay_status=""
if lights==1:
relay_status=relay_status+"L "
if fans==1:
relay_status=relay_status+"F "
if pump==1:
relay_status=relay_status+"P"
# print "Relay Status: ",relay_status
# print("-------------------")
json_body = [
{
"measurement": "WeatherData",
"tags": {
"Project": "Green-house"
},
"fields": {
"Temperature": temp,
"Humidity": humidity,
"Light": light_sensor_value,
"Moisture": moisture,
"Relay_Status": relay_status
}
}
]
try: #this should handle the case of writing -1 to influxdb
client.write_points(json_body)
try:
currentTime = datetime.datetime.now()
data = str(temp)+","+str(humidity)+","+str(moisture)+","+relay_status+","+str(currentTime)
payLoad = {'deviceID':'{deviceid}', 'passwd':'{password}', 'data':data}
r = requests.post("{server address}", data=payLoad)
print r
except:
print "Data not put in server"
pass
try:
URL = '{firebase-address}'
payload = {"greenhouse":{"temperature":str(temp),"humidity":str(humidity),"moisture":str(moisture),"relay_status":relay_status,"currentTime":str(currentTime)}}
firebase.put(URL, payload)
except:
print "Data not put into firebase"
pass
except:
if counter == 10:
subprocess.call(["avrdude", "-c", "gpio", "-p", "m328p"])#run avrdude
counter = 0
else:
counter = counter + 1
# if resistance>light_threshold:
# GPIO.output(relay_led,GPIO.LOW)
# else:
# GPIO.output(relay_led,GPIO.HIGH)
#  setRGB(255,127,0)
# str = "Greenhouse Data Display"
# for i in range(0,24):
# setText(str[:i])
# time.sleep(.1)
# time.sleep(2)
# setRGB(255,255,255)
# setText("Temp:" + temp + " F " + "Humidity :" + humidity + " %")
# time.sleep(2)
# setText("Moisture:" + moisture + " " + "Gas :" + gas)
# time.sleep(2)
except KeyboardInterrupt:
GPIO.output(relay_fans,GPIO.HIGH)
GPIO.output(relay_pump,GPIO.HIGH)
GPIO.output(relay_light,GPIO.HIGH)
grovepi.digitalWrite(Vcc,0)
# GPIO.output(relay_led,GPIO.HIGH)
break
except: #this should handle the case of data error from sensors themselves
if counter == 10:
subprocess.call(["avrdude", "-c", "gpio", "-p", "m328p"])#run avrdude
counter = 0
else:
counter = counter + 1
time.sleep(capture_interval) #sleep is done irrespective of data get or not