MQTT with ESP32 gives Timeout exceeded, Disconnecting.

Hello,
I am having a hard time with my ESP32 board the last few days.
I have built a custom soil moisture sensor with an ESP32 board that reads the soil moisture and post it through MQTT to HA. Then sleeps for 1 hour.
The whole project runs on a 18650 / 3000mAH battery using a battery shield V3 which has a 5V output.
As the esp32 has only one VCC I am using a GPIO pin to switch off the sensor too.
The soil moisture sensor was staying ON when esp was in deep sleep.
Something which was not really very good for power consumption and energy saving.
My sketch looks correct, at least to my eyes, but after a few hours of operating I am getting an error which I haven’t be able to solve.

1609060258: New client connected from 192.168.70.161 as plant-esp32 (p2, c1, k60, u'local-user').
1609060348: Client plant-esp32 has exceeded timeout, disconnecting.

I have read lots of forums and I have tried different versions of my sketch.
I tried a different version of PubSubClient library.
I tried to set the MQTT_KEEPALIVE to 60.
Unfortunately with no success.
After a couple of sensor readings, ESP fails to publish to HA (has exceed timeout, disconnecting), although it connects to broker.
My sketch looks like this:

#include <WiFi.h>

#define MQTT_KEEPALIVE 60
#include "PubSubClient.h" // Allows us to connect to, and publish to the MQTT broker

#define SensorPin A0

/* definitions for deepsleep */
#define uS_TO_S_FACTOR 1000000        /* Conversion factor for micro seconds to seconds */
#define TIME_TO_SLEEP 3600            /* Time ESP32 will go to sleep for 2 minutes (in seconds) */

int minsensor = 1200; //drowned in water
int maxsensor = 2800; //dry soil


// WiFi
const char* ssid = "XXXXX";
const char* wifi_password = "XXXXX";

// MQTT
// Make sure to update this for your own MQTT Broker!
const char* mqtt_server = "XXXX";
const char* plant_topic = "plant/moisture";
const char* mqtt_username = "local-user";
const char* mqtt_password = "12345";
const char* clientID = "plant-esp32";

// Initialise the WiFi and MQTT Client objects
WiFiClient wifiClient;
PubSubClient client(mqtt_server, 1883, wifiClient); // 1883 is the listener port for the Broker


void connect_MQTT() {

  Serial.print("Connecting to ");
  Serial.println(ssid);

  // Connect to the WiFi
  WiFi.begin(ssid, wifi_password);

  // Wait until the connection has been confirmed before continuing
  while (WiFi.status() != WL_CONNECTED) {
    delay(1500);
    Serial.print(".");
  }

  // Debugging - Output the IP Address of the ESP8266
  Serial.println("WiFi connected");
  Serial.print("IP address: ");
  Serial.println(WiFi.localIP());

  pinMode(13, OUTPUT);    // sets the digital pin 13 as output
  digitalWrite(13, HIGH); // sets the digital pin 13 on
  Serial.println("PIN 13 is ON");

  delay(1000);

  // original values
  float sensorValue = analogRead(SensorPin);
  Serial.println("Sensor Value is: " + String(sensorValue) );

  //map to percentage
  float percentage = map(sensorValue, maxsensor, minsensor, 0, 100);
  Serial.println("Percentage % is: " + String(percentage) );

  //////////////////////////////////////////////////////////////////////////////////////////////////
  //  while (!client.connected()) {
  //    Serial.println("Connecting to MQTT...");
  //
  //    if (client.connect(clientID, mqtt_username, mqtt_password )) {
  //
  //      Serial.println("connected");
  //      client.publish(plant_topic, String(percentage).c_str());
  //      Serial.println("Moisture sent!");
  //    }
  //    else
  //    {
  //      Serial.print("failed with state ");
  //      Serial.print(client.state());
  //      delay(2000);
  //
  //    }
  //  }

  /////////////////////////////////////////////////////////////////////////////////////////////////////

  // PUBLISH to the MQTT Broker
  client.connect(clientID, mqtt_username, mqtt_password);
  if (client.publish(plant_topic, String(percentage).c_str())) {
    Serial.println("Moisture sent!");
    //Serial.println(plant_topic);
  }

  // If the message failed to send, we will try again, as the connection may have broken.
  else

  {
    Serial.println("Moisture failed to send. Reconnecting to MQTT Broker and trying again");
    delay(100); // This delay ensures that client.publish doesn't clash with the client.connect call
    reconnect();
    client.publish(plant_topic, String(percentage).c_str());
    Serial.println("Moisture sent on recconect!");
  }

  /////////////////////////////////////////////////////////////////////////////////////////////////////

  digitalWrite(13, LOW);  // sets the digital pin 13 off
  Serial.println("PIN 13 is OFF");

  //  Serial.setTimeout(2000);
  Serial.println("Preparing ESP32 to sleep for " + String(TIME_TO_SLEEP) + " Seconds");
  Serial.println("Sleeping...");
  ESP.deepSleep(TIME_TO_SLEEP * uS_TO_S_FACTOR);


}


void setup() {

  Serial.begin(115200);
  connect_MQTT();

}


void reconnect() {
  // Loop until we're reconnected
  while (!client.connected()) {
    Serial.print("Attempting MQTT connection...");
    // Attempt to connect
    if (client.connect(clientID, mqtt_username, mqtt_password)) {
      Serial.println("connected");
      //    Subscribe
      client.subscribe(plant_topic);
    } else {
      Serial.print("failed, rc=");
      Serial.print(client.state());
      Serial.println(" try again in 5 seconds");
      // Wait 5 seconds before retrying
      delay(5000);
    }
  }

}

void loop() {
  Serial.println("Entering loop()");

  if (!client.connected()) {
    reconnect();
  }
  client.loop();
}

Any help / advise would be much appreciate and put me back to bed :slight_smile:
Thanks in advance
Makis

client.loop() is important to perform MQTT tasks and housekeeping in the background but the way your code is setup this never gets called so I would start looking there.

Thank you for your reply.
I realized that client,loop() has no use in my code as I set my ESP32 to deep sleep.
Is this not correct?

Would you please suggest some corrections to my sketch?

The MQTT keep alive is set in seconds. Your attempt to set it to 60 and then sleep for an hour was unlikely to work.

You may also find that your TCP socket times out too with no activity after the MQTT server drops your session. That probably doesn't matter though as you'll reconnect anyway.

When going to deep sleep you might consider cleaning up the house before sleeping.

Notice how I clean up before putting the ESP32 using MQTT to deep sleep.

void fDoTheThing( void * pvParameters )
{
  xEventGroupWaitBits (eg, evtSetupBME_Complete, pdTRUE, pdTRUE, portMAX_DELAY ); //
  connectToWiFi();
  connectToMQTT();
  while (1)
  {
    vTaskDelay( 1000 );
    xEventGroupSetBits( eg, evtDoBME ); // trigger tasks
    xSemaphoreTake( sema_ReadBME680, portMAX_DELAY ); // wait for task to be done
    client.disconnect();
    WiFi.disconnect(true);
    WiFi.mode(WIFI_OFF);
    // esp_sleep_enable_timer_wakeup( 1000000 * (60 * 6) ); //6minute sleep time  
    esp_sleep_enable_timer_wakeup( (1000000 * (60 * 3)) - 4000000 ); //3 minute sleep time  - 4 seconds
    //esp_sleep_enable_timer_wakeup( 1000000 * (60 * 5) ); //5 minute sleep time  
    //esp_sleep_enable_timer_wakeup( 1000000 * (60) ); // 1 minute slep time for troubleshooting, drains battery.
    esp_deep_sleep_start();
  } //while(1)
  vTaskDelete ( NULL );
} // void fDoTheThing( void * pvParameters )

When connecting to WiFi it is best to issue a disconnect before attempting the connection

void connectToMQTT()
{
  while ( !client.connected() )
  {
    client.setKeepAlive( 90 ); // setting keep alive to 90 seconds
    client.connect( clientID, mqtt_username, mqtt_password );
    vTaskDelay( 250 );
  }
}
//
void connectToWiFi()
{
  int i = 0;
  while ( WiFi.status() != WL_CONNECTED )
  {
    WiFi.disconnect();
    WiFi.begin( SSID, PWD );
    vTaskDelay( 4000 );
    i++;
    if ( i == 3 )
    {
      ESP.restart();
    }
  }
}
////

The disconnect command will cause a WiFi disconnect but more importantly WiFi disconnect insures the WiFi stack get cleared before a new connection connection is made. This way if funky data is in your stack space it goes away.

You do know how deep sleep on a ESP32 operates upon the modules that are contained within the ESP32?

In deep sleep mode, CPUs, most of the RAM, and all the digital peripherals which are clocked from APB_CLK are powered off. The only parts of the chip which can still be powered on are: RTC controller, RTC peripherals (including ULP coprocessor), and RTC memories (slow and fast).

Which means that the WiFi module will be turned off during deep sleep. Which means that you should consider that your ESP32 is starting all over from, almost, scratch.

Maybe this will help. Starts up connects to WiFi, MQTT, publishes, goes to sleep.

/*
   Project, use solar cells to generate power
   2/2/2020

*/
#include <WiFi.h>
#include <PubSubClient.h>
#include "certs.h"
#include "sdkconfig.h"
#include "esp_system.h"
#include "freertos/FreeRTOS.h"
#include "freertos/task.h"
#include "freertos/timers.h"
#include "freertos/event_groups.h"
#include "esp_sleep.h"
#include <Wire.h>
#include <SPI.h>
#include <Adafruit_Sensor.h>
#include "Adafruit_BME680.h"
////
EventGroupHandle_t eg;
#define evtDoBME ( 1 << 2 )
#define evtSetupBME_Complete ( 1 << 3 )
SemaphoreHandle_t sema_ReadBME680;
////////
WiFiClient wifiClient;
PubSubClient client(mqtt_server, mqtt_port, wifiClient);
//////
Adafruit_BME680 bme( GPIO_NUM_5 );
////
// RTC_DATA_ATTR int t_Difference = 0;
////
void setup()
{
  eg = xEventGroupCreate();
  sema_ReadBME680 = xSemaphoreCreateBinary();
  xTaskCreatePinnedToCore( fDoBME, "fDoBME", 20000, NULL, 3, NULL, 1 ); // assigned to core
  //start this task last
  xTaskCreatePinnedToCore( fDoTheThing, "fDoTheThing", 40000, NULL, 5, NULL, 1 ); // assigned to core
} // end setup()
////
void connectToMQTT()
{
  while ( !client.connected() )
  {
    client.setKeepAlive( 90 ); // setting keep alive to 90 seconds
    client.connect( clientID, mqtt_username, mqtt_password );
    vTaskDelay( 250 );
  }
}
//
void connectToWiFi()
{
  int i = 0;
  while ( WiFi.status() != WL_CONNECTED )
  {
    WiFi.disconnect();
    WiFi.begin( SSID, PWD );
    vTaskDelay( 4000 );
    i++;
    if ( i == 3 )
    {
      ESP.restart();
    }
  }
}
////
void fDoTheThing( void * pvParameters )
{
  xEventGroupWaitBits (eg, evtSetupBME_Complete, pdTRUE, pdTRUE, portMAX_DELAY ); //
  connectToWiFi();
  connectToMQTT();
  while (1)
  {
    vTaskDelay( 1000 );
    xEventGroupSetBits( eg, evtDoBME ); // trigger tasks
    xSemaphoreTake( sema_ReadBME680, portMAX_DELAY ); // wait for task to be done
    client.disconnect();
    WiFi.disconnect(true);
    WiFi.mode(WIFI_OFF);
    // esp_sleep_enable_timer_wakeup( 1000000 * (60 * 6) ); //6minute sleep time  
    esp_sleep_enable_timer_wakeup( (1000000 * (60 * 3)) - 4000000 ); //3 minute sleep time  - 4 seconds
    //esp_sleep_enable_timer_wakeup( 1000000 * (60 * 5) ); //5 minute sleep time  
    //esp_sleep_enable_timer_wakeup( 1000000 * (60) ); // 1 minute slep time for troubleshooting, drains battery.
    esp_deep_sleep_start();
  } //while(1)
  vTaskDelete ( NULL );
} // void fDoTheThing( void * pvParameters )
////
void fDoBME ( void *pvParameters )
{
  if (!bme.begin()) {
    log_i("Could not find a valid BME680 sensor, check wiring!");
    while (1);
  }
  // Set up oversampling and filter initialization
  bme.setTemperatureOversampling(BME680_OS_8X);
  bme.setHumidityOversampling(BME680_OS_2X);
  bme.setPressureOversampling(BME680_OS_4X);
  bme.setIIRFilterSize(BME680_FILTER_SIZE_3);
  bme.setGasHeater(320, 150); // 320*C for 150 ms
  float fTemperature = 0.0f;
  float fPressure = 0.0f;
  float fHumidity = 0.0f;
  uint32_t iGasResistance = 0;
  xEventGroupSetBits( eg, evtSetupBME_Complete ); // trigger task to begin
  for ( ;; )
  {
    xEventGroupWaitBits (eg, evtDoBME, pdTRUE, pdTRUE, portMAX_DELAY ); //
    //log_i( "Signal strength %d ", WiFi.RSSI() );
    fTemperature = bme.readTemperature();
    fTemperature = (fTemperature * 1.8f) + 32.0f; // (Celsius x 1.8) + 32
    fPressure = bme.readPressure();
    fPressure = fPressure / 133.3223684f; //converts to mmHg
    fHumidity = bme.readHumidity();
    iGasResistance = bme.readGas();
    log_i( "Temperature %f C, Pressure %f mmHg, Humidity %f, gasResistance %d", fTemperature, fPressure, fHumidity, iGasResistance );
    client.publish( topicOutsideTemp, String(fTemperature).c_str() );
    vTaskDelay( 5 ); // gives the Raspberry Pi 4 time to receive the message and process
    client.publish( topicOutsideHumidity, String(fHumidity).c_str() );
    vTaskDelay( 5 ); // no delay and RPi is still processing previous message
    client.publish( topicOutsideGasResistance, String(iGasResistance).c_str() );
    vTaskDelay( 5 );
    client.publish( topicOutsidePressure, String(fPressure).c_str() );
    vTaskDelay( 5 );
    xSemaphoreGive ( sema_ReadBME680 );
  } // for loop
  vTaskDelete ( NULL );
} // void fDoBME ( void *pvParameters )
////
////
void loop() {}
////