#include <MAX3010x.h>
#include "filters.h"
#include <SPIFFS.h>
#include <WiFi.h>
#include <NTPClient.h>
#include <WiFiUdp.h>
#include <ESP32_FTPClient.h> // Include the FTPClient library
// WiFi credentials
const char* ssid1 = "TempWiFi";
const char* password1 = "987654321";
const char* ssid2 = "EURTH";
const char* password2 = "3urth@5555";
// NTP settings for GMT
const char* ntpServer = "pool.ntp.org"; // NTP server for GMT
const long gmtOffset_sec = 0; // GMT offset for GMT (in seconds), which is 0 seconds
const int daylightOffset_sec = 0; // Daylight saving time offset for GMT (0, as GMT doesn't observe daylight saving time)
// Define the maximum file size (2MB)
const uint32_t maxFileSize = 2 * 1024 * 1024; // 2MB in bytes
// Define a buffer size (adjust as needed)
const int bufferSize = 1024; // 1KB buffer
char circularBuffer[bufferSize];
int bufferIndex = 0;
// Sensor (adjust to your sensor type)
MAX30102 sensor;
const auto kSamplingRate = sensor.SAMPLING_RATE_400SPS;
const float kSamplingFrequency = 400.0;
// Finger Detection Threshold and Cooldown
const unsigned long kFingerThreshold = 10000;
const unsigned int kFingerCooldownMs = 500;
// Edge Detection Threshold (decrease for MAX30100)
const float kEdgeThreshold = -2000.0;
unsigned long fingerNotDetectedDuration = 0;
// Filters
const float kLowPassCutoff = 5.0;
const float kHighPassCutoff = 0.5;
// Averaging
const bool kEnableAveraging = true;
const int kAveragingSamples = 5;
const int kSampleThreshold = 5;
LowPassFilter low_pass_filter_red(kLowPassCutoff, kSamplingFrequency);
LowPassFilter low_pass_filter_ir(kLowPassCutoff, kSamplingFrequency);
HighPassFilter high_pass_filter(kHighPassCutoff, kSamplingFrequency);
Differentiator differentiator(kSamplingFrequency);
MovingAverageFilter<kAveragingSamples> averager_bpm;
MovingAverageFilter<kAveragingSamples> averager_r;
MovingAverageFilter<kAveragingSamples> averager_spo2;
MinMaxAvgStatistic stat_red;
MinMaxAvgStatistic stat_ir;
float kSpO2_A = 1.5958422;
float kSpO2_B = -34.6596622;
float kSpO2_C = 112.6898759;
long last_heartbeat = 0;
long finger_timestamp = 0;
bool finger_detected = false;
float last_diff = NAN;
bool crossed = false;
long crossed_time = 0;
float current_value_red = 0.0;
float current_value_ir = 0.0;
bool isCalibrating = true;
unsigned long calibrationStartTime = 0;
const unsigned long calibrationDuration = 10000;
long kOutputIntervalMs = 1000;
long output_timestamp = 0;
int last_bpm = 0;
int last_spo2 = 0;
int last_RED = 0;
int last_IR = 0;
unsigned long lastSyncMillis = 0;
unsigned long localTime = 0;
unsigned long initialEpochTime = 0; // Variable to store the initial EPOCH time
// Add more credentials if needed
const char* ssidList[] = {ssid1, ssid2};
const char* passwordList[] = {password1, password2};
int numNetworks = sizeof(ssidList) / sizeof(ssidList[0]);
int currentNetwork = 0;
void writeToCSV(char* fileName, char* data) {
File file = SPIFFS.open(fileName, FILE_APPEND);
if (!file) {
Serial.println("Failed to open file");
return;
}
file.print(data);
if (file.size() >= maxFileSize) {
file.close();
file = SPIFFS.open(fileName, FILE_WRITE);
if (!file) {
Serial.println("Failed to open file for overwriting");
return;
}
}
file.close();
}
void setup() {
Serial.begin(115200);
// Attempt to connect to WiFi networks in the list
while (currentNetwork < numNetworks) {
Serial.print("Connecting to WiFi network: ");
Serial.println(ssidList[currentNetwork]);
WiFi.begin(ssidList[currentNetwork], passwordList[currentNetwork]);
int attempt = 0;
while (WiFi.status() != WL_CONNECTED && attempt < 5) {
delay(1000);
Serial.println("Connecting to WiFi...");
attempt++;
}
if (WiFi.status() == WL_CONNECTED) {
break; // Successful connection, exit loop
} else {
currentNetwork++; // Move to the next network in the list
}
}
if (currentNetwork == numNetworks) {
Serial.println("Failed to connect to any WiFi network.");
devRestart();
}
// Initialize and configure NTPClient
WiFiUDP ntpUDP;
NTPClient timeClient(ntpUDP, ntpServer, gmtOffset_sec, daylightOffset_sec);
timeClient.begin();
timeClient.update();
Serial.print("Synchronized time: ");
Serial.println(timeClient.getFormattedTime());
// Store the initial EPOCH time in milliseconds
initialEpochTime = timeClient.getEpochTime();
Serial.println(initialEpochTime);
delay(1000);
// Disconnect from Wi-Fi after time sync
WiFi.disconnect(true);
if (!SPIFFS.begin(true)) {
Serial.println("SPIFFS initialization failed.");
while (1);
}
if (SPIFFS.exists("/log.csv")) {
SPIFFS.remove("/log.csv");
Serial.println("Existing CSV file deleted.");
}
if (sensor.begin() && sensor.setSamplingRate(kSamplingRate)) {
Serial.println("Sensor initialized, Please Wait 10 Sec");
} else {
Serial.println("Sensor not found");
devRestart();
while (1);
}
calibrationStartTime = millis();
}
void devRestart() {
Serial.println("Restarting in 10 seconds");
delay(10000);
ESP.restart();
}
// Change according to your FTP server
char ftp_server[] = "ftpupload.net";
char ftp_user[] = "b15_35307737";
char ftp_pass[] = "everest@123";
// Initialize the FTPClient instance
// ESP32_FTPClient ftpClient("eurth.files.com", 21, "everest", "eVerest@123", 10000, 2);
// Initialize the FTPClient instance
ESP32_FTPClient ftpClient("185.27.134.11", 21, "b15_35307737", "everest@123", 10000, 2);
// ESP32_FTPClient ftpClient("44.241.66.173", 21, "dlpuser", "rNrKYTX9g7z3RgJRmxWuGHbeu", 30000, 2);
// local ftp server with python - https://stackoverflow.com/a/7303734/208928
// mkdir -p test; cd test; python3 -m pyftpdlib -u sairam -P sairam -p 2121 -i 0.0.0.0 -w -D
// ESP32_FTPClient ftpClient("192.168.0.242", 2121, "sairam", "sairam", 30000, 2);
// ...
void uploadLogFile(){
// Establish a WiFi connection
WiFi.begin(ssidList[currentNetwork], passwordList[currentNetwork]);
int attempt = 0;
while (WiFi.status() != WL_CONNECTED && attempt < 5) {
delay(1000);
Serial.println("Connecting to WiFi...");
attempt++;
}
if (WiFi.status() == WL_CONNECTED) {
Serial.println("Connected to WiFi.");
ftpClient.OpenConnection();
delay(5000);
File Logfile = SPIFFS.open("/log.csv", "r");
String macAddress = WiFi.macAddress();
String renamedFileName = "/"+ macAddress + "_" + String(localTime) + ".csv"; //+ "_" + String(localTime)
renamedFileName.replace(":", ""); // Remove colons from the MAC address
if (SPIFFS.rename("/log.csv", renamedFileName)) {
Serial.println("Log file renamed.");
Serial.println(renamedFileName);
if(ftpClient.isConnected()) {
ftpClient.InitFile("Type A");
renamedFileName.replace("/","");
ftpClient.NewFile(renamedFileName.c_str());
while (Logfile.available()) {
char buffer[1024];
size_t bytesRead = Logfile.readBytes(buffer, sizeof(buffer));
ftpClient.WriteData((unsigned char*)buffer, bytesRead);
Serial.println("Uploaded chunk");
}
Logfile.close();
// Close the file on the FTP server
ftpClient.CloseFile();
Serial.println("File uploaded successfully.");
ftpClient.CloseConnection();
deleteAllFilesSPIFFS();
Serial.println("All Local file deleted.");
delay(5000);
} else {
Serial.println("FTP connection failure");
delay(5000);
// return false;
}
// return true;
} else {
Serial.println("Wifi connection failure");
// devRestart();
}
// Disconnect from WiFi
WiFi.disconnect(true);
}
// Shutdown the device
delay(10000);
Serial.println("Device Going to Sleep for 10 min");
sensor.shutdown();
esp_deep_sleep(600e6); // Sleep for 600 seconds
}
void deleteAllFilesSPIFFS() {
// Mount SPIFFS
if (!SPIFFS.begin(true)) {
Serial.println("Failed to mount SPIFFS");
return;
}
// Open directory
File root = SPIFFS.open("/");
File file = root.openNextFile();
// Delete each file in the directory
while (file) {
if (file.isDirectory()) {
// Skip directories
} else {
// Delete file
SPIFFS.remove(file.name());
}
file = root.openNextFile();
}
}
void loop() {
unsigned long currentTime = millis();
long current_timestamp = millis();
// Update the local time using millis() and the initial EPOCH time
localTime = initialEpochTime + (currentTime / 1000);
if (current_timestamp - output_timestamp > kOutputIntervalMs) {
output_timestamp = current_timestamp;
if (kEnableAveraging) {
char logData[256];
snprintf(logData, sizeof(logData), "%lu,%d,%d\n",
localTime, last_bpm, last_spo2);
writeToCSV("/log.csv", logData);
Serial.print(logData);
last_bpm = 0;
last_spo2 = 0;
} else {
char logData[256];
snprintf(logData, sizeof(logData), "%lu,%d,%d\n",
localTime, last_bpm, last_spo2);
writeToCSV("/log.csv", logData);
Serial.print(logData);
}
}
if (isCalibrating && (currentTime - calibrationStartTime >= calibrationDuration)) {
isCalibrating = false;
// Serial.println("Calibration complete. You can now place your finger.");
}
auto sample = sensor.readSample(1000);
current_value_red = sample.red;
current_value_ir = sample.ir;
if (!isCalibrating) {
if (current_value_red > kFingerThreshold) {
if (millis() - finger_timestamp > kFingerCooldownMs) {
finger_detected = true;
}
if (finger_detected) {
current_value_red = low_pass_filter_red.process(current_value_red);
current_value_ir = low_pass_filter_ir.process(current_value_ir);
stat_red.process(current_value_red);
stat_ir.process(current_value_ir);
float current_value = high_pass_filter.process(current_value_red);
float current_diff = differentiator.process(current_value);
if (!isnan(current_diff) && !isnan(last_diff)) {
if (last_diff > 0 and current_diff < 0) {
crossed = true;
crossed_time = millis();
}
if (current_diff > 0) {
crossed = false;
}
if (crossed && current_diff < kEdgeThreshold) {
if (last_heartbeat != 0 && crossed_time - last_heartbeat > 300) {
int bpm = 60000 / (crossed_time - last_heartbeat);
float rred = (stat_red.maximum() - stat_red.minimum()) / stat_red.average();
float rir = (stat_ir.maximum() - stat_ir.minimum()) / stat_ir.average();
float r = rred / rir;
float spo2 = kSpO2_A * r * r + kSpO2_B * r + kSpO2_C;
if (spo2 > 100.0) {
spo2 = 100.0;
}
if (bpm > 50 && bpm < 250) {
if (kEnableAveraging) {
int average_bpm = averager_bpm.process(bpm);
int average_r = averager_r.process(r);
int average_spo2 = averager_spo2.process(spo2);
if (averager_bpm.count() >= kSampleThreshold) {
char logData[256];
snprintf(logData, sizeof(logData), "T (ms): %lu\tmA-Red: %.0f\tmA-IR: %.0f\tR-Value (avg): %d\tHRate (avg, bpm): %d\tSpO2 (avg, %%): %d\n",
localTime, current_value_red, current_value_ir, average_r, average_bpm, average_spo2);
//writeToCSV("/log.csv", logData);
last_bpm = average_bpm;
last_spo2 = average_spo2;
}
} else {
char logData[256];
snprintf(logData, sizeof(logData), "T (ms): %lu\tR-Value (current): %f\tHRate (current, bpm): %d\tSpO2 (current, %%): %f\n",
localTime, r, bpm, spo2);
//writeToCSV("/log.csv", logData);
last_bpm = bpm;
last_spo2 = spo2;
}
}
stat_red.reset();
stat_ir.reset();
}
crossed = false;
last_heartbeat = crossed_time;
}
}
last_diff = current_diff;
finger_timestamp = millis();
}
} else {
differentiator.reset();
averager_bpm.reset();
averager_r.reset();
averager_spo2.reset();
low_pass_filter_red.reset();
low_pass_filter_ir.reset();
high_pass_filter.reset();
stat_red.reset();
stat_ir.reset();
fingerNotDetectedDuration = millis() - finger_timestamp;
finger_detected = false;
last_bpm = 0;
last_spo2 = 0;
// If the finger is removed, upload the log file and shutdown the device
if (fingerNotDetectedDuration >= 180000) {
// Upload the log file
uploadLogFile();
}
delay(100);
}
}
// Update the local time using millis() (milliseconds since startup)
localTime = currentTime;
delay(10);
}