Nathalia OchoaHunter JoynerJonathan Krawchuk
Published © GPL3+

Houseplant Babysitter - MEGR3171 Team #26

A system that will take care of your plant by monitoring soil moisture, light, temperature & humidity, watering automatically when needed.

IntermediateFull instructions provided4 hours53
Houseplant Babysitter - MEGR3171 Team #26

Things used in this project

Hardware components

Argon
Particle Argon
×3
Photo resistor
Photo resistor
×1
Gravity: Analog Capacitive Soil Moisture Sensor- Corrosion Resistant
DFRobot Gravity: Analog Capacitive Soil Moisture Sensor- Corrosion Resistant
×1
DHT11 Temperature & Humidity Sensor (4 pins)
DHT11 Temperature & Humidity Sensor (4 pins)
×1
0.96" OLED 64x128 Display Module
ElectroPeak 0.96" OLED 64x128 Display Module
×1
Capacitor 10 µF
Capacitor 10 µF
×2
Gikfun 12V DC Peristaltic Pump
×1
Adafruit 5V Linear Voltage Regulator
×1
5V Relay Module
×1
Breadboard (generic)
Breadboard (generic)
×3
Grove - Relay
Seeed Grove - Relay
×1
DC POWER JACK 2.1MM BARREL-TYPE PCB MOUNT
TaydaElectronics DC POWER JACK 2.1MM BARREL-TYPE PCB MOUNT
×1
Mains Power Cord, 2.1mm Barrel Plug
Mains Power Cord, 2.1mm Barrel Plug
×1

Software apps and online services

Maker service
IFTTT Maker service
Particle Build Web IDE
Particle Build Web IDE
Fritzing
Google Sheets
Google Sheets

Story

Read more

Schematics

Sensor Cluster circuit

Water Pump circuit

OLED Screen circuit

Code

Sensor Cluster

C/C++
This code gathers sensor data for a given plant, converting the analog readings into easy to percentages. After a set time interval the code publishes the data to google sheets and to the OLED remote health monitor.
Publish data is stored via Json and is parsed on the other side of the cloud connection.
All variables are cloud accessible, able to be retrieved and edited remotely. This includes the target moisture value at which the code will trigger the water pump via the pumping publish event.
// This is code to handle monitoring the health of a plant as part of an IOT plant babysitter
// Authors: Hunter Joyner, Nathalia Ochoa, Jonathan Krawchuk

// Include Required Libraries
#include <Adafruit_DHT_Particle.h>
#include <JsonParserGeneratorRK.h>
// Define DHT sensor pin and model
#define DHTPIN D2  
#define DHTTYPE DHT11

// Define sensor interval
int SensInterval = 20000;

SYSTEM_THREAD(ENABLED);

DHT dht(DHTPIN, DHTTYPE);
// Define variable names
int light;
int humidity;
int temp;
int moisture;
int analogReadmoisture;
int analogReadlight;
int tgtmoist = 30;
int MoistureHigh = 2800;
int MoistureLow = 1000;
unsigned long LastInterval; 
// Used for timekeeping, 32 bit unsigned number overflows ~49 days



void setup() {
	Serial.begin(9600);      // Define baud rate for serial communication 
    pinMode(A3, INPUT);      // Set pin A3 to input, Light sensor connected here
    pinMode(A0, INPUT);      // Set pin A0 to input, Moisture sensor connected here
	dht.begin();             // Begin communication with DHT11
	LastInterval = millis(); // Set time at startup as initial last interval
	Particle.variable("AnalogMoisture", &analogReadmoisture, INT);   
	// Allows retreval of analog moisture sensor value for calibration
    Particle.variable("AnalogLight", &analogReadlight, INT);          
    // Allows retreval of analog light sensor value for calibration
    Particle.variable("SensorInterval", &SensInterval, INT);         
    // Retrieves current sensor interval
    Particle.variable("MoistureLowMark", &MoistureLow, INT);         
    // Retrieves Low Moisture Setting
    Particle.variable("MoistureHighMark", &MoistureHigh, INT);       
    // Retrieves High Moisture Setting
    Particle.function("SetTargetMoisture", tgtmoisture);              
    // Sets variable by cloud
    Particle.function("SetMoistureHigh", moisthigh);                  
    // Sets variable by cloud
    Particle.function("SetMoistureLow", moistlow);                    
    // Sets variable by cloud

	delay(10000);   // Wait at end of setup, provides DH11 sensor time to warm up
}

void loop() {

    if ((millis() - LastInterval) > SensInterval) 
    // Waits until the Sensor interval time has passed before taking a new set of readings
    {
       analogReadmoisture = analogRead(A0);     
       // Store analog value for cloud access
       analogReadlight = analogRead(A3);
       temp = dht.getTempCelcius();             
       // calls for temperature in deg C from DHT11
       humidity = dht.getHumidity();            
       // calls for humidity in % relative from DHT11
       delay(6000);                             
       // DH11 communication takes a bit otherwise initial values will be 0 for up to 2 intervals
       light = map(analogRead(A3), 140, 0, 0, 100);         
       // Pulls analog sensor reading from light sensor pin A3, converts to % out of 100 via map func
       moisture = map(analogRead(A0), MoistureHigh, MoistureLow, 0, 100); 
       // Pulls analog sensor reading from moisture sensor pin A0, converts to % out of 100 via map func
       createEventPayload(temp, humidity, light, moisture); 
       // Calls for event payload void func at end of code which packages values via Json
       LastInterval = millis();                             
       // Sets current timer as last interval to enable the next waiting period
       Particle.publish("OLEDinfo", String::format("{\"temp\":%d,\"humidity\":%d,\"light\":%d,\"moisture\":%d}", temp, humidity, light, moisture)); 
       // Alternative Json packaging of data meant for OLED screen argon
      // Tells the argon to publish an event when soil moisture % gets below 30 %
        if (moisture<30){ 
        Particle.publish("WaterPump", "Pumping");}  
        // Triggers pump for set duration in pump argon
        else {
        }
    }
  

}
// Json packaging and publishing of sensor data for google sheets graphing
void createEventPayload(int temp, int humidity, int light, int moisture)
{
 JsonWriterStatic<256> jw;
 {
   JsonWriterAutoObject obj(&jw);

   jw.insertKeyValue("temp", temp);
   jw.insertKeyValue("humidity", humidity);
   jw.insertKeyValue("light", light);
   jw.insertKeyValue("moisture_percentage", moisture);

 }
 Particle.publish("readingsplantbabysitter", jw.getBuffer(), PRIVATE);  // Publishes sensor data to google sheets via IFTT integration
}

// Responds to cloud function to update the variable remotely
int tgtmoisture(String tgt)            
    {
        if(tgt != "0")     // Sanity Check
        {
            tgtmoist = tgt.toInt();
            return 1;
        }
        
        else return -1;
    }
    
// Responds to cloud function to update the variable remotely    
int moisthigh(String high)            
    {
        if(high != "0")     // Sanity Check
        {
            MoistureHigh = high.toInt();
            return 1;
        }
        
        else return -1;
    }
// Responds to cloud function to update the variable remotely    
int moistlow(String low)            
    {
        if(low != "0")     // Sanity Check
        {
            MoistureLow = low.toInt();
            return 1;
        }
        
        else return -1;
    }

Water Pump

C/C++
Responds to the Sensor Cluster, turning on the pump for a set duration when told to do so. The duration the pump stays on can be retrieved and edited via cloud functions. The pump can also be manually triggered via cloud.
// This is code to handle the control of a peristaltic water pump as part of an IOT plant babysitter
// Authors: Hunter Joyner, Nathalia Ochoa, Jonathan Krawchuk

SYSTEM_THREAD(ENABLED);     

int WaterPumpPin = D7;      // Set Digital pin 7 as the water pump pin
int PumpOn = 10000;         // Set initial pumping duration


void setup() {
  pinMode(WaterPumpPin,OUTPUT);                                
  // Set pin with pump to output
  Particle.subscribe("WaterPump", PumpHandler);                
  // Subscribe to water pump on call from SensorCluster
  Particle.function("PumpDuration", SetPumpTime);              
  // Cloud function enables updating of pump duration setting
  Particle.variable("CurrentPumpTimeSetting", &PumpOn, INT);  
  // Cloud function to retreve current duration setting
  Particle.function("ManualPumping", TurnOnPump);              
  // Clout function to manually trigger pump
}


void loop() { 
    // nada
}


// Handler function, responds with set action based on subscribe data
void PumpHandler(const char *event, const char *data)
{

  if (strcmp(data,"Pumping")==0) {
    Particle.publish("PumpResponding");      
    // Call to publish event confirming it heard the sensor cluster
    digitalWrite(WaterPumpPin,HIGH);        
    // Sends Digital high signal to 5V relay, turning on pump
    delay(PumpOn);                           
    // Lets pump run for 10 seconds, often first on cycle will not pump water as the tube empties overtime, this is desired to prevent spills
    digitalWrite(WaterPumpPin,LOW);          
    // Sends Digital low signal to 5V relay, turning off pump
  }
  else 
    digitalWrite(WaterPumpPin,LOW);          
    // If event is somehow published otherwise, we do not want the pump to turn on! Water kills!
  }

int SetPumpTime(String duration)           
// Responds to Pump Duration function to update the variable remotely
    {
        if(duration != "0")     // Sanity Check
        {
            PumpOn = duration.toInt();
            return 1;
        }
        
        else return -1;
    }

int TurnOnPump(String action)               
// Responds to Manual Pumping function to turn on the pump
    {
        if(action == "TurnOn")  // Sanity Check
        {
          Particle.publish("PumpResponding");     
          // Call to publish event confirming it heard the sensor cluster
          digitalWrite(WaterPumpPin,HIGH);         
          // Sends Digital high signal to 5V relay, turning on pump
          delay(PumpOn);                           
          // Lets pump run for 10 seconds, often first on cycle will not pump water as the tube empties overtime, this is desired to prevent spills
          digitalWrite(WaterPumpPin,LOW);          
          // Sends Digital low signal to 5V relay, turning off pump   
          return 1; 
        }
        else return -1;
    }

OLED Screen

C/C++
Subscribes to the published plant data event from the sensor cluster. The code parses the packaged data and assigns them to local variables. The code then displays the plant health information on the OLED screen, periodically swapping between each parameter.
// This is code to handle OLED display of plant conditions remotely as part of an IOT plant babysitter
// Authors: Hunter Joyner, Nathalia Ochoa, Jonathan Krawchuk

// include req libraries
#include "Particle.h"
#include <Adafruit_SSD1306.h>
#include "application.h"
#define OLED_RESET D4

SYSTEM_THREAD(ENABLED);

Adafruit_SSD1306 oled(OLED_RESET);

void logEvent(const char *event, const char *data); //debug log func

// assign integer names
int temp;
int humidity;
int light;
int moisture;

void setup() {
    Particle.subscribe("OLEDinfo", OLED_Handler); 
    // Subscribe to sensor cluster data event, posted in alternative Json format
    oled.begin();  
    Time.zone(-4); // Set eastern std time
    Serial.begin(9600);
}

void loop() {
    
    // OLED time
    
oled.clearDisplay();
  delay(200);
  oled.setTextSize(2);
  oled.setTextColor(WHITE);
  oled.setCursor(0,0);
  oled.print("Time");
  oled.setCursor(0,32);
  oled.setTextSize(3);
  oled.print(Time.format(Time.now(), "%I:%M%p"));;
  oled.setTextColor(BLACK, WHITE); // 'inverted' text
  oled.display();
  delay(2500);
  
    // OLED Temperature
     
  oled.clearDisplay();
  delay(200);
  oled.setTextSize(2);
  oled.setTextColor(WHITE);
  oled.setCursor(0,0);
  oled.print("Temp");
  oled.setCursor(0,32);
  oled.setTextSize(3);
  oled.print(temp);
  oled.print(" deg");
  oled.setTextColor(BLACK, WHITE); // 'inverted' text
  oled.display();
  delay(2500);


  // OLED Light
  
  oled.clearDisplay();
  delay(200);
  oled.setTextSize(2);
  oled.setTextColor(WHITE);
  oled.setCursor(0,0);
  oled.print("Light");
  oled.setCursor(0,32);
  oled.setTextSize(3);
  oled.print(light);
  oled.print(" %");
  oled.setTextColor(BLACK, WHITE); // 'inverted' text
  oled.display();
  delay(2500);
  
    // OLED Moisture
    
  oled.clearDisplay();
  delay(200);
  oled.setTextSize(2);
  oled.setTextColor(WHITE);
  oled.setCursor(0,0);
  oled.print("Moisture");
  oled.setCursor(0,32);
  oled.setTextSize(3);
  oled.print(moisture);
  oled.print(" %");
  oled.setTextColor(BLACK, WHITE); // 'inverted' text
  oled.display();
  delay(2500);
  
 // OLED Water
  
  oled.clearDisplay();
  delay(200);
  oled.setTextSize(2);
  oled.setTextColor(WHITE);
  oled.setCursor(0,0);
  oled.print("Humidity");
  oled.setCursor(0,32);
  oled.setTextSize(3);
  oled.print(humidity);
  oled.print(" %");
  oled.setTextColor(BLACK, WHITE); // 'inverted' text
  oled.display();
  delay(2500);

}

// Logging function for debug purposes
void logValue(int indent, const char *key, JSONValue value) { 
    String prefixStr;
    if (indent > 0) {
        prefixStr.reserve(indent);
        for(int ii = 0; ii < indent; ii++) {
            prefixStr += String(" ");
        }
    }
    if (key) {
        prefixStr += String::format("key=\"%s\" ", key);
    }

    if (value.isNull()) {
        Log.info("%snull", prefixStr.c_str());
    }
    if (value.isNumber()) {
        Log.info("%sNumber: %s", prefixStr.c_str(), value.toString().data());
    }
    if (value.isBool()) {
        Log.info("%sBool: %s", prefixStr.c_str(), value.toString().data());
    }
    if (value.isString()) {
        Log.info("%sString: \"%s\"", prefixStr.c_str(), value.toString().data());
    }
    if (value.isArray()) {
        Log.info("%sArray", prefixStr.c_str());

        JSONArrayIterator iter(value);
        while (iter.next())
        {
            logValue(indent + 2, 0, iter.value());
        }
    }
    if (value.isObject()) {
        Log.info("%sObject", prefixStr.c_str());

        JSONObjectIterator iter(value);
        while (iter.next())
        {
            logValue(indent + 2, iter.name().data(), iter.value());
        }
    }

}

// Parse json data from subcribe function and assign variables
void OLED_Handler(const char *event, const char *data) 
{
    JSONValue outerObj = JSONValue::parseCopy(data);
    JSONObjectIterator iter(outerObj);
    while (iter.next())
    {
        if (iter.name() == "temp")       
        // Looks for which name is assigned to which set of data
        {
            temp = iter.value().toInt(); 
            // Assigns data value as integer under local variable name
        }
        else
        if (iter.name() == "humidity") 
        {
            humidity = iter.value().toInt();
        }
        else
        if (iter.name() == "light") 
        {
            light = iter.value().toInt();
        }
        else
        if (iter.name() == "moisture") 
        {
            moisture = iter.value().toInt();
        }

    }
}

Credits

Nathalia Ochoa

Nathalia Ochoa

0 projects • 1 follower
Hunter Joyner

Hunter Joyner

0 projects • 2 followers
Jonathan Krawchuk

Jonathan Krawchuk

0 projects • 2 followers

Comments

Add projectSign up / Login