#include <WiFi.h>
#include <PubSubClient.h>
#include <Adafruit_GFX.h>
#include <Adafruit_SSD1306.h>
#include <ESP32_SoftWire.h>
#define SCREEN_WIDTH 128
#define SCREEN_HEIGHT 64
#define OLED_RESET -1
Adafruit_SSD1306 display(SCREEN_WIDTH, SCREEN_HEIGHT, &Wire, OLED_RESET);
const int ecgPin = 34; // Pin connected to the potentiometer simulating AD8232
const int potPin = 35; // Pin connected to the potentiometer for heart rate adjustment
const char* mqtt_server = "broker.hivemq.com";
const char* mqtt_topic = "ecg/warning";
WiFiClient espClient;
PubSubClient client(espClient);
int16_t displayBuffer[SCREEN_WIDTH];
uint8_t bufferIndex = 0;
// ECG Parameters
const int SAMPLE_RATE = 200; // Higher sampling rate for better resolution
float heartRate = 60.0; // Initial heart rate
float phase = 0.0; // Phase of the ECG signal
const float PI_VAL = 3.14159;
// Time management
unsigned long lastUpdate = 0;
const int REFRESH_RATE = 5; // Reduced refresh rate for smoother signal
void setup() {
Serial.begin(115200);
pinMode(ecgPin, INPUT);
pinMode(potPin, INPUT);
Wire.begin();
if(!display.begin(SSD1306_SWITCHCAPVCC, 0x3C)) {
Serial.println(F("SSD1306 allocation failed"));
for(;;);
}
display.display();
delay(2000);
display.clearDisplay();
// Connect to WiFi
Serial.print("Connecting to WiFi");
WiFi.begin("Wokwi-GUEST", "", 6); // Use Wokwi-GUEST for Wokwi simulator
while (WiFi.status() != WL_CONNECTED) {
delay(100);
Serial.print(".");
}
Serial.println(" Connected!");
// Connect to MQTT
client.setServer(mqtt_server, 1883);
while (!client.connected()) {
Serial.println("Connecting to MQTT...");
if (client.connect("ESP32Client")) {
Serial.println("Connected to MQTT");
} else {
Serial.print("Failed with state ");
Serial.print(client.state());
delay(2000);
}
}
// Initialize buffer with baseline
for(int i = 0; i < SCREEN_WIDTH; i++) {
displayBuffer[i] = SCREEN_HEIGHT / 2;
}
}
float generateECGSample(float t) {
float sample = 0;
// P wave
if(t >= 0.0 && t < 0.1) {
sample += 0.15 * exp(-pow((t-0.05)/0.015, 2)); // Sharper P wave
}
// QRS Complex
if(t >= 0.12 && t < 0.20) {
sample -= 0.15 * exp(-pow((t-0.125)/0.005, 2)); // Q
sample += 1.0 * exp(-pow((t-0.15)/0.01, 2)); // R
sample -= 0.35 * exp(-pow((t-0.18)/0.005, 2)); // S
}
// T wave
if(t >= 0.35 && t < 0.50) {
sample += 0.25 * exp(-pow((t-0.43)/0.03, 2)); // T wave amplitude tweaked
}
// Baseline drift (to simulate some irregularities)
sample += 0.02 * sin(2 * PI_VAL * t * 0.5); // Slow sinusoidal drift
return sample;
}
void updateDisplay() {
display.clearDisplay();
// Show Heart Rate
display.setCursor(0, 0);
display.print("HR: ");
display.print((int)heartRate);
display.print(" BPM");
// Draw ECG Wave
for(int i = 0; i < SCREEN_WIDTH - 1; i++) {
int x1 = i;
int x2 = i + 1;
int y1 = displayBuffer[i];
int y2 = displayBuffer[i + 1];
display.drawLine(x1, y1, x2, y2, SSD1306_WHITE);
}
display.display();
}
void loop() {
unsigned long currentTime = millis();
// Update heart rate from potentiometer
int potValue = analogRead(potPin);
heartRate = map(potValue, 0, 4095, 50, 150); // Adjusted heart rate range for more realistic values
if(currentTime - lastUpdate >= REFRESH_RATE) {
lastUpdate = currentTime;
// Calculate new phase based on heart rate
float phaseIncrement = (heartRate / 30.0) / SAMPLE_RATE; // Artificially speed up by factor of 2
phase += phaseIncrement;
if(phase >= 1.0) phase = 0;
// Shift buffer left
for(int i = 0; i < SCREEN_WIDTH - 1; i++) {
displayBuffer[i] = displayBuffer[i + 1];
}
// Generate new sample
float newSample = generateECGSample(phase);
// Scale and offset for display
displayBuffer[SCREEN_WIDTH - 1] = (SCREEN_HEIGHT / 2) - (newSample * SCREEN_HEIGHT / 3);
// Update display
updateDisplay();
}
Serial.print("ECG Value: ");
Serial.println(displayBuffer[SCREEN_WIDTH - 1]);
Serial.print("Heart Rate: ");
Serial.println(heartRate);
// Send warning message if heart rate is in dangerous zone
if (heartRate < 60 || heartRate > 100) {
String payload = "Warning: Heart rate out of safe range! Current HR: " + String(heartRate);
client.publish(mqtt_topic, payload.c_str());
}
client.loop();
delay(100);
}