Zhengao DiW. Che
Published © GPL3+

Biomaker Challenge 2020-Automated plant growth chamber

An automated plant growth chamber to grow and monitor Arabidopsis thaliana seedlings for plant research.

BeginnerWork in progress1,078
Biomaker Challenge 2020-Automated plant growth chamber

Things used in this project

Hardware components

Open Smart Rich UNO R3
×1
Raspberry Pi 4 Model B
Raspberry Pi 4 Model B
×2
Camera Module
Raspberry Pi Camera Module
×1
7in LCD Touch Screen
×1
Intelligent LED Solutions 12-Channel Light Controller
×1
ILS ILK-PETUNIA-01 LED Light Kit, OSLON SSL Petunia
×1
Creality Ender-3 DIY 3D Printer
×1
Jumper wires (generic)
Jumper wires (generic)
×1
Grove - Temperature, Humidity, Pressure and Gas Sensor (BME680)
Seeed Studio Grove - Temperature, Humidity, Pressure and Gas Sensor (BME680)
×1
NTC temperature sensor
×1
Grove - 4-Channel SPDT Relay
Seeed Studio Grove - 4-Channel SPDT Relay
×1
RS PRO 1.75mm White PLA 3D Printer Filament, 1kg
×1
RS PRO Silicone Heater Mat, 5 W, 50 x 100mm, 12 V dc
To be used with a 12v dc power supply.
×1
Axial Fan 12 V dc
To be used with a 12v dc power supply.
×1
Digital Thermometer
×1
24 V dc DC Axial Fan
To be used with a 24v dc power supply.
×1
2 Way Terminal Blocks
×1
RS PRO DC Socket
×1
Fan Filter 80mm
×1

Software apps and online services

Arduino IDE
Arduino IDE

Story

Read more

Custom parts and enclosures

24-well plate v1.0

A 24-well plate for growing plant seedlings

24-well plate v2.0

An updated 24-well plate for germinating seeds and growing small seedlings.

Tamper

This is a simple soil tamper to be used with 24-well plates v2.0.

Code

Local-3.0.py

Python
This is a python script to run on Raspberry pi to read data from Arduino and save them to local folders before being uploaded to the cloud (by the Upload-x.x.py). It also controls auto-irrigation.
#!/usr/bin/python3

# This deals with all local work, incl. communicating with Arduino board, take images and sensor data and save to local folders.

import serial
from PIL import Image
import base64
from datetime import datetime, timedelta
import time 
import os

dir_base = '/home/pi/Documents/Chamber/'

water_interval = 60 # number of hours between each watering event

def irrigation(Water_Interval = water_interval):
    now = datetime.now()

    last_water_str = open('WaterLog','r').read().rstrip('\n')
    last_water = datetime.strptime(last_water_str, '%M.%H.%d.%m.%y') # read the time of last watering event from WaterLog file. Time is stored in this file as a string in the format 'minute.hour.day.month.year'
    delta = now - last_water
    if (delta.days*24 + delta.seconds/3600) > Water_Interval:
        ser.write(b'water\n') # send a 'water' signal to Arduino board
        os.system('echo ' + now.strftime('%M.%H.%d.%m.%y') + ' > WaterLog') 

if __name__ == '__main__':

    #--------------------------------------------------- Configure serial communication---------------------------------
    ser = serial.Serial('/dev/ttyUSB0', 9600, timeout=1)
    ser.flush()
    count = 0 

    now = datetime.now()
    current_time = now.strftime('%Y-%m-%d %H:%M:%S')
    print(str(current_time) + ': Communication between Arduino established!')
    #--------------------------------------------------- Loop---------------------------------

    while True:

        if ser.in_waiting == 0:
            continue

        now = datetime.now()
        current_time = now.strftime('%Y-%m-%d %H:%M:%S')
        current_time_v2 = now.strftime('%Y-%m-%d_%H.%M.%S')
        current_day = now.strftime('%y%m%d')
        yesterday = datetime.today() - timedelta(days = 1)
        yester_day = yesterday.strftime('%y%m%d')
        past = datetime.today() - timedelta(days = 3)
        past_day = past.strftime('%y%m%d')

        #-----------------------------------image capture -------------------------------------------------------------
        os.system('fswebcam -d v4l2:/dev/video0 -q --no-banner --jpeg 80 --set brightness=40% --save /home/pi/Documents/Chamber/snapshot.jpg') # uses fswebcam to take a picture. -q: quite mode. -r: image resolution. --jpeg 60: quality of the images (can be 0-95).
        count = count + 1
        if count % 10 == 0:
            print(str(current_time) + ': ' + str(count) + ' images taken')
        elif count == 1:
            print(str(current_time) + ': ' + str(count) + ' image taken')

        #-----------------------------------sensor data upload-------------------------------------------------------------

        if ser.in_waiting > 0:
            
            line = ser.readline().decode('utf-8').rstrip()
            parameter = line.split(',') # a list of parameters: Temp (BME680), pressure, humidity, Temp (LM75), Temp (NTC), Pressure, Humidity

            header = 'Time,Temp_LM75/oC,Temp_BME680/oC,Temp_NTC/oC,Pressure/kPa,Humidity/%'

        #---------------------------------Save csv files and images together into folders-------------------------

        if not os.path.exists(current_day): # On a new day, create a folder named with the date and save upcoming .csv and images on that date to that folder.
            os.makedirs(current_day)
            os.system('echo ' + header + ' >> ' + current_day + '/log_$(date +%y%m%d).csv') # create .csv log file for that day and add the header

        datalog = str(current_time) + ',' + str(line)
        os.system('echo ' + datalog + ' >> ' + current_day + '/log_$(date +%y%m%d).csv')
    
        if count % 60 == 0: # Take a high-resolution image 
            os.system('raspistill -o ' + current_day + '/' + current_time_v2 + '.jpg')

        #-------------------------------Auto-irrigation every two days-----------------------
        if count % 120 == 0: # Check if it is time to water every 1 hour.
            irrigation()

        time.sleep(30) # repeat every roughly 30 seconds (actually a bit longer than 60s as the image cature takes another 1-2 s)
        ser.reset_input_buffer()

Upload-3.0.py

Python
This is a python script to run on Raspberry pi to upload environment data and images to Adafruit and Google Drive.
#!/usr/bin/python3

# This deals with all uploading work, incl. uploading images and sensor data to Adafruit and Googl drive.

from Adafruit_IO import Client
from PIL import Image
import base64
from datetime import datetime, timedelta
import time 
import os
import pandas as pd
import shutil
from pydrive.drive import GoogleDrive
from pydrive.auth import GoogleAuth

gauth = GoogleAuth()
gauth.CommandLineAuth()
drive = GoogleDrive(gauth) # Create GoogleDrive instance with authenticated GoogleAuth instance.

dir_base = '/home/pi/Documents/Chamber/'

def folderid(date):
    # Input a date and return the folder ID of that date
    folders = drive.ListFile({'q': "title='" + date + "' and mimeType='application/vnd.google-apps.folder' and trashed=false"}).GetList()
    
    if len(folders) == 1: # make sure folder is unique
        return(folders[0]['id'])
    elif len(folders) == 0:
        print('Error: Cannot Find Folder on Google Drive: ' + date)
    elif len(folders) > 1: 
        print('Error: Duplicate Folder on Google Drive: ' + date)

def check_folder(date):
    # Input a date and check if folder of that date exists on google drive
    folders = drive.ListFile({'q': "title='" + date + "' and mimeType='application/vnd.google-apps.folder' and trashed=false"}).GetList()
    
    if len(folders) == 0: # No folder
        return(0)
    else:
        return(1)

if __name__ == '__main__':

    #--------------------------------------------------- Configure Adafruit-IO---------------------------------
    print('Connecting to Adafruit-IO...')
    aio = Client(username = 'Deebug',key = 'xxx') # Put username and AIO key here
    print('Adafruit-IO Connected!')

    camera_feed = aio.feeds('pi-camera')
    image = '/home/pi/Documents/Chamber/snapshot.jpg' 

    temp_bme_feed = aio.feeds('temp-bme')
    temp_ntc_feed = aio.feeds('temp-ntc')
    temp_lm75_feed = aio.feeds('temp-lm75') 
    pressure_feed = aio.feeds('pressure') 
    hum_feed = aio.feeds('humidity') # TODO Can add more sensor feeds

    count = 0 # This is to count how many images have been taken and uploaded and also report the number of loops.

    now = datetime.now()
    current_time = now.strftime('%Y-%m-%d %H:%M:%S')
    print(str(current_time) + ': live stream and data logging starts.')

    #--------------------------------------------------- Loop---------------------------------

    while True:

        now = datetime.now()
        current_time = now.strftime('%Y-%m-%d %H:%M:%S')
        current_time_v2 = now.strftime('%Y-%m-%d_%H.%M.%S')
        current_day = now.strftime('%y%m%d')
        yesterday = datetime.today() - timedelta(days = 1)
        yester_day = yesterday.strftime('%y%m%d')
        past = datetime.today() - timedelta(days = 3)
        past_day = past.strftime('%y%m%d')

        #-----------------------------------Upload to Adafruit-------------------------------------------------------------
        # Image
        with open(image, "rb") as imageFile: # read snapshot and encode it to base64 format
            base_str = base64.b64encode(imageFile.read())
            base_str = base_str.decode('utf-8')

            aio.send_data(camera_feed.key, base_str) # Send data to Adafruit-IO. First argument is the name of your feed.
            
            count = count + 1
            if count % 10 == 0:
                print(str(current_time) + ': ' + str(count) + ' images uploaded to Adafruit-io...')
            elif count == 1:
                print(str(current_time) + ': ' + str(count) + ' image uploaded to Adafruit-io...')

        # sensor data 
        if not os.path.exists(current_day): # This is to make sure that on a new day, Upload-x.x.py will only upload sensor data 'after' Local-x.x.py has created that new day's folder and csv file. If not, Upload-x.x.py will in theory wait for one loop.
            continue

        filename = './'+ current_day + '/log_' + current_day + '.csv'
        df = pd.read_csv(filename)
        parameter = df.iloc[-1,1:] # a list of parameters: Temp (BME680), pressure, humidity, Temp (LM75), Temp (NTC), Pressure, Humidity

        aio.send_data(temp_lm75_feed.key, parameter[0])
        aio.send_data(temp_bme_feed.key, parameter[1])
        aio.send_data(temp_ntc_feed.key, parameter[2])
        aio.send_data(pressure_feed.key, parameter[3])
        aio.send_data(hum_feed.key, parameter[4])

        #---------------------------------Upload to Google Drive-------------------------
        #Upload csv
        if not check_folder(current_day): # On a new day, create a folder on Google drive named with the date, save .csv of previous date, and save images on that date.

            folder_metadata = {'title' : current_day, 'mimeType' : 'application/vnd.google-apps.folder'}
            folder = drive.CreateFile(folder_metadata)
            folder.Upload()

            #-------------------------------Upload yesterday's .csv log file to google drive-----------------------
            if os.path.exists(yester_day):
                csv_file = drive.CreateFile({'title' : 'log_' + yester_day + '.csv', 'mimeType':'image/jpeg', 'parents': [{'id': folderid(yester_day)}]})
                csv_file.SetContentFile(yester_day + '/log_' + yester_day + '.csv')
                csv_file.Upload()

            #-------------------------------Auto delete data from 3 days ago on Raspberry pi to save local memory-----------------------
            if check_folder(past_day): # check if old data has been saved on google drive
                shutil.rmtree(past_day)                                                 
        
        #Upload images
        img_list = [x for x in os.listdir(current_day) if x.endswith(".jpg")]
        if img_list:
            for i in img_list:

                    img_file = drive.CreateFile({'title' : i, 'mimeType':'image/jpeg', 'parents': [{'id': folderid(current_day)}]})
                    img_file.SetContentFile(current_day + '/' + i)
                    img_file.Upload()

                    os.remove(current_day + '/' + i)          

        time.sleep(30) # repeat every roughly 30 seconds (actually a bit longer than 60s as the image cature takes another 1-2 s)

Arduino_Control_main_V2.ino

Arduino
This is the script to run on Arduino board to record sensor data and send to Raspberry pi.
#include <Wire.h>
#include <SoftwareSerial.h>

#include "seeed_bme680.h" 
#include "RichUNOLM75.h" 
#include "Sensors.h" 
#include <multi_channel_relay.h>
#include "RichUNODS1307.h"

// bme680 sensor
#define IIC_ADDR  uint8_t(0x76)
Seeed_BME680 bme680(IIC_ADDR); /* IIC PROTOCOL */
float BME_temp;
float BME_humi;
float BME_pressure;

// Arduino built in temperature sensor
LM75 temper75;  // initialize an LM75 object "temper" for temperature


// analog sensors of the Arduino sensors, including: NTC sensors.
#define NTC_Pin A0;
NTC_temp temperNTC(0);//Define input pin for NTC sensors, calculate the correct sensor reading
float Temp_LM75_built_in;
float Temp_NTC_read;
// relay
Multi_Channel_Relay relay;
int Relay_status;
// macro to covert status to bit
#define BIT(n,i) (n>>i&1)

// Chamber Temperature
double ChamberTemp;

// Water system
unsigned long WaterStartTime;
unsigned long WaterCurrentTime;
bool WaterRunning;

// Time on Arduino Board
DS1307 clock;//define a object of DS1307 class
unsigned long LightFanStartTime;
unsigned long LightFanCurrentTime;
bool LightFanRun;

void setup() {
  // put your setup code here, to run once:
  
  Serial.begin(9600);
  while (!Serial);  
  while (!bme680.init()) {
        Serial.println("bme680 init failed ! can't find device!");
        delay(10000);
    }
    
    // Set up relay address
   relay.begin(0x11);

   //Flag to show if water is running
    WaterRunning=0;

    // Set up the clock
    clock.begin();
    // Flag to show if the light fun is operating
    LightFanRun=1;
    LightFanStartTime=millis();
}

void loop() {
  // put your main code here, to run repeatedly:
  if (bme680.read_sensor_data()) {
        Serial.println("Failed to perform reading :(");
        return;
    }
    
    Temp_LM75_built_in = temper75.getTemperatue();//get temperature from LM75 sensor
    Temp_NTC_read = temperNTC.read_ntc_sensor_temp();// get temperature from NTC sensor

    BME_temp=bme680.sensor_result_value.temperature-2;
    BME_humi=bme680.sensor_result_value.humidity;
    BME_pressure=bme680.sensor_result_value.pressure / 1000.0;
 
    
    // Arduino Built in Temp
    Serial.print(Temp_LM75_built_in);
    Serial.print(",");
    
    // Bme Temp oC
    Serial.print(BME_temp);
    Serial.print(",");

    // NTC Temp 
    Serial.print(Temp_NTC_read);
    Serial.print(",");
     
    // Bme Pressure kPa
    Serial.print(BME_pressure);
    Serial.print(",");
    
    // Bme Humidity %
    Serial.print(BME_humi);
    Serial.print("\n");

    ChamberTemp=BME_temp;
    // Temperature fan control for the chamber
    // BIT(Relay_status,1) convert the status into binary number, the bit at 0 corresponds to switch 1.
    Relay_status=relay.getChannelState();
    if (BIT(Relay_status,3)!=1 && ChamberTemp > 24){
      relay.turn_on_channel(4);
    }

    if (BIT(Relay_status,3)==1 && ChamberTemp < 22){
      relay.turn_off_channel(4);
    }
    
    // Temperature heater control for the chamber
  if (BIT(Relay_status,2)==!1 && ChamberTemp < 21){
      relay.turn_on_channel(3);
    }
   if (BIT(Relay_status,2)==1 && ChamberTemp > 22){
      relay.turn_off_channel(3);
    }
    

    // Watering the system under command from PI

    if (Serial.available() > 0) {
    // read the incoming byte:
    String Command = Serial.readStringUntil('\n');
    if (Command=="water"){
      // turn on the relay switch 1
      relay.turn_on_channel(1);
      // Mark the water running status as on
      WaterRunning = 1;
      // record the watering start time
      WaterStartTime = millis();
    }
  }

  // Watering time count down
  if (WaterRunning==1){
    WaterCurrentTime=millis();
    if(WaterCurrentTime-WaterStartTime>=30000){
      relay.turn_off_channel(1);
      WaterRunning=0;
    }
  }
   clock.getTime();
   if (clock.hour>=0 && clock.hour<=8){
      if (LightFanRun==1){
        relay.turn_on_channel(2);
        LightFanRun=0;
      }   
   }
   else if(clock.minute>=0&& clock.minute<=1){
        if (LightFanRun==0){
        relay.turn_off_channel(2);// turn on the light fan
        LightFanRun=1;
        }
   }
   else if(clock.minute>=30&& clock.minute<=31){
        if (LightFanRun==0){
        relay.turn_off_channel(2);// turn on the light fan
        LightFanRun=1;
        }
   }
   else {
        if (LightFanRun==1){
        relay.turn_on_channel(2);// turn off the light fan
        LightFanRun=0;
        }
    }
   
   
    delay(500);
}

Sensors.cpp

Arduino
#include <SoftwareSerial.h>
#include "Sensors.h"

NTC_temp::NTC_temp () {
  PIN = NTC_ANALOG_PIN;
}

NTC_temp::NTC_temp (int pin) {
  PIN = pin;
}



float NTC_temp::read_ntc_sensor_temp(){
  float Temp_NTC_sensor=0.0; // the temperature reading from the NTC sensore
  int R0=10000;
  int B_parameter=3950;
  int T0=25;
  
  int NUMSAMPLES=5;
  int samples[NUMSAMPLES];
  float average;
  uint8_t i;
  
  // take N samples in a row, with a slight delay
  for (i=0; i< NUMSAMPLES; i++) {
   samples[i] = analogRead(PIN);
   delay(10);
  }
  // average all the samples out
  average = 0;
  for (i=0; i< NUMSAMPLES; i++) {
     average += samples[i];
  }
  average /= NUMSAMPLES;

  
  // convert the value to resistance
  //average = 1023 / average - 1;
  //average = R0 / average;

  
  float steinhart;
  // convert the value to resistance
  average = average/1023;
  steinhart=average*R0/(1-average);
  steinhart=log(steinhart/R0);
  
  //steinhart = average / R0;     // (R/Ro)
  //steinhart = log(steinhart);                  // ln(R/Ro)
  steinhart /= B_parameter;                   // 1/B * ln(R/Ro)
  steinhart += 1.0 / (T0 + 273.15); // + (1/To)
  steinhart = 1.0 / steinhart;                 // Invert
  steinhart -= 273.15;                         // convert absolute temp to C
  
  return steinhart;
}

Sensors.h

Arduino
// This header file for sensors used Bio-Maker 2021 projects
// Team member: W.Che and Z.Di.
// Date: 2021.03.22

#ifndef _Sensors_H
#define _Sensors_H
#include <Arduino.h>

#endif

#define NTC_ANALOG_PIN 0

class NTC_temp {
    int PIN;
    
  public:
    NTC_temp ();
    NTC_temp (int);
    float read_ntc_sensor_temp (void);
};

Multi_Channel_Relay_Arduino_Library-master.zip

Arduino
This is the library for Multi_Channel_Relay_Arduino_Library.
No preview (download only).

Credits

Zhengao Di

Zhengao Di

2 projects • 1 follower
W. Che

W. Che

0 projects • 0 followers

Comments