ESP8266 HVAC control

Now I just need to figure out the code to make the fridge only run for 5 minutes at a time, otherwise the temp is a roller coaster and I don’t want to be quick cycling it too much.

Well, the fermentation fridge was the first on my list, next up is my reef aquarium heater

Shutting it off after 5 minutes sort of defeats the purpose of having a thermostat. It might make more sense set the Simple Timer to run TempUpdate less frequently (e.g. every 15 minutes), or to increase the hysteresis level in the App settings, so that the compressor runs for longer periods, but less frequently. That assumes, of course, that whatever you’re fermenting can handle the temperature swings.

If the problem stems from large fluctuations in the sensor readings, you could try average together more readings from the sensor to get a more stable TempAct.

I have it checking every 5 minutes(ill have to check my code), but what is happening is that in that time frame, the temp drops too low. I have the hysteria set at 2. Trying to get me my temp prob down into the Carboy which should work a lot better, but not a lot of space on the cap to install a thermowell. This is the kind of temp swing I am getting.

Assuming you’re getting good readings from your temperature probe (which you’ve already noted may be a problem), the nature of any thermostat is that temperature control precision increases proportionally with the frequency of heating / cooling cycles. If you need both precise temperature control AND infrequent cycles, the answer is to use better insulation on the carboy.

Hi there,

This is what i did with your code @chrome1000, Im using to control humidity for mushrooms.

Items List:

  • Arduino Uno
  • Relay module
  • Ethernet Shield
  • Sensor Shield v5 (to have better wiring for sensors)
  • 2 DHT22 Sensors

I use two sensors, one is just for reading and the other is for controlling humidity. The first sensor is wired 12m and the other 16m from the arduino.

I was using a electrovalve to control the water that goes in to mist system. Now i bought a ultrasonic fogger machine beacuse it make better Fog for grow mushrooms.

Now i have a problem to control this ultrasonic machine. I want to add a Delay when it turn ON beacuse i dont want to turn on/off it every second when the humidity goes down. This machine runs in 220v, soo i control it with electrical contactor.

If someone have a clue to solve it, it will be a good one. I appreciate that!:pray::grin:

I´m new in this stuff, soo if you see something wrong or to be improve please let me known.

P.S: - sorry for my grammar, english is not my native language. :sweat_smile:
- Soon i will post some pics. of the proyect.

Blink dashboard:

  • Incubacion = Incubation room Temperature (red one)
  • Incubacion= Incubation room Humidity (green one)
  • Frutificacion= grow room Temperature(yellow one)
  • Seleccionar humedad deseada= Select desired humidity (Slider)
  • Humedad Frutificacion= grow room Humidity
  • Humedad Deseada= Selected Humidity

Code:

#include <SPI.h>
#define BLYNK_PRINT Serial   
#include <Ethernet.h>
#include <BlynkSimpleEthernet.h>
#include <SimpleTimer.h>
#include <avr/wdt.h> 
#include <DHT.h>



char auth[] = "authToken";
//setup Ethernet 
byte mac[] = { 0xDE, 0xAD, 0xBE, 0xEF, 0xFE, 0xED };
byte ip[] = { IP };
byte gateway[] = { gateway };
byte subnet[] = { subnet };

SimpleTimer timer;

#define DHTPIN 5
#define DHTPIN1 3     
#define DHTTYPE DHT22 //sensor 1
#define DHTTYPE1 DHT22 //sensor 2

DHT dht(DHTPIN, DHTTYPE);
DHT dht1(DHTPIN1, DHTTYPE1);

int HumDes = 85;
int PreviousHumDes = 85;
int HumAct = dht.readHumidity();
int HumCorrection = 0; // i dont use corrections for the sensors
int UpdateFrequency = 4000L; 
float LastRead;

int RelayPin = 7;

boolean FirstRead = true;
WidgetLED led1(V2); //for led on/off dashboard 

void setup() 
{
  wdt_disable();
  
  Ethernet.begin(mac,ip, gateway, subnet); //call setup ethernet
  
  dht.begin(); //sensor 1
  delay(2250);  
  dht1.begin(); //sensor 2
  delay(2250);

  pinMode(RelayPin,OUTPUT);
  digitalWrite(RelayPin,HIGH);
 

  Serial.begin(115200);
  delay(10);
  Blynk.begin(auth);
  Serial.println("Startup");
  Serial.println("");
  Serial.print("IP = ");
  Serial.println(Ethernet.localIP());
  
  timer.setInterval(UpdateFrequency, HumUpdate);
  
  wdt_enable(WDTO_8S);
}

BLYNK_WRITE(V3){
  HumDes = param.asInt();
  Blynk.virtualWrite(V1,HumDes);
}


void HumUpdate() //Algorithm for turnig Electrovalve
{
{
  float ReadH = dht.readHumidity();
  float t = dht.readTemperature(); 
  float h1 = dht1.readHumidity();
  float t1 = dht1.readTemperature();
    
  Blynk.virtualWrite(V6, t);
  Blynk.virtualWrite(V7, h1);
  Blynk.virtualWrite(V8, t1);

  if (isnan(ReadH)) {
    Serial.println(F("Failed to read from DHT sensor!"));
    return;
    }
    
  if (FirstRead == true){
    HumAct = (int)(ReadH + HumCorrection);
    FirstRead = false;
    Serial.print("First humidiy reading (corrected): ");
    Serial.println(HumAct);
    LastRead = ReadH;
    return;   
  }
    
  else   { 
    int HumAvg = (int)((ReadH + LastRead + (2 * HumCorrection))/2);
    if (HumAvg >= HumAct + 1){
      HumAct = HumAct + 1;
    }
    if (HumAvg <= HumAct - 1){
      HumAct = HumAct -1;
    }

    LastRead = ReadH;
  }
  Blynk.virtualWrite(V0,ReadH);  //I just wanna see the actual humidity
  Serial.print("Actual Humidity (corrected): ");
  Serial.println(HumAct);
    
    if (HumAct < HumDes){
      digitalWrite(RelayPin,LOW);
      led1.on();
      }
 
    else if (HumAct >= (HumDes)) {
    digitalWrite(RelayPin,HIGH);
    led1.off();
    }
 
}
}

void loop() 
{
  Blynk.run();
  timer.run();
  wdt_reset();
}

The answer to preventing your humidifier from turning on and off so frequently is to include a hysteresis level. This is necessary in thermostats, as well. If you check my code, there are hysteresis levels included for both heating and cooling. Hysteresis is the amount of additional change that you want the system to make beyond the nominal level. If I have the heat set to 72F with a 2 degree hysteresis, the heat will turn on at 71F, but will actually remain on until the temperature reaches 74 degrees. The set temperature and hysteresis define a minimum and maximum acceptable range of temperatures.

For example, if you wanted to maintain 70% humidity in your growing environment, you might tell the humidifier to turn on when humidity drops below 68% (your minimum). If you added a hysteresis level of 5%, the humidifier would continue to run until it reached 73% humidity (your maximum).

Hope this is helpful.

1 Like

You could also define minimum run times, so that you wait until the minimum runtime has expired before you check if the humidity is below the minimum minimum humidity hysteresis level.

Pete.

1 Like

Thanks a lot for the fast response!! @chrome1000 @PeteKnight, i will try it!!

I’ve added a few new features to the ever evolving thermostat code. In addition to fixing some minor bugs, here’s what’s new::

  1. Humity compensation: It’s Summer here in Chicago, and I’ve noticed that whenever there’s a lot of humidity in the air, I set the air conditioning temperature lower to compensate. So, it occurred to me that my thermostat could and should do this for me. I’ve now added a “use perceived temperature” option in the settings. EDIT: I originally used a simplified version of the formula that meteorologists use for calculating the heat index, or “feels like” temperature, but have since changed it to a simple linear offset of 1- 6 degrees when the humidity is above 40%… Humidity is reported on pin V2.

  2. Widget color setting: In response to the recent availability of a light color themed Blynk dashboard, I’ve added widget color themes to the SETTINGS menu. Red and blue are still reserved for showing the active heating and cooling status, but all other Blynk colors can now be selected as themes right from the dashboard. One caveat: avoid setting the widget color to white when you have the Blynk LIGHT theme enabled. You won’t be able to see the white widgets on the white background. If you do this, you’ll have to switch back to dark mode to see your widgets again.

  3. Various timers have been replaced with the much simpler Blynk.timer.

/**************************************************************************** 
 *  ThermoX v0.18.0 Thermostatic HVAC control
 *  
 *  Compares readings from a DHT11 temperature sensor with desired temperature
 *  from the Blynk application, and runs the fan unit as necessary to heat or 
 *  cool.  Hysteresis levels for both Summer and Winter are independently 
 *  adjustable from 2 to 6 degrees. The temperature sensor readings can be 
 *  offset up or down. All settings are saved to EEPROM, and 
 *  automatically reloaded on startup.
 *  
 *  "Home" setting is triggered by IFTTT iOS location channel, and results in an
 *  action on the Maker channel. The Maker channel parameters are as follows:
 *       URL: http://blynk-cloud.com:8080/YOUR_TOKEN/pin/V31
 *       Method: PUT
 *       Content Type: application/json
 *       Body: ["1"]    
 *  "Away" mode requires an identical IFTTT recipe, but with
 *       Body: ["0"]
 *  
 *  Added color coding (red/blue) for heating and cooling modes to dashboard widgets.
 *  Added a setting for widget color change. The red / blue "on" states will still be used
 *  heating or cooling.
 *  
 *  Added humidity sensing and a "perceived temperature" setting that takes into account the 
 *  different way people perceive temperature when humidity is high. It's only for Summer mode, 
 *  and only when the temperature is above 70 degrees F. 
 *  
 *  Changed Simple Timer calls to Blynk Timer. Settings buttons and menu timers are all 
 *  monitored by it now.
 *  
 *  Changed Manual mode to a 15 minute pulse mode.
 *  
 *  Added IFTTT / Amazon Echo integration for operating with voice commands. Uses 
 *  the IFTTT Maker Channel in the same way as above, but with an Amazon Alexa voice 
 *  trigger. To manually run the fan, use the following Maker Channel parameters: 
 *       URL: http://blynk-cloud.com:8080/YOUR_TOKEN/pin/V6
 *       Method: PUT
 *       Content Type: application/json
 *       Body: ["1"]    
 *  Make identical recipes (now called applets) for Temperature Up and Temperature 
 *  Down, substituting Body values of ["2"] and ["3"], repsectively.  
 *  
 *  Added a press-and-hold requirement to enter the settings menu, as well as
 *  a Menu timeout and reset after a period of inactivity. 
 *  Added manual overrides to force system run or halt, independent of other factors.
 *  
 *  WiFi connection is now simplified with Tapzu's WiFiManager. Wifi automatically
 *  reconnects to last working credentials. If the last SSID is unavailable, it
 *  creates an access point ("BlynkAutoConnect"). Connect any wifi device to the
 *  access point, and a captive portal pop up to receive new wifi credentials.
 *  
 *  The hardware is minimal: an ESP-01, a single relay on GPIO 0, and a DHT11
 *  temperature sensor on GPIO 2.
 *  
*****************************************************************************
*/
#include <ESP8266WiFi.h>  //https://github.com/esp8266/Arduino
#include <BlynkSimpleEsp8266.h>
#include <ESP8266WebServer.h>
#include <DNSServer.h>
#include <WiFiManager.h>  //https://github.com/tzapu/WiFiManager
#include "DHT.h"
#include <EEPROM.h>

#define UpdateFrequency 10000 //How often a new temperature will be read
#define MenuTimeOut 7000 //Menu timeout from inactivity
#define LongPress 650 //How long SETTINGS button needs to be pressed to enter menu
#define RelayPin 2


const String BLYNK_BLUE =    "#04C0F8";
const String BLYNK_RED   =   "#D3435C";
const String BLYNK_WHITE  =  "#FFFFFF";
const String BLYNK_BLACK =   "#000000";
const String BLYNK_GREEN  =  "#23C48E";
const String BLYNK_YELLOW =  "#ED9D00";
const String BLYNK_DARK_BLUE = "#5F7CD8";

String NormalWidgetColor = BLYNK_WHITE;
String PreviousWidgetColor = BLYNK_WHITE;

DHT dht(0,DHT11); //Initialize the sensor. Use pin 0. Sensor type is DHT11.

// Timer for temperature updates
BlynkTimer timer;

//WiFi and Blynk connection variables
char auth[] = "Your_token_here"; // Blynk token "YourAuthToken"

//Thermostat variables
int TempDes = 70;             //Desired temperature setting
int PreviousTempDes;
int TempAct = 70;             //Actual temperature, as measured by the DHT11 sensor
int BadRead = 0;              //Counts consecutive failed readings of the DHT11 sensor
float LastRead = 70;          // Previous temperature reading
int Humidity = 50; 

// Preference variables
int Hysteresis_W = 2;         //Summer and Winter hysteresis levels
int Hysteresis_S = 2;
int TempCorrection = 0;       //Used to adjust readings, if the sensor needs calibration
boolean UsePerceivedTemp = false; // Use humidity-adjusted perceived temperature, instead of actual temperature

// Current condition variables
boolean Winter = true; 
boolean Home = true;
boolean ManualRun = false;    // used to run fan, overriding thermostat algorithm
boolean ManualStop = false;   // used to stop fan, overriding thermostat algorithm
int MenuItem = 0;             // Settings menu selection variable
boolean ButtonPressed = false;// Settings button state
boolean LongHold = false;     // Flag showoing a long hold detected on the SETTINGS button
int ButtonTimer;              // Timer for detecting long press of Settings button
String Response = "";         // Text output to SETTINGS value widget
boolean FanState = 0;         // Is the fan on or off?
int MenuTimer;                // Timer for resetting SETTINGS menu after a timeout has elapsed

String myHostname = "ThermoX";

void setup() {
  
  // Create an access point if no wifi credentials are stored
  WiFi.hostname(myHostname);
  WiFiManager wifi;
  wifi.autoConnect("ThermoX"); 
  Blynk.config(auth);
  
  dht.begin(); //Start temperature sensor
  delay(1500);

  //Initialize the fan relay. Mine is "off" when the relay is set HIGH.
  pinMode(RelayPin,OUTPUT); 
  digitalWrite(RelayPin,HIGH);
 
  Serial.begin(115200);
  delay(10);
  
  //Load any saved settings from the EEPROM
  EEPROM.begin(20);  
  Serial.println(F("STARTUP : LOADING SETTINGS FROM MEMORY"));
  Serial.println(F(""));
  GetPresets();

  PreviousTempDes = TempDes; 
  
  MenuReset();

  timer.setInterval(UpdateFrequency, TempUpdate); // Update temp reading and relay state
  timer.setInterval(30000L, OtherUpdates);        // Refreshes non-urgent dashboard info
  MenuTimer = timer.setInterval(15000L, MenuReset);// 15 second inactivity timeout on Settings menu
}


// Main loop
void loop() {
  Blynk.run();
  timer.run();
}


//*********************** Thermostat Functions **********************************

// This is the decision algorithm for turning the HVAC on and off
void TempUpdate (){

  float ReadF = dht.readTemperature(true); //Get a new reading from the temp sensor
  
    
  if (isnan(ReadF)) {
    BadRead++;
    return;
  }

  //To compensate for some instability in the DHT11, the corrected temperature is
  //averaged with previous read, and any change is limited to 1 degree at a time. 
  int TempAvg = int((ReadF + LastRead + (2 * TempCorrection))/2);

  // Use "perceived temperature" offset (when turned on in SETTINGS)
  if(UsePerceivedTemp == true && Winter == false && ReadF > 70 && Humidity > 40){
    TempAvg += int((Humidity - 40) / 10);
  }
  
  if (TempAvg > TempAct){
    TempAct += 1;
  }
  else if (TempAvg < TempAct){
    TempAct -= 1;
  }

  LastRead = ReadF;
  BadRead = 0;        // Reset counter for failed sensor reads
  
  Blynk.virtualWrite(V0,TempAct); //Report the corrected temperature in app

  // Decision algorithm for running HVAC
  if (!ManualRun && !ManualStop){   // Make sure it's not in one of the manual modes
    // If I'm home, run the algorithm
    if (Home){
      if (Winter){
        //If I'm home, it's Winter, and the temp is too low, turn the relay ON
        if (TempAct < TempDes){
          Fan(1);
        }
        //Turn it off when the space is heated to the desired temp + a few degrees
        else if (TempAct >= (TempDes + Hysteresis_W)) {
          Fan(0);
        }
      }
      else if (!Winter){
        //If I'm home, it's Summer, and the temp is too high, turn the relay ON
        if (TempAct > TempDes){
          Fan(1);
        }
        //Turn it off when the space is cooled to the desired temp - a few degrees
        else if (TempAct <= (TempDes - Hysteresis_S)){
          Fan(0);
        }
     }
    }
    // If I'm not home, turn the relay OFF
    else {
      Fan(0);
    }
  }
}


// Turn the HVAC on or off
void Fan(boolean RunFan){
  FanState = RunFan;

  // Set the proper color for the Desired Temp gauge and ON/OFF LED
  //(red = heating, blue = cooling, white gauge or LED off = within desired range)
  if (Winter && FanState){
      Blynk.setProperty(V0, "color", BLYNK_RED);
      Blynk.setProperty(V7, "color", BLYNK_RED);
    }
    else if (!Winter && FanState){
      Blynk.setProperty(V0, "color", BLYNK_BLUE);
      Blynk.setProperty(V7, "color", BLYNK_BLUE);
    }
    else{
      // Return widgets to their "off" state color, depending on theme
        Blynk.setProperty(V0, "color", NormalWidgetColor);      
    }
    
  digitalWrite(RelayPin,!FanState); // Relay turns fan on with LOW input, off with HIGH
  Blynk.virtualWrite(V7,FanState * 1023);// fan "ON" LED on dashboard
}


// Resets from manual run to normal mode
void KillManual(){
  ManualRun = false;
}


//Match temp gauge to slider in Blynk app 
BLYNK_WRITE(V3){
  TempDes = param.asInt();
  Blynk.virtualWrite(V1,TempDes);   
}


// Updates dashboard information on the Blynk app
void OtherUpdates(){
  Blynk.virtualWrite(V29,Home * 1023);  // Update "home" LED on dashboard
  Blynk.virtualWrite(V1,TempDes);       //Update desired temp on the dashboard
   
   // Notify when the temperature sensor fails repeatedly, and turn off the fan.
   if(MenuItem == 0 && !ButtonPressed){
     if (BadRead > 10){
       Blynk.virtualWrite(V10, String("<<< SENSOR MALFUNCTION >>>"));
       BadRead = 0;
       if (!ManualRun){ //Manual mode supersedes a malfunction condition
        Fan(0);
       }
     }
     // Clear notification when sensor reads correctly again
     else{
      MenuReset();
     }
   }
   
   if (TempDes != PreviousTempDes){   //update the EEPROM if desired temperature had changed.
    EEPROM.write(3,TempDes);
    EEPROM.commit();
    PreviousTempDes = TempDes;  
   }

   // Change widget colors
   if(NormalWidgetColor != PreviousWidgetColor){
    SetNewWidgetColor();
   }

  // To stabilize perceived temperature calculation, only update humidity readings between fan cycles
  if(!FanState){
    float ReadH = dht.readHumidity();          // Read humidity (percent)

    // Only update humidity if it's a good read from the sensor. To mitigate any
    // instability, average with previous reading, change by only 1% per reading
    if(!(isnan(ReadH))){
      int HumidityAvg = (ReadH + Humidity) / 2;
      if (HumidityAvg > Humidity){
        Humidity += 1;
      }
      if (HumidityAvg < Humidity){
        Humidity -=1;
      }
    }
     Blynk.virtualWrite(V2, Humidity);
  }   
}


//************************ External Data Sources ************************************


// Receives commands from IFTTT Maker Channel via the Amazon Echo. 
// Pin V6 is not actually associated with a Blynk dashboard widget.
BLYNK_WRITE(V6){
  int AlexaCommand = param.asInt();

  switch(AlexaCommand){
    // Turn manual running on / off
    case 1:
      if (ManualRun){
          ManualRun = false;
        }
      else{
        ManualRun = true;
        ManualStop = false;
        Fan(1);
      }   
      break;
    // Increase desired temperature by 2 degrees
    case 2: 
      TempDes += 2;
      Blynk.virtualWrite(V3, TempDes);  //Update the slider widget
      break;
    // Decrease desired temperature by 2 degrees
    case 3:
      TempDes -= 2;
      Blynk.virtualWrite(V3, TempDes);  //Update the slider widget
      break;
  }
}

//Get location (home or away) from the IFTTT iOS location and Maker channels
BLYNK_WRITE(V31)
{   
  Home = param.asInt();
  
  if (Home){ //Turn the HOME LED widget on or off
    Blynk.virtualWrite(V29,1023);
  }
  else Blynk.virtualWrite(V29,0);
}


//************************** Settings Menu Functions *******************************

// Dashboard SETTINGS button. Press-and-hold to enter menu. Short press for next item.
BLYNK_WRITE(V4) {
  // When the SETTINGS button is pressed, start a timer to check for a long press
  if(param.asInt()){
    ButtonTimer = timer.setTimeout(750, LongHoldDetect);
    ButtonPressed = true;
    timer.restartTimer(MenuTimer);
  }
   
  // Button has been released
  else {
    ButtonPressed = false;        // Reset the button press flag
      
    // If the long hold function wasn't called, it's a short press. Avance or reset the menu.
    if (!LongHold){ 
      timer.deleteTimer(ButtonTimer);   // Kill the long hold timer if it hasn't been activated.    
      if (MenuItem == 0){
        MenuReset(); // Remind user to hold 2 seconds to enter menu
      }
      else{
        NextMenuItem(); // Advance to next menu item
      }
    }
    // Reset the long press flag
    LongHold = false;
  }
}

// Checks for long press condition on SETTINGS button
void LongHoldDetect(){
  // If the button is still depressed, it's a long hold
  if (ButtonPressed){
    LongHold = true;
    // Enter or exit the SETTINGS menu, if it was a long press 
    if (MenuItem == 0){
      NextMenuItem(); // Enter the SETTINGS menu
    }
    else{
      MenuReset(); // Exit the SETTINGS menu
    }
  }
}


//Cycles through the Settings Menu in the Labeled Value widget
void NextMenuItem(){
  
  MenuItem += 1;
  if (MenuItem > 10){
    MenuItem = 1;
  }
    
  switch(MenuItem){
      case 1:
        if (ManualRun){
          Response = "CANCEL PULSE?";
        }
        else{
          Response = "15 MIN PULSE?";
        }
        break;

      case 2:
        if (UsePerceivedTemp){
          Response = "USE ACTUAL TEMP?";
        }
        else Response = "USE PERCEIVED TEMP?";
        break;

      case 3:
        if (ManualStop){
          Response = "END SYSTEM HALT?";
        }
        else{
          Response = "HALT SYSTEM?";
        }
        break;
        
     case 4:
      if (Home){
        Response = "LOCATION: HOME";
      }
      else Response = "LOCATION: AWAY";
      break;


    case 5:
      if (Winter){
        Response = "MODE : WINTER";
      }
      else Response = "MODE : SUMMER";
      break;

    case 6:
      if (Winter){
        Response = "HYSTERESIS: ";
        Response +=  Hysteresis_W;
        Response += " DEG";   
      }
      else{
        Response = "HYSTERESIS: ";
        Response += Hysteresis_S;
        Response += " DEG";
      }
      break;

    case 7:
      Response = "TEMP CORRECTION: ";
      Response += TempCorrection;
      Response += " DEGREES";
      break;

    case 8:
      Response = "CHANGE WIDGET COLOR?";
      break;
      

    case 9:
      Response = "CLEAR WIFI SETTINGS?";
      break;

    case 10:
       Response = "RESET ALL DEFAULTS?";
       break;
  }
  Blynk.virtualWrite(V10,Response);
}


//Dashboard MODIFY button. Executes change of selected menu item 
BLYNK_WRITE(V5){
  
  if ((MenuItem > 0) && (param.asInt())){
    timer.restartTimer(MenuTimer);
    
    switch(MenuItem){

        //Forced on
      case 1:
        if (ManualRun){
          ManualRun = false;
          Response = "15 MIN PULSE?";
        }
        else{
          ManualRun = true;
          ManualStop = false;
          Fan(1);
          Response = "PULSE: ON";
          timer.setTimeout(900000L, KillManual);
        }   
        break;

       //Change season
      case 2:
        if (UsePerceivedTemp){
          Response = "ACTUAL TEMP MODE";
          UsePerceivedTemp = false;
          EEPROM.write(5,0);
          EEPROM.commit();
        }
        else {
          Response = "PERCEIVED TEMP MODE";
          UsePerceivedTemp = true;
          EEPROM.write(5,1);
          EEPROM.commit();
        }
        if(UsePerceivedTemp){
          Blynk.setProperty(V0, "label", "             Perceived Temperature");
        }
        else{
          Blynk.setProperty(V0, "label", "               Actual Temperature");
        } 
        break; 

        //Forced halt
      case 3:
        if (ManualStop){
          ManualStop = false;
          Response = "HALT SYSTEM?";
        }
        else {
          ManualStop = true;
          ManualRun = false;
          Fan(0);
          Response = "SYSTEM HALTED";
        }
        break;

         //Change location manually
      case 4:
        if (Home){
          Home = false;
          Response = "LOCATION : AWAY";
        }
        else {
          Home = true;
          Response = "LOCATION : HOME";
        }
        break;
        
      //Change season
      case 5:
        if (Winter){
          Response = "MODE : SUMMER";
          Winter = false;
          EEPROM.write(4,0);
          EEPROM.commit();
        }
        else {
          Response = "MODE : WINTER";
          Winter = true;
          EEPROM.write(4,1);
          EEPROM.commit();
        } 
        break;
        
      //Change hysteresis level of currently selected season
      case 6:
        if (Winter){
          Hysteresis_W += 1;
          if (Hysteresis_W > 6){
            Hysteresis_W = 1;
          }
          EEPROM.write(1,(Hysteresis_W));
          EEPROM.commit();
          Response = "WINTER HYSTERESIS: ";
          Response += Hysteresis_W;
          Response += " DEG";
        }
        else{
          Hysteresis_S += 1;
          if (Hysteresis_S > 6){
            Hysteresis_S = 1;
          }
          EEPROM.write(2,(Hysteresis_S));
          EEPROM.commit();
          Response = "SUMMER HYSTERESIS: ";
          Response += Hysteresis_S;
          Response += " DEG";
          }
        break;

      // Correct faulty DHT11 readings
      case 7:
        TempCorrection +=1;
        if (TempCorrection > 5){
          TempCorrection = -10;
        }
        EEPROM.write(0,(TempCorrection + 10));
        EEPROM.commit();
        Response = "TEMPERATURE CORRECTION: ";
        Response += TempCorrection;
        Response += " DEG";
        break;

      //Change default widget "off" color to contrast with light or dark theme
      case 8:
        if(NormalWidgetColor == BLYNK_BLACK){
          NormalWidgetColor = BLYNK_WHITE;
          Response = "WHITE";
          EEPROM.write(6, 1);
        }
        else if(NormalWidgetColor == BLYNK_WHITE){
          NormalWidgetColor = BLYNK_GREEN;
          Response = "GREEN";
          EEPROM.write(6, 3);
        }
        else if(NormalWidgetColor == BLYNK_GREEN){
          NormalWidgetColor = BLYNK_DARK_BLUE;
          Response = "DARK BLUE";
          EEPROM.write(6, 5);
        }
        else if(NormalWidgetColor == BLYNK_DARK_BLUE){
          NormalWidgetColor = BLYNK_YELLOW;
          Response = "YELLOW";
          EEPROM.write(6, 4);
        }
        else if(NormalWidgetColor == BLYNK_YELLOW){
          NormalWidgetColor = BLYNK_BLACK;
          Response = "BLACK";
          EEPROM.write(6, 2);
        }
        SetNewWidgetColor();
        break;

      //Clear stored SSID and password
      case 9:
        Response = "ERASING WIFI CREDENTIALS";
        WiFi.begin("FakeSSID","FakePW"); //replace current WiFi credentials with fake ones
        delay(1000);
        ESP.restart();
        break;

      //Clear current temperature settings
      case 10:
        Response = "All settings reset to default!";
        Winter = true;
        Hysteresis_W = 2;
        Hysteresis_S = 2;
        break;
    }
    Blynk.virtualWrite(V10, Response);
  }
}


// Reset the Menu at startup or after timing out from inactivity
void MenuReset(){
  MenuItem = 0;
  Blynk.virtualWrite(V10, String("HOLD 2 SEC TO ENTER/EXIT MENU"));
}


//**************************** Miscellaneous *********************************

void SetNewWidgetColor(){
  if(!FanState){
    Blynk.setProperty(V0, "color", NormalWidgetColor);
  }
  Blynk.run();
  Blynk.setProperty(V1, "color", NormalWidgetColor);
  Blynk.run();
  Blynk.setProperty(V2, "color", NormalWidgetColor); 
  Blynk.run();
  Blynk.setProperty(V3, "color", NormalWidgetColor);
  Blynk.run();
  Blynk.setProperty(V4, "color", NormalWidgetColor);
  Blynk.run();
  Blynk.setProperty(V5, "color", NormalWidgetColor);
  Blynk.run();
  Blynk.setProperty(V10, "color", NormalWidgetColor);
  Blynk.run();
  Blynk.setProperty(V29, "color", NormalWidgetColor);
  Blynk.run();
  PreviousWidgetColor = NormalWidgetColor;
}

//Retrieves saved values from EEPROM
void GetPresets(){
  TempCorrection = EEPROM.read(0);
  if ((TempCorrection < 0) || (TempCorrection > 15)){
    TempCorrection = 0;
  }
  else{
    TempCorrection -= 10; // 10 was added at EEPROM save to account for negative values      
  }

  UsePerceivedTemp = EEPROM.read(5);
  if(UsePerceivedTemp){
    Blynk.setProperty(V0, "label", "             Perceived Temperature");
  }
  else{
    Blynk.setProperty(V0, "label", "               Actual Temperature");
  }

  Winter = EEPROM.read(4);
  Hysteresis_W = EEPROM.read(1);
  Hysteresis_S = EEPROM.read(2);

  if ((Hysteresis_W < 2) || (Hysteresis_W > 6)){
      Hysteresis_W = 2;
  }
  if ((Hysteresis_S < 2) || (Hysteresis_S > 6)){
      Hysteresis_S = 2;
  }
  
  TempDes = EEPROM.read(3);
  if ((TempDes < 50) || (TempDes > 80)){
    TempDes = 70;
  }

  int DashboardColor = EEPROM.read(6);
  if(DashboardColor > 0 && DashboardColor <= 5){
    switch(DashboardColor){
      case 1:
        NormalWidgetColor = BLYNK_WHITE;
        break;

      case 2:
        NormalWidgetColor = BLYNK_BLACK;
        break;

       case 3:
        NormalWidgetColor = BLYNK_GREEN;
        break;

      case 4:
        NormalWidgetColor = BLYNK_YELLOW;
        break;

       case 5:
        NormalWidgetColor = BLYNK_DARK_BLUE;
        break;
    }
    SetNewWidgetColor();
  }
}

3 Likes

ecam314 can you post your sketch with the DS18
b20, I’m replacing my spa controls, with this.
Thanks.
Bill

@wjflier it rough, but it works ok for what i am doing. the OTA is working correctly on it also.


/**************************************************************************** 
 *  WiFi connection is now simplified with Tapzu's WiFiManager. Wifi automatically
 *  
 *  reconnects to last working credentials. If the last SSID is unavailable, it
 *  creates an access point ("BlynkAutoConnect"). Connect any wifi device to the
 *  access point, and a captive portal pop up to receive new wifi credentials.
 *  
 *  
*****************************************************************************
*/
#include <ESP8266WiFi.h>
#include <BlynkSimpleEsp8266.h>
#include <ESP8266WebServer.h>
#include <DNSServer.h>
#include <WiFiManager.h>
#include <EEPROM.h>
#include <OneWire.h>
#include <DallasTemperature.h>
#include <ArduinoOTA.h>
 
#define UpdateFrequency (60 * 1000L)  //How often a new temperature will be read
#define RelayPin D1
#define ONE_WIRE_BUS D2

OneWire oneWire(ONE_WIRE_BUS);
DallasTemperature sensors(&oneWire);

// Timer for temperature updates
BlynkTimer timer;

//WiFi and Blynk connection variables
char auth[] = "*************"; // Blynk token 

//Thermostat variables
int TempDes = 65; //Desired temperature setting
int PreviousTempDes;
int TempAct = 70; //Actual temperature, as measured by the DS18B20 sensor
int LastRead = 70; 

// Preference variables
int Hysteresis_C = 1;
int TempCorrection = 0; //Used to adjust readings, if the sensor needs calibration

// Current condition variables
boolean Cooling = true; //default is false
boolean FanState = 0; // is the fan on or off?

bool Connected2Blynk = false;

void Uptime()
{
  // You can send any value at any time.
  // Please don't send more that 10 values per second.
  Blynk.virtualWrite(V6, millis() / 1000);
}

void setup() {
  Serial.begin(9600);
  delay(10);
  
  //Creates an AP if no wifi credentials are stored
  WiFiManager wifi;
  wifi.autoConnect("FermentationFridge"); 
//  delay(30);  
  Blynk.config(auth);  // in place of Blynk.begin(auth, ssid, pass);
  Blynk.connect(3333);  // timeout set to 10 seconds and then continue without Blynk
  while (Blynk.connect() == false) {}
    // Wait until connected
  Serial.println("Connected to Blynk server");
  ArduinoOTA.setHostname("FermentationFridge"); 
  ArduinoOTA.begin();
  Serial.println("Ready");
  Serial.print("IP address: ");
  Serial.println(WiFi.localIP());
    
  //Initialize the fan relay. Mine is "off" when the relay is set LOW.
  pinMode(RelayPin,OUTPUT); 
  digitalWrite(RelayPin,LOW);

  
  //Load any saved settings from the EEPROM
  EEPROM.begin(20);  
  Serial.println(F("STARTUP : LOADING SETTINGS FROM MEMORY"));
  Serial.println(F(""));
  GetPresets();

  PreviousTempDes = TempDes; 
  
  //MenuReset();

  timer.setInterval(UpdateFrequency, TempUpdate); // Update temp reading and relay state
  timer.setInterval(UpdateFrequency, Uptime); // Update temp reading and relay state
}

//******************************************************************************************************************************************************

// Main loop
void loop() {
  Blynk.run();
  timer.run();
  ArduinoOTA.handle();
}


//******************************************************************************************************************************************************

bool isFirstConnect = false; // Keep this flag not to re-sync on every reconnection

// This function will run every time Blynk connection is established
BLYNK_CONNECTED() {
  if (isFirstConnect) {
    // Request Blynk server to re-send latest values for all pins
    Blynk.syncAll();

    isFirstConnect = false;
  }
}


void CheckConnection(){
  Connected2Blynk = Blynk.connected();
  if(!Connected2Blynk){
    Serial.println("Not connected to Blynk server"); 
    Blynk.connect(3333);  // timeout set to 10 seconds and then continue without Blynk  
  }
  else{
    Serial.println("Connected to Blynk server");     
  }
}


// This is the decision algorithm for turning the HVAC on and off
void TempUpdate (){
    {
  OtherUpdates(); //Refeshes dashboard information
  sensors.requestTemperatures();                  // Polls the sensors.
  TempAct = sensors.getTempFByIndex(0);   // Stores temperature. Change to getTempCByIndex(0) for celcius.
  Blynk.virtualWrite(V0, TempAct);         // Send temperature to Blynk app virtual pin 0.
  Serial.print(F("Actual temperature: "));
  Serial.println(TempAct);
}
// Decision algorithm for running HVAC
if (Cooling)
        //If I'm home, it's Summer, and the temp is too high, turn the relay ON
        if (TempAct > TempDes){
          FanState = 1;
          Fan();
        }
        //Turn it off when the space is cooled to the desired temp - a few degrees
        else if (TempAct <= TempDes/* - Hysteresis_C)*/ && FanState){
          FanState = 0;
          Fan();
        }
     }

//Match temp gauge to slider in Blynk app 
BLYNK_WRITE(V3){
  TempDes = param.asInt();
  Blynk.virtualWrite(V1,TempDes);
}

// Updates dashboard information on the Blynk app
void OtherUpdates(){
  Blynk.virtualWrite(V1,TempDes); //Update desired temp on the dashboard   
   if (TempDes != PreviousTempDes){ //update the EEPROM if desired temperature had changed.
    EEPROM.write(3,TempDes);
    EEPROM.commit();
    Serial.print(F("New desired temperature saved: "));
    Serial.println(TempDes);
    PreviousTempDes = TempDes;  
   }
}
// Turn the HVAC on or off
    void Fan(){
      digitalWrite(RelayPin,FanState); // Relay turns fan on with LOW input, off with HIGH
      Blynk.virtualWrite(V7,FanState * 1023);// fan "ON" LED on dashboard
      Blynk.virtualWrite(V8,HIGH);// fan "ON" Graph
      Serial.print(F("Fan state: "));
      Serial.println(FanState);
    }

    
//Retrieves saved values from EEPROM
void GetPresets(){
{
  TempCorrection = EEPROM.read(0);
  if ((TempCorrection < 0) || (TempCorrection > 10)){
    TempCorrection = 0;
    Serial.println(F("No saved temperature correction."));
  }
  else{
    TempCorrection -= 5; // 5 was added at EEPROM save to account for negative values
    Serial.print(F("Temperature correction: "));
    Serial.print(TempCorrection);
    Serial.println(F(" degrees."));      
  }

  Cooling = EEPROM.read(4);
  Hysteresis_C = EEPROM.read(2);
  if ((Hysteresis_C < 0) || (Hysteresis_C > 2)){
      Hysteresis_C = 0;
  }
  
  if (Cooling){
    Serial.println(F("Season setting: Cooling / heating"));
    Serial.print(F("Cooling hysteresis: "));
    Serial.print(Hysteresis_C);
    Serial.println(F(" degrees."));   
    
  TempDes = EEPROM.read(3);
  if ((TempDes < 50) || (TempDes > 80)){
    TempDes = 70;
    Serial.println(F("No saved temperature setting."));
  }
  else {
    Serial.print(F("Desired temperature: "));
    Serial.print(TempDes);
    Serial.println(F(" degrees."));   
  }
  Serial.println("");
     }
  }
}

Eric, thank you for your incredible work. I would like to ask for your help, if/when you can. It is possible to email you or contact you directly? Thank you!

@mugur If you tap on my screen name or picture, you’ll get a popup window that allows you to send a message to me directly.

I guess i am not allowed yet… Maybe i am too “young” on this community… :frowning:

Moved you up to “member”, should be OK now.

it worked! thank you very much.

I received a question this morning about adapting the ThermoX code to control separate heating and cooling systems. Here’s what I suggested:

Use the original relay (RelayPin) for your heating circuit. Add a second relay for the cooling circuit, and call it RelayPin2. Select an output pin that you want to use, and add a new define statement to the others. If you decided to use pin 14, it would look something like:

#define RelayPin2 14  //Use GPIO 14 for the second relay

Then initialize your new relay pin in the Setup function:

//Initialize the second fan relay. Mine is “off” when the relay is set HIGH.
pinMode(RelayPin2, OUTPUT);
digitalWrite(RelayPin2, HIGH);

Then alter the Fan function to look like this, so that it turns on the cooling relay in Summer, and heating relay in Winter:

void Fan(boolean FanState){
  if (FanState){
    if(Winter){
      digitalWrite(RelayPin,LOW);  // Relay that controls the heating system
      digitalWrite(RelayPin2, HIGH); // Make sure the other relay is off
    }
    else{
      digitalWrite(RelayPin2, LOW);  // Realy that controls the cooling system
      digitalWrite(RelayPin, HIGH); // Make sure the other relay is off
    }
     Blynk.virtualWrite(V7,1023);
      // Serial.println(F(“Fan is on.”));
  }
  else{
    digitalWrite(RelayPin,HIGH);
    digitalWrite(RelayPin2, HIGH);
    Blynk.virtualWrite(V7,0);
    // Serial.println(F(“Fan is off”));
  }
}

Caveat: Swap the HIGHs for LOWs, and vice-versa if your relays are normally open. Also, I haven’t tested this variation of the code, so if there are bugs, be kind. :slight_smile:

ecam314,
sorry I didn’t get back to you sooner, Thanks.

I was using an old Anduino I got it

Not sure if I missed it but what DHT library are you using?