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.
There are two possible sleep patterns to implement and various sub-sleep possible configurations:
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);
}