Need guidance on temp & humidity controller for greenhouse

Hi Guys,

I have a small greenhouse in my backyard that I would like to monitor and control. The parts that I used are as follow:

  1. Wemos D1 R2
  2. Arduino sensor shield
  3. 16x2 I2C LCD
  4. 2 relay module, 1 is connected to a fan, 2 is connected to a humidifier
  5. Dht22

The first code that I wrote is about controlling the wemos using Blynk application. It works fine but the downside is I cannot set the setpoints for temp and humidity. I would like to implement EEPROM functions so that I would not lose my setting after reboot or power loss. Also, this device will be placed at another rural location with no internet connection. Therefore, I added 3 push buttons (Menu, Up, Down) so that I could set the setpoints manually.

The flow of the menu will be:
Page 1 (Homepage) will display Fan Status and Pump status on 1st line. Temp and humidity readings on 2nd line.
Page 2 and 3 will be setting the temperature higher and lower setpoints
Page 4 and 5 will be setting the humidity higher and lower setpoints.

If the menu button is pressed, the LCD display will return to homepage after a few seconds of no input and the setting will be saved.

#define BLYNK_TIMEOUT_MS  750  
#define BLYNK_HEARTBEAT   17
#define BLYNK_PRINT Serial
#define DHTPIN D2 //arduino shield v5 pin 4
#define button D3
#define DHTTYPE DHT22
#define LCDWIDTH 16
#define LCDHEIGHT 2 


#include <ESP8266WiFi.h>
#include <Wire.h>
#include <LiquidCrystal_I2C.h>
#include <BlynkSimpleEsp8266.h>
#include "DHT.h"
#include <EEPROM.h>

BlynkTimer timer;

// You should get Auth Token in the Blynk App.
// Go to the Project Settings (nut icon).
//char auth[] = ""; //blynk cloud
char auth[] = ""; //local server


// Your WiFi credentials.
// Set password to "" for open networks.
char ssid[] = "";
char pass[] = "";
//char server[]          = "blynk-cloud.com";
//unsigned int port      = 8442;

int LED = D5; //arduino shield v5 pin 7
int relayFan = D3; //arduino shield v5 pin 8
int relayHum = D4; //arduino shield v5 pin 9
int status = WL_IDLE_STATUS;

float hum;
float temp;
float humLow = 80.0;
float humHigh = 95.0;
float tempLow = 30.0;
float tempHigh = 32.0;

char blank[] = "                ";
int fanStatus = 0;
int humStatus = 0;
char welcomeMessage[] = "TEMP & HUMIDITY METER";
char fanString[] = "FAN:";
char fanStringStatus[10] = "";
char pumpString[] = "PUMP:";
char pumpStringStatus[10] = "";
char statusString[25] = "FAN:OFF PUMP:OFF";
char ledOn[] = "LED ON";
char ledOff[] = "LED OFF";

LiquidCrystal_I2C lcd(0x27, 2, 1, 0, 4, 5, 6, 7);
DHT dht(DHTPIN, DHTTYPE);

void setup()
{
  dht.begin();
  lcd.begin(16,2);
  lcd.setBacklightPin(3, POSITIVE);
  lcd.setBacklight (HIGH);
  lcd.clear();
  lcd.home();
  lcd.print(welcomeMessage); //if use this line sensor is working fine
  
  // Debug console
  Serial.begin(115200);
  pinMode(LED, OUTPUT);
  pinMode(button, INPUT);
  pinMode(relayFan, OUTPUT);
  pinMode(relayHum, OUTPUT);
  Blynk.connectWiFi(ssid, pass);
  timer.setInterval(11000L, CheckConnection);
  Blynk.config(auth, IPAddress(192,168,1,188), 8080);
  Blynk.connect();                     
   
  //Blynk.begin(auth, ssid, pass);
  Blynk.virtualWrite(V7, tempHigh);
  Blynk.virtualWrite(V8, tempLow);
  Blynk.virtualWrite(V9, humHigh);
  Blynk.virtualWrite(V10, humLow);
}

void loop()
{
  timer.run();
  if(Blynk.connected())
  {
      Blynk.run();
  }
  readSensor();
  printSerial();
  printLcd();
  switchFan(tempLow, tempHigh);
  switchHum(humLow, humHigh);
 }

BLYNK_WRITE(V3) 
{
  int pinValue = param.asInt(); // Assigning incoming value from pin V3 to a variable
  if (pinValue == 1) 
  {
    digitalWrite(LED, HIGH); // Turn LED on.
    printLcdText(ledOn, 0, 1);
  } 
  else 
  {
    digitalWrite(LED, LOW); // Turn LED off.
    printLcdText(ledOff, 0, 1);
  }
}


BLYNK_WRITE(V7) //set tempHigh
{
  float pinValue = param.asFloat(); // Assigning incoming value from pin V3 to a variable
  int newTemp = ((pinValue + 0.005)*10);
  tempHigh = (newTemp/10.0);
  if(tempHigh > tempLow)
  {
    switchFan(tempLow, tempHigh);
  }
  else
  {
    tempHigh = tempLow; //set tempHigh = tempLow limit if tempHigh is lower than tempLow
    Blynk.virtualWrite(V7, tempLow);
  }  
}

BLYNK_WRITE(V8) //set tempLow
{
  float pinValue = param.asFloat(); // Assigning incoming value from pin V3 to a variable
  int newTemp = ((pinValue + 0.005)*10);
  tempLow = (newTemp/10.0);
  if(tempLow<tempHigh)
  {
    switchFan(tempLow, tempHigh);
  }
  else
  {
    tempLow = tempHigh;
    Blynk.virtualWrite(V8, tempHigh);
  }  
}

BLYNK_WRITE(V9) //set humHigh
{
  float pinValue = param.asFloat(); // Assigning incoming value from pin V3 to a variable
  int newHum = ((pinValue + 0.05)*10);
  humHigh = (newHum/10.0);
  if(humHigh> humLow)
  {
    switchHum(humLow, humHigh);
  }
  else
  {
    humHigh = humLow; //set tempHigh = tempLow limit if tempHigh is lower than tempLow
    Blynk.virtualWrite(V9, humLow);
  }
}

BLYNK_WRITE(V10) //set humLow
{
  float pinValue = param.asFloat(); // Assigning incoming value from pin V3 to a variable
  int newHum = ((pinValue + 0.05)*10);
  humLow = (newHum/10.0);
  if( humLow < humHigh)
  {
    switchHum(humLow, humHigh);
  }
  else
  {
    humLow = humHigh;
    Blynk.virtualWrite(V10, humHigh);
  }
}


void printLcdText(char *text, int lcdCol, int lcdRow)
{
  
  lcd.setCursor(lcdCol, lcdRow);
  lcd.print(blank);
  lcd.setCursor(lcdCol, lcdRow);
  lcd.print(text);
}
void readSensor()
{
  // Wait a few seconds between measurements.
  delay(2000);

   hum = dht.readHumidity();
  temp = dht.readTemperature();
}
void CheckConnection()
{    
  // check every 11s if connected to Blynk server
  if(!Blynk.connected())
  {
    Serial.println("Not connected to Blynk server"); 
    Blynk.connect();  // try to connect to server with default timeout
  }
  else{
    Serial.println("Connected to Blynk server");     
  }
}

void printSerial()
{
  Serial.println(statusString);
  
  Serial.print(F("Temperature: "));
  Serial.print(temp);
  Serial.print(F("°C  Humidity: "));
  Serial.print(hum);
  Serial.println(F("% "));
  //setpoint printout
  Serial.print(F("tempHigh: "));
  Serial.print(tempHigh);
  Serial.print(F(" tempLow: "));
  Serial.println(tempLow);
  Serial.print(F("humHigh: "));
  Serial.print(humHigh);
  Serial.print(F(" humLow: "));
  Serial.println(humLow);
  Serial.print(F("fanStatus: "));
  Serial.println(fanStatus);
}

void printLcd()
{
  if (isnan(hum) || isnan(temp)) 
  {
    lcd.setCursor(0,1);
    lcd.print("Gagal sensor");
    Serial.println(F("Failed to read from DHT sensor!"));
    return;
  }

  lcd.setCursor(0,0);  
  lcd.print(statusString);
  lcd.setCursor(0,1);
  lcd.print(F("T:"));
  lcd.print(temp, 1);
  lcd.print(F("C "));
  lcd.print(F("H:"));
  lcd.print(hum, 1);
  lcd.print(F("%"));
  
  updateBlynk();
}

void updateBlynk()
{
  Blynk.virtualWrite(V5, hum);
  Blynk.virtualWrite(V6, temp);
  
  if (fanStatus == 0)
  {
    Blynk.virtualWrite(V11, "OFF");
  }else
  {
    Blynk.virtualWrite(V11, "ON");
  }
   if (humStatus == 0)
  {
    Blynk.virtualWrite(V12, "OFF");
  }else
  {
    Blynk.virtualWrite(V12, "ON");
  }
}

void switchFan(float low, float high) 
{
  if (temp >= high) 
  {
    digitalWrite(relayFan, HIGH); // Turn Fan on.
    fanStatus = 1;
    updateBlynk();
    strcpy(statusString, fanString);
    strcat(statusString, "ON ");
    if (humStatus == 0)
    {
      strcat(statusString, pumpString);
      strcat(statusString, "OFF");
    }
    else
    {
      strcat(statusString, pumpString);
      strcat(statusString, "ON ");
    }
      
  } 
  else if(temp <= low)
  {
    digitalWrite(relayFan, LOW); // Turn Fan off.
    fanStatus = 0;
    updateBlynk();
    strcpy(statusString, fanString);
    strcat(statusString, "OFF ");
    strcat(statusString, pumpString);
    strcat(statusString, "OFF");
      }
}

void switchHum(float lowH, float highH) 
{
  if (hum <= lowH && fanStatus == 1) 
  {
    digitalWrite(relayHum, HIGH); // Turn Pump on.
    humStatus = 1;
    updateBlynk();

    strcpy(statusString, fanString);
    strcat(statusString, "ON  ");
    strcat(statusString, pumpString);
    strcat(statusString, "ON ");
      } 
  else if(hum >= highH && fanStatus ==1)
  {
    digitalWrite(relayHum, LOW); // Turn Pump off.
    humStatus = 0;
    updateBlynk();

    strcpy(statusString, fanString);
    strcat(statusString, "ON  ");
    strcat(statusString, pumpString);
    strcat(statusString, "OFF");
  }
}

Sorry for the 2nd post as apparently I had exceeded the 9000 characters.

I managed to write the code for the menu and the push buttons following other examples. It runs ok by itself. However, I am not sure how to combine both codes or where to place the functions for the LCD Menu. Can someone point me to the correct directions on how should I be able to complete my project?

My goal is to be able to control my greenhouse either manually or using Blynk application.

Future upgrades will be using gsm module and telegram bot. But for now, I just need a dumb temperature and humidity controller.

I appreciate any input or guidance. Thank you in advance.

#define LCDWIDTH 16
#define LCDHEIGHT 2 

#include <LiquidCrystal_I2C.h>
LiquidCrystal_I2C lcd(0x27, 2, 1, 0, 4, 5, 6, 7);

//Input & Button Logic
const int numOfInputs = 3;
const int inputPins[numOfInputs] = {D5,D6,D8};//arduino pin D5/7, D6/8, D8/10
int inputState[numOfInputs];
int lastInputState[numOfInputs] = {LOW,LOW,LOW};
bool inputFlags[numOfInputs] = {LOW,LOW,LOW};
long lastDebounceTime[numOfInputs] = {0,0,0};
long debounceDelay = 5;

//Variables for autorreturn
unsigned long previousMillis = 0;
unsigned long interval = 10000; //Desired wait time

//LCD Menu Logic
const int numOfScreens = 5;
int currentScreen = 0;
String screens[numOfScreens][4] = {{"FAN:", "PUMP:", "T:", "H:"}, {"Temp High","degC", "", ""}, {"Temp Low", "degC", "", ""}, 
{"Humidity High","%", "", ""},{"Humidity Low","%", "", ""}};
float parameters[numOfScreens];
void setup() {
  for(int i = 0; i < numOfInputs; i++) {
    pinMode(inputPins[i], INPUT);
    digitalWrite(inputPins[i], HIGH); // pull-up 20k
  }
  lcd.begin(16, 2);
  lcd.setBacklightPin(3, POSITIVE);
  lcd.setBacklight (HIGH);
  printScreen();
}

void loop() {
  setInputFlags();
  resolveInputFlags();
  returnHome();
}

void setInputFlags() {
  for(int i = 0; i < numOfInputs; i++) {
    int reading = digitalRead(inputPins[i]);
    if (reading != lastInputState[i]) { 
      lastDebounceTime[i] = millis();
    }
    if ((millis() - lastDebounceTime[i]) > debounceDelay) { 
      if (reading != inputState[i]) { 
        inputState[i] = reading;
        if (inputState[i] == HIGH) { 
          inputFlags[i] = HIGH;
        }
      }
    }
    lastInputState[i] = reading; 
  }
}

void resolveInputFlags() {
  for(int i = 0; i < numOfInputs; i++) {
    if(inputFlags[i] == HIGH) {
      inputAction(i);
      inputFlags[i] = LOW;
      printScreen();
    }
  }
}

void inputAction(int input) {
      if(input == 0) {
      if (currentScreen == numOfScreens-1) {
        currentScreen = 0;
      }else{
        currentScreen++;
      }
  }else if(input == 1) {
    parameterChange(0);
  }else if(input == 2) {
    parameterChange(1);
  }
}

void parameterChange(int key) {
  if(key == 0) { //if up button is pressed
    parameters[currentScreen]++;
  }else if(key == 1) { //if down button is pressed
    parameters[currentScreen]--;
  }
}

void printScreen() {
  lcd.clear();
  lcd.print(screens[currentScreen][0]);
  lcd.setCursor(0,1);
  lcd.print(parameters[currentScreen]);
  lcd.print(" ");
  lcd.print(screens[currentScreen][1]);
}

void returnHome()
{
  unsigned long currentMillis = millis();
        
     if (currentMillis - previousMillis > interval) 
     {  //If interval is reached, return to home page 1
        previousMillis = currentMillis;
        lcd.clear();
        currentScreen = 0;    
        printScreen();
     }  
     for(int i = 0; i < numOfInputs; i++) 
     {
      int reading = digitalRead(inputPins[i]);
      if (reading == HIGH)
      {
        previousMillis = currentMillis;// Reset millis counter If any button is pressed 
      }
     }
}

Your request is a little confusing! Are you asking for help merging the code from post #0 with the code from post #1? I can't see the point of using Blynk in an isolated area with no internet access.

Yes, I need help on how to implement 3 buttons menu on my current Blynk code.

Also yes regarding being useless using blynk in isolated area. That is why I mentioned I also need to make a dumb version temp and humidity controller. Is it possible to have 1 version of code and just disable blynk to the device that doesn’t need it?

I don't think you'll need to disable Blynk. If it can't connect, it will just do nothing.

Does your remote location have 3G/4G access? If so, when you visit, you take a smartphone, start up a WiFi hotspot and allow Blynk to connect and interact with it that way, so no need for LCD & buttons.

Have you tried merging the two sketches? What problems did you have?

Thank you PaulRB for spending your time replying my thread.

My other place might have a 2G/3G access. Another reason for me wanting to make a dumb version is for my local helper to monitor my greenhouse without me being there too.

I had tried merging the 2 sketches. I will try to post it tomorrow. Thank you again.

Have you considered sending the data via MQTT to a MQTT local broker, MQTT software is free?

You can use, running on the same computer serving as a MQTT Broker, a produce, free, like Node-Red to make a graphical use interface.

Me I use a RPi as a MQTT Broker. I run a Python script that manages the MQTT information.

Example.

Outside is a ESP32 weather station that publishes the data to the MQTT broker and from there sends the data to a ESP32 on a night stand so we can view the outside weather info. The outside weather info is, also, placed into a Db on our web site. I can visit the website, see it is a hot day, set the AC temperature and enable AC, from a place with internet access.

My wife, at work can view the Indexed Air Quality level, and change the humidity level.

Idahowalker:
Have you considered sending the data via MQTT to a MQTT local broker, MQTT software is free?

You can use, running on the same computer serving as a MQTT Broker, a produce, free, like Node-Red to make a graphical use interface.

Me I use a RPi as a MQTT Broker. I run a Python script that manages the MQTT information.

Example.

Outside is a ESP32 weather station that publishes the data to the MQTT broker and from there sends the data to a ESP32 on a night stand so we can view the outside weather info. The outside weather info is, also, placed into a Db on our web site. I can visit the website, see it is a hot day, set the AC temperature and enable AC, from a place with internet access.

My wife, at work can view the Indexed Air Quality level, and change the humidity level.

Thank you for your reply. I am not familiar with MQTT and will try to study it for future applications. Thank you

I had tried combining the 2 sketches as attached. I attached the file as I keep getting 9000 characters exceeded error.

The problems that I have are:

  1. The LCD Displaying the data read from DHT22 is not displayed as fast as blynk or serial monitor.
  2. The buttons are not working. Screen only showing the homepage and pressing any button is not making any changes to the display.
  3. EEPROM.write and EEPROM.read is not showing the correct value. (note: I had created another button in blynk app to call the EEPROM.commit() functions when there are changes to the values). I might do something wrong here.

ThermLCDButtonsMenu.ino (12.1 KB)

Your code is really over-complex to achieve simple functions. Look for ways to simplify it.

As far as I can see, the LCD is only updated when a button is pressed?

There is a delay(2000) in readSensor(). This might be causing some problems with the button debounce code. I suggest setting up another BlynkTimer to run readSensor() every 2 seconds without needing to use delay().

Any example on which part of the code that is too complex? I am sorry as I am still learning.

I managed to get the eeprom functions to work and I can saved the setting to the flash memory. So my problems are left with number 1 and 2.

PaulRB:
Your code is really over-complex to achieve simple functions. Look for ways to simplify it.

As far as I can see, the LCD is only updated when a button is pressed?

There is a delay(2000) in readSensor(). This might be causing some problems with the button debounce code. I suggest setting up another BlynkTimer to run readSensor() every 2 seconds without needing to use delay().

You're right about the delay() causing problems. So I created another BlynkTimer to run. It runs normally for the first few seconds and after that begin to show error reading from the DHT in endless fast loop.

I also created another version using millis in the readSensor() functions and also the same result.

void readSensor()
{
  unsigned long currentMillis = millis();
        
  if ((currentMillis - prevReadMillis) > readInterval) 
  {
    prevReadMillis = currentMillis;
    hum = dht.readHumidity();
    temp = dht.readTemperature();
  }  
}

Did I write this function wrongly?

reading from the DHT in endless fast loop.

The point of using the BlynkTimer was that it would only be called every 2000ms or so.

Did I write this function wrongly?

I can’t see anything wrong, but I don’t know what value you gave readInterval.

PaulRB:
The point of using the BlynkTimer was that it would only be called every 2000ms or so.I can't see anything wrong, but I don't know what value you gave readInterval.

readInterval is 2 seconds.

Will the BlynkTimer works even if Blynk not connected to server? In the mean time I use millis() to read the sensor every 2 seconds.

I managed to merged both sketches and able to save the setpoints from Blynk or by pressing the buttons to the flash memory. I am able to get the buttons to work if only the readSensor(), switchFan() and switchHum() functions were disabled like so.

 void loop() 
{
  timer.run();
  if(Blynk.connected())
  {
      Blynk.run();
  }
  printSerial();
  //readSensor(); //if these 3 functions were commented, the buttons functions are working
  //switchFan();
  //switchHum(); 
  setInputFlags(); //if these 3 functions were commented, the readSensor functions are working
  resolveInputFlags();
  returnHome();

}

If all the functions in the loop were uploaded, the device is able to read the data from the sensor and display on the lcd and blynk. However, if any of the buttons were pressed, then the sensor will display Nan.

Is it possible that pressing any buttons shorted the sensor such that it won't be able to read any readings?