Automated Self Watering with ESP8266 WiFi

Hi All,

I’m working on a self-automated plant watering system. About to upload the code but I seem to have run into some problems! I have an error saying ‘D1 was not declared in this scope’. The details are below. Thanks in advance!

• Hardware model + communication type: ESP8266 Node MCU v3 WiFi
• Smartphone OS (Android)
• Code in chunks GitHub - Emilostuff/PlantKeeper: Automated Plant Watering System

// Libraries
#include <ESP8266WiFi.h>
#include <BlynkSimpleEsp8266.h>
#include<ADS1115_WE.h>
#include<Wire.h>
#include <TimeLib.h>
#include <WidgetRTC.h>

// Networking
char auth[] = "";   
char ssid[] = "";
char pass[] = "";

// Blynk
BlynkTimer timer;
BlynkTimer timer1;
WidgetRTC rtc;

// System state
int plantSelect = 0;                            // plant 1 selected on default (index 0)
bool systemOn = 0;
bool systFlag = false;
long ontime;
long lastWater[4] = {0, 0, 0, 0};               // 0 means not set
bool pumpOn[4] = {false, false, false, false};

// Settings (can be adjusted from app)
int modes[4] = {0, 0, 0, 0};
int amount[4] = {0, 0, 0, 0};
int interval[4] = {0, 0, 0, 0};
int thresh[4] = {0, 0, 0, 0};
int minInterval[4] = {0, 0, 0, 0};

// sensor/ADC
ADS1115_WE adc(0x48);
float tf = 0.1;                                 // trust factor for smoothing filter
float sensor[4] = {100, 100, 100, 100};         // Set highest start value to avoid unwanted triggers
float sensorDry[4] = {2760, 2680, 2780, 2760};  // Reading from when fully emerged in water
float sensorWet[4] = {1460, 1210, 1510, 1500};  // Reading from when in 'dry' air

#define water

// BLYNK ///////////////////////////////////////

BLYNK_CONNECTED()
{
 // Synchronize unix-time on connection
 rtc.begin();
}


// IN-APP EVENT CALLS ///////////////////////
// for when the user presses any button in the app

// System on-off button event
BLYNK_WRITE(V7)
{
 // change system state
 systemOn = param.asInt();

 if (systemOn) {
   if (systFlag) {
     // system has just been turned on!
     systFlag = false;

     // set lastWater to now:
     ontime = now();
     Blynk.virtualWrite(V34, ontime, ontime, ontime, ontime);
     Blynk.syncVirtual(V34);
   }
   // system was turned on when connected -> do nothing
 } else {
   // system is off
   systFlag = true;
 }
}

// Manual water button event
BLYNK_WRITE(V8)
{
 // if button was pressed and plant is eligible for water
 if (param.asInt() == 1 and now() - lastWater[plantSelect] > 5 and systemOn) {
   // execute water routine
   water(plantSelect);
 } else {
   // reset water button to unpressed state
   Blynk.virtualWrite(V8, 0);
 }
}

// Reload button event
BLYNK_WRITE(V2) 
{
 // Reload requested -> update display values in app
 if (param.asInt() == 1); {
   Blynk.virtualWrite(V1, modes[plantSelect]);
   Blynk.virtualWrite(V3, amount[plantSelect]);
   Blynk.virtualWrite(V4, interval[plantSelect]);
   Blynk.virtualWrite(V5, thresh[plantSelect]);
   Blynk.virtualWrite(V9, minInterval[plantSelect]);
 }
}

// Plant Select event
BLYNK_WRITE(V0)
{
 // Plant selected for edit -> store updated value
 plantSelect = param.asInt() - 1;

 // update displays
 Blynk.virtualWrite(V1, modes[plantSelect]);
 Blynk.virtualWrite(V3, amount[plantSelect]);
 Blynk.virtualWrite(V4, interval[plantSelect]);
 Blynk.virtualWrite(V5, thresh[plantSelect]);
 Blynk.virtualWrite(V9, minInterval[plantSelect]);
}

// Mode select event
BLYNK_WRITE(V1)
{
 // Store updated value depending on selected plant
 modes[plantSelect] = param.asInt();

 // save updated value on server
 Blynk.virtualWrite(V30, modes[0], modes[1], modes[2], modes[3]);
}

// Amount change event
BLYNK_WRITE(V3)
{
 // Store updated value depending on selected plant
 amount[plantSelect] = param.asInt();

 // save to server
 Blynk.virtualWrite(V31, amount[0], amount[1], amount[2], amount[3]);
}

// Interval change event
BLYNK_WRITE(V4)
{
 // Store updated value depending on selected plant
 interval[plantSelect] = param.asInt();

 // save to server
 Blynk.virtualWrite(V32, interval[0], interval[1], interval[2], interval[3]);
}

// Threshold change event
BLYNK_WRITE(V5)
{
 // Store updated value depending on selected plant
 thresh[plantSelect] = param.asInt();

 // save to server
 Blynk.virtualWrite(V33, thresh[0], thresh[1], thresh[2], thresh[3]);
}

// Minimum interval change event
BLYNK_WRITE(V9)
{
 // Store updated value depending on selected plant
 minInterval[plantSelect] = param.asInt();

 // save to server
 Blynk.virtualWrite(V35, minInterval[0], minInterval[1], minInterval[2], minInterval[3]);
}



// GET CALLS //////////////////////////////
// used when 'sync' is called at startup 

BLYNK_WRITE(V30)
{
 // Get values from server:
 for (int i = 0; i < 4; i++) {
   modes[i] = param[i].asInt();
 }
}

BLYNK_WRITE(V31)
{
 // Get values from server:
 for (int i = 0; i < 4; i++) {
   amount[i] = param[i].asInt();
 }
}

BLYNK_WRITE(V32)
{
 // Get values from server:
 for (int i = 0; i < 4; i++) {
   interval[i] = param[i].asInt();
 }
}

BLYNK_WRITE(V33)
{
 // Get values from server:
 for (int i = 0; i < 4; i++) {
   thresh[i] = param[i].asInt();
 }
}

BLYNK_WRITE(V35)
{
 // Get values from server:
 for (int i = 0; i < 4; i++) {
   minInterval[i] = param[i].asInt();
 }
}

// Last Water - retreive values from server
BLYNK_WRITE(V34)
{
 // Get values from server:
 for (int i = 0; i < 4; i++) {
   lastWater[i] = param[i].asInt();
 }
}



// STATUS DISPLAY FUNCTIONS /////////////////////
// when app request current plant status 

BLYNK_READ(V11)
{
 Blynk.virtualWrite(V11, getStatus(0));
}
BLYNK_READ(V12)
{
 Blynk.virtualWrite(V12, getStatus(1));
}
BLYNK_READ(V13)
{
 Blynk.virtualWrite(V13, getStatus(2));
}
BLYNK_READ(V14)
{
 Blynk.virtualWrite(V14, getStatus(3));
}


void setup()
{

 // Serial
 Serial.begin(9600);

 // Blynk 
 Blynk.begin(auth, ssid, pass);

 // fetch stored data from server
 Blynk.syncVirtual(V0);
 Blynk.syncVirtual(V7);
 Blynk.syncVirtual(V30);
 Blynk.syncVirtual(V31);
 Blynk.syncVirtual(V32);
 Blynk.syncVirtual(V33);
 Blynk.syncVirtual(V34);
 Blynk.syncVirtual(V35);

 // Sensors
 Wire.begin(D1, D2);  // SDA, SCL
 if (!adc.init()) {
   Serial.println("ADS1115 not connected!");
 }
 adc.setVoltageRange_mV(ADS1115_RANGE_6144);
 
 // timers
 timer.setInterval(30000L, control);     // for control loop, run every 30 secs
 timer1.setInterval(5000L, readSensors); // for sensor read loop, run every 5 sec

 // RTC
 setSyncInterval(10 * 60);               // Sync interval in seconds (10 minutes)
 
 // pin assignments
 pinMode(D7, OUTPUT);
 pinMode(D4, OUTPUT);
 pinMode(D5, OUTPUT);
 pinMode(D6, OUTPUT);

 // set pumps to OFF (active-low)
 digitalWrite(D7, HIGH);
 digitalWrite(D4, HIGH);
 digitalWrite(D5, HIGH);
 digitalWrite(D6, HIGH);

 // Set a reasonable start value for sensors (a little above the triggering threshold)
 for (int i = 0; i < 4; i++) {
   sensor[i] = thresh[i] + 10;
 }

 Serial.println("Setup Complete");
}


void loop()
{
 // main blynk loop
 Blynk.run();

 // timers
 timer.run();
 timer1.run();

}


void control () 
{
 // check if system is on
 if (systemOn) {
   
   // check if it's time to water
   for (int i = 0; i < 4; i++) {
     if (plantCheck(i)) {
       // yes -> execute water routine
       water(i);
     }
   }
 }
}


bool plantCheck (int plant) 
{
 // find elapsed time since last water
 long elapsedTime = now() - lastWater[plant];

 // check if in auto mode
 if (modes[plant] == 2 and elapsedTime > minInterval[plant] * 60 * 60 and sensor[plant] < thresh[plant]) {
   // minimun interval exceed and moisturelevels too low -> time to water!
   return true;
 }

 // check if in timer mode
 else if (modes[plant] == 3 and elapsedTime > interval[plant] * 60 * 60 * 24) {
   // time interval has been exceeded -> time to water:
   return true;
 }

 // no hit, no water
 return false;
}


void water(int plant) 
{
 Serial.print("Watering plant "); Serial.println(plant + 1);

 // set flag (used for status update)
 pumpOn[plant] = true;

 // push-update status in app and then turn pump on, and
 switch (plant) {
   case 0:
     Blynk.virtualWrite(V11, getStatus(plant));
     digitalWrite(D6, LOW);
     break;
   case 1:
     Blynk.virtualWrite(V12, getStatus(plant));
     digitalWrite(D5, LOW);
     break;
   case 2:
     Blynk.virtualWrite(V13, getStatus(plant));
     digitalWrite(D4, LOW);
     break;
   case 3:
     Blynk.virtualWrite(V14, getStatus(plant));
     digitalWrite(D7, LOW);
     break;
 }

 // delay loop
 long startTime = millis();
 while (millis() - startTime < waterdur(plant)) {
   // keep everything running in the meantime (except the control loop)
   Blynk.run();
   timer1.run();
 }

 // remove flag
 pumpOn[plant] = false;

 // turn pump off and push new status
 switch (plant) {
   case 0:
     digitalWrite(D6, HIGH);  
     Blynk.virtualWrite(V11, getStatus(plant));
     break;
   case 1:
     digitalWrite(D5, HIGH);  
     Blynk.virtualWrite(V12, getStatus(plant));
     break;
   case 2:
     digitalWrite(D4, HIGH);  
     Blynk.virtualWrite(V13, getStatus(plant));
     break;
   case 3:
     digitalWrite(D7, HIGH);  
     Blynk.virtualWrite(V14, getStatus(plant));
     break;
 }

 // Reset water button (if used)
 Blynk.virtualWrite(V8, 0);

 // update lastwater to server
 lastWater[plant] = now();
 Blynk.virtualWrite(V34, lastWater[0], lastWater[1], lastWater[2], lastWater[3]);
}


int waterdur (int plant) 
{
 // convert ml to ms for controlling pump on-time, different values depending on the chosen pump

 switch (plant) {
   case 0:
     return amount[plant] * 57 + 350; // set experimentally
   case 1:
     return amount[plant] * 50 + 320; // set experimentally
   case 2:
     return amount[plant] * 59 + 350; // set experimentally
   case 3:
     return amount[plant] * 47 + 340; // set experimentallyl
 } 
}

void readSensors() 
{
 float reading[4];

 // read raw values
 reading[0] = readChannel(ADS1115_COMP_0_GND);
 reading[1] = readChannel(ADS1115_COMP_1_GND);
 reading[2] = readChannel(ADS1115_COMP_2_GND);
 reading[3] = readChannel(ADS1115_COMP_3_GND);

 // convert to percentage, filter and constrain (if we got a rouge reading)
 for (int i = 0; i<4; i++) {
   reading[i] = map(reading[i], sensorDry[i], sensorWet[i], 0, 100);
   sensor[i] = tf * reading[i] + (1 - tf) * sensor[i];
   sensor[i] = constrain(sensor[i], 0, 100);
 }

 // Write to server
 Blynk.virtualWrite(V21, sensor[0]); 
 Blynk.virtualWrite(V22, sensor[1]); 
 Blynk.virtualWrite(V23, sensor[2]); 
 Blynk.virtualWrite(V24, sensor[3]); 
}


float readChannel(ADS1115_MUX channel) 
{
 float voltage = 0.0;
 adc.setCompareChannels(channel);
 adc.startSingleMeasurement();
 while (adc.isBusy()) {
   Blynk.run(); // nope!
 }
 voltage = adc.getResult_mV(); 
 return voltage;
}

String getStatus (int plant) 
{
 // string variables
 String mode = "";
 String status = "";

 if (pumpOn[plant] == true) {
   // pump is on
   return "Watering ...";
   
 } else if (systemOn) {
   // determine mode
   switch (modes[plant]) {
     case 0:
       // not set up yet
       return "Not set up yet!";
       break;
     case 1:
       mode = "Manual";
       break;
     case 2:
       mode = "Auto";
       break;
     case 3:
       mode = "Timer";
       break;
   }

   // Determine time of last water
   if (lastWater[plant] == ontime) {
     status = "not watered.";
   } else {
     // calculate difference
     long diff = now() - lastWater[plant];

     // onvert between minutes, hours, days and too much
     if (diff < 60) {
       status = "just now.";

     } else if (diff < 2 * 60) {
       status = "1 min ago.";
       
     } else if (diff < 60 * 60) {
       status = String(diff / 60) + " mins ago.";
       
     } else if (diff < 60 * 60 * 2) {
       status = "1 hour ago";

     } else if (diff < 60 * 60 * 24) {
       status = String(diff / (60 * 60)) + " hours ago.";

     } else if (diff < 60 * 60 * 24 * 2) {
       status = "1 day ago";

     } else if (diff < 60 * 60 * 24 * 31) {
       status = String(diff / (60 * 60 * 24)) + " days ago.";

     } else {
       status = "+1 month ago.";
     }
   }

   // return concatenated results
   return mode + ",  " + status;

 } else {
   // system is off
   return "Off";
 } 
}

I’m guessing that you’ve selected “Generic ESP8266” as your board type in the Arduino IDE, rather than NodeMCU.

Also, you don’t need to do this:

see the “Creating Interval Timers” section of this post:

Pete.

Hi Pete, thanks for the post. I really enjoyed reading the Timer article.

Regarding timer.run() how can I assign it to run a certain timer ID?

Thanks in advance!

I don’t really understand the question.

Pete.