parent
1134cadbe7
commit
98c0039af7
|
|
@ -0,0 +1,476 @@
|
|||
#include <Wire.h>
|
||||
#include <Adafruit_GFX.h>
|
||||
#include <Adafruit_ILI9341.h>
|
||||
#include <MAX30105.h>
|
||||
#include <heartRate.h>
|
||||
#include <OneWire.h>
|
||||
#include <DallasTemperature.h>
|
||||
#include <WiFi.h>
|
||||
#include <ArduinoJson.h>
|
||||
#include <HTTPClient.h>
|
||||
#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();
|
||||
}
|
||||
Loading…
Reference in New Issue