I have a simple program that saves a data set (temp, humidity, pressure), to an SD card every 5 minutes.
Its been running for 26 days now, so rather a lot of data.
Example

I'd like to be able to copy it to EXCEL and structure it like this:
time day1 day 2 day3
00:00 temp temp temp
00:05
with separate sheets for temp, humidity, pressure
but to keep it (relatively) simple lets just record and analyse the temperature readings.
Can anyone suggest a way to do this?
/*********
Rui Santos & Sara Santos - Random Nerd Tutorials
Complete project details at https://RandomNerdTutorials.com/esp32-datalogger-download-data-file/
Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files.
The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
rev1 Modified to show local time
all "success" messages commented out to make errors easier to fololow
*********/
#include <Arduino.h>
#include <WiFi.h>
#include <AsyncTCP.h>
#include <ESPAsyncWebServer.h>
#include "FS.h"
#include "SD.h"
#include "SPI.h"
#include <Adafruit_BME280.h>
#include <Adafruit_Sensor.h>
#include "time.h"
#include <WiFiUdp.h>
#include "creds.h" //my wifi credentials
// NTP server to request epoch time
const char* ntpServer = "pool.ntp.org";
/*********
Connections as per tutorial:
BME280: Vin 3v3; GND GND; SCL GPIO 22; SDA GPIO 21;
SD module: Vcc 3V3; CS GPIO 5; MOSI GPIO23; CLK GPIO 18; MISO GPIO 19; GND GND;
SD Card module requires Vcc to ESP32 Vin
*********/
//D2 LED to GND to indicate reading in progress
const int ledPin = 2;
int ledState = LOW; // ledState used to set the LED
// Variables to hold sensor readings
float temp;
float hum;
float pres;
String dataMessage;
String timeBuffer; // for local time
// File name where readings will be saved
const char* dataPath = "/data.txt";
// Timer variables
unsigned long lastTime = 0;
unsigned long timerDelay = 300; //300 seconds = 5 min between readings; converted to msec in setup
// Create AsyncWebServer object on port 80
AsyncWebServer server(80);
// BME280 connect to ESP32 I2C (GPIO 21 = SDA, GPIO 22 = SCL)
Adafruit_BME280 bme;
// Init BME280
void initBME(){
if (!bme.begin(0x76)) {
Serial.println("Could not find a valid BME280 sensor, check wiring!");
while (1);
}
}
// Init microSD card
void initSDCard(){
if(!SD.begin()){
Serial.println("Card Mount Failed");
return;
}
uint8_t cardType = SD.cardType();
if(cardType == CARD_NONE){
Serial.println("No SD card attached");
return;
}
Serial.print("SD Card Type: ");
if(cardType == CARD_MMC){
Serial.println("MMC");
} else if(cardType == CARD_SD){
Serial.println("SDSC");
} else if(cardType == CARD_SDHC){
Serial.println("SDHC");
} else {
Serial.println("UNKNOWN");
}
uint64_t cardSize = SD.cardSize() / (1024 * 1024);
Serial.printf("SD Card Size: %lluMB\n", cardSize);
} //end of card initialisation
// Reconnect microSD card unless already connected
void connSDCard(){
if(!SD.exists(dataPath)){
Serial.print("File not accessible: ");
SD.begin();
Serial.println("Reconnected SD card ");
}
return;
}
// Write to the SD card
void writeFile(fs::FS &fs, const char * path, const char * message) {
//Serial.printf("Writing file: %s\n", path);
connSDCard();
File file = fs.open(path, FILE_WRITE);
if(!file) {
Serial.println("Failed to open file for writing");
return;
}
if(file.print(message)) {
//Serial.println("File written");
} else {
Serial.println("Write failed");
}
file.close();
}
// Append data to the SD card
void appendFile(fs::FS &fs, const char * path, const char * message) {
//Serial.printf("Appending to file: %s\n", path);
connSDCard();
File file = fs.open(path, FILE_APPEND);
if(!file) {
Serial.println("Failed to open file for appending");
return;
}
if(file.print(message)) {
//Serial.println("Message appended");
} else {
Serial.println("Append failed");
}
file.close();
}
// Delete file
void deleteFile(fs::FS &fs, const char * path){
Serial.printf("Deleting file: %s\r\n", path);
connSDCard();
if(fs.remove(path)){
Serial.println("- file deleted");
} else {
Serial.println("- delete failed");
}
}
// Function that gets current epoch time and converts to local time - from dataloggerLittleFSHighcharts.zip https://drive.google.com/drive/folders/1pCMxZDTGQYR0_KZi7bPpzTpGjn2A6r3M
String getTime(){
struct tm timeinfo;
if(!getLocalTime(&timeinfo)){
Serial.println("No time available (yet)");
}
char timeStringBuff[25];
strftime( timeStringBuff,
sizeof(timeStringBuff),
//"%d/%m/%Y %H:%M:%S %Z",
"%a %d/%m/%Y %H:%M:%S",
&timeinfo);
String timeBuffer(timeStringBuff);
return timeBuffer.c_str();
}
// Function that initializes wi-fi
void initWiFi() {
WiFi.mode(WIFI_STA);
WiFi.begin(ssid, password);
Serial.print("Connecting to WiFi ..");
while (WiFi.status() != WL_CONNECTED) {
Serial.print('.');
delay(1000);
}
Serial.println("connected"); //so IP is shown at start of line
Serial.println(WiFi.localIP());
}
void setup() {
Serial.begin(115200);
Serial.println(__FILE__); //print name of sketch for easy identification
timerDelay *=1000; //convert from seconds to msec
pinMode (ledPin, OUTPUT);
initWiFi();
initBME();
initSDCard();
configTime(0, 0, ntpServer);
// If the data.txt file doesn't exist
// Create a file on the SD card and write the data labels
File file = SD.open("/data.txt");
if(!file) {
Serial.println("File doesn't exist");
Serial.println("Creating file...");
writeFile(SD, "/data.txt", "Day, Date, Time, Temperature, Humidity, Pressure \r\n");
}
else {
Serial.println("File already exists");
}
file.close();
// Handle the root URL
server.on("/", HTTP_GET, [](AsyncWebServerRequest *request){
request->send(SD, "/index.html", "text/html");
});
// Handle the download button
server.on("/download", HTTP_GET, [](AsyncWebServerRequest *request){
Serial.println("HTTP: Downloading data ");
request->send(SD, "/data.txt", String(), true);
});
// Handle the View Data button
server.on("/view-data", HTTP_GET, [](AsyncWebServerRequest *request){
// request->send(SD, "/data.txt", "text/txt"); doesnt work in firefox
Serial.println("HTTP: Showing data on page ");
request->send(SD, "/data.txt", "text/plain", false);
});
// Handle the delete button
server.on("/delete", HTTP_GET, [](AsyncWebServerRequest *request){
Serial.print("HTTP: Deleting data file ");
deleteFile(SD, dataPath);
request->send(200, "text/plain", "data.txt was deleted.");
});
// Uncomment the following line if you need to serve more static files like CSS and javascript or favicon
//server.serveStatic("/", SD, "/");
server.begin();
}
void loop() {
if ((millis() - lastTime) > timerDelay) {
//Get time
timeBuffer = getTime(); //modified to now show gmt time
//Get sensor readings
temp = bme.readTemperature();
//temp = 1.8*bme.readTemperature() + 32;
hum = bme.readHumidity();
pres = bme.readPressure()/100.0F;
//Concatenate all info separated by commas
dataMessage = timeBuffer + ", " + String(temp, 1) + ", " + String(hum, 1) + ", " + String(pres, 1)+ "\r\n"; //modified to show real time; /r/n is cr lf so no println needed
Serial.print("Saving data: ");
Serial.print(dataMessage); //dont make extra line after printing the data line
//Append the data to file
appendFile(SD, "/data.txt", dataMessage.c_str());
// if the LED is off turn it on and vice-versa:
ledState = (ledState == LOW) ? HIGH : LOW;
digitalWrite(ledPin, ledState);
lastTime = millis();
}
}
