#include <Wire.h>
#include <WiFi.h>
#include <HTTPClient.h>
#include <ArduinoJson.h>
#include <Adafruit_SSD1306.h>
const char* ssid = "Wokwi-GUEST";
// 🔴 PASTE YOUR ACTIVE LOCALTUNNEL OR HUGGING FACE URL HERE
const char* serverUrl = "https://petite-places-fetch.loca.lt";
Adafruit_SSD1306 display(128, 64, &Wire, -1);
const int WINDOW_SIZE = 1500; // Full 30-second clinical scan at 50Hz
float ppg_buffer[WINDOW_SIZE];
int sample_idx = 0;
void setup() {
Serial.begin(115200);
Wire.begin(21, 22);
display.begin(SSD1306_SWITCHCAPVCC, 0x3C);
display.clearDisplay();
display.setTextColor(WHITE);
WiFi.begin(ssid, "");
while(WiFi.status() != WL_CONNECTED) delay(500);
Serial.println("WiFi Connected! Starting 30s Edge-Computing Scan...");
}
void processAndSend() {
display.clearDisplay();
display.setCursor(0,10);
display.setTextSize(1);
display.println("Processing Math...");
display.display();
Serial.println("\n[1] Finalizing 1,500 points and standardizing...");
// 1. Calculate Mean and StdDev for Standardization
float sum = 0;
for(int i=0; i<WINDOW_SIZE; i++) sum += ppg_buffer[i];
float mean = sum / WINDOW_SIZE;
float sq_sum = 0;
for(int i=0; i<WINDOW_SIZE; i++) {
sq_sum += pow(ppg_buffer[i] - mean, 2);
}
float stdev = sqrt(sq_sum / WINDOW_SIZE);
if (stdev == 0) stdev = 0.00001; // Prevent division by zero
// 2. Estimate HR using a basic peak detector
int peaks = 0;
float threshold = mean + (stdev * 0.5);
for(int i=1; i<WINDOW_SIZE-1; i++) {
if(ppg_buffer[i] > ppg_buffer[i-1] && ppg_buffer[i] > ppg_buffer[i+1] && ppg_buffer[i] > threshold) {
peaks++;
}
}
// 30 seconds of data. Multiply peaks by 2 for Beats Per Minute (BPM)
float calculated_hr = peaks * 2.0;
if (calculated_hr < 40) calculated_hr = 40.0;
if (calculated_hr > 220) calculated_hr = 220.0;
// 3. Prepare the 2D JSON Array
// 32000 bytes optimized for the ESP32 RAM limit
DynamicJsonDocument doc(32000);
JsonArray signal = doc.createNestedArray("signal");
JsonArray ppg_json = signal.createNestedArray();
JsonArray hr_json = signal.createNestedArray();
for(int i=0; i<WINDOW_SIZE; i++) {
// Standardization: (x - mean) / stdev
ppg_json.add((ppg_buffer[i] - mean) / stdev);
// Heart Rate Normalization: (HR - 30) / (220 - 30)
hr_json.add((calculated_hr - 30.0) / (220.0 - 30.0));
}
String body;
serializeJson(doc, body);
display.clearDisplay();
display.setCursor(0,10);
display.println("Sending to AI...");
display.display();
Serial.println("[2] Firing HTTP POST over Tunnel...");
HTTPClient http;
http.setTimeout(30000); // 30 second timeout for the massive payload
http.begin(serverUrl);
http.addHeader("Content-Type", "application/json");
http.addHeader("Bypass-Tunnel-Reminder", "true");
http.addHeader("User-Agent", "FibWatch-Medical-IoT");
int code = http.POST(body);
Serial.print("Server Response Code: "); Serial.println(code);
if(code == 200 || code == 201) {
display.clearDisplay();
display.setCursor(0,10);
display.setTextSize(2);
display.println("Data Sent!");
display.display();
delay(4000);
} else {
Serial.println("Error: Could not reach your server.");
display.clearDisplay();
display.setCursor(0,10);
display.println("SERVER ERROR");
display.display();
delay(3000);
}
http.end();
}
void loop() {
Wire.requestFrom(0x22, 1);
if(Wire.available()) {
ppg_buffer[sample_idx++] = (float)Wire.read();
// Update progress bar every 150 samples (10%)
if(sample_idx % 150 == 0) {
int prog = (sample_idx * 100) / WINDOW_SIZE;
display.clearDisplay();
display.setCursor(0,0);
display.setTextSize(1);
display.print("Scanning: "); display.print(prog); display.println("%");
display.fillRect(0, 20, prog * 1.28, 10, 1);
display.display();
}
if(sample_idx >= WINDOW_SIZE) {
processAndSend();
sample_idx = 0;
}
}
delay(20); // 50Hz sampling rate
}