Project: ESP8266 Plant Watering

  1. Project: Bugtrakt
  2. Project: Imgalib
  3. Project: Go Fish!
  4. Project: trewest.dev
  5. Arduino Light effects
  6. Project: Mini Stacker
  7. Project: Arduino NES Controller
  8. Project: Metroidvania-Like
  9. Project: Susminer
  10. Project: Farming Demo
  11. Project: VRC Basement
  12. Project: Hash Clear
  13. Project: Titan Help
  14. Project: Computer Craft Scripts
  15. Project: ESP8266 Plant Watering
  16. Project: Golf Demo
  17. Project: Ghost Holic
  18. Project: Multi-Chat Viewer

Status: Untested
Type: Microcontroller
Technology: ESP8266, Arduino, PlatformIO

After I had become somewhat comfortable with programming for Microcontrollers using Arduino/PlatformIO I decided that I’d make something that’d be a little practical, something that’d water some plants for me whenever the soil was too dry. Thankfully there’s kits for this already made with some simple code! Using relays to power on and off water pumps and taking in data from soil moisture meters makes it a very simple affair. But that’s no fun for a programmer, so I figured I’d change up the formula.
This caused me to utilize pumps to manage water PH (Up and Down) to keep it as neutral as possible, while also being able to mix in some nutrients as needed, making it usable for Hydroponics which was a field of interest to me at the time. And then of course there’s still a pump that feeds water to everything as needed. Additionally, I had water level sensors that could make sure we’re not running pumps dry and accidentally killing them. But since this is a IOT board I of course had to try and make it capable of internet connection and remote monitoring, which I did do some things for but since I didn’t get to the stage of deploying the device and code I never really expanded beyond toggling pumps on and off, and never made a decent web interface for it. Ideally I would’ve made it use websockets to connect to a local server of some kind that’d store historical data, but again I didn’t get to the refinement stage.

A plant of mine

So, why didn’t I test and use this code in the end? Well there’s a few things. It was getting costly for one and investing more into was difficult to justify. Secondly my garden had a lot of troubles such as my pets frequently messing with the plants, so adding fragile computers to the mix of it didn’t seem like a great idea. Third it was somewhat unnecessary as Florida is a very relaxed climate for plants, we have enough rainwater that plants don’t need too much help. And fourth I was starting to run into issues with the board I was using losing too much power running everything, I’m not trained in electrical engineering and stuff like this is just a hobby for me, but I’m guessing running everything was just drawing too many amps from the microcontroller, which would cause it to reset. And while I could simplify it down some more and hope it’d be fine, I’d rather not and just try and make some more cool stuff with the things I have than risk burning something out.

Early test video
#include <Arduino.h>
#include <ESPAsyncTCP.h>
#include <ESPAsyncWebServer.h>
#include <Wire.h> 
#include "SSD1306Wire.h"

SSD1306Wire display(0x3c, SDA, SCL);

const char ssid[] = "";
const char password [] = "";
bool isMyFlipped;
long long prevMili;

const char webpage [] PROGMEM = R"=====(
<html>
  <head>
    <title>ESP12E Simple Web Page</title>
  </head>
  <body>
    <p>Hello from ESP12E Module!</p>
  </body>
</html>
)=====";

#define LOGO16_GLCD_HEIGHT 16 
#define LOGO16_GLCD_WIDTH  16 

#define relayPin D5
#define phUpPin D6
#define phDownPin D7
#define nutrientPin D8

#define _wl_low D3
#define _wl_high D4

bool isPumpOn = false;
bool isPHUpOn = false;
bool isPHDownOn = false;
bool isNutrientOn = false;

bool wLevelLow = false;
bool wLevelHigh = false;

bool watering = false;

short waterState = 0; // water low

short forceTime = 0;
#define forceDelay 30000
String IPaddress;

static const unsigned char PROGMEM logo16_glcd_bmp[] =
{ B00000000, B11000000,
  B00000001, B11000000,
  B00000001, B11000000,
  B00000011, B11100000,
  B11110011, B11100000,
  B11111110, B11111000,
  B01111110, B11111111,
  B00110011, B10011111,
  B00011111, B11111100,
  B00001101, B01110000,
  B00011011, B10100000,
  B00111111, B11100000,
  B00111111, B11110000,
  B01111100, B11110000,
  B01110000, B01110000,
  B00000000, B00110000 };

//const char lol [] = "{ a:10, b:20, c:30}";

AsyncWebServer server(80);
/*
void onRequest(AsyncWebServerRequest *request) 
{
  request->send(404);
}*/


void setup() {
  // put your setup code here, to run once:
  Serial.begin(115200);

  Serial.println("trying to start the display") ;

  display.init();

  //display.flipScreenVertically();
  display.clear();

  display.setFont(ArialMT_Plain_10);
  display.setTextAlignment(TEXT_ALIGN_RIGHT);
  display.flipScreenVertically();
  display.drawString(128, 54, String(millis()));
  display.drawString(128, 42, "Initializing!" );

  display.displayOn();
  display.display();
  

  pinMode(relayPin,OUTPUT);
  pinMode(phUpPin,OUTPUT);
  pinMode(phDownPin,OUTPUT);
  pinMode(nutrientPin,OUTPUT);

  pinMode(13, OUTPUT);

  digitalWrite(13, HIGH);

  pinMode (_wl_low,INPUT);
  pinMode(_wl_high,INPUT);

  Serial.printf("Connecting to %s\n",ssid);

  WiFi.mode(WIFI_STA);
  WiFi.begin(ssid,password);

  if (WiFi.waitForConnectResult() != WL_CONNECTED) 
  {
    Serial.println("WiFi failed to connect.");
    delay(1000);
    ESP.restart();
  }
  Serial.printf("Connection successful\n");
  Serial.printf("Web Server IP address: %s \n", WiFi.localIP().toString().c_str());

  //String myip = "http://" + WiFi.localIP().toString();
  IPaddress = WiFi.localIP().toString();

  server.on("/index.json", HTTP_GET, [] (AsyncWebServerRequest *request) {
    char lol [256];
    sprintf(lol, "Uptime: %ld milliseconds", millis());

    request->send_P(200, "text/plain", lol);
  }
  );

  server.on("/", HTTP_GET, [] (AsyncWebServerRequest *request) {
    request->redirect("/index.json");
    Serial.printf("%ld: Redirecting\n",millis() );
    //request->send_P(200, "text/html", webpage);
  }
  );

  isMyFlipped = false;

  server.on("/forcePump", HTTP_GET, [] (AsyncWebServerRequest *request) {
    //request->redirect("/index.json");
    Serial.printf("%ld: Forcing Pump\n",millis() );
    isPumpOn = !isPumpOn;
    String response = "";
    if (isPumpOn) 
    {
      response = "Forcefully turned the pump on";
    }
    else 
    {
      response = "Forcefully turned the pump off";
    }
    forceTime = forceDelay;
    request->send_P(200, "text/html", response.c_str());
  }
  );

  server.on("/forceNutrient", HTTP_GET, [] (AsyncWebServerRequest *request) {
    //request->redirect("/index.json");
    Serial.printf("%ld: Forcing Nutrient Pump\n",millis() );
    isNutrientOn = !isNutrientOn;
    String response = "";
    if (isNutrientOn) 
    {
      response = "Forcefully turned the Nutrient pump on";
    }
    else 
    {
      response = "Forcefully turned the Nutrient pump off";
    }
    forceTime = forceDelay;
    request->send_P(200, "text/html", response.c_str());
  }
  );

  server.on("/forcePHUp", HTTP_GET, [] (AsyncWebServerRequest *request) {
    //request->redirect("/index.json");
    Serial.printf("%ld: Forcing PH Up Pump\n",millis() );
    isPHUpOn = !isPHUpOn;
    String response = "";
    if (isPHUpOn) 
    {
      response = "Forcefully turned the PH Up pump on";
    }
    else 
    {
      response = "Forcefully turned the PH Up pump off";
    }
    forceTime = forceDelay;
    request->send_P(200, "text/html", response.c_str());
  }
  );
  server.on("/forcePHDown", HTTP_GET, [] (AsyncWebServerRequest *request) {
    //request->redirect("/index.json");
    Serial.printf("%ld: Forcing PH Down Pump\n",millis() );
    isPHDownOn = !isPHDownOn;
    String response = "";
    if (isPHDownOn) 
    {
      response = "Forcefully turned the PH Down pump on";
    }
    else 
    {
      response = "Forcefully turned the PH Down pump off";
    }
    forceTime = forceDelay;
    request->send_P(200, "text/html", response.c_str());
  }
  );
  
  server.begin();
}
void drawScr1(int dt);
void loop() {
  //Delta time is the time between loops happening, a somewhat important value for keeping time.
  int deltaTime = millis() -  prevMili;
  prevMili = millis();
  drawScr1(deltaTime);
  


  if (forceTime > 0) 
  {
    forceTime -= deltaTime;
  }
  else 
  {
    // here we'll check if the state has changed
    isPumpOn = watering;
  }

  if (isPumpOn) 
  {
    digitalWrite(relayPin, LOW);
  }
  else 
  {
    digitalWrite(relayPin, HIGH);
  }

  if (isNutrientOn) 
  {
    digitalWrite(nutrientPin, LOW);
  }
  else 
  {
    digitalWrite(nutrientPin, HIGH);
  }

  if (isPHUpOn) 
  {
    digitalWrite(phUpPin, LOW);
  }
  else 
  {
    digitalWrite(phUpPin, HIGH);
  }

  if (isPHDownOn) 
  {
    digitalWrite(phDownPin, LOW);
  }
  else 
  {
    digitalWrite(phDownPin, HIGH);
  }

  if (digitalRead(_wl_low) == LOW ){
  wLevelLow = true;
  Serial.println("Low Switch On");
  }else {
    wLevelLow = false;
  }

if (digitalRead(_wl_high) == LOW) {
  wLevelHigh = true;
  Serial.println("High Switch On");
  } else {
    wLevelHigh = false;
  }

if (wLevelHigh && !wLevelLow) { // High
  waterState = 2;
  //isPumpOn = true;
  watering = true;
  Serial.println("WlH");
} else if (!wLevelHigh && wLevelLow) // Low 
{
  waterState = 0;
  watering = false;
  //isPumpOn = false;
  Serial.println("WL Low");
} else if (wLevelLow == false && wLevelHigh == false) 
{
  waterState = 1; // Water is between high and low
  
  //isPumpOn = false;
  Serial.println("WL Medium");
} else if (wLevelLow == true && wLevelHigh == true) 
{
  waterState = 3; // Water is between high and low
  //isPumpOn = false;
  watering = false;
  Serial.println("Fucked");
}

}

void drawScr1(int dt) //Draw Screen One
{
  display.clear();
  display.setFont(ArialMT_Plain_10);
  display.setTextAlignment(TEXT_ALIGN_RIGHT);
  display.drawString(128, 54, String(millis()));

  if (isPumpOn) 
  {
    display.drawString(128, 42, "W.Pump:on");
  }
  else 
  {
    display.drawString(128, 42, "W.Pump:off");
  }

  if (isPHUpOn) 
  {
    display.drawString(128, 30, "PH U. Pump:on");
  }
  else 
  {
    display.drawString(128, 30, "PH U. Pump:off");
  }

  if (isPHDownOn) 
  {
    display.drawString(128, 18, "PH D. Pump:on");
  }
  else 
  {
    display.drawString(128, 18, "PH D. Pump:off");
  }
  display.setTextAlignment(TEXT_ALIGN_LEFT);

  display.drawString(0, 54, "IP:" + String(IPaddress) );

  if (isNutrientOn) 
  {
    display.drawString(0, 42, "N. Pump:on");
  }
  else 
  {
    display.drawString(0, 42, "N. Pump:off");
  }
  display.drawString(0, 30, "W.S: " + String(waterState));

  display.displayOn();
  display.display();
}