From 98c0039af7a08a986f72e74a6bd83fc6eec2af66 Mon Sep 17 00:00:00 2001 From: Firman_Nabil Date: Tue, 4 Aug 2026 11:39:25 +0700 Subject: [PATCH] Upload files to "/" Source Code ESP32 --- HealthMonitor_v2_fixed.ino | 476 +++++++++++++++++++++++++++++++++++++ 1 file changed, 476 insertions(+) create mode 100644 HealthMonitor_v2_fixed.ino diff --git a/HealthMonitor_v2_fixed.ino b/HealthMonitor_v2_fixed.ino new file mode 100644 index 0000000..ed7d995 --- /dev/null +++ b/HealthMonitor_v2_fixed.ino @@ -0,0 +1,476 @@ +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include "spo2_algorithm.h" + +#define TFT_CS 5 +#define TFT_RST 4 +#define TFT_DC 2 +#define ONE_WIRE_BUS 13 + +const char* ssid = "sembarang"; +const char* password = "12345678"; +const char* serverName = "http://10.107.21.239/iot/save_data.php"; + +Adafruit_ILI9341 tft(TFT_CS, TFT_DC, TFT_RST); +MAX30105 particleSensor; +OneWire oneWire(ONE_WIRE_BUS); +DallasTemperature ds18b20(&oneWire); + +enum State { + WAIT_FINGER, + MEASURE_BPM, + WAIT_TEMP_STABILIZE, + MEASURE_TEMP, + SHOW_RESULT +}; + +State currentState = WAIT_FINGER; + +const byte RATE_SIZE = 8; +byte rates[RATE_SIZE]; +byte rateSpot = 0; +byte validRateCount = 0; +long lastBeat = 0; +int beatAvg = 0; +int lastStoredBeatAvg = 0; +long lastIR = 0; + +float suhuCelcius = 0; + +unsigned long stateStart = 0; +unsigned long lastTempRead = 0; +unsigned long lastSerial = 0; + +float bpmFilteredSum = 0; +int bpmFilteredCount = 0; + +float tempSum = 0; +int tempCount = 0; + +int finalBPM = 0; +float finalTemp = 0; +int finalSpO2 = 0; +int systolic = 0; +int diastolic = 0; + +bool resultSent = false; + +// Heart Animation +bool heartBeatAnimation = false; +unsigned long heartAnimStart = 0; + +// Variabel baru untuk stabilisasi suhu +float initialTemp = 0; +bool tempStabilized = false; +unsigned long tempStableStart = 0; + +const float TEMP_RISE_THRESHOLD = 0.5; + +// SpO2 Variables +uint32_t irBuffer[100]; +uint32_t redBuffer[100]; +int32_t bufferLength = 100; +int32_t spo2 = 0; +int8_t validSPO2 = 0; +int32_t heartRateSpO2 = 0; +int8_t validHeartRate = 0; +int spo2Sum = 0; +int spo2Count = 0; + +// Countdown +unsigned long lastCountdownUpdate = 0; + +void showMessage(const char* line1, const char* line2, const char* line3 = "") { + tft.fillScreen(ILI9341_BLACK); + tft.setTextColor(ILI9341_YELLOW); + tft.setTextSize(2); + tft.setCursor(15, 50); + tft.println(line1); + tft.setTextSize(1); + tft.setCursor(15, 100); + tft.println(line2); + tft.setCursor(15, 120); + tft.println(line3); +} + +// ==================== UPDATE STATUS ==================== +String getStatusBPM(int bpm) { + if (bpm < 60) return "Rendah"; + if (bpm > 100) return "Tinggi"; + return "Normal"; +} + +String getStatusTemp(float temp) { + if (temp < 35.5) return "Rendah"; + if (temp > 38.0) return "Tinggi"; + return "Normal"; +} + +String getStatusSpO2(int spo2Val) { + if (spo2Val < 95) return "Rendah"; + if (spo2Val > 100) return "Tinggi"; + return "Normal"; +} + +String getStatusBP(int sys, int dia) { + if (sys < 90 || dia < 60) return "Rendah"; + if (sys > 120 || dia > 80) return "Tinggi"; + return "Normal"; +} + +void showFinalResult() { + tft.fillScreen(ILI9341_BLACK); + tft.setTextSize(2); + tft.setTextColor(ILI9341_GREEN); + tft.setCursor(45, 15); + tft.println("HASIL AKHIR"); + + tft.setTextSize(2); + tft.setTextColor(ILI9341_WHITE); + + tft.setCursor(20, 55); + tft.print("BPM : "); + tft.print(finalBPM); + tft.print(" "); + tft.setTextColor(ILI9341_CYAN); + tft.println(getStatusBPM(finalBPM)); + + tft.setCursor(20, 80); + tft.setTextColor(ILI9341_WHITE); + tft.print("SpO2 : "); + tft.print(finalSpO2); + tft.print("% "); + tft.setTextColor(ILI9341_CYAN); + tft.println(getStatusSpO2(finalSpO2)); + + tft.setCursor(20, 105); + tft.setTextColor(ILI9341_WHITE); + tft.print("TEMP : "); + tft.print(finalTemp, 1); + tft.print(" C "); + tft.setTextColor(ILI9341_CYAN); + tft.println(getStatusTemp(finalTemp)); + + tft.setCursor(20, 130); + tft.setTextColor(ILI9341_WHITE); + tft.print("BP : "); + tft.print(systolic); + tft.print("/"); + tft.print(diastolic); + tft.print(" "); + tft.setTextColor(ILI9341_CYAN); + tft.println(getStatusBP(systolic, diastolic)); +} + +void showTempWaitingScreen() { + tft.fillScreen(ILI9341_BLACK); + tft.setTextColor(ILI9341_GREEN); + tft.setTextSize(2); + tft.setCursor(30, 20); + tft.println("MENGUKUR SUHU"); + + tft.setTextColor(ILI9341_WHITE); + tft.setCursor(20, 70); + tft.print("BPM : "); + tft.println(finalBPM); + + tft.setCursor(20, 100); + tft.print("SpO2: "); + tft.print(finalSpO2); + tft.println("%"); + + tft.setCursor(20, 145); + tft.setTextColor(ILI9341_YELLOW); + tft.println("Genggam Tabung Besi"); +} + +void drawHeart(bool big) { + uint16_t color = ILI9341_RED; + tft.fillRect(240, 10, 70, 60, ILI9341_BLACK); + if (big) { + tft.fillCircle(255, 22, 10, color); + tft.fillCircle(275, 22, 10, color); + tft.fillTriangle(245, 28, 285, 28, 265, 58, color); + } else { + tft.fillCircle(257, 24, 7, color); + tft.fillCircle(273, 24, 7, color); + tft.fillTriangle(250, 28, 280, 28, 265, 50, color); + } +} + +void setup() { + Serial.begin(115200); + WiFi.begin(ssid, password); + + tft.begin(); + tft.setRotation(1); + tft.fillScreen(ILI9341_BLACK); + + ds18b20.begin(); + + if (!particleSensor.begin(Wire, I2C_SPEED_FAST)) { + showMessage("MAX30102 ERROR", "Check Sensor"); + while (true); + } + + particleSensor.setup(0x5F, 8, 2, 400, 411, 4096); + particleSensor.setPulseAmplitudeRed(0x1F); + particleSensor.setPulseAmplitudeIR(0x1F); + + showMessage("TEMPEL JARI", "Pada cahaya merah", "Menunggu BPM..."); +} + +void loop() { + readBPMRealtime(); + readTemperatureRealtime(); + + if (currentState == MEASURE_BPM) { + if (heartBeatAnimation) { + drawHeart(true); + if (millis() - heartAnimStart > 180) heartBeatAnimation = false; + } else { + drawHeart(false); + } + } + + if (millis() - lastSerial > 1000) { + lastSerial = millis(); + Serial.print("IR: "); Serial.print(lastIR); + Serial.print(" | BPM: "); Serial.print(beatAvg); + Serial.print(" | SpO2: "); Serial.print(spo2); + Serial.print(" | TEMP: "); Serial.println(suhuCelcius, 1); + } + + switch (currentState) { + case WAIT_FINGER: + if (beatAvg > 0) { + bpmFilteredSum = 0; bpmFilteredCount = 0; + spo2Sum = 0; spo2Count = 0; + lastStoredBeatAvg = 0; + stateStart = millis(); + lastCountdownUpdate = millis(); + currentState = MEASURE_BPM; + showMessage("MENGUKUR BPM", "Jangan lepaskan jari", "40 detik..."); + } + break; + + case MEASURE_BPM: + if (millis() - lastCountdownUpdate >= 1000) { + lastCountdownUpdate = millis(); + int sisa = 40 - ((millis() - stateStart) / 1000); + if (sisa < 0) sisa = 0; + tft.fillRect(20, 140, 200, 30, ILI9341_BLACK); + tft.setCursor(20, 140); + tft.setTextColor(ILI9341_CYAN); + tft.setTextSize(2); + tft.print("Sisa: "); tft.print(sisa); tft.println(" detik"); + } + + if (beatAvg > 0 && beatAvg != lastStoredBeatAvg) { + Serial.print("BEATAVG BARU = "); Serial.println(beatAvg); + lastStoredBeatAvg = beatAvg; + + if (bpmFilteredCount == 0) { + bpmFilteredSum += beatAvg; bpmFilteredCount++; + } else { + float runningAvg = bpmFilteredSum / bpmFilteredCount; + if (abs(beatAvg - runningAvg) <= 10) { + bpmFilteredSum += beatAvg; bpmFilteredCount++; + } + } + } + + if (millis() - stateStart >= 40000UL) { + finalBPM = (bpmFilteredCount > 0) ? round(bpmFilteredSum / bpmFilteredCount) : 0; + finalSpO2 = (spo2Count > 5) ? round((float)spo2Sum / spo2Count) : 0; + + initialTemp = suhuCelcius; + tempStabilized = false; + tempSum = 0; tempCount = 0; + stateStart = millis(); + lastCountdownUpdate = millis(); + + currentState = WAIT_TEMP_STABILIZE; + showMessage("MENGUKUR SUHU", "Menunggu Suhu Stabil", ""); + } + break; + + case WAIT_TEMP_STABILIZE: + if (suhuCelcius > initialTemp + TEMP_RISE_THRESHOLD) { + if (!tempStabilized) { + tempStabilized = true; + tempStableStart = millis(); + } + } + + if (tempStabilized && (millis() - tempStableStart >= 5000UL)) { + stateStart = millis(); + lastCountdownUpdate = millis(); + currentState = MEASURE_TEMP; + showTempWaitingScreen(); + } + + if (millis() - stateStart >= 25000UL) { + stateStart = millis(); + lastCountdownUpdate = millis(); + currentState = MEASURE_TEMP; + showTempWaitingScreen(); + } + break; + + case MEASURE_TEMP: + tempSum += suhuCelcius; + tempCount++; + + if (millis() - lastCountdownUpdate >= 1000) { + lastCountdownUpdate = millis(); + int sisa = 40 - ((millis() - stateStart) / 1000); + if (sisa < 0) sisa = 0; + tft.fillRect(20, 200, 200, 30, ILI9341_BLACK); + tft.setCursor(20, 200); + tft.setTextColor(ILI9341_CYAN); + tft.setTextSize(2); + tft.print("Sisa: "); tft.print(sisa); tft.println(" dtk"); + } + + if (millis() - stateStart >= 40000UL) { + finalTemp = (tempCount > 0) ? tempSum / tempCount : suhuCelcius; + currentState = SHOW_RESULT; + showFinalResult(); + } + break; + + case SHOW_RESULT: + if (!resultSent) { + resultSent = true; + sendDataToServer(finalBPM, finalTemp); + } + break; + } + + delay(10); +} + +void readBPMRealtime() { + long irValue = particleSensor.getIR(); + long redValue = particleSensor.getRed(); + + if (lastIR == 0) lastIR = irValue; + long filteredIR = (irValue * 0.3) + (lastIR * 0.7); + lastIR = filteredIR; + + static int bufferIndex = 0; + if (currentState == MEASURE_BPM) { + redBuffer[bufferIndex] = redValue; + irBuffer[bufferIndex] = irValue; + bufferIndex++; + if (bufferIndex >= bufferLength) { + maxim_heart_rate_and_oxygen_saturation(irBuffer, bufferLength, redBuffer, &spo2, &validSPO2, &heartRateSpO2, &validHeartRate); + if (validSPO2 && spo2 > 70 && spo2 <= 100) { + spo2Sum += spo2; + spo2Count++; + } + bufferIndex = 0; + } + } + + if (filteredIR > 25000) { + if (checkForBeat(filteredIR)) { + heartBeatAnimation = true; + heartAnimStart = millis(); + long delta = millis() - lastBeat; + lastBeat = millis(); + + float bpm_raw = 60.0 / (delta / 1000.0); + if (delta < 450 || delta > 1200) return; + + if (bpm_raw > 50 && bpm_raw < 150) { + static float lastValidBPM = 0; + if (lastValidBPM > 0 && abs(bpm_raw - lastValidBPM) > 8) return; + lastValidBPM = bpm_raw; + + rates[rateSpot++] = (byte)bpm_raw; + rateSpot %= RATE_SIZE; + if (validRateCount < RATE_SIZE) validRateCount++; + + beatAvg = 0; + for (byte x = 0; x < validRateCount; x++) beatAvg += rates[x]; + beatAvg /= validRateCount; + } + } + } +} + +void readTemperatureRealtime() { + if (millis() - lastTempRead >= 2000) { + lastTempRead = millis(); + ds18b20.requestTemperatures(); + float t = ds18b20.getTempCByIndex(0); + if (t > 10 && t < 50) suhuCelcius = t + 1.5; + } +} + +void sendDataToServer(int bpm, float temp) { + + if (WiFi.status() != WL_CONNECTED) return; + + HTTPClient http; + + String url = + "http://10.107.21.239/iot/save_data.php?action=insert" + "&bpm=" + String(bpm) + + "&spo2=" + String(finalSpO2) + + "&temperature=" + String(temp, 1) + + "&systolic=120" + "&diastolic=80"; + + Serial.println(url); + + http.begin(url); + + int httpCode = http.GET(); + + Serial.print("HTTP Code : "); + Serial.println(httpCode); + + if (httpCode > 0) { + Serial.println(http.getString()); + } + + http.end(); + + delay(1500); + + getPredictionFromServer(); +} + +void getPredictionFromServer() { + HTTPClient http; + http.setTimeout(10000); + http.begin("http://10.107.21.239/iot/save_data.php?action=latest"); + int httpCode = http.GET(); + if (httpCode == 200) { + String payload = http.getString(); + Serial.println("===== RESPONSE SERVER ====="); + Serial.println(payload); + JsonDocument doc; + deserializeJson(doc, payload); + systolic = doc["systolic"] | 120; + diastolic = doc["diastolic"] | 80; + Serial.print("Systolic dari server : "); + Serial.println(systolic); + + Serial.print("Diastolic dari server : "); + Serial.println(diastolic); + showFinalResult(); + } + http.end(); +} \ No newline at end of file