Temperature and humidity controller

Hi everyone

I have read a thousand and one posts, followed a bunch of youtubers and copied a bunch of tutorials. Seems like I just don't have the capacity any more to figure this out on my own even with the WWW full of tutorials and how to's.

I have an Arduino Nano, DHT11 sensor and a 16ch relay, low level Triger.
I need it to, well lets go with only the measuring of the temperature for now and if the temp. is above 25°C the relay must switch on for 20seconds and then stay closed for 20 minutes.
This is to spray the water curtain to cool a greenhouse. I can not let is keep the valve open as this will flood my greenhouse. same will be implemented for the humidity

The "HIGH" and "LOW" in the sketch is inverted as I am using an LED to simulate the relay before connecting everything to go to work.

Apparently I cant even figure out how to attach my sketch so Ill just past it in here.

This was the final attempt and the closest I could get to the result I am looking for. The "delay" will be removed and the Serial.prints this was only to try and make sense of what the heck I am trying to do.
Also a 16x2 I2C LCD will be added to display the current temperature and humidity.

#include <DHT.h>

#define DHTPIN 2
#define DHTTYPE DHT11
DHT dht (DHTPIN, DHTTYPE);

int State = 0;
const int maxTemp (28);
const int mistV (3); // Pin for the misting valve relay

// This is supposed to control the time the valve will stay closed
unsigned long CurrentClose = 0;
unsigned long PreviousClose = 0;
const int StayClosed = 1000*10; // 1000*1 = 1 second

// This is to control the time the valve will stay open
unsigned long CurrnetOpen = 0;
unsigned long PreviousOpen = 0;
const int StayOpen = 1000*5;  // 1000*1 = 1 second


void setup() // Start up the everything
{
  dht.begin();
  Serial.begin(9600);
  pinMode (mistV, OUTPUT);
}

void loop() // Type of declarations of the State machine
{
  delay (1000);
  mistValve();
  Serial.println(State);
}
// Open the mist valve for "X" and keep closed for "Y" when Max Temp was exceeded
void mistValve()
{
  switch (State)
  {  

// Reset (0)
  case 0:
  {State = 1;}
  break;

// (1) Take Temperature reading. If temp is over Max Temp chack time at (2) to see if it may open the valve
  case 1:
  {
    float t= dht.readTemperature();
    Serial.println (t,1);
    if (t >= maxTemp)
    {
      State = 2;
      }
    }
  break;
  
// (2) Check the Closed valve timer to see if the valve may open. if true open valve at (3)
  case 2:
  {
    unsigned long CurrentClose = millis();
    if (CurrentClose - PreviousClose > StayClosed)
    {
      PreviousClose = CurrentClose;
      State = 3;
      }
  }
  break;

// (3) Open the valve and check duration at (4)
  case 3:
  {
    digitalWrite (mistV, HIGH);
    Serial.println( "Valve Open");
    State = 4;
    }
  break;

// (4) Check the Open valve timer and close when time runs out
  case 4:
  {
    unsigned long CurrnetOpen = millis();
    if (CurrnetOpen - PreviousOpen > StayOpen)
    {
      PreviousOpen = CurrnetOpen;
      State = 5;
      }
  }
  break;

// (5) The time the valve will stay closed (5)
  case 5:
  {
    digitalWrite (mistV, LOW);
    Serial.println( "Valve Closed");
    State = 0;
    }
  break;

}
}

Hi. I've written a little example task you can try out.

#define MAX_TEMPERATURE 25.0

#define ON_PERIOD (20 * 1000UL)

#define OFF_PERIOD (20 * 60 * 1000UL)

enum ControllerState {
    PollTemperature,
    OnPeriod,
    OffPeriod
};

static float getTemperature() {
    // Implement function...
}

static void openValve() {
    // Implement function...
}

static void closeValve() {
    // Implement function...
}

static void mistValveTask() {

    static enum ControllerState currentState = PollTemperature;

    static uint32_t t = 0;

    switch (currentState) {

        case PollTemperature:

            if (getTemperature() >= MAX_TEMPERATURE) {
                t = millis();
                openValve();
                currentState = OnPeriod;
            }

            break;

        case OnPeriod:

            if (millis() - t >= ON_PERIOD) {
                t += ON_PERIOD;
                closeValve();
                currentState = OffPeriod;
            }

            break;

        case OffPeriod:

            if (millis() - t >= OFF_PERIOD) {
                currentState = PollTemperature;
            }

            break;

    }
}

void setup() {
    // Implement function...
}

void loop() {
    mistValveTask();
}

I've done no testing, but it should do what you want. You'll need to fill in the setup, getTemperature, openValve and closeValve functions.

Let me know if you need any clarification.

1 Like

Thank you very much. I will try it out first thing in the morning.
South Africa decided it is my turn to sit without power.

I really appreciate your assistance.

The structure of the code looks good. What happens when it run and what is not happening?

You should code these with the UL suffix otherwise the compiler assumes a 16 bit integer.

#define ON_PERIOD (20 * 1000UL)
#define OFF_PERIOD (20 * 60 * 1000UL)
1 Like

When compiling for an 8bit AVR, that's indeed correct. I'll update my post.

Here is the serial output I get from my code

1

27.7

1

28.0 // As in the trial code 28°C

2

3

Valve Open // This is 2 seconds in where the code is for 10 sec.

4

5

Valve Closed // This is a further 2 sec. where I had 5 in the code

0

1

I have been busy with this one today. I can't make head from tail in the code, with the head were the tail should be (the way most if not all examples are coded) What I am actually saying is this is out of the norm for me. Even my poor Arduino IDE is getting confused between all the versions I have saved.

I added some comments in the code where I am stuck. below is the latest attempt

#include <DHT.h>
#include <LiquidCrystal_I2C.h>

#define DHTPIN 2
#define DHTTYPE DHT11
DHT dht (DHTPIN, DHTTYPE);
LiquidCrystal_I2C lcd (0x3f, 16, 2);

#define MAX_TEMPERATURE 28.0

#define ON_PERIOD (5 * 1000UL) //(20 * 1000UL)

#define OFF_PERIOD (1 * 1000UL) // (20 * 60 * 1000UL)
#define mistV (3)

enum ControllerState {
    PollTemperature,
    OnPeriod,
    OffPeriod
};

static float getTemperature() {

    // The only output I get in the serial monitor is the gettemperature and the loop.

    float temp = dht.readTemperature(); // Can I still use "t" for the temperature with "t = millis" used in the PollTemperature?
    Serial.println (temp, 1);  // How would I send it from here or the loop to the Switch case?
}

static void openValve() {
    // Implement function...
    digitalWrite (mistV, HIGH);  // What I added here feels to simple
    Serial.println ("Mist Valve open"); 
}

static void closeValve() {
    // Implement function...
    digitalWrite (mistV, LOW); // and here.
    Serial.println ("Mist Valve Closed");
}

static void mistValveTask() {

    static enum ControllerState currentState = PollTemperature;

    // with the "UL" suffix added to the ON_PERIOD and OFF_PERIOD will the unit32 be correct
    static uint32_t t = 0;  

    switch (currentState) {

        case PollTemperature:

            if (getTemperature() >= MAX_TEMPERATURE) {
                t = millis();
                openValve();
                currentState = OnPeriod;
                Serial.println("PollTemperature");
            }

            break;

        case OnPeriod:

            if (millis() - t >= ON_PERIOD) {
                t += ON_PERIOD;
                closeValve();
                currentState = OffPeriod;
                Serial.println("OnPeriod");
            }

            break;

        case OffPeriod:

            if (millis() - t >= OFF_PERIOD) {
                currentState = PollTemperature;
                Serial.println("OffPeriod");
            }

            break;

    }
}

void setup() {
    // Implement function...
    dht.begin();                // Starts the Serial Comunication
    Serial.begin(9600);         // Sets the Serial Comunication speed
    pinMode (mistV, OUTPUT);    // Sets mistV pin as output
}

void loop() { // Will a delay here have an impact on the millis? Need lower resolution on the serial monitor for the testing
  //delay(1000);
  mistValveTask();
  Serial.println ("loop");
}

Yes you can. The other 't' value you're referring to is declared in the scope of the mistValveTask function. Though I recommend that you define and stick to a rule regarding single letter variable names. For instance I only ever use 't' as a timestamp where a single one is needed. It quickly becomes very difficult to read code only using variable names like a, b, c, etc.

Like this:

static float getTemperature() {
    float temp = dht.readTemperature(); 
    Serial.println (temp, 1); 
    return temp;
}

The 'return' keyword returns a value to the calling function. The keyword before the function name indicates the type which the function returns, so in this case the function returns a 'float' which is what your 'temp' variable is.

If it opens the valve it seems perfectly fine to me. You've successfully made an abstraction that opens the valve, which makes the logic easier to read in the mistValveTask function. Granted it's a short function but I don't think that's an issue at all. You can consider adding the 'inline' modifier to the function signature though, but unless you read up on what that means I would just leave it as is.

static void closeValve() {
    // Implement function...
    digitalWrite (mistV, LOW); // and here.
    Serial.println ("Mist Valve Closed");
}

Same as above.

// with the "UL" suffix added to the ON_PERIOD and OFF_PERIOD will the unit32 be correct
    static uint32_t t = 0;

Yes. The UL suffix means it's an unsigned long. Which is what a uint32_t is on an 8bit AVR and is what the millis function returns. You can change the declaration to unsigned long if you want and it'll function identically, I just personally like the type definitions better.

void loop() { // Will a delay here have an impact on the millis? Need lower resolution on the serial monitor for the testing
  //delay(1000);
  mistValveTask();
  Serial.println ("loop");
}

Adding a delay will not interfere with the functionality of the millis function since relies on interrupts, but it might interfere with the rest of the code. In this instance though adding a small delay will probably not matter much except add a little jitter. I encourage you to consider another approach though.

Feel free to ask if there's anything else.

1 Like

I thank you. The program is running like a clock.
Regarding the 'inline' modifier, can you recommend a page I can visit to read up on it?

That's good news! I'm glad you got it working.

Well any C/C++ reference will do.

Here's one: https://en.cppreference.com/w/c/language/inline

It gets a little technical though.

This topic was automatically closed 180 days after the last reply. New replies are no longer allowed.