Implementation of deep sleep for Seeed Studio ESP32-c3 running MQTT client

In advance, this is my first post so apologies for incorrect posting.

Hoping someone can help me figure this one out. I've been playing around with deep sleep on a ESP32-c3 with mixed success. My goal is to eventually use the board as dormant most of the time, wake up, connect to wifi and an MQTT server, collect and send data, and then go back to sleep. In case it matters, I am using running Arduino IDE 1.8.19 on a Linux Mint (Cinnamon).

As a universal constant, the serial monitor displays output until the board goes to sleep for the first time. After that it will change color (between white and grey) where the changes seemingly correspond to the wake sleep cycle but no other text is output. That being the case, I rigged up a simple LED and used it to successfully test the TimerWakeUp example implementation so I know that the board and its deep sleep function works correctly in a simple application. However, when I incorporate the same code into my MQTT client code, it successfully runs through the entire code once, goes to sleep, and never wakes up again (verified by both LED and MyMQTT which I use to monitor message traffic).

I have tried just about everything that I can think of with zero luck in solving the issue. Note that the MQTT client code works just fine when the deep sleep function is not present. In fact, if I comment out the deep sleep call and uncomment the code in the loop, it works just fine. I have tried with and without Serial.flush(), disconnecting MQTT client before sleep, disconnecting from the wifi both alone and in combination. In each case, there is no difference and I cannot get the board to wake from the first deep sleep short of using the reset button. The critical part is that I need the code to start an AP (AutoconnectAP in this case) to input MQTT particulars after which the MTTP broker IP address and port information are stored so the AP will not be started (this has been verified). I have also verified that the AutoconnectAP is not starting after the unit goes to sleep. Needless to say, I'm a bit lost.

I have attached my code and the serial output until the unit undergoes deep sleep for the first time.

Thanks, in advance, for any help!!

#include <FS.h>                   //this needs to be first, or it all crashes and burns...
#include <WiFiManager.h>          //https://github.com/tzapu/WiFiManager
#include <PubSubClient.h>
#include <Adafruit_MPU6050.h> //added
#include <Adafruit_Sensor.h>  //added
#include <Wire.h>             //added
Adafruit_MPU6050 mpu;         //added

#ifdef ESP32
  #include <SPIFFS.h>
#endif

#include <ArduinoJson.h>          //https://github.com/bblanchon/ArduinoJson

//from my file
WiFiClient espClient;
PubSubClient client(espClient);
unsigned long lastMsg = 0;
#define MSG_BUFFER_SIZE  (50)
char msg[MSG_BUFFER_SIZE];
char msg2[MSG_BUFFER_SIZE];
int value = 0;
int settemp = 25;
int acttemp;
int MQTTconnect;
//end from my file

const int LED_PIN = 8;

RTC_DATA_ATTR int bootCount = 0;
#define uS_TO_S_FACTOR 1000000ULL /* Conversion factor for micro seconds to seconds */
#define TIME_TO_SLEEP 60000          /* Time ESP32 will go to sleep (in seconds) */
//#define WAKE_UP_TIME 30000

//define your default values here, if there are different values in config.json, they are overwritten.
char mqtt_server[40] = "xxx.xxx.xx.xx";
char mqtt_port[6] = "1883";
//char api_token[34];

//flag for saving data
bool shouldSaveConfig = true;//false;

//callback notifying us of the need to save config
void saveConfigCallback () {
  Serial.println("Should save config");
  shouldSaveConfig = true;
}

void reconnect() {
  // Loop until we're reconnected
  while (!client.connected()) {
    MQTTconnect = 0;
    Serial.print("Attempting MQTT connection...");
    // Create a random client ID
    String clientId = "ESP8266Client-";
    clientId += String(random(0xffff), HEX);
    // Attempt to connect
    if (client.connect(clientId.c_str())) {
      Serial.println("connected");
      MQTTconnect = 1;
    } else {
      Serial.print("failed, rc=");
      Serial.print(client.state());
      Serial.println(" try again in 5 seconds");
      // Wait 5 seconds before retrying
      delay(5000);
    }
  }
}

void TestMPU6050() {
    Serial.println("Adafruit MPU6050 test!");

  // Try to initialize!
  if (!mpu.begin()) {
    Serial.println("Failed to find MPU6050 chip");
    while (1) {
      delay(10);
    }
  }
  Serial.println("MPU6050 Found!");

  mpu.setAccelerometerRange(MPU6050_RANGE_8_G);
  Serial.print("Accelerometer range set to: ");
  switch (mpu.getAccelerometerRange()) {
    case MPU6050_RANGE_2_G:
      Serial.println("+-2G");
      break;
    case MPU6050_RANGE_4_G:
      Serial.println("+-4G");
      break;
    case MPU6050_RANGE_8_G:
      Serial.println("+-8G");
      break;
    case MPU6050_RANGE_16_G:
      Serial.println("+-16G");
      break;
  }
  mpu.setGyroRange(MPU6050_RANGE_500_DEG);
  Serial.print("Gyro range set to: ");
  switch (mpu.getGyroRange()) {
    case MPU6050_RANGE_250_DEG:
      Serial.println("+- 250 deg/s");
      break;
    case MPU6050_RANGE_500_DEG:
      Serial.println("+- 500 deg/s");
      break;
    case MPU6050_RANGE_1000_DEG:
      Serial.println("+- 1000 deg/s");
      break;
    case MPU6050_RANGE_2000_DEG:
      Serial.println("+- 2000 deg/s");
      break;
  }

  mpu.setFilterBandwidth(MPU6050_BAND_21_HZ);
  Serial.print("Filter bandwidth set to: ");
  switch (mpu.getFilterBandwidth()) {
    case MPU6050_BAND_260_HZ:
      Serial.println("260 Hz");
      break;
    case MPU6050_BAND_184_HZ:
      Serial.println("184 Hz");
      break;
    case MPU6050_BAND_94_HZ:
      Serial.println("94 Hz");
      break;
    case MPU6050_BAND_44_HZ:
      Serial.println("44 Hz");
      break;
    case MPU6050_BAND_21_HZ:
      Serial.println("21 Hz");
      break;
    case MPU6050_BAND_10_HZ:
      Serial.println("10 Hz");
      break;
    case MPU6050_BAND_5_HZ:
      Serial.println("5 Hz");
      break;
  }
}

void SetupWiFi() {
    //  setup_wifi();
  client.setServer(mqtt_server, 1883);
  //client.setCallback(callback);

  //clean FS, for testing
  //SPIFFS.format();

  //read configuration from FS json
  Serial.println("mounting FS...");

  if (SPIFFS.begin()) {
    Serial.println("mounted file system");
    if (SPIFFS.exists("/config.json")) {
      //file exists, reading and loading
      Serial.println("reading config file");
      File configFile = SPIFFS.open("/config.json", "r");
      if (configFile) {
        Serial.println("opened config file");
        size_t size = configFile.size();
        // Allocate a buffer to store contents of the file.
        std::unique_ptr<char[]> buf(new char[size]);

        configFile.readBytes(buf.get(), size);

        #if defined(ARDUINOJSON_VERSION_MAJOR) && ARDUINOJSON_VERSION_MAJOR >= 6
        DynamicJsonDocument json(1024);
        auto deserializeError = deserializeJson(json, buf.get());
        serializeJson(json, Serial);
        if ( ! deserializeError ) {
        #else
          DynamicJsonBuffer jsonBuffer;
          JsonObject& json = jsonBuffer.parseObject(buf.get());
          json.printTo(Serial);
          if (json.success()) {
          #endif
            Serial.println("\nparsed json");
            strcpy(mqtt_server, json["mqtt_server"]);
            strcpy(mqtt_port, json["mqtt_port"]);
//          strcpy(api_token, json["api_token"]);
          } else {
            Serial.println("failed to load json config");
          }
          configFile.close();
          }
      }
    } else {
      Serial.println("failed to mount FS");
    }
//end read

  // The extra parameters to be configured (can be either global or just in the setup)
  // After connecting, parameter.getValue() will get you the configured value
  // id/name placeholder/prompt default length
  WiFiManagerParameter custom_mqtt_server("server", "mqtt server", mqtt_server, 40);
  WiFiManagerParameter custom_mqtt_port("port", "mqtt port", mqtt_port, 6);
  // WiFiManagerParameter custom_api_token("apikey", "API token", api_token, 32);

  //WiFiManager
  //Local intialization. Once its business is done, there is no need to keep it around
  WiFiManager wifiManager;

  //set config save notify callback
  wifiManager.setSaveConfigCallback(saveConfigCallback);

  //set static ip
  //setSTAStaticIPConfig(IPAddress(xxx,xxx, xx, xxx), IPAddress(xxx,xxx,xx, x), IPAddress(255, 255, 255, 0));

  //add all your parameters here
  wifiManager.addParameter(&custom_mqtt_server);
  wifiManager.addParameter(&custom_mqtt_port);
  // wifiManager.addParameter(&custom_api_token);

  //reset settings - for testing
  /* using this part to force opening AutoConnectAP the first time through the code
   *  afterwards, the MQTT settings should be held in memory */
  if (bootCount == 1) {
    wifiManager.resetSettings();  //trying to comment here 2/27/26
    bootCount++;
    Serial.println("Boot number: " + String(bootCount));
  }
  
  //set minimum quality of signal so it ignores AP's under that quality
  //defaults to 8%
  //wifiManager.setMinimumSignalQuality();

  //sets timeout until configuration portal gets turned off
  //useful to make it all retry or go to sleep
  //in seconds
  //wifiManager.setTimeout(120);

  //fetches ssid and pass and tries to connect
  //if it does not connect it starts an access point with the specified name
  //here  "AutoConnectAP" and goes into a blocking loop awaiting configuration
  if (!wifiManager.autoConnect("AutoConnectAP", "password")) {
    Serial.println("failed to connect and hit timeout");
    delay(3000);
    //reset and try again, or maybe put it to deep sleep
    ESP.restart();
    delay(5000);
   }

  //if you get here you have connected to the WiFi
  Serial.println("connected...yay :)");

  //read updated parameters
  strcpy(mqtt_server, custom_mqtt_server.getValue());
  strcpy(mqtt_port, custom_mqtt_port.getValue());
  // strcpy(api_token, custom_api_token.getValue());
  Serial.println("The values in the file are: ");
  Serial.println("\tmqtt_server : " + String(mqtt_server));
  Serial.println("\tmqtt_port : " + String(mqtt_port));

//save the custom parameters to FS
  if (shouldSaveConfig) {
    Serial.println("saving config");
    #if defined(ARDUINOJSON_VERSION_MAJOR) && ARDUINOJSON_VERSION_MAJOR >= 6
    DynamicJsonDocument json(1024);
    #else
    DynamicJsonBuffer jsonBuffer;
    JsonObject& json = jsonBuffer.createObject();
    #endif
    json["mqtt_server"] = mqtt_server;
    json["mqtt_port"] = mqtt_port;
    //json["api_token"] = api_token;
  
    File configFile = SPIFFS.open("/config.json", "w");
    if (!configFile) {
      Serial.println("failed to open config file for writing");
    }

#if defined(ARDUINOJSON_VERSION_MAJOR) && ARDUINOJSON_VERSION_MAJOR >= 6
    serializeJson(json, Serial);
    serializeJson(json, configFile);
#else
    json.printTo(Serial);
    json.printTo(configFile);
#endif
    configFile.close();
    //end save
  }
  Serial.println("local ip");
  Serial.println(WiFi.localIP());
}

void SetupDeepSleep() {
  esp_sleep_enable_timer_wakeup(TIME_TO_SLEEP * uS_TO_S_FACTOR);
  Serial.println("ESP32 set to sleep for " + String(TIME_TO_SLEEP/1000) + " Seconds");

  /*
  Next we decide what all peripherals to shut down/keep on
  By default, ESP32 will automatically power down the peripherals
  not needed by the wakeup source, but if you want to be a poweruser
  this is for you. Read in detail at the API docs
  http://esp-idf.readthedocs.io/en/latest/api-reference/system/deep_sleep.html
  Left the line commented as an example of how to configure peripherals.
  The line below turns off all RTC peripherals in deep sleep.
  */
  //esp_deep_sleep_pd_config(ESP_PD_DOMAIN_RTC_PERIPH, ESP_PD_OPTION_OFF);
  //Serial.println("Configured all RTC Peripherals to be powered down in sleep");
}

void setup() {
  //put your setup code here, to run once:
  /* testing LED */
  pinMode(LED_PIN, OUTPUT);             
  digitalWrite(LED_PIN, HIGH);
  delay(1000);
  digitalWrite(LED_PIN, LOW);
  
  if (bootCount > 1); {                 //vestigial tail left over from testing
    //reconnect();
  }
  
  Serial.begin(115200);
  delay(10000); //Take some time to open up the Serial Monitor
    
  //Increment boot number and print it every reboot
  ++bootCount;
  Serial.println("Boot number: " + String(bootCount));

  TestMPU6050();
  SetupWiFi();
  SetupDeepSleep();
  
//loop instructions
//  if (!client.connected()) {
//    reconnect();
//  }
//  client.loop();

  /* Get new sensor events with the readings */
  sensors_event_t a, g, temp;
  mpu.getEvent(&a, &g, &temp);

  /* Send data via MQTT */
  int tilt_deg = (atan2(a.acceleration.y,a.acceleration.z)*(180/3.14));
  Serial.print("Angle = ");
  Serial.println(tilt_deg);
  char acttemp[20];
  sprintf(acttemp,"%d",tilt_deg);
  snprintf (msg, MSG_BUFFER_SIZE, "%s", acttemp);
  client.publish("ESP8266_TC/tilt",msg); 
//
  int tilt_t = temp.temperature; //this is in C
  Serial.print("Temp = ");
  Serial.println(tilt_t);
  char acttemp_2[20];
  sprintf(acttemp_2,"%d",tilt_t);
  snprintf (msg, MSG_BUFFER_SIZE, "%s", acttemp_2);
  client.publish("ESP8266_TC/tilt",msg);

  /* deep sleep function */
  Serial.println("Going to sleep now");
  client.disconnect();
  delay(1000);
  WiFi.disconnect();
  delay(1000);
  esp_deep_sleep_start();
  Serial.println("This will never be printed");
}

void loop() {
//  if (!client.connected()) {
//    reconnect();
//  }
//  client.loop();
//  
//  /* Get new sensor events with the readings */
//  sensors_event_t a, g, temp;
//  mpu.getEvent(&a, &g, &temp);
//
//  int tilt_deg = (atan2(a.acceleration.y,a.acceleration.z)*(180/3.14));
//  Serial.print("Angle = ");
//  Serial.println(tilt_deg);
//  char acttemp[20];
//  sprintf(acttemp,"%d",tilt_deg);
//  snprintf (msg, MSG_BUFFER_SIZE, "%s", acttemp);
//  client.publish("ESP8266_TC/tilt",msg); 
//
//  int tilt_t = temp.temperature; //this is in C
//  Serial.print("Temp = ");
//  Serial.println(tilt_t);
//  //int atemp = ;
//  char acttemp_2[20];
//  sprintf(acttemp_2,"%d",tilt_t);
//  snprintf (msg, MSG_BUFFER_SIZE, "%s", acttemp_2);
//  client.publish("ESP8266_TC/tilt",msg); 
//  
//  //Serial.println("");
////  digitalWrite(LED_BUILTIN, LOW);
//  delay(100);
////  digitalWrite(LED_BUILTIN, HIGH);
//  delay(6000);
}

Serial output:
09:20:26.970 -> Boot number: 1
09:20:26.970 -> Adafruit MPU6050 test!
09:20:27.268 -> MPU6050 Found!
09:20:27.268 -> Accelerometer range set to: +-8G
09:20:27.268 -> Gyro range set to: +- 500 deg/s
09:20:27.268 -> Filter bandwidth set to: 21 Hz
09:20:27.268 -> mounting FS...
09:20:27.268 -> E (11639) SPIFFS: mount failed, -10025
09:20:27.301 -> failed to mount FS
09:20:27.301 -> *wm:resetSettings 
09:20:27.865 -> *wm:SETTINGS ERASED 
09:20:27.865 -> Boot number: 2
09:20:27.865 -> *wm:AutoConnect 
09:20:27.898 -> *wm:No wifi saved, skipping 
09:20:27.898 -> *wm:AutoConnect: FAILED for  16 ms
09:20:27.898 -> *wm:StartAP with SSID:  AutoConnectAP
09:20:28.395 -> *wm:AP IP address: 192.168.4.1
09:20:28.395 -> *wm:Starting Web Portal 
09:20:51.931 -> *wm:11 networks found
09:20:57.799 -> *wm:Connecting to NEW AP: TheDocs
09:20:57.832 -> *wm:connectTimeout not set, ESP waitForConnectResult... 
09:20:59.921 -> *wm:Connect to new AP [SUCCESS] 
09:20:59.921 -> *wm:Got IP Address: 
09:20:59.921 -> *wm:xxx.xxx.xx.xx 
09:20:59.921 -> Should save config
09:21:00.949 -> *wm:config portal exiting 
09:21:00.949 -> connected...yay :)
09:21:00.949 -> The values in the file are: 
09:21:00.949 -> 	mqtt_server : xxx.xxx.xx.xx
09:21:00.949 -> 	mqtt_port : 1883
09:21:00.949 -> saving config
09:21:00.949 -> failed to open config file for writing
09:21:00.949 -> {"mqtt_server":"xxx.xxx.xx.xx","mqtt_port":"1883"}local ip
09:21:00.949 -> xxx.xxx.xx.xx
09:21:00.949 -> ESP32 set to sleep for 60 Seconds
09:21:00.949 -> Angle = 122
09:21:00.949 -> Temp = 22
09:21:00.949 -> Going to sleep now

ESP32-C3 has an issue with serial output and deep sleep. As in, when Serial.print… is used and there’s no serial device connected it may not come out of deep sleep.

And guess what: when entering deep sleep the Serial gets disconnected.

I usually check whether the serial port is connected or not and skip any Serial.print… if not

static bool serialPortConnected = false;
void setup() {
  serialPortConnected = usb_serial_jtag_is_connected();
  if (serialPortConnected) {
    Serial.begin(115200);
  }
  ...
}

void loop() {
  ...
  if (serialPortConnected) {
    Serial.println(F("whatever"));
  }
  ...
}

hope this helps

you may also want to turn the WiFi off before the deep sleep otherwise some parts of the MCU are not sleeping. I noticed this when I was looking for unusual power draw during deep sleep.

...
WiFi.disconnect();
WiFi.mode(WIFI_OFF);
...

Thanks! Where or how did you find this out?

Do you know if it affects light sleep also?

I may have been having similar problems using light sleep with my ESP32C3.

Any info I have ever seen on sleep modes the WiFi is turned OFF at a higher level often called modem sleep. Here is the specs for the XIAO ESP32-C3. BTW, there are esp32 variants with even lower sleep current.

Here are some XIAO deep sleep specs according to Google AI

I have experienced this (C3 not waking up) in one of my projects (3 boards) and found this page: ESP32C3 will not wake up after deepsleep time - XIAO - Seeed Studio Forum

I’m not sure about light sleep but if it happens to your device, please try the solution and let us know

Thanks for the response! I'm not sure, however, that the call to Serial is the problem.

As I mentioned in my initial post, the TimerWakeUp example for the ESP32 seems to work just fine and it has calls to Serial throughout (code from TimerWakeUp included below) and no code included to drop those if the serial connection is unavailable.

Just to be sure, I went through my MQTT client code and commented out any Serial calls and reran without success... Still not waking up from deep sleep.

void setup() {
  Serial.begin(115200);
  delay(1000);  //Take some time to open up the Serial Monitor

  //Increment boot number and print it every reboot
  ++bootCount;
  Serial.println("Boot number: " + String(bootCount));

  //Print the wakeup reason for ESP32
  print_wakeup_reason();

  /*
  First we configure the wake up source
  We set our ESP32 to wake up every 5 seconds
  */
  esp_sleep_enable_timer_wakeup(TIME_TO_SLEEP * uS_TO_S_FACTOR);
  Serial.println("Setup ESP32 to sleep for every " + String(TIME_TO_SLEEP) + " Seconds");

  /*
  Next we decide what all peripherals to shut down/keep on
  By default, ESP32 will automatically power down the peripherals
  not needed by the wakeup source, but if you want to be a poweruser
  this is for you. Read in detail at the API docs
  http://esp-idf.readthedocs.io/en/latest/api-reference/system/deep_sleep.html
  Left the line commented as an example of how to configure peripherals.
  The line below turns off all RTC peripherals in deep sleep.
  */
  //esp_deep_sleep_pd_config(ESP_PD_DOMAIN_RTC_PERIPH, ESP_PD_OPTION_OFF);
  //Serial.println("Configured all RTC Peripherals to be powered down in sleep");

  /*
  Now that we have setup a wake cause and if needed setup the
  peripherals state in deep sleep, we can now start going to
  deep sleep.
  In the case that no wake up sources were provided but deep
  sleep was started, it will sleep forever unless hardware
  reset occurs.
  */
  Serial.println("Going to sleep now");
  Serial.flush();
  esp_deep_sleep_start();
  Serial.println("This will never be printed");
}

Do you know you could write

Serial.printf("Boot number: %d\n", bootCount);

which is neater and avoids inefficient use of String?

Not that this is the cause of your problem....

well then, you will have to do the “work it up” style of debug :slight_smile:

start with commenting out all the custom code except for the sleep instructions then uncomment one block at a time as long as the MCU wakes up. First enable the WiFi, then the MQTT connections, then the sensors, … etc.

when the MCU doesn’t wake up you will know what block of code caused it.

please share that with us.

I eventually found this (ESP32, MQTT Subscribe broken when using deep sleep · Issue #634 · knolleary/pubsubclient · GitHub) thread which, once implemented, finally solved my issue.

Turns out that the client.loop() was being called only once before the data had been published and the board was going back to sleep without the client ever publishing. Simple solution was to move client.loop() to the end of the code before implementing the deep sleep function and everything works great now.