PRO002 - Hydroponic Farm


Move to ESP8266

Lights and Pump

Nothing fancy here. The ESP is 3.3V logic and I had some issue using 5V relais, npn transistors and the digital outputs of the nodemcu. Probably my (lack of) soldering skillz are the issue but sometimes the connections were working, sometimes not. Next best thing: using two solid state relais, as you can see on the figure below.

Hydroponics circuit
Fig.1 - Nodemcu v2 with solid state relais.

Software controlling the lamp and the pump is nothing fancy. The lamp is on for 9 hours and then off for 15. The pump is on for 10 minutes and off for 4 hours. No pump when the light is off. Since the ESP has not Real Time Clock, the micros() function is used and each minute I update a parameter to keep track of the time pased during each cycle.

Web interface

Wanting to be able to update the settings of the pump and the light remotely, and the reason why I am trying the ESP is for finding out what the Wifi module can do.
The easiest way I found was using the ESP8266WebServer and the GET method. Programming the Web-page, of which the first version looks like this:

Web layout
Fig.2 - Web layout.

Getting the information afterward is then easy, checking the content of the parameters attached to the html forms.

if (server.arg("newLightOn") != "") {
    String ipt1 = server.arg("newLightOn");
    LightCycle_on = ipt1.toInt();
    LightCycletime = (((LightStatus + 1) % 2) * LightCycle_off) + (((LightStatus) % 2) * LightCycle_on);
  }

The one thing I don't like the way the html page is built, writing it as one long string, which is not really clear and cumbersome to add css styling.

Coming up next

As you might already see in the first image I connected a BME sensor as well, and in the future I want to monitor the temperature and moister, so that I can update pump and light timings for instance.

Remarks

When resetting the ESP module I need to decouple one of the digital outputs for it to boot up correctly. Uh, if you have any idea why, please let me know.

Not convinced about my settings for the Wifi. I am only able to connect to the module when I am close, and even then it is not consistent. Even though I want the ESP to connect to the avaiable Wifi another hotspot is still created that I can sometimes more easily connect to "esp8266". Could this be something lingering in the flash from a previous build?

UPDATE:

Previous issues were both my fault (which was to be epected). An additional line of code makes sure the ESP does not make his own access point by correclty setting the wifi.mode(WIFI_STA) as a STATION, as defined here.

I was unhappy with the ESP8266's behavior at startup and had some issues connecting to if from a distance. Both are ok, either because of the added line of code as mentioned earlier, or, and I think this mainly solved the start-up bahavior, I do not longer use the GPIO0 pin, which is apparently used UART or FLASH mode of the modue, which makes this page an interesting read.

The code

Below the full code of the project so far, feel free reach out if you have comments or remarks:

#include <ESP8266WiFi.h>
#include <WiFiClient.h>
#include <ESP8266WiFiMulti.h>
#include <ESP8266mDNS.h>
#include <ESP8266WebServer.h>

ESP8266WiFiMulti wifiMulti;   // Create an instance of the ESP8266WiFiMulti class, called 'wifiMulti'
ESP8266WebServer server(80);  // Create a webserver object that listens for HTTP request on port 80
void handleRoot();            // Function for HTTP handlers
void handleNotFound();        // Function for HTTP handlers

// Parameters for UV light controls
int LightOutputPin = 12;                  // Pin on the ESP controlling the light
int LightManual = 0;                     // Manually control state of the light
int LightStatus = 0;                     // Current status of the light  
int LightCycle_on  = 8 * 60;             // Minutes.
int LightCycle_off = 16 * 60;            // Minutes.
int LightCycletime = LightCycle_off;     // Cycle to start with
int Light_minutes = 0;                   // Timer to keep track of the current cycle
// Parameters for the nutrient pump
int PumpOutputPin = 14;                  // Pin on the ESP controlling the pump
int PumpManual = 0;                      // manually control state of the light
int PumpStatus = 0;                      // Current status of the pump
int PumpCycle_on = 10;                   // Minutes.
int PumpCycle_off = 5 * 60;              // Minutes.
int PumpCycletime = PumpCycle_off;       // Cycle to start with
int Pump_minutes = 0;                    // Timer to keep track of the current cycle
// Manual control active yes or no?
int manualCtrl_active = 0;
// timer for the micros() to keep track of the minutes
unsigned long timer;
// Visualizations for the site
String ControlType[2] = {"automatic", "manual"};
String activeState[2] = {"OFF", "ON"};

void setup(void) {
  // Setting the initial states of the digital outputs
  pinMode(LightOutputPin, OUTPUT);
  pinMode(PumpOutputPin, OUTPUT);
  digitalWrite(LightOutputPin, LightStatus);
  digitalWrite(PumpOutputPin, PumpStatus);

  Serial.begin(9600);                       // Start the Serial communication to send messages to the computer
  delay(10);
  Serial.println('
');

// Settings if you want an access point for the ESP instead of connecting to your Wifi
  //WiFi.softAP("ssid", "password");        // we want its own wifi network not connect to existing   
  //IPAddress myIP = WiFi.softAPIP();
  //Serial.print("AP IP address:");
  //Serial.println(myIP);                   // Send the IP address of the ESP8266 to the computer

  WiFi.mode(WIFI_STA);                        // configures the wifi mode as a STATION

// Settings if you want the ESP to connect with existing WIFI network
  wifiMulti.addAP("SSID", "Password");      // add Wi-Fi networks you want to connect to
  Serial.println("Connecting ...");
  int i = 0;
  
// Wait for the Wi-Fi to connect: scan for Wi-Fi networks, and connect to the strongest of the networks above  
  while (wifiMulti.run() != WL_CONNECTED) { 
    delay(250);
    Serial.print('.');
  }
  Serial.println('
');
  Serial.print("Connected to ");
  Serial.println(WiFi.SSID());              // Tell us what network we're connected to
  Serial.print("IP address:	");
  Serial.println(WiFi.localIP());           // Send the IP address of the ESP8266 to the computer


  if (MDNS.begin("esp8266")) {              // Start the mDNS responder for esp8266.local (not sure what)
    Serial.println("mDNS responder started");
  } else {
    Serial.println("Error setting up MDNS responder!");
  }

  server.on("/", HTTP_GET, handleRoot);     // Call the 'handleRoot' function when a client requests URI "/"
  server.onNotFound(handleNotFound);        // When a client requests an unknown URI (i.e. something other than "/"), call function "handleNotFound"
  server.begin();                           // Actually start the server
  Serial.println("HTTP server started");
}

void loop(void) {

  if ((micros() - timer) >= 60 * 1000000) { // ESP has no RTC and micro's only lasts ~70 minutes
    Pump_minutes = Pump_minutes + 1;        // 
    Light_minutes = Light_minutes + 1;      //
    timer = micros();
  }

  server.handleClient();                   // Listen for HTTP requests from clients
  setLightStatus();                        // Determine the status of the UV light
  setPumpStatus();                         // Determine the status of the nutrient pump
}

void handleRoot() {                         // When URI / is requested, capture the inputs and send the webpage
// Capture any available inputs 
  if (server.arg("newLightOn") != "") {
    String ipt1 = server.arg("newLightOn");
    LightCycle_on = ipt1.toInt();
    LightCycletime = (((LightStatus + 1) % 2) * LightCycle_off) + (((LightStatus) % 2) * LightCycle_on);
  }
  if (server.arg("newLightOff") != "") {
    String ipt2 = server.arg("newLightOff");
    LightCycle_off = ipt2.toInt();
    LightCycletime = (((LightStatus + 1) % 2) * LightCycle_off) + (((LightStatus) % 2) * LightCycle_on);
    //Serial.println(String(LightCycle_off));
  }
  if (server.arg("newPumpOn") != "") {
    String ipt3 = server.arg("newPumpOn");
    PumpCycle_on = ipt3.toInt();
    PumpCycletime = (((PumpStatus + 1) % 2) * PumpCycle_off) + (((PumpStatus) % 2) * PumpCycle_on);
    //Serial.println(String(PumpCycle_on));
  }
  if (server.arg("newPumpOff") != "") {
    String ipt4 = server.arg("newPumpOff");
    PumpCycle_off = ipt4.toInt();
    PumpCycletime = (((PumpStatus + 1) % 2) * PumpCycle_off) + (((PumpStatus) % 2) * PumpCycle_on);
    //Serial.println(String(PumpCycle_off));
  }
  if (server.arg("HydroponicCtrl") != "") {
    if (server.arg("HydroponicCtrl") == "manual") {
      manualCtrl_active = 1;
      String ipt5 = server.arg("LightSetMan");
      LightManual = ipt5.toInt();
      String ipt6 = server.arg("PumpSetMan");
      PumpManual = ipt6.toInt();
      //Serial.println(String(LightManual));
    } else {
      timer = micros();
      manualCtrl_active = 0;
      Pump_minutes = 0;
      Light_minutes = 0;
      LightCycletime = (((LightStatus +1) % 2) * LightCycle_off) + (((LightStatus) % 2) * LightCycle_on);
      PumpCycletime = (((PumpStatus +1) % 2) * PumpCycle_off) + (((PumpStatus) % 2) * PumpCycle_on);
    }
  }

  setLightStatus();                        // Determine the status of the UV light
  setPumpStatus();                         // Determine the status of the nutrient pump

  // Visualize the web page:
  server.send(200, "text/html", "<div style="width: 50%; border:solid;"><h1>Hydroponic control settings</h1>  <h2>Manual control settings</h2>  <form action="/" method="GET"><label for="lightCtrl">Manually control the outputs?</label></br>  <select name="HydroponicCtrl" id="HydroponicCtrl">  <option value="manual">Manual Control</option>  <option value="automatic">Automatic Control</option></select></br>  <select name="LightSetMan" id="LightSetMan">  <option value="1">Light ON</option>  <option value="0">Light OFF</option></select>  <select name="PumpSetMan" id="PumpSetMan">  <option value="1">Pump ON</option>  <option value="0">Pump OFF</option></select></br>  <input type="submit" value="Submit">  </select></form>  <h2>Light control settings</h2>  <form action="/" method="GET"> Light on time [min]: (currently: " + String(float(LightCycle_on) / 60) + " h)</br><input type="number" name="newLightOn"></br>  Light off time [min]: (currently: " + String(float(LightCycle_off) / 60) + " h)</br><input type="number" name="newLightOff"></br><input type="submit" value="Submit"></form>  <h2>Pump control settings</h2>  <form action="/" method="GET"> Pump on time [min]: (currently: " + String(float(PumpCycle_on) / 60) + " h)</br><input type="number" name="newPumpOn"></br>  Pump off time [min]: (currently: " + String(float(PumpCycle_off) / 60) + " h)</br><input type="number" name="newPumpOff"></br><input type="submit" value="Submit"></form></div>  <div style="width: 50%; border:solid;"><h1>Hydroponic control states</h1><table style="width:60%; border:solid;">  <tr><td>Controller type:</td><td>"+ ControlType[manualCtrl_active] +"</td></tr>  <tr><td>Light Status:</td><td>"+activeState[LightStatus]+"</td></tr>  <tr><td>Cycletime remaining:</td><td>"+(LightCycletime - Light_minutes)+" min</td></tr>  <tr><td>Pump Status:</td><td>"+activeState[PumpStatus]+"</td></tr>  <tr><td>Cycletime remaining:</td><td>"+(PumpCycletime - Pump_minutes)+" min</td></tr>  </table></div>");
}

void handleNotFound() {
  server.send(404, "text/plain", "404: Not found"); // Send HTTP status 404 (Not Found) when there's no handler for the URI in the request
}

void setLightStatus() { // Determine and set the status of the Light

  if (manualCtrl_active == 1) {
    digitalWrite(LightOutputPin, LightManual);
    LightStatus = LightManual;
  }else {
    if ((Light_minutes) >= (LightCycletime)) {
      LightStatus = (LightStatus + 1) % 2;
      digitalWrite(LightOutputPin, LightStatus);
      Light_minutes = 0;
      LightCycletime = (((LightStatus + 1) % 2) * LightCycle_off) + (((LightStatus) % 2) * LightCycle_on);
      //Serial.println(String(Light_minutes));
    }
  }
}

void setPumpStatus() { // Determine and set the status of the pump

if (manualCtrl_active == 1) {
    digitalWrite(PumpOutputPin, PumpManual);
    PumpStatus = PumpManual;
  }else if(LightStatus == 0){                // no pump when the lights are out
    digitalWrite(PumpOutputPin, 0);
    PumpStatus = 0;
    }else{
    if ((Pump_minutes) >= (PumpCycletime)) {
      PumpStatus = (PumpStatus + 1) % 2;
      digitalWrite(PumpOutputPin, PumpStatus);
      Pump_minutes = 0;
      PumpCycletime = (((PumpStatus + 1) % 2) * PumpCycle_off) + (((PumpStatus) % 2) * PumpCycle_on);
      //Serial.println(String(Pump_minutes));
    }
  }
}
         

Project overview:

Project introductionAugust 7th 2020
Setting up a setupSeptember 13th 2020
Move to the ESPOctober 19th 2020