element14 Community
element14 Community
    Register Log In
  • Site
  • Search
  • Log In Register
  • Community Hub
    Community Hub
    • What's New on element14
    • Feedback and Support
    • Benefits of Membership
    • Personal Blogs
    • Members Area
    • Achievement Levels
  • Learn
    Learn
    • Ask an Expert
    • eBooks
    • element14 presents
    • Learning Center
    • Tech Spotlight
    • STEM Academy
    • Webinars, Training and Events
    • Learning Groups
  • Technologies
    Technologies
    • 3D Printing
    • FPGA
    • Industrial Automation
    • Internet of Things
    • Power & Energy
    • Sensors
    • Technology Groups
  • Challenges & Projects
    Challenges & Projects
    • Design Challenges
    • element14 presents Projects
    • Project14
    • Arduino Projects
    • Raspberry Pi Projects
    • Project Groups
  • Products
    Products
    • Arduino
    • Avnet & Tria Boards Community
    • Dev Tools
    • Manufacturers
    • Multicomp Pro
    • Product Groups
    • Raspberry Pi
    • RoadTests & Reviews
  • About Us
    About the element14 Community
  • Store
    Store
    • Visit Your Store
    • Choose another store...
      • Europe
      •  Austria (German)
      •  Belgium (Dutch, French)
      •  Bulgaria (Bulgarian)
      •  Czech Republic (Czech)
      •  Denmark (Danish)
      •  Estonia (Estonian)
      •  Finland (Finnish)
      •  France (French)
      •  Germany (German)
      •  Hungary (Hungarian)
      •  Ireland
      •  Israel
      •  Italy (Italian)
      •  Latvia (Latvian)
      •  
      •  Lithuania (Lithuanian)
      •  Netherlands (Dutch)
      •  Norway (Norwegian)
      •  Poland (Polish)
      •  Portugal (Portuguese)
      •  Romania (Romanian)
      •  Russia (Russian)
      •  Slovakia (Slovak)
      •  Slovenia (Slovenian)
      •  Spain (Spanish)
      •  Sweden (Swedish)
      •  Switzerland(German, French)
      •  Turkey (Turkish)
      •  United Kingdom
      • Asia Pacific
      •  Australia
      •  China
      •  Hong Kong
      •  India
      •  Japan
      •  Korea (Korean)
      •  Malaysia
      •  New Zealand
      •  Philippines
      •  Singapore
      •  Taiwan
      •  Thailand (Thai)
      •  Vietnam
      • Americas
      •  Brazil (Portuguese)
      •  Canada
      •  Mexico (Spanish)
      •  United States
      Can't find the country/region you're looking for? Visit our export site or find a local distributor.
  • Translate
  • Profile
  • Settings
Mixing Electronics & Water
  • Challenges & Projects
  • Project14
  • Mixing Electronics & Water
  • More
  • Cancel
Mixing Electronics & Water
Blog (Semi)Automated Plant Irrigation System
  • Blog
  • Forum
  • Documents
  • Events
  • Polls
  • Files
  • Members
  • Mentions
  • Sub-Groups
  • Tags
  • More
  • Cancel
  • New
Join Mixing Electronics & Water to participate - click to join for free!
  • Share
  • More
  • Cancel
Group Actions
  • Group RSS
  • More
  • Cancel
Engagement
  • Author Author: urkraft
  • Date Created: 1 Feb 2018 9:50 PM Date Created
  • Views 8664 views
  • Likes 17 likes
  • Comments 81 comments
  • ardintermediate
  • arduino_projects
  • conservation_projects
  • mixewaterch
  • openarduinoch
Related
Recommended

(Semi)Automated Plant Irrigation System

urkraft
urkraft
1 Feb 2018

Semi-automated Plant Irrigation System

Summary

This is an automated system for watering my olive tree.

 

The main components are:

  • A soil moisture sensor
  • A 10 gallon bucket of water with a water level sensor
  • An Adafruit Feather HUZZAH ESP8266 microcontroller (hereafter referred to as “feather”)
  • A peristaltic water pump

 

The two sensors are connected to inputs of the feather, and the water pump is controlled by an output signal from the feather. There are two built in LED’s on the feather:

  • Red: used to indicate that the soil is dry enough to warrant watering
  • Blue: Used to indicate that the bucket is empty (requires a refill of water)

 

There are three criteria which all have to be met in order to turn on the pump (and water the plant):

  1. There must be water in the bucket
  2. The lack of moisture in the soil must be greater than a specified threshold value
  3. A specified minimum amount of time must have elapsed since the pump was last turned off while watering.

 

Any one of the following criteria will cause the pump to be turned off:

  • No water left in the bucket
  • The moisture level of the soil is higher than a specified threshold value (this criteria also triggers starting of the timer which keeps track of the amount of time that has elapsed since the pump was turned off).

 

Basically, the feather just goes in a loop checking the criteria above and taking the necessary action. Each loop iteration takes approximately 20-30 seconds to complete.

 

A feature that I would also like to implement is to have the system send me a message whenever it detects that the water bucket is empty (and possibly send a new reminder each day for a week before giving up). Unfortunately I have not found any official libraries with SMTP capabilities that I can use to accomplish this, so I do not know when or if I will be able to accomplish this goal.

 

I have tested the system quite a bit while implementing it (during the last 2 days) and feel confident that I have found and fixed all of the problems I have encountered along the way, but experience has taught me that the test of time is a very important test – and that test has only just begun.

 

Parts used

  • 1 Adafruit Feather HUZZAH ESP8266 (https://learn.adafruit.com/adafruit-feather-huzzah-esp8266/overview)
  • 1 5v 2.1A DC power supply w/micro usb contact (for the HUZZAH)
  • 1 12v 1A DC power supply (https://www.kjell.com/no/produkter/elektro-og-verktoy/stromforsyning/stromforsyning/ac-dc/fast-utgangsspenning/ac-dc-stromadapter-12-v-(dc)-12-w-p44382)
  • 1 power jack for the 12v power supply
  • 1 soil moisture sensor (https://www.kjell.com/no/produkter/elektro-og-verktoy/arduino/moduler/luxorparts-jordfuktmaler-p87941)
  • 1 water level switch NC (https://www.kjell.com/no/produkter/elektro-og-verktoy/elektronikk/electromechanics/strombrytere/nivabrytere/nivastrombryter-nc-p36037)
  • 1 22k ohm 1/8w resistor
  • 2 10k 1/8w resistor
  • 1 33 ohm 1/4w resistor
  • 2 1N4001 diodes
  • 1 Peristaltic Pump (https://www.kjell.com/no/produkter/elektro-og-verktoy/elektronikk/electromechanics/motorer/luxorparts-vaeskepumpe-peristaltisk-p90782)
  • 1 VR05R241A single pole DIL relay (https://www.kjell.com/no/produkter/elektro-og-verktoy/elektronikk/electromechanics/releer/1-polet-dil-rele-5-v-dc-0-5-a-30-v-p36110)
  • 1 Breadboard
  • Assorted jumper wires

 

Schematic Drawing

image

Breadboard

image

Code

#include 

//==========
// CONSTANTS
//==========

// wifi
const char* MY_SSID     = "";
const char* PASSWORD = "";

// I/O
const int LED = 0;  // (Output) indicates pump status (pump ON => LOW signal => lit)
const int PUMP = 4; // (Output) turns water pump on (set LOW) and off (set HIGH)
const int WATER_EMPTY_LED = 2;  // (Output) indicates water reservoir status 
                                // (empty => LOW signal => lit)
const int WATER_LEVEL = 14;  // (Input) to detect water in reservoir (water => HIGH)

// A0 : ADC (Analog Input) used to read soil humidity: high value => dry, low value => wet 
// Max value is approx. 825 - bone dry
// Min value is approx. 470 - drowning in water
const int PUMP_ON_THRESHOLD = 700;  // ADC input value >= this value => Turn on water pump
const int PUMP_OFF_THRESHOLD = 650; // ADC input value <= this value => Turn off water pump
                                    // (and do not turn on again for at least a day)
const long MINIMUM_TIME = 1000 * 60 * 60 * 24; // minimum time between watering (1 day)

//==========
// VARIABLES
//==========

// timing
unsigned long previousMillis = 0;
unsigned long currentMillis = 0;

// soil moisture reading
int moisture = 800; // previous 
int tmpMoisture = 0;  //  new (temporary)

//======
// SETUP
//======

void setup() {
  // initialize Serial
  Serial.begin(115200);
  delay(100);

  Serial.println();
  Serial.println();
  Serial.print("Connecting to ");
  Serial.println(MY_SSID);

  // initialize info
  WiFi.begin(MY_SSID, PASSWORD);
  WiFi.config(IPAddress(192, 168, 33, 95), IPAddress(192, 168, 33, 1), IPAddress(192, 168, 33, 1));
  
  while (WiFi.status() != WL_CONNECTED) {
    delay(500);
    Serial.print(".");
  }

  Serial.println("");
  Serial.println("WiFi connected");  
  Serial.println("IP address: ");
  Serial.println(WiFi.localIP());

  Serial.println();
  Serial.println();

  Serial.println("Soil moisture sensor");

  // initialize IO
  pinMode(WATER_LEVEL, INPUT);
  pinMode(PUMP, OUTPUT);
  digitalWrite(PUMP, HIGH);
  pinMode(LED, OUTPUT);
  digitalWrite(LED, HIGH);
  pinMode(WATER_EMPTY_LED, OUTPUT);
  digitalWrite(WATER_EMPTY_LED, HIGH);
  delay(2000); // give soil moisture sensor a chance to stabilize
}

void loop() {
  
  // check water reservoir
  while (digitalRead(WATER_LEVEL) == LOW) {  // water reservoir is empty 
    digitalWrite(WATER_EMPTY_LED, LOW);  // turn on the WATER_EMPTY_LED
    digitalWrite(PUMP, HIGH);  // make sure the water pump is turned off
    Serial.println("No water in reservoir => fill it up!");
    soil_moisture(false); // get soil moisture reading and update the soil moisture indicator LED
    // future code to send message at appropriate intervals goes here ...
    delay(10000); // allow moisture sensor to stabilize before next reading
  }
  digitalWrite(WATER_EMPTY_LED, HIGH); // The reservoir has water => turn off the WATER_EMPTY_LED
  soil_moisture(true);  // get the soil moisture reading and do appropriate actions
  delay(10000); // make sure that the moisture sensor gets time to stabilize before next reading
}


void soil_moisture(bool waterAvailable) {
  tmpMoisture = analogRead(A0); // take a soil moisture reading
  if (tmpMoisture != moisture) {  
    // only do the following if the moisture level has changed
    moisture = tmpMoisture;
    // print the moisture data to the serial interface ...
    Serial.print("moisture = ");
    Serial.println(moisture);
    if (moisture >= PUMP_ON_THRESHOLD) { // soil is dry enough for watering ...
      digitalWrite(LED, LOW); // turn on the indicator LED ...
      Serial.print("Dry threshold reached - ");
      if (waterAvailable) { // water is in the reservoir => OK to continue ...
        Serial.print("and water is available - ");
        currentMillis = millis();
        if ((previousMillis == 0) or (currentMillis - previousMillis >= MINIMUM_TIME)) {
          // the appropriate amount of time has transpired since the last watering => OK to water
          digitalWrite(PUMP, LOW);  // turn on pump
          Serial.println("water the plant!");
        } else {
          // not enough time has transpired since last watering => need to wait before watering
          Serial.println("but wait a bit longer.");
        }
      } else {
        // water reservoir is empty ...
        Serial.println("but the water reservoir is empty - fill it up!");
      }
    } else if (moisture <= PUMP_OFF_THRESHOLD) {
        // soil is moist enough ...
        digitalWrite(LED, HIGH);  // turn off the indicator LED
        digitalWrite(PUMP, HIGH); // make sure the water pump is off
        // update previousMillis. Want to wait at least MINIMUM_TIME before next watering
        previousMillis = millis();
        Serial.println("The soil moisture level is high - turning off the water pump.");
    } else {
      // moisture level between thresholds - no action necessary
      Serial.println("The soil moisture level is adequate - no action is being taken.");
    }
  }
}

 

 

You don't have permission to edit metadata of this video.
Edit media
x
image
Upload Preview
image

  • Sign in to reply

Top Comments

  • urkraft
    urkraft over 8 years ago +7
    Status update: Here is a picture of the latest breadboard configuration (although i would be amazed if it would be helpful to anyone as it looks like a rats nest). It is now connected to all the sensors…
  • ntewinkel
    ntewinkel over 8 years ago in reply to urkraft +5
    Thanks Raymond! Hey I just found a case - it fits inside a TicTac container! -Nico
  • ntewinkel
    ntewinkel over 7 years ago in reply to urkraft +5
    What if you replaced the corroded prongs with copper (or copper plated) nails ($5 for a bag here at Home Depot)? Or you could probably use a few cutoff bits of copper house wiring (likely free even at…
Parents
  • urkraft
    urkraft over 8 years ago

    Status update 22feb2018:

    Unfortunately it appears that the soil hygrometer that i have been using does not appear to be accurate or reliable. Since it appeared to be widely used in Arduino projects that are published i just assumed that it was a good choice - or at least good enough. But my experience with it so far indicates otherwise. Already while i was documenting the project i began noticing posts/discussions by others who were havving issues with them. Here are a few customer evaluations at amazon.com:

     

    • Nope STAY AWAY. Corroded within days in SC soil.
    • Had these for a little over a month and the PC board probes have been in the dirt for a little over a week. Pulled one out to see how it was faring (as the board signaled "dry conditions", which it wasn't), and the traces were almost gone. My well water is slightly acidic (approx. 6.8 to 6.9 pH) with many dissolved solids making it seem unlikely to be the reason the traces have been eaten away in such a short period of time. With the rapid deterioration, it would seem likely my plants would have died before their third watering. Very poor engineering. Disappointed. Now I have to wonder how much lead is in the solder covering the copper traces and how I can still utilize the rest of the circuits.
    • Did not last. I know you get what you pay for but I wouldn't have thought that after 1 day of use the probes corroded.
    • Awful quality. Corroded away very quickly. I wouldn't suggest buying them from long term watering/soil readings.
    • As others have stated, the quality of the probe is inferior. After only a few days of use the metal on probes has come off.
    • The readings are all over the place. I tried two at the same time in a cup of water and both sensors had different readings and they were off by more than 15%. Also noticed that over time, the reading gradually declines. Very inconsistent!
    • Ok if you have a short term project to mess around with but they fail pretty fast, The copper corrodes and the reading values go crazy.
    • really inaccurate and inconsistant. There is now way I could automate my watering because this sensor is really more of a toy or learning device but really won't work well in a true control system.

     

    My experience with this sensor is consistent with these evaluations - and my conclusions likewise. I noticed quite early that the readings were erratic - both from the digital pin as well as the analog. My application displays the values on the OLED display and the values are varying constantly.

     

    Today i decided to change my approach in order to get more stable readings. I would in essence make two changes:

     

    1. Only take readings once each second.
    2. Put the readings in a 16 position ring buffer and use the averages of the last 16 readings after each reading.

     

    I decided to make a little sketch to test the code on another Arduino. I have 2 soil hygrometers, so i could play around with the code while making actual readings with the sensor dipped in water. What i observed was that the readings are less erratic - as expected - but even though the sensor is submerged in water (which should represent totally saturated soil that cannot possibly get more saturated) the values have been drifting quite a bit. After running the code for about an hour it appears to be quite stable around 491, but there is still a little bit of drifting going on. I do not understand why there should be any drifting at all when the sensor prongs are submerged in water, but it is. It wouldn't be too bad though if that was the worst of it, but the fact that the copper will corrode pretty quickly is totally unacceptable.

     

    Here is the code that i have been using to test this:

     

     

    /* =================================================================================
     *  Ring Buffer:
     *  ============
     *  Program for testing use of ring buffer for averaging soil hygrometer readings
     *  ================================================================================
     */
    
    
    const int HYGROMETER_READINGS_SIZE = 16;
    const unsigned long SECOND = 1000UL;
    
    
    int hygrometerReadings[16] = {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0};
    int hygroReading= 0;
    int hygroReadingPos = 0;
    int hygroReadingTotal = 0;
    
    
    unsigned long lastReadingTime = millis();
    unsigned long thisReadingTime;
    
    
    void setup() {
      Serial.begin(9600);
      delay(100);
    
    
    }
    
    
    void loop() {
      thisReadingTime = millis();
      if ((thisReadingTime - lastReadingTime) >= SECOND) {
        lastReadingTime = thisReadingTime;
        Serial.println(getHygrometerReading());
      }
    }
    
    
    int getHygrometerReading() {
      
      hygroReadingTotal -= hygrometerReadings[hygroReadingPos];
      hygrometerReadings[hygroReadingPos++] = hygroReading = analogRead(A0);
      if (hygroReadingPos == HYGROMETER_READINGS_SIZE) {
        hygroReadingPos = 0;
      }
      hygroReadingTotal += hygroReading;
      return (hygroReadingTotal / HYGROMETER_READINGS_SIZE);
    }

     

    The last few days i have been looking for an alternative sensor to use, but have not been successful. In chapter 10 of his book "Arduino Projects for Dummies", Brock Craft documents his project for building an automated garden. There he builds his own soil hygrometer using galvanized finishing nails, plaster of paris, wires, and some clear vinyl tube. Although his sensor appears to be much more durable than the ones i have been experimenting with, i have seen a few posts where people who use similar sensors say that it holds moisture much longer than soil does and therefore is not reliable. I plan to keep looking, but the only real alternatives that i have found are either based only on giving visual indicators or much more expensive than someone in my economical circumstances can afford.

     

    Even though testing indicates that the soil hygrometer sensor being used is nothing more than a toy i decided to implement the code that i used for testing it (above) in the system code. Here is the updated sketch that i am now using in the Arduino Uno for the irrigation system:

     

    /**********************************************************************************************
    * Program for semi-automated irrigation using Arduino Uno
    * =======================================================
    * 
    * Summary:
    * --------
    * 
    * The main components of the system are:
    * 
    * - An Arduino Uno (hereafter referred to as “Uno”) which controls everything
    * - A 9V DC 1A power adapter for the Uno
    * - An ESP-01 WiFi module which is used to facilitate messaging
    * - A FC-28 soil hygrometer (moisture sensor) for detecting humidity in soil
    * - A water level switch NC mounted on a float for detecting when the water reservoir is empty
    * - A Keyes KY-040 rotary encoder is used to adjust threshold values used by the sketch
    * - A 0.96" 128 x 64 pixel OLED graphical display used to display status information
    * - A peristaltic water pump to pump water from the water reservoir to the soil
    * - A 12V DC 1A power adapter for the water pump
    * - A 10 gallon bucket of water (water reservoir)
    * - A potted plant to water
    * 
    * Operation:
    * 
    * The two sensors (soil hygrometer and water level switch) are connected to inputs of the Uno 
    * (A0 and D11 respectively) and the water pump is controlled by an output signal (D10) from 
    * the Uno. Two LED’s are controlled by output signals from the Uno and are used as status 
    * indicators:
    * 
    * - Red  : (D13) Lit to indicate that the soil is dry enough to warrant watering
    * - Blue : (D12) Lit to indicate that the water reservoir is empty (requires a refill of water)
    * 
    * A 0.96" 128 x 64 piksel OLED graphical display is also used to display status information
    * and to adjust threshold levels for turning the water pump on and off. It uses the Uno's I2C 
    * interface (A5(SCL) and A4(SDA)).
    * 
    * A Keyes KY-040 rotary encoder is used together with the OLED display to adjust the threshold 
    * values used by the script. It uses the D2 through D5 pins as input. The system goes into 
    * adjustment modus when the knob is depressed. The user can then scroll through the items which
    * can be adjusted. The item to calibrate is choosen by depressing the switch once more when it 
    * displayed. This causes the system to display the current value being used for that item. The
    * user can then increase the value by turning the knob clockwise (whereupon the displayed value 
    * increases) or counter clockwise (whereupon the displayed value decreases). To choose the 
    * displayed value the user must depress the knob - whereupon the system also exits adjustment 
    * mode and commenses to use the new values.
    * 
    * There are three criteria which all have to be met in order to turn on the pump (and water 
    * the plant):
    * 
    * 1)  There must be water in the reservoir
    * 2)  The soil moisture reading in the soil must be greater than a specified high threshold 
    *     value (indicating the soil is too dry)
    * 3)  A specified minimum amount of time must have elapsed since the pump was last turned off 
    *     while watering (but not if this was due to entering adjustment modus).
    *     
    * Any one of the following criteria will cause the pump to be turned off:
    * 
    * - No water left in the water reservoir
    * - The moisture reading of the soil is lower than a specified low threshold value 
    *   (indicating the soil has been irrigated sufficiently. This criteria also triggers the 
    *   timer which keeps track of the amount of time that has elapsed since the pump was turned 
    *   off).
    * - the pump has been on continually for at least MAX_PUMP_ON_TIME milliseconds.
    * - (only temporarily) entering adjustment modus
    *   
    * Basically, the Uno just goes in a loop checking the criteria above and taking action 
    * when necessary. Each loop iteration takes approximately 20-30 seconds to complete. If,
    * however the knob of the rotary encoder is depressed and this is detected, the function
    * for adjustment modus is called and normal operation is not resumed until after returning
    * from the function. Before the adjustment function is called the water pump and on time 
    * timer are both stopped if the pump is on. Both are then resumed upon returning from the
    * adjustment function.
    * 
    * Notes:
    * 
    * GPIO ESP_WATER_EMPTY is used to signal to the ESP-01 that the water reservoir is empty 
    * (via the GP0 pin of the ESP-01). The ESP-01 is configured to use its GPIO0 as an input. A LOW 
    * signal on this pin will trigger the sendMessage(FILL_RESERVOIR_URL) function on the ESP-01. 
    * This function invokes a PHP script on my Raspberry Pi server via a HTTP GET request. The
    * paramater it uses indicates which script to invoke. This one (FILL_RESERVOIR_URL) results
    * in an e-mail being sent to me informing me that the water reservoir is empty. The ESP-01
    * also calls this function with CONNECT_SUCCESS_URL as the parameter from its setup() function. 
    * The resulting e-mail informs me that the the ESP-01 has successfully connected to the WiFi 
    * and it also reminds me that any custom thresholds need to be re-entered (since this e-mail
    * will only be sent whenever the ESP-01 restarts => the Uno has also been restarted.
    * 
    * Future features to impliment:
    * 
    * - replace Uno with custom design (with ability to reprogram by attaching adequate
    *   IO interface)
    */
    #include 
    #include 
    #include <adafruit_gfx.h>
    #include <adafruit_ssd1306.h>
    
    
    #define OLED_RESET 4
    Adafruit_SSD1306 display(OLED_RESET);
    
    
    #define NUMFLAKES 10
    #define XPOS 0
    #define YPOS 1
    #define DELTAY 2
    
    
    
    
    #define LOGO16_GLCD_HEIGHT 16 
    #define LOGO16_GLCD_WIDTH  16 
    #if (SSD1306_LCDHEIGHT != 32)
    #error("Height incorrect, please fix Adafruit_SSD1306.h!");
    #endif
    
    
    //==============
    // encoder stuff
    //==============
    #define ENCODER_CLK 2
    #define ENCODER_DT 3
    #define ENCODER_SW 4
     
    int encoderCounter = 0; 
    int encoderClkState;
    int encoderClkLastState;  
    int encoderSwLastState;
    int encoderSwState;
    int encoderPosition = 0;
    
    
    // I/O
    const int DRY_LED = 13;  // (Output) used to indicate what pump status should be ON (set HIGH) when the pump should be on
    const int PUMP = 10; // (Output) used to turn the pump on (set HIGH) and off (set LOW)
    const int WATER_EMPTY_LED = 12;  // (Output) used to indicate if the reservoir is empty (set HIGH => ON)
    const int WATER_LEVEL = 11;  // (Input) used to detect if there is water in the reservoir (HIGH) or if it is empty (LOW)
    const int ESP_WATER_EMPTY = 9;  // (Output) used to signal the ESP-01 that the water reservoir is empty (set LOW => empty)
    
    
    // A0 : ADC (Analog Input) used to read soil humidity: high value => dry, low value => wet 
    // Max value is approx. 1018 - bone dry
    // Min value is approx. 530 - drowning in water
    const unsigned long MINIMUM_TIME = 1000UL * 60UL * 60UL * 24UL; // minimum time that must transpire between irrigation (1 day)
    //const unsigned long MINIMUM_TIME = 60UL * 1000UL; // minimum time that must transpire between irrigation (only for testing)
    const unsigned long MAX_PUMP_ON_TIME = 1000UL * 60UL * 15UL; // maximum time pump can be turned on for (15 minutes)
    //const unsigned long MAX_PUMP_ON_TIME = 60UL * 1000UL; // maximum time pump can be turned on for (only for testing)
    
    
    //==========
    // VARIABLES
    //==========
    
    
    // timing
    unsigned long lastIrrMillis = 0UL;
    unsigned long currentMillis = 0UL;
    unsigned long pumpOnMillis = 0UL;
    
    
    unsigned long lastReadingTime = millis();
    unsigned long thisReadingTime;
    
    
    // soil moisture reading
    int pumpOnThresh = 200;  // Turn on the pump when ADC input value >= this value
    int pumpOffThresh = 190; // Turn off the pump when ADC input value <= this value (and do not trun on again for at least a day)
    bool pumpIsOn = false;
    int moisture = 800; // previous
    int waterLevel;
    
    
    const int HYGROMETER_READINGS_SIZE = 16;
    const unsigned long SECOND = 1000UL;
    
    
    int hygrometerReadings[16] = {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0};
    int hygroReading= 0;
    int hygroReadingPos = 0;
    int hygroReadingTotal = 0;
    
    
    //int tmpMoisture = 0;  //  new (temporary)
    
    
    int displayLine = 0;
    const int DISPLAY_LINE_INCREASE = 8;
    const int DISPLAY_LINE_MAX = 32;
    
    
    //======
    // SETUP
    //======
    
    
    void setup() {
      // initialize Serial
      Serial.begin(9600);
      delay(100);
    
    
      Serial.println("Soil moisture sensor");
    
    
      // initialize IO
      pinMode(WATER_LEVEL, INPUT_PULLUP);
      
      pinMode(PUMP, OUTPUT);
      digitalWrite(PUMP, LOW);
      pumpIsOn = false;
      
      pinMode(DRY_LED, OUTPUT);
      digitalWrite(DRY_LED, LOW);
      
      pinMode(WATER_EMPTY_LED, OUTPUT);
      digitalWrite(WATER_EMPTY_LED, LOW);
    
    
      pinMode(ENCODER_CLK, INPUT);
      pinMode(ENCODER_DT, INPUT);
      pinMode(ENCODER_SW, INPUT);
      
      // by default, we'll generate the high voltage from the 3.3v line internally! (neat!)
      display.begin(SSD1306_SWITCHCAPVCC, 0x3C);  // initialize with the I2C addr 0x3C (for the 128x32)
      // init done
      
      display.setTextSize(1);
      display.setTextColor(WHITE);
    
    
      pinMode(ESP_WATER_EMPTY, OUTPUT);
      digitalWrite(ESP_WATER_EMPTY, HIGH);
    }
    
    
    void loop() {
      // rotary encoder stuff
      encoderSwState = digitalRead(ENCODER_SW);
      if ((encoderSwLastState == encoderSwState) and (encoderSwState == false)) {
        unsigned long deltaTime;
        if (pumpIsOn) {
          deltaTime = millis() - pumpOnMillis;
          digitalWrite(PUMP, LOW);
        }
        chooseMenu();
        if (pumpIsOn) {
          pumpOnMillis = millis() - deltaTime;
          digitalWrite(PUMP, HIGH);
        }
        delay(1000);
      }
    
    
      //irrigation stuff
      waterLevel = readInput(WATER_LEVEL);  // read water level
      if (waterLevel == LOW) {  // water reservoir is empty
        digitalWrite(PUMP, LOW); // turn off pump
        pumpIsOn = false;
        digitalWrite(WATER_EMPTY_LED, HIGH); // turn on LED indicator
        digitalWrite(ESP_WATER_EMPTY, LOW);  // signal ESP-01 to send e-mail informing water reservoir is empty
        // Serial.println("The water reservoir is empty => fill it up!");
      } else {
        digitalWrite(WATER_EMPTY_LED, LOW); // turn off LED indicator
        digitalWrite(ESP_WATER_EMPTY, HIGH);  // signal ESP-01 that water reservoir contains water
      }
    
    
      thisReadingTime = millis();
      if ((thisReadingTime - lastReadingTime) >= SECOND) {
        lastReadingTime = thisReadingTime;
        moisture = getHygrometerReading();
        if (moisture >= pumpOnThresh) {
          digitalWrite(DRY_LED, HIGH);  // turn on LED indicator
          if (pumpIsOn) {
            currentMillis = millis();
            if ((pumpOnMillis == 0) or ((currentMillis - pumpOnMillis) >= MAX_PUMP_ON_TIME) or (currentMillis < pumpOnMillis)) {
              digitalWrite(PUMP, LOW); // turn pump off
              pumpIsOn = false;
              lastIrrMillis = millis();
            }
          } else {  // pump is off
            currentMillis = millis();
            if ((lastIrrMillis == 0) or (currentMillis - lastIrrMillis >= MINIMUM_TIME)) {
              if (waterLevel == HIGH) {
                digitalWrite(PUMP, HIGH);  // turn on pump
                pumpIsOn = true;
                pumpOnMillis = millis();
              } else {
                // Serial.println("but no water in reservoir!");
              }
            } else {
              // Serial.println("but not enough time has passed since last irrigation.");
            }
          }
        } else if (moisture <= pumpOffThresh) {
          digitalWrite(DRY_LED, LOW); // turn off LED indicator
          if (pumpIsOn) {
            digitalWrite(PUMP, LOW); // turn off pump
            pumpIsOn = false;
            lastIrrMillis = millis();  // update lastIrrMillis in preparation for MINIMUM_TIME criteria calculation
          } else {
            // Serial.println("The soil moisture level is high and the water pump is already off.");
          }
        } else {
          // Serial.println("The soil moisture level is adequate => no action needed.");
        }
      }
      display.clearDisplay();
      display.setCursor(0, 0);
      display.print("PumpOnTres: ");
      display.print(pumpOnThresh);
      display.setCursor(0, 8);
      display.print("Value: ");
      display.print(moisture);
      if (pumpIsOn) {
        display.print(" (ON)");
      } else {
        display.print(" (OFF)");
      }
      display.setCursor(0, 16);
      display.print("PumpOffTres: ");
      display.print(pumpOffThresh);
      display.setCursor(0, 24);
      if (waterLevel == LOW) {
        display.print("Bucket is empty.");
      } else {
        display.print("Bucket has water.");
      }
      display.display();
    }
    
    
    int readInput(int input) {
      int millisStep = 20;
      int reading;
      for(int readings = 3; readings; readings--) {
        reading = debounce(digitalRead(input));
        delay(millisStep);
      }
      return reading;
    }
    
    
    int debounce (int SampleA) {
      static int SampleB = 0;
      static int SampleC = 0;
      static int LastDebounceResult = 0;
      LastDebounceResult = LastDebounceResult & (SampleA | SampleB | SampleC) | (SampleA & SampleB & SampleC);
      SampleC = SampleB;
      SampleB = SampleA;
      return LastDebounceResult;
    }
    
    
    void chooseMenu() {
      char* menu[] = {"Change PumpOnTres", "Change PumpOffTres"};
      int menuLen = 2;
      bool skipIt = true;
      int pos = 0;
    
    
      display.clearDisplay();
      display.setCursor(0, 0);
      display.print("Choose menu:");
      display.setCursor(0, 8);
      display.print(menu[pos]);
      display.setCursor(0, 16);
      display.print("press dial");
      display.setCursor(0, 24);
      display.print("to set.");
      display.display();
      delay(200);
      
      // Reads the initial state of the ENCODER_SW
      encoderSwLastState = false;
      //delay(80);
      encoderSwState = digitalRead(ENCODER_SW);
    
    
      while (!((encoderSwLastState == encoderSwState) and (encoderSwState == false))) {
        encoderSwLastState = encoderSwState;
        encoderSwState = digitalRead(ENCODER_SW);
        
        encoderClkState = digitalRead(ENCODER_CLK);
        if (encoderClkState != encoderClkLastState) {
          if (!skipIt) {
            if (digitalRead(ENCODER_DT) != encoderClkState) {
              pos++;
              pos %= menuLen;
            } else {
              pos--;
              if (pos < 0) {
                pos = menuLen - 1;
              }
            }
            display.clearDisplay();
            display.setCursor(0, 0);
            display.print("Choose menu:");
            display.setCursor(0, 8);
            display.print(menu[pos]);
            display.setCursor(0, 16);
            display.print("press dial");
            display.setCursor(0, 24);
            display.print("to set.");
            display.display();
          }
          skipIt = !skipIt;
        }
        encoderClkLastState = encoderClkState;
      }
      display.clearDisplay();
      display.setCursor(0, 0);
      display.print("Choose menu:");
      display.setCursor(0, 8);
      display.print(menu[pos]);
      display.setCursor(0, 16);
      display.print("menu chosen.");
      // display.setCursor(0, 24);
      // display.print(onThresh);
      display.display();
      delay(1000);
      if (pos == 0) {
        pumpOnThresh = getEncoderData("PumpOnTres", pumpOnThresh);
      } else if (pos == 1) {
        pumpOffThresh = getEncoderData("PumpOffTres", pumpOffThresh);
        // pumpOffThresh = setLowThresh();
      }
    }
    
    
    int getEncoderData(char* theParam, int startPos) {
      bool skipIt = true;
      // int onThresh = pumpOnThresh;
    
    
      display.clearDisplay();
      display.setCursor(0, 0);
      display.print("Change ");
      display.print(theParam);
      display.print(":");
      display.setCursor(0, 8);
      display.print(startPos);
      display.setCursor(0, 16);
      display.print("press dial");
      display.setCursor(0, 24);
      display.print("to set.");
      display.display();
      delay(200);
      
      // Reads the initial state of the ENCODER_SW
      encoderSwLastState = false;
      encoderSwState = digitalRead(ENCODER_SW);
    
    
      while (!((encoderSwLastState == encoderSwState) and (encoderSwState == false))) {
        encoderSwLastState = encoderSwState;
        encoderSwState = digitalRead(ENCODER_SW);
        
        encoderClkState = digitalRead(ENCODER_CLK);
        if (encoderClkState != encoderClkLastState) {
          if (!skipIt) {
            if (digitalRead(ENCODER_DT) != encoderClkState) {
              if (startPos < 1023) {
                startPos++;
              }
            } else {
              if (startPos > 0) {
                startPos--;
              }
            }
            display.clearDisplay();
            display.setCursor(0, 0);
            display.print("Change ");
            display.print(theParam);
            display.print(":");
            display.setCursor(0, 8);
            display.print(startPos);
            display.setCursor(0, 16);
            display.print("press dial");
            display.setCursor(0, 24);
            display.print("to set.");
            display.display();
          }
          skipIt = !skipIt;
        }
        encoderClkLastState = encoderClkState;
      }
      display.clearDisplay();
      display.setCursor(0, 0);
      display.print("Change ");
      display.print(theParam);
      display.print(":");
      display.setCursor(0, 8);
      display.print(startPos);
      display.setCursor(0, 16);
      display.print(theParam);
      display.print(" set to");
      display.setCursor(0, 24);
      display.print(startPos);
      display.display();
      delay(1000);
      return startPos;
    }
    
    
    int getHygrometerReading() {
      
      hygroReadingTotal -= hygrometerReadings[hygroReadingPos];
      hygrometerReadings[hygroReadingPos++] = hygroReading = analogRead(A0);
      if (hygroReadingPos == HYGROMETER_READINGS_SIZE) {
        hygroReadingPos = 0;
      }
      hygroReadingTotal += hygroReading;
      return (hygroReadingTotal / HYGROMETER_READINGS_SIZE);
    }

     

    I only just compiled it and uploaded it to the Uno, but it appears to be functioning as expected and the soil humidity data appears much less erratic than previously.

     

    -raymond

    • Cancel
    • Vote Up 0 Vote Down
    • Sign in to reply
    • More
    • Cancel
  • beacon_dave
    beacon_dave over 8 years ago in reply to urkraft

    How about trying one of these Adafruit temperature/humidity sensors:

    https://www.adafruit.com/product/1298

    There appears to be a Sensiron library for Arduino for it

    https://github.com/practicalarduino/SHT1x

     

    https://learn.adafruit.com/wireless-gardening-arduino-cc3000-wifi-modules

    • Cancel
    • Vote Up +2 Vote Down
    • Sign in to reply
    • More
    • Cancel
  • urkraft
    urkraft over 8 years ago in reply to beacon_dave

    Thanks for the tip Dave! It certainly appears to be a better solution. I will certainly try to get one if i can ever get a job and be able to afford one image. In the mean time i will try to find feedback from anyone having experience using it in actual real life irrigation projects.

     

    Regards,

    -raymond

    • Cancel
    • Vote Up +1 Vote Down
    • Sign in to reply
    • More
    • Cancel
Comment
  • urkraft
    urkraft over 8 years ago in reply to beacon_dave

    Thanks for the tip Dave! It certainly appears to be a better solution. I will certainly try to get one if i can ever get a job and be able to afford one image. In the mean time i will try to find feedback from anyone having experience using it in actual real life irrigation projects.

     

    Regards,

    -raymond

    • Cancel
    • Vote Up +1 Vote Down
    • Sign in to reply
    • More
    • Cancel
Children
  • beacon_dave
    beacon_dave over 8 years ago in reply to urkraft

    Have you considered using multiple cheaper sensors and averaging given that you have spare analogue inputs on your Uno ?

     

    I recall that when using the household probe meters that you get widely varying results depending upon where and how deep you probe in the soil. Some of my plants had shallow but wide surface root systems whereas others had narrow but deep root systems, so it was important to probe to the right depth and in the right areas depending upon the plant in my case.

    • Cancel
    • Vote Up +3 Vote Down
    • Sign in to reply
    • More
    • Cancel
  • urkraft
    urkraft over 8 years ago in reply to beacon_dave

    Hello again Dave,

     

    My olive plant is not very big and neither is the pot it is planted in, so i doubt that i would need to use multiple sensors to monitor the soil humidity. The one that i am using at present is quite deep in the soil so that it should better detect moisture accumulated at the bottom of the pot. The problems i am experiencing with readings from the soil hygrometers i have is not due to their placement. When the forks are submerged in water (which is as humid as you can get) the reading values are erratic and varying all the time. Furthermore, many others who have used these have observed that they corrode quite quickly. They are just not up to the task. Thanks anyway for your input. Your advice is noted and i have also read other blog posts where the same observations you have pointed out have been mentioned, so i have no doubt that your point is valid and should always be considered.

     

    Regards,

    raymond

    • Cancel
    • Vote Up +1 Vote Down
    • Sign in to reply
    • More
    • Cancel
  • beacon_dave
    beacon_dave over 8 years ago in reply to urkraft

    FYI you can put the Atmel ATmega microcontrollers into a special low noise ADC mode to reduce the noise from its internal clocks by putting the controller to sleep and then awakening it after the ADC has completed. This overview may be of interest:

    https://www.youtube.com/watch?v=VeodIyVU5Ic

     

    Not surprising that the PCB type sensors corrode quickly - perhaps try replacing them with thicker copper strip / pipe ?

    • Cancel
    • Vote Up +2 Vote Down
    • Sign in to reply
    • More
    • Cancel
  • urkraft
    urkraft over 8 years ago in reply to beacon_dave

    VERY COOL! I will have to check that out image!

    • Cancel
    • Vote Up +1 Vote Down
    • Sign in to reply
    • More
    • Cancel
  • beacon_dave
    beacon_dave over 8 years ago in reply to urkraft

    Also perhaps check out some of the Atmel/Microchip AVR application notes as there is at least one which outlines some best practices for improving ADC input measurement accuracy.

     

     

    [1.9 Best Practices for Improving ADC Performance

     

    http://ww1.microchip.com/downloads/en/AppNotes/00002538A.pdf#page=8 ]

     

    If your sensor is some distance from the Arduino then you may find an external ADC IC located closer to the probe may give more stable readings.

    • Cancel
    • Vote Up +2 Vote Down
    • Sign in to reply
    • More
    • Cancel
element14 Community

element14 is the first online community specifically for engineers. Connect with your peers and get expert answers to your questions.

  • Members
  • Learn
  • Technologies
  • Challenges & Projects
  • Products
  • Store
  • About Us
  • Feedback & Support
  • FAQs
  • Terms of Use
  • Privacy Policy
  • Legal and Copyright Notices
  • Sitemap
  • Cookies

An Avnet Company © 2026 Premier Farnell Limited. All Rights Reserved.

Premier Farnell Ltd, registered in England and Wales (no 00876412), registered office: Farnell House, Forge Lane, Leeds LS12 2NE.

ICP 备案号 10220084.

Follow element14

  • X
  • Facebook
  • linkedin
  • YouTube