c++automationarduinoesp32actuator

How to control a linear actuator with an ESP32 and a BME280 sensor


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. 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();
     }
}

Solution

  • 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); 
    }