0

I'm trying to program an EPS32 board to open and close a linear actuator depending of the temperature a BME 280 sensor gets. It is for an automated gardening project :)

My code is functional but I would like to perfect it.

The actuator is currently connected with two GPIO pin, one to open and he other to close.

GOALS:

    1. Switch off relays when not used.
    1. Replace the use of delay() with millis()
  1. Currently, the actuator is opening when temperature is above 27c°. Relay is on for 7 seconds which let the time to the actuator to go full course. Then, the relay is switch off. But if the temperature stay above 27, the "if" part of the loop is going on an on, meaning it turn on for 7 seconds, switch off for 7 seconds and so on... I would like to switch all relays off when not needed to save energy and extend components life. I suppose there might be something to do with && or even the analogRead() function to get the state of the actuator and apply actions from that but it is too complicated for me.

  2. To open the actuator, I decided to time the course of the actuator and let the relay on for that timing with a delay() function. There is probably a different approach to this ( with analogRead() I suppose) but I find it simple and working. I would like to replace delay() with millis() function in order not to block the rest of the script (I plan to add fans, water valves, a float switch sensor...).

May you show me the way ?

#include <Adafruit_BME280.h>
#include <WiFi.h>

const char* ssid     = "#####"; // ESP32 and ESP8266 uses 2.4GHZ wifi only
const char* password = "#####";

//MQTT Setup Start
#include <PubSubClient.h>
#define mqtt_server "####.###.##.##"
WiFiClient espClient;
PubSubClient client(espClient);
#define mqttActuator "growShed/Acuator"
#define mqttTemp1 "growShed/temp1"

int actuatorUp = 15; //GPIO de l'actuator
int actuatorDown = 2; //GPIO de l'actuator
int actuatorUpread = (!digitalRead(actuatorUp)); //read state of gpio : on or off
int actuatorDownread = (!digitalRead(actuatorDown)); //read state of gpio : on or off

//MQTT Setup End

Adafruit_BME280 bme1; // I2C

//utiliser les virgules sur les capteurs suivants
float temp1, hum1, pres1;

//gestion des délais entre chaque captations de data
unsigned long millisNow1 = 0; //for delay purposes (laisser 0)
unsigned int sendDelay1 = 2000; //delay before sending sensor info via MQTT

//gestion des délais entre chaque captations de data
unsigned long millisNow3 = 0; //for delay purposes (laisser 0)
unsigned int sendDelay3 = 7000; //delay before sending sensor info via MQTT

void setup() {
  // put your setup code here, to run once:
  Serial.begin(9600);
  Serial.println();
  
  // begin Wifi connect
  Serial.println("Connecting to ");
  Serial.println(ssid);
  WiFi.mode(WIFI_STA);
  WiFi.disconnect();
  delay(2000);
  WiFi.begin(ssid, password);
  
  while (WiFi.status() != WL_CONNECTED) {
    delay(500);
    Serial.println(".");
  }

  Serial.println("");
  Serial.println("WiFi connected");  
  Serial.println("IP address (wifi RaspPi) : ");
  Serial.println(WiFi.localIP());
  //end Wifi connect

  client.setServer(mqtt_server, 1883);

  //BME
  delay(5000);
  unsigned status;
  status = bme1.begin(0x76); 
    if (!status) {
        Serial.println("Could not find a valid BME280 sensor, check wiring, address, sensor ID!");
        Serial.print("SensorID was: 0x"); Serial.println(bme1.sensorID(),16);
        Serial.print("        ID of 0xFF probably means a bad address, a BMP 180 or BMP 085\n");
        Serial.print("   ID of 0x56-0x58 represents a BMP 280,\n");
        Serial.print("        ID of 0x60 represents a BME 280.\n");
        Serial.print("        ID of 0x61 represents a BME 680.\n");
        while (1);
    }

  //Acuator
    pinMode(actuatorUp, OUTPUT);
    digitalWrite(actuatorDown, HIGH); //Begin with actuator switched off
    pinMode(actuatorDown, OUTPUT);
    digitalWrite(actuatorUp, HIGH); //Begin with actuator switched off
  //End Actuator

}

bool getValues() {

  temp1 = round(bme1.readTemperature()); //rounded values
  actuatorUpread = (digitalRead(actuatorUp)); //inverted value as the relay is inverted

  Serial.print("BME 1 Temperature = ");
  Serial.print(temp1);
  Serial.println(" °C");

  Serial.print("État de l'actuator haut = ");
  Serial.println(actuatorUpread);

  Serial.print("État de l'actuator bas = ");
  Serial.print(actuatorDownread);

  Serial.println();
}

void reconnect() {
  
// Loop until we're reconnected
  int counter = 0;
  while (!client.connected()) {
    if (counter==5){
      ESP.restart();
    }
    counter+=1;
    Serial.println("Attempting MQTT connection...");
    // Attempt to connect
   
    if (client.connect("growTentController")) {
      Serial.println("connected");
    } else {
      Serial.println("failed, rc=");
      Serial.println(client.state());
      Serial.println(" try again in 5 seconds");
      // Wait 10 seconds before retrying
      delay(5000);
    }
  }
  
}

void loop() {
  // put your main code here, to run repeatedly:
   if (!client.connected()){
    reconnect();
   }
     if (millis() > millisNow1 + sendDelay1){
    if (getValues()) {
   client.publish(mqttTemp1, String(temp1).c_str(),true);  
   client.publish(mqttActuator, String(actuatorUpread).c_str(),true);
   millisNow1 = millis();
    }
  }

 //Acuator moving according to BME280 temperature sensor
  temp1 = bme1.readTemperature();
  if (millis() > millisNow3 + sendDelay3){
  if ((temp1 > 27)){
    digitalWrite(actuatorUp, LOW);
    delay(7000); // approx the time it take to execute the full course of the acuator.
    digitalWrite(actuatorUp, HIGH);
     }
  else {
    digitalWrite(actuatorDown, LOW);
    delay(7000); // approx the time it take to execute the full course of the acuator
    digitalWrite(actuatorDown, HIGH);
    }
    millisNow3 = millis();
     }
}
NiphitAe
  • 295
  • 1
  • 5
  • 16

1 Answers1

1

I think this is what you want to do. This uses a state variable to keep track of what state the actuator is in.

When the actuator is closed, the loop will check the temperature and, if the temperature goes above 27 degrees, it will start opening the actuator. Then the loop will simply monitor the time using millis() until the actuator is open - so you can put other code in the loop for other sensors.

The same happens if the actuator is open, it will close the actuator when the temperature is lower than 26 degrees (kept a degree difference here to stop the code constantly triggering if the temperature hovers around 27 degrees).

Currently this will read the temperature on every iteration of the loop whilst the actuator is either open or closed - so I've put a small delay at the end of the loop to restrict this.

Please note - I have not tested any of the changes in the code as I don't have the hardware to hand. But it compiles, and should give you an idea of how you can use a state model with millis() to handle this sort of thing without delaying the loop.

#include <Adafruit_BME280.h>
#include <WiFi.h>

const char* ssid     = "#####"; // ESP32 and ESP8266 uses 2.4GHZ wifi only
const char* password = "#####";

//MQTT Setup Start
#include <PubSubClient.h>
#define mqtt_server "####.###.##.##"
WiFiClient espClient;
PubSubClient client(espClient);
#define mqttActuator "growShed/Acuator"
#define mqttTemp1 "growShed/temp1"

int actuatorUp = 15; //GPIO de l'actuator
int actuatorDown = 2; //GPIO de l'actuator
int actuatorUpread = (!digitalRead(actuatorUp)); //read state of gpio : on or off
int actuatorDownread = (!digitalRead(actuatorDown)); //read state of gpio : on or off

//MQTT Setup End

Adafruit_BME280 bme1; // I2C

//utiliser les virgules sur les capteurs suivants
float temp1, hum1, pres1;

//gestion des délais entre chaque captations de data
unsigned long millisNow1 = 0; //for delay purposes (laisser 0)
unsigned int sendDelay1 = 2000; //delay before sending sensor info via MQTT

//gestion des délais entre chaque captations de data
unsigned long millisNow3 = 0; //for delay purposes (laisser 0)
unsigned int sendDelay3 = 7000; //delay before sending sensor info via MQTT


// The states the actuator can be in. 
enum ActuatorState  
{
   OPENING,
   OPEN,
   CLOSING,
   CLOSED 
};
ActuatorState actuatorState; 

// When opening or closing the actuator - this is the time to stop. 
unsigned long actuatorEndTime = 0 ;

void setup() {
  // put your setup code here, to run once:
  Serial.begin(9600);
  Serial.println();
  
  // begin Wifi connect
  Serial.println("Connecting to ");
  Serial.println(ssid);
  WiFi.mode(WIFI_STA);
  WiFi.disconnect();
  delay(2000);
  WiFi.begin(ssid, password);
  
  while (WiFi.status() != WL_CONNECTED) {
    delay(500);
    Serial.println(".");
  }

  Serial.println("");
  Serial.println("WiFi connected");  
  Serial.println("IP address (wifi RaspPi) : ");
  Serial.println(WiFi.localIP());
  //end Wifi connect

  client.setServer(mqtt_server, 1883);

  //BME
  delay(5000);
  unsigned status;
  status = bme1.begin(0x76); 
    if (!status) {
        Serial.println("Could not find a valid BME280 sensor, check wiring, address, sensor ID!");
        Serial.print("SensorID was: 0x"); Serial.println(bme1.sensorID(),16);
        Serial.print("        ID of 0xFF probably means a bad address, a BMP 180 or BMP 085\n");
        Serial.print("   ID of 0x56-0x58 represents a BMP 280,\n");
        Serial.print("        ID of 0x60 represents a BME 280.\n");
        Serial.print("        ID of 0x61 represents a BME 680.\n");
        while (1);
    }

  //Acuator
    pinMode(actuatorUp, OUTPUT);
    digitalWrite(actuatorDown, HIGH); //Begin with actuator switched off
    pinMode(actuatorDown, OUTPUT);
    digitalWrite(actuatorUp, HIGH); //Begin with actuator switched off

    actuatorState = CLOSED ; 

  //End Actuator

}

bool getValues() {

  temp1 = round(bme1.readTemperature()); //rounded values
  actuatorUpread = (digitalRead(actuatorUp)); //inverted value as the relay is inverted

  Serial.print("BME 1 Temperature = ");
  Serial.print(temp1);
  Serial.println(" °C");

  Serial.print("État de l'actuator haut = ");
  Serial.println(actuatorUpread);

  Serial.print("État de l'actuator bas = ");
  Serial.print(actuatorDownread);

  Serial.println();
}

void reconnect() {
  
// Loop until we're reconnected
  int counter = 0;
  while (!client.connected()) {
    if (counter==5){
      ESP.restart();
    }
    counter+=1;
    Serial.println("Attempting MQTT connection...");
    // Attempt to connect
   
    if (client.connect("growTentController")) {
      Serial.println("connected");
    } else {
      Serial.println("failed, rc=");
      Serial.println(client.state());
      Serial.println(" try again in 5 seconds");
      // Wait 10 seconds before retrying
      delay(5000);
    }
  }
  
}

void loop() {
  // put your main code here, to run repeatedly:
   if (!client.connected()){
    reconnect();
   }
     if (millis() > millisNow1 + sendDelay1){
    if (getValues()) {
   client.publish(mqttTemp1, String(temp1).c_str(),true);  
   client.publish(mqttActuator, String(actuatorUpread).c_str(),true);
   millisNow1 = millis();
    }
  }

  //Acuator moving according to BME280 temperature sensor
  switch ( actuatorState ) {
    case OPEN: 
      // Does it need closing (use value lower than 27 so we're not constantly opening and 
      // closing the actuator if the temperature is floating around 27)
      temp1 = bme1.readTemperature();
      if ( temp1 < 26 ) {
        digitalWrite(actuatorDown, LOW);
        actuatorState = CLOSING; 
        actuatorEndTime = millis() + 7000 ;
      }
      break; 
      
    case CLOSED: 
      // Does it need opening? 
      temp1 = bme1.readTemperature();
      if ( temp1 > 27 ) {
        digitalWrite(actuatorUp, LOW);
        actuatorState = OPENING; 
        actuatorEndTime = millis() + 7000; 
      }
      break ;

    case OPENING: 
      // Is it fully open? 
      if ( millis() >= actuatorEndTime ) {
        digitalWrite(actuatorUp, HIGH);
        actuatorState = OPEN ;
      }
      break ;

    case CLOSING: 
      // Is it fully closed? 
      if ( millis() >= actuatorEndTime ) {
        digitalWrite(actuatorDown, HIGH);
        actuatorState = CLOSED ;
      }
      break ;
  }

  // small delay to run the loop - and do the temperature check 5 times per second. 
  delay(200); 
}
cguk70
  • 611
  • 3
  • 7
  • Thank you ! That works well ! I didn't know switch() and it might be a thing I use a lot in the future ! There are few lines I don't understand : ActuatorState actuatorState; // When opening or closing the actuator - this is the time to stop. unsigned long actuatorEndTime = 0 ; Could you explain it to me like I'm 5 please :) ? – NiphitAe Jun 24 '22 at 10:36
  • Glad it worked. `ActuatorState` is an enum data type, and `actuatorState` is a variable of that type. Since it is an enum, it can only hold one of the values `OPENING`, `OPEN`, `CLOSING` or `CLOSED`. You can do the same thing with an `int` data type and some `#define`s to define the states - but `enum` does all that for you. – cguk70 Jun 24 '22 at 10:54
  • `actuatorEndTime` is just used to hold the time in millis when the loop should stop moving the actuator when it is either `OPENING` or `CLOSING`. You can see in the loop code I set it to`millis() + 7000` when needed so that `(millis() >= actuatorEndTime)` will be true after 7 seconds have elapsed and stop moving the actuator. – cguk70 Jun 24 '22 at 10:56