Development steps

Project planning and development stages

Step 8 - Implement ESP32 sleep patterns for power conservation

Back to project development

Implement ESP32 sleep patterns for power conservation & battery indicator

  • Code sleep patterns for ESP32 to save battery power;
  • Add a battery indicator;

Battery indicator

Unfortunately a battery level indicador won't be possible to implement has I have run out of pins in the XIAO.

The solution for this project will be to laser cut a transparent or translucent acrylic cover. This will allow for the user to see the charging LED when the XIAO is connected to a USB Port.

Sleep patterns

There are two possible sleep patterns to implement and various sub-sleep possible configurations:

  • Deep sleep mode: The CPUs, most of the RAM, and all digital peripherals that are clocked from APB_CLK are powered off. The only parts of the chip that remain powered on are - RTC controller - RTC fast memory.
  • Light sleep: The digital peripherals, most of the RAM, and CPUs are clock-gated and their supply voltage is reduced. Upon exit from Light-sleep, the digital peripherals, RAM, and CPUs resume operation and their internal states are preserved.

Also, in Light sleep and Deep Sleep the wireless peripherals are powered down that means no bluetooth or Wifi connections are maintained.

I don't need these connections but there is also settings to configure this parts.

I will be implementing a light sleep mode and for that I'm going to be using esp_sleep_enable_timer_wakeup(); and esp_light_sleep_start();

    
#include <TinyGPS++.h>
#include <SoftwareSerial.h>
#include <Wire.h>
#include <Adafruit_GFX.h>
#include <Adafruit_SSD1306.h>
#include <Adafruit_Sensor.h>
#include <Adafruit_BME280.h>
#include "pitches.h"
#include "FS.h"
#include "SD.h"
#include "SPI.h"
#include <elapsedMillis.h>
#include <Bounce2.h>

#define SCREEN_WIDTH 128
#define SCREEN_HEIGHT 64
static const int RXPin = 20, TXPin = 21;
static const uint32_t GPSBaud = 9600;

const int SDPin = 2;
bool sdCardAvailable;

const int BuzzerPin = 3;

const int ButtonPin1 = 4;  // Button 1 pin
const int ButtonPin2 = 5;  // Button 2 pin

const float maxDistance = 20;
float initialLatitude = 42.597281;
float initialLongitude = -5.566546;

Adafruit_SSD1306 display(SCREEN_WIDTH, SCREEN_HEIGHT, &Wire, -1);
TinyGPSPlus gps;
String gpstext;
int gpscount = 0;
int gpsttlcount = 30;
float distance;
unsigned long gpsStartTime;
bool waitingForGPS = false;
bool newData = false;

SoftwareSerial ss(21, 20);

elapsedMillis timeElapsed;
bool stopwatchStarted = false;
bool stopwatchPaused = true;
unsigned long stopwatchStartMillis = 0;
unsigned long stopwatchElapsedMillis = 0;
bool stopwatchReset = false;

Adafruit_BME280 bme;
#define SEALEVELPRESSURE_HPA (1013.25)

#include "melody.h"
#include "icons.h"

uint64_t sleepMicros = 850;

#define DEBUG
#ifdef DEBUG 1
#define DebugPrintln(str) Serial.println(str)
#define DebugPrint(str) Serial.print(str)
#define DebugPrintf(str, str2) Serial.printf(str, str2)
#else
#define DebugPrintln(str)
#define DebugPrint(str)
#define DebugPrintf(str, str2)
#endif

// Initialize debouncers
Bounce debouncer1 = Bounce();
Bounce debouncer2 = Bounce();

// Initialize panel index
int panelIndex = 0;

void initDisplay() {
    if (!display.begin(SSD1306_SWITCHCAPVCC, 0x3C)) {
    DebugPrintln(F("SSD1306 allocation failed"));
    for (;;)
        ;
    }
    display.setRotation(2);
    display.clearDisplay();
    display.setTextColor(WHITE);
    display.drawBitmap((display.width() - 128) / 2, 2, epd_bitmap_person_hiking_solid, 128, 64, SSD1306_WHITE);
    display.display();
    delay(2000);
}

// Initialize SD card
void initSDCard() {
    if (!SD.begin(SDPin)) {
    sdCardAvailable = false;
    DebugPrintln("SD Card failed, or not present!");
    } else {
    // We're ready to go
    sdCardAvailable = true;
    DebugPrintln("SD card initialized!");
    }
}

void initBME() {
    bool status = bme.begin(0x76);
    if (!status) {
    DebugPrintln("No valid BME280 sensor!");
    while (1)
        ;
    }
}

// Write to the SD card
void writeFile(fs::FS &fs, const char *path, const char *message) {
    DebugPrintf("Writing file: %sn", path);
    File dataFile = fs.open(path, FILE_WRITE);
    if (!dataFile) {
    DebugPrintln("Failed to open file for writing");
    return;
    }
    if (dataFile.print(message)) {
    DebugPrintln("File written");
    } else {
    DebugPrintln("Write failed");
    }
    dataFile.close();
}

// Append data to the SD card
void appendFile(fs::FS &fs, const char *path, const char *message) {
    DebugPrintf("Appending to file: %s", path);
    DebugPrintln();
    File dataFile = fs.open(path, FILE_APPEND);
    if (!dataFile) {
    DebugPrintln("Failed to open file for appending");
    return;
    }
    if (dataFile.print(message)) {
    DebugPrintln("Message appended");
    } else {
    DebugPrintln("Append failed");
    }
    dataFile.close();
}

void setup() {
    debouncer1.attach(ButtonPin1);
    debouncer1.interval(50);
    debouncer2.attach(ButtonPin2);
    debouncer2.interval(50);
    Serial.begin(9600);
    initDisplay();
    initBME();
    initSDCard();
    pinMode(BuzzerPin, OUTPUT);
    ss.begin(GPSBaud);
    /*  while (!gps.location.isValid()) {
    while (ss.available() > 0) {
        gps.encode(ss.read());
        Serial.println("NOT VALID");
        delay(1000);
    }
    Serial.println("VALID");
    } */

    if (sdCardAvailable == true) {
    File dataFile = SD.open("/gpslog.csv");
    if (!dataFile) {
        DebugPrintln("File doesn't exist");
        DebugPrintln("Creating file...");
        writeFile(SD, "/gpslog.csv", "");
    } else {
        DebugPrintln("File already exists");
    }
    dataFile.close();
    }
    esp_sleep_enable_timer_wakeup(sleepMicros);
}
void loop() {
    debouncer1.update();
    debouncer2.update();



    if (debouncer1.fell() && debouncer2.fell() && panelIndex == 2) {
    stopwatchStarted = false;
    stopwatchPaused = true;
    stopwatchStartMillis = 0;
    stopwatchElapsedMillis = 0;
    } else {
    if (debouncer1.fell()) {
        panelIndex++;
        if (panelIndex > 3) {  // 4 panels
        panelIndex = 0;
        }
    }

    // Check if Button2 is pressed
    if (debouncer2.fell()) {
        if (panelIndex == 2) {  // Stopwatch Panel
        if (!stopwatchStarted) {
            stopwatchStarted = true;
            stopwatchPaused = false;
            stopwatchReset = false;
            stopwatchStartMillis = millis();
        } else {
            if (stopwatchPaused) {
            stopwatchPaused = false;
            stopwatchStartMillis = millis() - stopwatchElapsedMillis;
            } else {
            stopwatchPaused = true;
            stopwatchElapsedMillis = millis() - stopwatchStartMillis;
            }
        }
        }
    }
    }


    // Check if we are currently waiting for GPS data.
    if (waitingForGPS) {
    // If we have been waiting for less than a second...
    if (millis() - gpsStartTime < 1000) {
        handleGPS();
    } else {
        // We have waited for a second, so stop waiting.
        waitingForGPS = false;
    }
    } else {
    // If we are not currently waiting for GPS data, start.
    waitingForGPS = true;
    gpsStartTime = millis();
    }

    // Update the display.
    updateDisplay();
    esp_light_sleep_start();
}

void handleGPS() {
    // GPS data handling
    boolean newData = false;
    while (ss.available() > 0) {
    if (gps.encode(ss.read())) {
        //DebugPrint("Satellites: ");
        //DebugPrintln(gps.satellites.value());
        // start gps file
        // See if we have a complete GPS data string
        if (displayInfo() != "0") {
        // Get GPS string
        gpstext = displayInfo();
        // Check GPS Count
        // DebugPrintln(gpscount);
        if (gpscount == gpsttlcount) {
            DebugPrintln(gpstext);
            if (sdCardAvailable == true) {
            appendFile(SD, "/gpslog.csv", gpstext.c_str());
            }
        }
        // Increment GPS Count
        gpscount = gpscount + 1;
        if (gpscount > gpsttlcount) {
            gpscount = 0;
        }
        }
        // End GPS file
        newData = true;
        float distance = getDistance(gps.location.lat(), gps.location.lng(), initialLatitude, initialLongitude);
        // DebugPrint("Satellites: ");
        // DebugPrintln(gps.satellites.value());
        // DebugPrint("Distance:");
        // DebugPrintln(distance);
        if (distance > maxDistance) {
        //DebugPrintln("outside");
        } else {
        //DebugPrintln("inside");
        playBuzzer();
        }
    }
    }
}

void updateDisplay() {
    // Panel update logic
    display.clearDisplay();
    display.setCursor(0, 0);
    int displaydistance = getDistance(gps.location.lat(), gps.location.lng(), initialLatitude, initialLongitude);

    switch (panelIndex) {
    case 0:
        display.setTextSize(1);
        display.setCursor(0, 0);
        display.drawBitmap(0, 0, epd_bitmap_clock_solid, 12, 12, SSD1306_WHITE);
        display.setCursor(16, 2);
        display.print(String(gps.time.hour()) + ":" + String(gps.time.minute()) + String(gps.date.day()) + "/" + String(gps.date.month()));
        display.setCursor(46, 2);
        display.drawBitmap(100, 0, epd_bitmap_satellite_solid, 12, 12, SSD1306_WHITE);
        display.setTextSize(1);
        display.setCursor(118, 2);
        display.print(gps.satellites.value());

        display.drawLine(0, 14, 128, 14, SSD1306_WHITE);
        display.drawBitmap(0, 20, epd_bitmap_location_dot_solid, 12, 12, SSD1306_WHITE);
        display.setCursor(20, 24);
        display.print(String(displaydistance / 1000) + " km");
        display.drawBitmap(80, 20, epd_bitmap_mountain_sun_solid, 12, 12, SSD1306_WHITE);
        display.setCursor(95, 24);
        display.print((String(gps.altitude.meters(),0)) + "m");
        display.drawBitmap(0, 40, epd_bitmap_gauge_solid, 12, 12, SSD1306_WHITE);
        display.setCursor(20, 44);
        display.println(String(gps.speed.kmph()) + " km/h");

        display.display();
        // Show panel 0
        if (newData == true) {
        }
        break;
    case 1:
        // Show panel 1
        displayWeather();
        break;
    case 2:
        // Show panel 2 (Stopwatch)
        displayStopwatch();
        break;
    case 3:
        // Show panel 3 (Coordinates)
        displayCoordinates();
    break;
    }
    display.display();

    // Reset newData for the next loop iteration
    if (newData == true) {
    newData = false;
    }
}


void displayStopwatch() {
    if (stopwatchStarted && !stopwatchPaused) {

    unsigned long stopwatchElapsedSecs = stopwatchElapsedMillis / 1000;
    unsigned long hours = stopwatchElapsedSecs / 3600;
    unsigned long mins = (stopwatchElapsedSecs % 3600) / 60;
    unsigned long secs = stopwatchElapsedSecs % 60;

    String timeString = String(hours) + ":" + (mins < 10 ? "0" + String(mins) : String(mins)) + ":" + (secs < 10 ? "0" + String(secs) : String(secs));
    if (!stopwatchPaused) {
        stopwatchElapsedMillis = millis() - stopwatchStartMillis;
    }

    display.clearDisplay();
    display.setTextSize(2);
    display.setTextColor(SSD1306_WHITE);
    // Display the large icon (64x64) in the center at the top of the screen.
    display.drawBitmap((display.width() - 24) / 2, 2, epd_bitmap_stopwatch_solid, 24, 24, SSD1306_WHITE);

    // Center the text under the icon
    int textWidth = (display.width() - timeString.length() * 10) / 2;  // Calculate the text width to center it
    display.setCursor(textWidth, 40);
    display.println(timeString);
    display.display();
    } else {
    display.clearDisplay();
    // Display the large icon (64x64) in the center at the top of the screen.
    display.setTextSize(2);
    display.drawBitmap((display.width() - 24) / 2, 2, epd_bitmap_stopwatch_solid, 24, 24, SSD1306_WHITE);
    display.setCursor(30, 40);
    if (!stopwatchStarted) {
        display.println("START?");
    } else {
        display.println("PAUSED");
    }
    display.display();
    }
}

void displayWeather() {
    display.clearDisplay();
    // display temperature

    display.setTextSize(1);
    display.setCursor(0, 0);
    display.drawBitmap(0, 0, epd_bitmap_clock_solid, 12, 12, SSD1306_WHITE);
    display.setCursor(16, 2);
    display.print(String(gps.time.hour()) + ":" + String(gps.time.minute()) + " " + String(gps.date.day()) + "/" + String(gps.date.month()));
    display.setCursor(46, 2);
    display.drawBitmap(100, 0, epd_bitmap_satellite_solid, 12, 12, SSD1306_WHITE);
    display.setTextSize(1);
    display.setCursor(118, 2);
    display.print(gps.satellites.value());
    display.drawLine(0, 14, 128, 14, SSD1306_WHITE);
    display.drawBitmap(0, 20, epd_bitmap_temperature_low_solid, 12, 12, SSD1306_WHITE);
    display.setCursor(20, 24);
    display.print(String(bme.readTemperature()) + "C");
    display.drawBitmap(60, 20, epd_bitmap_droplet_solid, 12, 12, SSD1306_WHITE);
    display.setCursor(75, 24);
    display.println(String(bme.readHumidity()) + " %");
    display.drawBitmap(0, 44, epd_bitmap_wind_solid, 12, 12, SSD1306_WHITE);
    display.setCursor(20, 48);
    display.print(String(bme.readPressure() / 100.0F) + " hPa");
    display.display();
}

void displayCoordinates() {
    display.clearDisplay();
    // display temperature

    display.setTextSize(1);
    display.setCursor(0, 0);
    display.drawBitmap(0, 0, epd_bitmap_clock_solid, 12, 12, SSD1306_WHITE);
    display.setCursor(16, 2);
    display.print(String(gps.time.hour()) + ":" + String(gps.time.minute()) + " " + String(gps.date.day()) + "/" + String(gps.date.month()));
    display.setCursor(46, 2);
    display.drawBitmap(100, 0, epd_bitmap_satellite_solid, 12, 12, SSD1306_WHITE);
    display.setTextSize(1);
    display.setCursor(118, 2);
    display.print(gps.satellites.value());
    display.drawLine(0, 14, 128, 14, SSD1306_WHITE);
    display.drawBitmap(0, (display.height() - 24) / 2, epd_bitmap_map_location_dot_solid, 24, 24, SSD1306_WHITE);
    display.setCursor(30, 24);
    display.print("Lat: " + String(gps.location.lat(), 6));
    display.setCursor(30, 48);
    display.print("Lng: " + String(gps.location.lng(), 6));
    display.display();
}

// Function to return GPS string
String displayInfo() {
    // Define empty string to hold output
    String gpsdata = "";
    // Get latitude and longitude
    if (gps.location.isValid()) {
    // Serial.println("Latitude:"+String(gps.location.lat(), 6));
    // Serial.println("Longitude:"+String(gps.location.lng(), 6));
    gpsdata = String(gps.location.lat(), 6);
    gpsdata += (",");
    gpsdata += String(gps.location.lng(), 6);
    gpsdata += (",");
    } else {
    return "0";
    }

    // Get Date
    if (gps.date.isValid()) {
    gpsdata += String(gps.date.year());
    gpsdata += ("-");
    if (gps.date.month() < 10) gpsdata += ("0");
    gpsdata += String(gps.date.month());
    gpsdata += ("-");
    if (gps.date.day() < 10) gpsdata += ("0");
    gpsdata += String(gps.date.day());
    } else {
    return "0";
    }

    // Space between date and time
    gpsdata += (" ");

    // Get time
    if (gps.time.isValid()) {
    if (gps.time.hour() < 10) gpsdata += ("0");
    gpsdata += String(gps.time.hour());
    gpsdata += (":");
    if (gps.time.minute() < 10) gpsdata += ("0");
    gpsdata += String(gps.time.minute());
    gpsdata += (":");
    if (gps.time.second() < 10) gpsdata += ("0");
    gpsdata += String(gps.time.second());
    } else {
    return "0";
    }

    // Return completed string
    return gpsdata+("\n");
}

// Calculate distance between two points
float getDistance(float flat1, float flon1, float flat2, float flon2) {
    // Variables
    float dist_calc = 0;
    float dist_calc2 = 0;
    float diflat = 0;
    float diflon = 0;
    // Calculations
    diflat = radians(flat2 - flat1);
    flat1 = radians(flat1);
    flat2 = radians(flat2);
    diflon = radians((flon2) - (flon1));
    dist_calc = (sin(diflat / 2.0) * sin(diflat / 2.0));
    dist_calc2 = cos(flat1);
    dist_calc2 *= cos(flat2);
    dist_calc2 *= sin(diflon / 2.0);
    dist_calc2 *= sin(diflon / 2.0);
    dist_calc += dist_calc2;
    dist_calc = (2 * atan2(sqrt(dist_calc), sqrt(1.0 - dist_calc)));
    dist_calc *= 6371000.0;  //Converting to meters
    return dist_calc;
}

void playBuzzer() {
    DebugPrintln("inside");
    display.clearDisplay();
    display.setTextColor(SSD1306_WHITE);
    display.setCursor(0, 0);
    display.setTextSize(2);
    display.print("YOU HAVE");
    display.setCursor(0, 20);
    display.setTextSize(2);
    display.print("ARRIVED!");
    display.display();
    playBuzzer();
    delay(1000);
    for (int thisNote = 0; thisNote < notes * 2; thisNote = thisNote + 2) {
    divider = melody[thisNote + 1];
    if (divider > 0) {
        noteDuration = (wholenote) / divider;
    } else if (divider < 0) {
        noteDuration = (wholenote) / abs(divider);
        noteDuration *= 1.5;
    }
    tone(BuzzerPin, melody[thisNote], noteDuration * 0.9);
    delay(noteDuration);
    noTone(BuzzerPin);
    }
    delay(5000);
}