Amol Disale
Published © GPL3+

SmartMali

A Smart Pot for Indoor Gardening! {Wondering what Mali means?}

IntermediateFull instructions provided2 days5,784

Things used in this project

Hardware components

SparkFun ESP8266 Thing - Dev Board
SparkFun ESP8266 Thing - Dev Board
×1
SparkFun Soil Moisture Sensor (with Screw Terminals)
SparkFun Soil Moisture Sensor (with Screw Terminals)
×1
Light Dependent Resistor (LDR) 12mm
Light Dependent Resistors are very useful especially in light/dark sensor circuits. The light detector itself is just 12mm in diameter with a lens and epoxy sealed metal package.
×1
Ultrasonic Sensor - HC-SR04 (Generic)
Ultrasonic Sensor - HC-SR04 (Generic)
×1
Seeed Studio Grove - Temperature&Humidity Sensor (High-Accuracy & Mini)
Guide for use:- http://wiki.seeed.cc/Grove-TemptureAndHumidity_Sensor-High-Accuracy_AndMini-v1.0/
×1
ADS1115 16-Bit ADC Module
It's a I2C based ADC Module having 4 Channel with Programmable Gain Amplifier
×1
Resistor 1k ohm
Resistor 1k ohm
Also required 100K (Quantity - 1),10K (Quantity - 4), 330 (Quantity - 2)
×3
4Pin 20CM XH2.54MM Female Connector Cable
Also used the male connectors for this cable!
×3
2Pin 20CM XH2.54MM Female Connector Cable
Also used the male connectors for this cable!
×2
Seeed Studio Grove - Universal 4 Pin Cable
Only female grove cable was used!
×1
Single Row 2.54mm Straight Female Headers
10 pins Headers used (Quantity - 3)
×1
Single Row 2.54mm Straight Mmale Headers
3 pins Header used (Quantity - 1)
×1
General Purpose PCB
×1
R385 High Lift DC12V Self-Priming Water Pump
This water pump has more lift power than the RS-360, bigger outlets diameters, and the rubber retainers helps to secure the motor to some stationary object. Also i used Polyurethane(PU) based pipe for this pump with Outside Diameter : 8mm and Inside Diameter : 6mm.
×1
STMicroelectronics STP55NF06 MOSFET's
To control DC Water Pump and LED Grow Light.
×2
2n2222 Transistor
To switch MOSFET's ON and OFF.
×2
4 Channel 5V Relay Module with Optocoupler Isolation
This is a 5V 4-Channel Relay interface module, for controlling various appliances, and other equipments with large current. It can be controlled directly by microcontroller (3.3V or 5V).
×1

Software apps and online services

Arduino IDE
Arduino IDE
Cayenne
myDevices Cayenne

Hand tools and fabrication machines

Soldering iron (generic)
Soldering iron (generic)
3D Printer (generic)
3D Printer (generic)
Laser Cutting Machine
To cut an acrylic base for the project!

Story

Read more

Custom parts and enclosures

Base

Motor (Water pump), Solid state relay and PCB is attached to this acrylic sheet which has been cut using Laser Cutting Machine.

Upper case covering for Base part 1

For covering the base

Upper case covering for Base part 2

For covering the base

LED Grow Light Part 1

This part holds the led strip light in place

Soil Moisture sensor holder part 1

For holding the soil moisture sensor

Soil Moisture sensor holder part 2

For holding the soil moisture sensor

Schematics

Block Diagram

It's an high level overview which will help you understand how the setup works on a higher level.

Electronic Components (Sensors and Actuators) Connections with ESP8266

Code

ESP8266 SmartMali Code

Arduino
You need to install ESP8266 Add-on in your arduino ide.
Also you will need to install below arduino libraries required for successfully compiling the below code.
Grove – Temperature & Humidity Sensor (High-Accuracy &Mini) –
https://github.com/Seeed-Studio/Grove_Temper_Humidity_TH02

Cayenne MQTT ESP8266 Library –
https://github.com/myDevicesIoT/Cayenne-MQTT-ESP8266

ADS1115 ADC Library –
https://github.com/adafruit/Adafruit_ADS1X15
/*
 * 
 * Make sure you install the ESP8266 Board Package via the Arduino IDE Board Manager 
 * and select the correct ESP8266 board before compiling. 
 * Also make sure all the required libraries are installed before compiling this code
 * ADS1115 uses I2C for communication
 * Also we need Adafruit ADS1115 library for reading the analog pins of ADS1115
 * TH02 is a digital Temperature and Humidity sensor that uses I2C for communication
 * Also we need Seeedstudio TH02 library for reading this sensor
 * Grove – Temperature & Humidity Sensor (High-Accuracy &Mini) –
 * https://github.com/Seeed-Studio/Grove_Temper_Humidity_TH02
 * In ESP8266 EEPROM library uses one sector of flash located just after the SPIFFS.
 */

//#define CAYENNE_DEBUG
#define CAYENNE_PRINT Serial

#include <CayenneMQTTESP8266.h>
#include <ESP8266WiFi.h>
#include <Wire.h>
#include <Adafruit_ADS1015.h>
#include <TH02_dev.h>
#include "Arduino.h"
#include <EEPROM.h>

Adafruit_ADS1115 ads;  /* Use this for the 16-bit version */

// WiFi network info. 
char ssid[] = "Litasddfgchgdart";       //Add your home wifi network ssid  
char wifiPassword[] = "Au23ftfdgdfg11";      //Add wifi password 

// Cayenne authentication info. This should be obtained from the Cayenne Dashboard. 
char username[] = "9c5e0-retrtrr-65c9-f526";       //Add your username 
char password[] = "98b38f9dfgdfgdfgdfgc3fd9819be59ddc03c8e";   //Add your password 
char clientID[] = "1ccd5ccxgdfgdfgfgff3788e";       //Add your clientID 


unsigned long lastMillis = 0;

//GPIO Connections
const int trigPin = 12;
const int echoPin = 13;

const int mosfet1 = 5;   //ESP8266 GPIO5 Connected to motor (water pump) 
const int mosfet2 = 4;   //ESP8266 GPIO4 Connected to led grow light 

/*
 * The current address in the EEPROM (i.e. which byte we're going to write to next) 
 * at address 0 we are going to write light level (LDR) threshold 
 * at address 20 we are going to write soil moisture sensor threshold
*/

//Variables for LDR light level data storage
int lightLevel = 0;
int ldr_addr = 0;

//Variables for soil moisture level data storage
int moistureLevel = 0;
int moisture_addr = 20;

//Variables for water level data storage
int waterLevelcm = 0;
int waterContainerHeight = 90;  //in cm water reservoir container's total height
int waterLevelmin = 10;
int motorONTime = 1000;        //Motor (Water Pump) will be ON for this amount of time

//Variables for Temperature and Humidity data storage
float temperature = 0;
float humidity = 0;

//WiFi Variables
long wifiRSSI = 0;

/*
 * Publish data every 30 seconds (30000 milliseconds). 
 * Change this value to publish at a different interval.
 */
int uploadInterval = 30000; //in milliseconds

/*
 * This function reads an integer value in the EEPROM memory
*/
int eepromReadInt(int address){
   int value = 0x0000;
   value = value | (EEPROM.read(address) << 8);
   value = value | EEPROM.read(address+1);
   return value;
}

/*
 * This function writes an integer value in the EEPROM memory
*/
void eepromWriteInt(int address, int value){
   EEPROM.write(address, (value >> 8) & 0xFF );
   EEPROM.write(address+1, value & 0xFF);
   
  /*
  * EEPROM.write does not write to flash immediately, 
  * instead you must call EEPROM.commit() 
  * whenever you wish to save changes to flash. 
  */
   EEPROM.commit();
}

void setup() {
  //configure GPIO pins as input/output
 pinMode(mosfet1, OUTPUT);     // Initialize the mosfet1 pin as an output 
 pinMode(mosfet2, OUTPUT);     // Initialize the mosfet2 pin as an output 

 /*
  * The MOSFET circuit designed is Active low 
  * Means if we provide LOW voltage to mosfet1 or mosfet2 it will turn ON
  */
 digitalWrite(mosfet1, HIGH);    // Turn the mosfet1 OFF 
 digitalWrite(mosfet2, HIGH);    // Turn the mosfet2 OFF 
   
  pinMode(trigPin, OUTPUT);
  pinMode(echoPin, INPUT);

 /*
  * You need to call EEPROM.begin(size) before you start reading or writing, 
  * size being the number of bytes you want to use. 
  * Size can be anywhere between 4 and 4096 bytes.
  */
  EEPROM.begin(512); // Initlize EEPROM with 512 Bytes
  Serial.begin(9600);

  /* 
  * Power up,delay 150ms,until voltage is stable
  */
  delay(150);
  
  Serial.println("");
  
  Serial.println(" ");
  Serial.println("------------------------------------------------------------------------ ");
  
  Serial.println("Smart Mali Project - A Smart Plant Pot!");
    
  ads.setGain(GAIN_ONE);        // 1x gain   +/- 4.096V  1 bit = 0.125mV
  
  ads.begin();
  
  /* Reset HP20x_dev */
  TH02.begin();
  delay(100);
  
   /* Determine TH02_dev is available or not */ 
   int TH02_available = TH02.isAvailable();
   Serial.print("TH02 Available or not:- ");
   Serial.println(TH02_available);
   if (TH02_available == 0)
   {
     Serial.println("TH02_dev is available.\n"); 
   }
   else
   {
     Serial.println("TH02_dev is not available.\n"); 
   }
  
  delay(1000);                      // Wait for a second
  
  Cayenne.begin(username, password, clientID, ssid, wifiPassword);
  
  Serial.println(" ");
  Serial.println("------------------------------------------------------------------------ ");
}

void loop() {
  Cayenne.loop(); 
   
   if (millis() - lastMillis > uploadInterval) {
    lastMillis = millis();

    long val = 0;
    for (int number = 0; number < 10; number++) 
    {
        lightLevel = ads.readADC_SingleEnded(0);
        val = val + lightLevel;
        delay(100);
    }
    lightLevel = val/10;

    val = 0;
    for (int number = 0; number < 10; number++) 
    {
      moistureLevel = ads.readADC_SingleEnded(3);  
      val = val + moistureLevel;
      delay(100);
    }
    moistureLevel = val/10;

    Serial.print("Light Level: ");   
    Serial.print(lightLevel);
    Serial.print("\t Moisture Level: ");
    Serial.println(moistureLevel);

    /*
     * The ultrasonic sensor is triggered by a HIGH pulse of 10 or more microseconds.
     * Give a short LOW pulse beforehand to ensure a clean HIGH pulse
     */
    digitalWrite(trigPin, LOW);
    delayMicroseconds(2);
    digitalWrite(trigPin, HIGH);
    delayMicroseconds(10);
    digitalWrite(trigPin, LOW); 

    /*
     * Read the signal from the sensor: a HIGH pulse whose
     * duration is the time (in microseconds) from the sending
     * of the ping to the reception of its echo off of an object.
     */
    long duration = pulseIn(echoPin, HIGH);
    waterLevelcm = waterContainerHeight - int(microsecondsToCentimeters(duration));
    Serial.print("Water Level: ");
    Serial.println(waterLevelcm);

    temperature = TH02.ReadTemperature(); 
    humidity = TH02.ReadHumidity();  
    Serial.print("Temperature: ");   
    Serial.print(temperature);
    Serial.print("\t Humidity: ");
    Serial.println(humidity);

    /*
     * the received wifi signal strength:
     */
    wifiRSSI = WiFi.RSSI(); 
    Serial.print("RSSI:");
    Serial.println(wifiRSSI);
    Serial.println("");

    int data = eepromReadInt(ldr_addr);
    Serial.print("LDR Data EEPROM Read: ");   Serial.println(data);
    if (lightLevel < data)
    {
      digitalWrite(mosfet2, LOW);    // Turn the Relay2 ON
      Serial.println("LED GROW LIGHT is turned ON");
      Cayenne.virtualWrite(12, 21);
    }
    else
    {
      digitalWrite(mosfet2, HIGH);    // Turn the Relay2 OFF
      Serial.println("LED GROW LIGHT is turned OFF");
      Cayenne.virtualWrite(12, 20);
    }
    
    data = eepromReadInt(moisture_addr);
    Serial.print("Moisture Data EEPROM Read: ");   Serial.println(data);
    if((moistureLevel < data) && (waterLevelcm > waterLevelmin))
    {
      digitalWrite(mosfet1, LOW);    // Turn the Relay1 ON
      Serial.println("Motor (Water Pump) is turned ON");
      
      delay(motorONTime);
      
      digitalWrite(mosfet1, HIGH);    // Turn the Relay1 OFF
      Serial.println("Motor (Water Pump) is turned OFF");
    }
    else
    {
      digitalWrite(mosfet1, HIGH);    // Turn the Relay1 OFF
      Serial.println("Motor (Water Pump) is turned OFF");
    }
       
    /*
     * Write data to Cayenne here. 
     * This code sends the current sensor readings to Cayenne.
     */
    Cayenne.virtualWrite(1, lastMillis);
    Cayenne.luxWrite(2, lightLevel);
    Cayenne.virtualWrite(3, moistureLevel);
    Cayenne.virtualWrite(4, waterLevelcm);
    Cayenne.celsiusWrite(5, temperature);
    Cayenne.virtualWrite(6, humidity);
    Cayenne.virtualWrite(7, wifiRSSI);
    Cayenne.virtualWrite(12, 0);
    
    Serial.println(" ");
    Serial.println("------------------------------------------------------------------------ ");
  }
  
}

long microsecondsToCentimeters(long microseconds)
{
  /*
   * The speed of sound is 340 m/s or 29 microseconds per centimeter.
   * The ping travels out and back, so to find the distance of the
   * object we take half of the distance travelled.
   */
  /********************************************Ultrasonic sensors****************************************************** 
  Speed of sound = 340 m/s 
  Now,         1 metre = 100 centimetre            and              1 seconds = 1000000 microseconds 
  Speed of sound = 340 * 100 cm/(1000000 microseconds) = 0.034 cm per us = (1/29.412) cm per us  is approx. (1/29) cm per us 
  The Ultrasonic burst travels out & back.So to find the distance of the object we have to divide the time the echo pin was high by 2 
  Distance = (Time echo pin was high/2) * speed of sound 
          = (Time echo pin was high/2) * (1/29) 
  *********************************************************************************************************************/   

  return microseconds / 29 / 2;
}

CAYENNE_IN(8)
{
  CAYENNE_LOG("CAYENNE_IN_DEFAULT(%u) - %s, %s", request.channel, getValue.getId(), getValue.asString());

  /*
   * Process message here. 
   * If there is an error set an error message 
   * using getValue.setError(), e.g getValue.setError("Error message");
   */
  Serial.print("Data Received:-= ");
  Serial.println(getValue.asInt());
  int data = getValue.asInt();
  
  /*
   * The ultrasonic sensor is triggered by a HIGH pulse of 10 or more microseconds.
   * Give a short LOW pulse beforehand to ensure a clean HIGH pulse
   */
  digitalWrite(trigPin, LOW);
  delayMicroseconds(2);
  digitalWrite(trigPin, HIGH);
  delayMicroseconds(10);
  digitalWrite(trigPin, LOW);
   
  /*
   * Read the signal from the sensor: a HIGH pulse whose
   * duration is the time (in microseconds) from the sending
   * of the ping to the reception of its echo off of an object.
   */
  long duration = pulseIn(echoPin, HIGH);
  waterLevelcm = waterContainerHeight - int(microsecondsToCentimeters(duration));
  Serial.print("Water Level: ");
  Serial.println(waterLevelcm);
  
  if ((data == 1) && (waterLevelcm > waterLevelmin))
  {
      digitalWrite(mosfet1, LOW);    // Turn the Relay1 ON
      Serial.println("Motor (Water Pump) is turned ON");
      Cayenne.virtualWrite(12, 11);
  }
  else
  {
      digitalWrite(mosfet1, HIGH);    // Turn the Relay1 OFF
      Serial.println("Motor (Water Pump) is turned OFF");
      Cayenne.virtualWrite(12, 10);
  }
}

CAYENNE_IN(9)
{
  CAYENNE_LOG("CAYENNE_IN_DEFAULT(%u) - %s, %s", request.channel, getValue.getId(), getValue.asString());
  
  /*
   * Process message here. 
   * If there is an error set an error message 
   * using getValue.setError(), e.g getValue.setError("Error message");
   */
  Serial.print("Data Received:-= ");
  Serial.println(getValue.asInt());
  int data = getValue.asInt();
  
  if (data == 1)
  {
      digitalWrite(mosfet2, LOW);    // Turn the Relay2 ON
      Serial.println("LED GROW LIGHT is turned ON");
      Cayenne.virtualWrite(12, 21);
  }
  else
  {
      digitalWrite(mosfet2, HIGH);    // Turn the Relay2 OFF
      Serial.println("LED GROW LIGHT is turned OFF");
      Cayenne.virtualWrite(12, 20);
  }
}

CAYENNE_IN(10)
{
  CAYENNE_LOG("CAYENNE_IN_DEFAULT(%u) - %s, %s", request.channel, getValue.getId(), getValue.asString());
  
  /*
   * Process message here. 
   * If there is an error set an error message 
   * using getValue.setError(), e.g getValue.setError("Error message");
   */
  Serial.print("Data Received:-= ");
  Serial.println(getValue.asInt());
  int data = 0;
  data = getValue.asInt();
  if (data == 1)
  {
      Serial.println("Starting the LDR sampling");
      Cayenne.virtualWrite(12, 31);
      
      long val = 0;
      for (int number = 0; number < 10; number++) 
      {
        lightLevel = ads.readADC_SingleEnded(0);
        val = val + lightLevel;
        delay(100);
      }
          
      lightLevel = val/10;

      /*
       * We are doing LDR sampling and then averaging the value
       * Then this averaged value will act as the LDR threshold below 
       * which if Light level falls we will swiitch ON the LED grow light.
       * For executing this you just have to press LDR Sample Start button 
       * on the mobile app or Cayenne Dashboard
       * write the value to the appropriate byte of the EEPROM.
       * This value will remain there even when the board is turned off.
       */
      eepromWriteInt(ldr_addr, lightLevel);     
  }
}

CAYENNE_IN(11)
{
  CAYENNE_LOG("CAYENNE_IN_DEFAULT(%u) - %s, %s", request.channel, getValue.getId(), getValue.asString());

  /*
   * Process message here. 
   * If there is an error set an error message 
   * using getValue.setError(), e.g getValue.setError("Error message");
   */
  Serial.print("Data Received:-= ");
  Serial.println(getValue.asInt());
  int data = 0;
  data = getValue.asInt();
  if (data == 1)
  {
      Serial.println("Starting the Soil Moisture sampling");
      Cayenne.virtualWrite(12, 41);

      long val = 0;
      for (int number = 0; number < 10; number++) 
      {
        moistureLevel = ads.readADC_SingleEnded(3);  
        val = val + moistureLevel;
        delay(100);
      }
      
      moistureLevel = val/10;


      /*
       * We are doing soil moisture sampling and then averaging the value
       * Then this averaged value will act as the soil moisture threshold below 
       * which if soil moisture level falls we will swiitch ON the Motor (water pump).
       * For executing this you just have to press Soil Moisture Sampling button 
       * on the mobile app or Cayenne Dashboard
       * write the value to the appropriate byte of the EEPROM.
       * This value will remain there even when the board is turned off.
       */
      eepromWriteInt(moisture_addr, moistureLevel);
  }
}

Code

Credits

Amol Disale

Amol Disale

9 projects • 100 followers
I am passionate about the idea of open-source hardware and software. I am ready to help in prototyping IoT, Smart Home, and other products.

Comments