An automated greenhouse


Edsard Boelen, 20-mar-2016
After I saw a ted talk about indoor growing I thought it would be fun to build an indoor greenhouse... Special plant grow leds aren't expensive and with a raspberry pi a nice online monitor and control system can be build.



Overview


The greenhouse will have the following capabilities:

Casing


For the conveniece I bought 2 'socker' greenhouse casings at the local IKEA for 12 euro's it has a 40x20cm surface and has a height of 35 cm.

Lights


The lights are the most important part, for the first casing I used 6 strips of 40cm with 5630 leds drawing 17 watts of power.
An IRF3205 mosfet will drive the leds so they can be turned on and off by the raspberry pi. Unfortunately the gate threshold voltage is to high for the raspberry pi so i will have an extra transistor pulling up the voltage.
The other controller will use 3 FQP30N06 mosfets which don't need an extra transistor.
. . .

To controll it from a raspberry pi, connect the gate to GPIO 27 (pin 13), then in a bash shell in raspbian running on the pi it can be controlled by the following line:
  
  gpio -g mode 27 out
  gpio -g write 27 1

If the gpio utillity is not available, install it from wiringpi.com
To make them work in a web interface I installed apache on the raspberry pi and used the following code:
<?php
   if(isset($_REQUEST['l'])){
        if($_REQUEST['l'] == 'on'){
          system('gpio write 2 0');
        }
        if($_REQUEST['l'] == 'off'){
          system('gpio write 2 1');
        }
   }
?>
<script type="text/javascript">
$(document).ready(function(){
        $('.lighton').click(function(e){
                var xhttp = new XMLHttpRequest();
                xhttp.open("GET","buttons.php?l=on",true);
                xhttp.send();
        });
        $('.lightoff').click(function(e){
                var xhttp = new XMLHttpRequest();
                xhttp.open("GET","buttons.php?l=off",true);
                xhttp.send();
        });
});
</script>
<hr/>
<table class="buttontable" width="100%">
<tr>
<td> &nbsp; </td><td><button type="button" class="lighton  btn btn-primary btn-block">light on</button></td>
<td> &nbsp; </td><td><button type="button" class="lightoff btn btn-primary btn-block">light off</button></td>
<td> &nbsp; </td>
</tr>
<tr><td colspan="4"> &nbsp; <br></td></tr>
</table>
<hr/>

DHT22 Temp and humidity sens


.  
The DHT22 is my allround temperature and humidity sensor, for the raspberry pi, it is not fully reliable because it uses the 1-wire interface which needs precision timing of 50us. A multitasking OS cannot guarantee that all the time. Anyway, if the crc fails I will just retry a couple of times. I connected the data pin to and used the adafruit python library for reading. download the version I used here

import sys

import Adafruit_DHT
import time
humidity, temperature = Adafruit_DHT.read_retry(Adafruit_DHT.AM2302, 4)

if temperature < 10 or humidity > 110:
        time.sleep(3)
        humidity, temperature = Adafruit_DHT.read_retry(Adafruit_DHT.AM2302, 4)
if humidity is not None and temperature is not None:
        print time.strftime("%Y-%m-%d %H:%M:%S")+'_0_{0:0.1f}_{1:0.1f}'.format(temperature, humidity)
else:
        print 'Failed to get reading. Try again!'
        sys.exit(1)

the following crontab will log the temperatures every 15 minutes.
   0,15,30,45 * * * * /local/readtemp.py >> /data/temps.txt
Below you will see the first results on how they are stored in the text files.
2016-04-01 23:30:03_0_20.2_58.4
2016-04-01 23:45:05_0_20.1_56.6
2016-04-02 00:00:03_0_20.1_56.9
2016-04-02 00:15:03_0_19.8_60.0
2016-04-02 00:30:02_0_19.5_61.5

Camera


I had a raspberry pi camera laying around so i used that one. It is placed 40cm from the case and will make an image every hour. the following script will also create the appropiate file name.

now=$(date +"%y-%m-%d_%H"); sudo raspistill -o /data/img/img_$now.jpg


and camera2 at exactly 40 cm from the case

Webinterface


The webinterface will show some nice charts and a good current overview by using bar graphs. The charts are being generated by hicharts. the bars are some css tricks and looks like as following:


The code for the vertical bars are as follows It will read the last line from the temp logs and place them in bootstrap bars:
<style>
center{
 vertical-align: text-top;
}

.col-sm-4{
	width: 100%;
	text-align: center;
}

.vertical-bars {
    margin-top: 20px;
    text-align: center; }
    .vertical-bars li {
      display: inline-block;
      position: relative;
      width: 45px;
      height: 200px;
      background: #f0f0f0;
      margin: 0 10px; }
      @media (max-width: 300px) {
        .vertical-bars li {
          width: 25px; } }
      .vertical-bars li span.vbar {
        position: absolute;
        animation-duration: 3s;
       animation-name: height;
        left: 0;
        right: 0;
        bottom: 0; }
      .vertical-bar li span.title {
        position: absolute;
        left: 0;
        right: 0;
        text-align: center;
        bottom: -20px; }
        
        @keyframes height {
        0%, 100% {
            transition-timing-function: cubic-bezier(1, 0, 0.65, 0.85);
        }
        0% {
            height: 0;
        }
        100% {
            max-height: 100%;
        }

}
</style>

<?php

    function last_line($filename){
        $line = '';

        $f = fopen($filename, 'r');
        $cursor = -1;

        fseek($f, $cursor, SEEK_END);
        $char = fgetc($f);

        /**
        * Trim trailing newline chars of the file
        */
        while ($char === "\n" || $char === "\r") {
            fseek($f, $cursor--, SEEK_END);
            $char = fgetc($f);
        }

        /**
        * Read until the start of file or first newline char
        */
        while ($char !== false && $char !== "\n" && $char !== "\r") {
            /**
            * Prepend the new char
            */
            $line = $char . $line;
            fseek($f, $cursor--, SEEK_END);
            $char = fgetc($f);
        }
        return $line;
    }

    $living = last_line('temps.txt');
    $alivin = explode("_", $living);

    if($alivin[2] < 110 && $alivin[3] < 110){
        $tempp =  ( $alivin[2] / 35 ) * 100;
    }else{
            $alivin[3] = 0;
    }
?>

<center>Current</center><br>
<div class="container-fluid">
<div class="row">
<div class="col-sm-4">
<div class="vertical-bars xs-center">
    <ul class="list-inline">
        <li><span class="vbar" style="height:<?=$tempp?>%; background:#009900;"></span><span class="title"><?=$alivin[2]?>C</span></li>
        <li><span class="vbar" style="height:<?=$alivin[3]?>%; background:#00bbff;"></span><span class="title"><?=$alivin[3]?>%</span></li>
        <li><span class="vbar" style="height:75%; background:#ffaa00;"></span><span class="title">100</span></li>
    </ul>
</div>
</div>
</div>
</div>


The charts are build using hicharts it receives the data as JSON arrays.


The following php code will read the generated text files as in the exampole above and outputs all the data as a big json array:
<?php
$dfield = 2;
if(isset($_GET['typenr'])){

	$dfield = $_GET['typenr'];
}

if(isset($_GET['name']) && strlen($_GET['name']) < 6){
    $filename = $_GET['name'].".txt";

    $notFirst = false;
    echo "[";
    $handle = fopen($filename, "r");
    if ($handle) {
        while (($line = fgets($handle)) !== false) {
            // process the line read.
            $aData = explode("_",$line);
            if($aData !== false && $aData[$dfield] > 10 && $aData[$dfield] < 110){
            if($notFirst){
                echo ",";
            }
            echo"[".strtotime($aData[0]).",".$aData[$dfield]."]";
            echo "\n";
            $notFirst=true;
            }
        }

        fclose($handle);
        echo "]";
    } else {
        // error opening the file.
    } 
}
?>


Then the following code will generate 2 graphs, one for the temperature and one for humidity. The data is received using ajax.
<script src="https://code.highcharts.com/highcharts.js"></script>
<script src="https://code.highcharts.com/modules/exporting.js"></script>
<script type="text/javascript">

$(document).ready(function () {
   $.getJSON('getdata.php?name=temp', function (data) {

        $('#container_temp').highcharts({
            chart: {
                zoomType: 'x'
            },
            title: {
                text: 'temps'
            },
            subtitle: {
                text: document.ontouchstart === undefined ?
                        'Click and drag in the plot area to zoom in' : 'Pinch the chart to zoom in'
            },
            xAxis: {
                type: 'datetime'
            },
            yAxis: {
                title: {
                    text: 'Temps'
                },
		plotLines: [{
                    value: 20,
                    color: 'green',
                    dashStyle: 'shortdash',
                    width: 2,
                    label: {
                        text: 'minimum'
                    }
                }, {
                    value: 30,
                    color: 'red',
                    dashStyle: 'shortdash',
                    width: 2,
                    label: {
                        text: 'maximum'
                    }
                }]
            },
            legend: {
                enabled: false
            },
            plotOptions: {
                area: {
                    fillColor: {
                        linearGradient: {
                            x1: 0,
                            y1: 0,
                            x2: 0,
                            y2: 1
                        },
                        stops: [
                            [0, Highcharts.getOptions().colors[2]],
                            [1, Highcharts.Color(Highcharts.getOptions().colors[2]).setOpacity(0).get('rgba')]
                        ]
                    },
                    marker: {
                        radius: 2
                    },
                    lineWidth: 1,
                    states: {
                        hover: {
                            lineWidth: 1
                        }
                    },
                    threshold: null
                }
            },

            series: [{
                type: 'area',
		color: '#009900',
                name: 'temp1',
                data: data
            }]
        });
      });
   // });
   
     $.getJSON('getdata.php?name=temp&typenr=3', function (data) {

        $('#container_hum').highcharts({
            chart: {
                zoomType: 'x'
            },
            title: {
                text: 'humidity'
            },
            subtitle: {
                text: document.ontouchstart === undefined ?
                        'Click and drag in the plot area to zoom in' : 'Pinch the chart to zoom in'
            },
            xAxis: {
                type: 'datetime'
            },
            yAxis: {
                title: {
                    text: 'Hum'
                },
		plotLines: [{
                    value: 50,
                    color: 'blue',
                    dashStyle: 'shortdash',
                    width: 2,
                    label: {
                        text: 'minimum'
                    }
                }]
            },
            legend: {
                enabled: false
            },
            plotOptions: {
                area: {
                    fillColor: {
                        linearGradient: {
                            x1: 0,
                            y1: 0,
                            x2: 0,
                            y2: 1
                        },
                        stops: [
                            [0, Highcharts.getOptions().colors[0]],
                            [1, Highcharts.Color(Highcharts.getOptions().colors[0]).setOpacity(0).get('rgba')]
                        ]
                    },
                    marker: {
                        radius: 2
                    },
                    lineWidth: 1,
                    states: {
                        hover: {
                            lineWidth: 1
                        }
                    },
                    threshold: null
                }
            },

            series: [{
                type: 'area',
                name: 'hum',
                data: data
            }]
        });
      });

  });
</script>

<div id="container_temp" style="min-width: 310px; height: 300px; margin: 0 auto"></div>
<div id="container_hum" style="min-width: 310px; height: 300px; margin: 0 auto"></div>


 



Version 2 Lights


The second greenhouse will have 4x40cm of special grow light strip. That will be a total of 96 type 5050 leds drawing 12 Watts of power.

I made 2 bars with 2 strips each for an even distribution but less mounting work and more space to access the plants.

The led color will be in a ratio of 4 red one blue led. As you can see below.

The image below shows why the green part of the light spectrum is not needed.

Fans



It won't happen often, but a small fan would help a lot if the temperature gets too high, every 15 minutes the temperature will be checked, if it is above 30 degrees, then the fan will run until the next measurement. It will also be driven by a mosfet, but the speed will be brought down by a LM317 and a variable resistor.

Light intensity sensor



The light intensity can easily be measured by using an LDR. But unfortunately, the raspberry pi has no analog inputs!. It is advised to use an mcp3008. That is a 10bit ADC with an SPI interface with 8 analog inputs. I will use the first one for light inside the case, one for outside light. The other six can be used for soil measurement.
The LDR will simply be connected as a voltage divider with a resistor of 1k.
.

The reading will be done with a python script as shown below.

import time
import os
import RPi.GPIO as GPIO

GPIO.setwarnings(False)
GPIO.setmode(GPIO.BCM)
DEBUG = 1

# read SPI data from MCP3008 chip, 8 possible adc's (0 thru 7)
def readadc(adcnum, clockpin, mosipin, misopin, cspin):
        if ((adcnum > 7) or (adcnum < 0)):
                return -1
        GPIO.output(cspin, True)

        GPIO.output(clockpin, False)  # start clock low
        GPIO.output(cspin, False)     # bring CS low

        commandout = adcnum
        commandout |= 0x18  # start bit + single-ended bit
        commandout <<= 3    # we only need to send 5 bits here
        for i in range(5):
                if (commandout & 0x80):
                        GPIO.output(mosipin, True)
                else:
                        GPIO.output(mosipin, False)
                commandout <<= 1
                GPIO.output(clockpin, True)
                time.sleep(0.01)
                GPIO.output(clockpin, False)
                time.sleep(0.01)
        adcout = 0
        # read in one empty bit, one null bit and 10 ADC bits
        for i in range(12):
                GPIO.output(clockpin, True)
                time.sleep(0.01)
                GPIO.output(clockpin, False)
                time.sleep(0.01)
                adcout <<= 1
                if (GPIO.input(misopin)):
                        adcout |= 0x1

        GPIO.output(cspin, True)
        
        adcout >>= 1       # first bit is 'null' so drop it
        return adcout

# change these as desired - they're the pins connected from the
# SPI port on the ADC to the Cobbler
SPICLK = 17
SPIMISO = 23
SPIMOSI = 24
SPICS = 25

# set up the SPI interface pins
GPIO.setup(SPIMOSI, GPIO.OUT)
GPIO.setup(SPIMISO, GPIO.IN)
GPIO.setup(SPICLK, GPIO.OUT)
GPIO.setup(SPICS, GPIO.OUT)

last_read = 0       # this keeps track of the last potentiometer value
tolerance = 5       # to keep from being jittery we'll only change
                    # volume when the pot has moved more than 5 'counts'

# we'll assume that the pot didn't move

# read the analog pin
ldr1 = readadc(2, SPICLK, SPIMOSI, SPIMISO, SPICS)
ldr2 = readadc(3, SPICLK, SPIMOSI, SPIMISO, SPICS)
mois1 = readadc(5, SPICLK, SPIMOSI, SPIMISO, SPICS)
mois2 = readadc(7, SPICLK, SPIMOSI, SPIMISO, SPICS)
        
print time.strftime("%Y-%m-%d %H:%M:%S")+'_0_'+str(ldr1)+'_'+str(ldr2)+'_'+str(mois1)+'_'+str(mois2)

Soil moisture



Soil has different resistances in relation to its dryness. I got a couple of probes which are just coated copper plates. I put one in a dry part and one in a wet part of the ground and measured the resistance. one was 10k ohm and the dry one about 80k. So for soil moisture, I used the same chip as for the light meter, only a 50K resistor instead of 1k.

Water pump



The water pump has a flow of 40ml per second when it runs on 12V. at 5V the flow is 20ml per second, the pump will not function properly on a lower voltage. A 2 liter milk can will be the water store. A water level indicator will check if there is enough water in the can and can alert if not.
  gpio -g mode 11 input up
  gpio -g read 11
.
The water tube will go to a splitter, each small tube will feed a plant.

.


Experiment !





Below is an image of the main board. On left left is a raspberry pi 2. The top right is a LM2596 DC DC converter. On the right side you see 3 FQP30N06 MOSFETS that drive the lights, fan and water pump. For the fan and water pump there are also 2 LM317 voltage regulators with 2 variable resitors next to it. And finally in the center is the MCP3008 with a ldr connected to one input..