#include #include #include #include "MAX30105.h" #include "heartRate.h" #include #include #include #define BUZZER_PIN 27 // --- WiFi & Thinger --- #define SSID "AR ID" #define PASSWORD "012345678" #define USERNAME "amalia_lia" #define DEVICE_ID "esp32-mood" #define DEVICE_CREDENTIAL "esp32-mood" ThingerESP32 thing(USERNAME, DEVICE_ID, DEVICE_CREDENTIAL); // --- MAX30102 --- MAX30105 particleSensor; const int SDA_PIN = 21; const int SCL_PIN = 22; const int GSR_PIN = 34; // --- TFT ST7789 --- #define TFT_CS 5 #define TFT_DC 2 #define TFT_RST 4 Adafruit_ST7789 tft = Adafruit_ST7789(TFT_CS, TFT_DC, TFT_RST); // --- Variabel Global --- float beatAvg = 0; float gsrLevel = 0; String currentMood = "Unknown"; // --- Fungsi Mood --- String determineMood(float bpm, float gsr) { if (bpm == 0 || gsr == 0) return "Gagal"; String moodHR = (bpm <= 90) ? "Tenang" : (bpm <= 100 ? "Cemas" : "Tegang"); String moodGSR = (gsr <= 4.0) ? "Tenang" : (gsr <= 6.0 ? "Cemas" : "Tegang"); if (moodHR == "Tenang" && moodGSR == "Tenang") return "Tenang"; if (moodHR == "Cemas" && moodGSR == "Cemas") return "Cemas"; if (moodHR == "Tegang" || moodGSR == "Tegang") return "Tegang"; return "Cemas"; } // --- TFT Gambar Wajah --- void drawArc(int x, int y, int r1, int r2, int start_angle, int end_angle, uint16_t color) { for (int angle = start_angle; angle <= end_angle; angle++) { float rad = angle * PI / 180; int x1 = x + cos(rad) * r1; int y1 = y + sin(rad) * r1; int x2 = x + cos(rad) * r2; int y2 = y + sin(rad) * r2; tft.drawLine(x1, y1, x2, y2, color); } } void drawFace(String mood) { uint16_t faceColor = ST77XX_WHITE; if (mood == "Tenang") faceColor = ST77XX_YELLOW; else if (mood == "Cemas") faceColor = 0xFD20; else if (mood == "Tegang") faceColor = ST77XX_RED; tft.fillScreen(ST77XX_BLACK); tft.fillCircle(120, 90, 50, faceColor); // Mata if (mood == "Tegang") { tft.fillCircle(100, 75, 8, ST77XX_BLACK); tft.fillCircle(140, 75, 8, ST77XX_BLACK); tft.fillCircle(85, 60, 4, ST77XX_BLUE); tft.fillCircle(155, 60, 3, ST77XX_BLUE); } else { tft.fillCircle(100, 75, 5, ST77XX_BLACK); tft.fillCircle(140, 75, 5, ST77XX_BLACK); } // Mulut if (mood == "Tenang") { drawArc(120, 105, 18, 21, 45, 135, ST77XX_BLACK); } else if (mood == "Cemas") { tft.fillRect(105, 110, 30, 4, ST77XX_BLACK); } else if (mood == "Tegang") { drawArc(120, 120, 18, 21, 225, 315, ST77XX_BLACK); } tft.setTextColor(ST77XX_WHITE); tft.setTextSize(2); tft.setCursor(10, 160); tft.print("HR : "); tft.print(beatAvg, 1); tft.println(" BPM"); tft.setCursor(10, 185); tft.print("GSR : "); tft.print(gsrLevel, 2); tft.setCursor(10, 210); tft.print("Mood : "); tft.println(mood); } // --- SETUP --- void setup() { Serial.begin(115200); WiFi.begin(SSID, PASSWORD); Wire.begin(SDA_PIN, SCL_PIN); pinMode(GSR_PIN, INPUT); pinMode(BUZZER_PIN, OUTPUT); digitalWrite(BUZZER_PIN, LOW); // Awalnya mati if (!particleSensor.begin(Wire, I2C_SPEED_FAST)) { Serial.println("[ERROR] MAX30102 tidak terdeteksi!"); while (1); } particleSensor.setup(0x1F, 4, 2, 100, 411, 4096); SPI.begin(18, -1, 23); tft.init(240, 240); tft.setRotation(1); tft.fillScreen(ST77XX_BLACK); thing.add_wifi(SSID, PASSWORD); thing["mood"] >> [](pson &out) { out["bpm"] = beatAvg; out["gsr"] = gsrLevel; out["mood"] = currentMood; }; Serial.println("[READY] Silakan sentuhkan jari ke sensor."); } // --- LOOP --- void loop() { Serial.println("=== Mulai Pengukuran (10 detik) ==="); unsigned long startTime = millis(); unsigned long lastBeat = 0; float bpmSum = 0; int validBeats = 0; unsigned long gsrSum = 0; int gsrSamples = 0; while (millis() - startTime < 10000) { particleSensor.check(); long irValue = particleSensor.getIR(); int gsrValue = analogRead(GSR_PIN); gsrSum += gsrValue; gsrSamples++; if (checkForBeat(irValue)) { unsigned long now = millis(); if (lastBeat > 0) { float delta = (now - lastBeat) / 1000.0; float bpm = 60.0 / delta; if (bpm >= 40 && bpm <= 180) { bpmSum += bpm; validBeats++; Serial.print("[BEAT] BPM valid: "); Serial.println(bpm, 1); } } lastBeat = now; } delay(10); } // Hitung rata-rata beatAvg = (validBeats > 0) ? bpmSum / validBeats : 0; float avgGSR = gsrSum / gsrSamples; bool isFingerDetected = (avgGSR > 200); if (!isFingerDetected || beatAvg == 0) { beatAvg = 0; gsrLevel = 0; currentMood = "Gagal"; tft.fillScreen(ST77XX_BLACK); tft.setTextColor(ST77XX_WHITE); tft.setTextSize(2); tft.setCursor(20, 100); tft.println("Tempel jari Anda"); tft.setCursor(20, 130); tft.println("untuk mulai"); } else { gsrLevel = map(avgGSR, 300, 2500, 100, 700) / 100.0; gsrLevel = constrain(gsrLevel, 1.0, 7.0); currentMood = determineMood(beatAvg, gsrLevel); drawFace(currentMood); if (currentMood == "Tegang") { // Buzzer bunyi panjang (beepppppppppp) digitalWrite(BUZZER_PIN, HIGH); delay(2000); // 2 detik digitalWrite(BUZZER_PIN, LOW); } else if (currentMood == "Cemas") { // Buzzer bunyi pendek berulang (beep beep beep) for (int i = 0; i < 3; i++) { digitalWrite(BUZZER_PIN, HIGH); delay(300); // 0.3 detik bunyi digitalWrite(BUZZER_PIN, LOW); delay(300); // 0.3 detik jeda } } } // Kirim ke Thinger.io thing.write_bucket("Emotion_data", "mood", [](pson &data) { data["bpm"] = beatAvg; data["gsr"] = gsrLevel; data["mood"] = currentMood; }); thing.handle(); Serial.print("BPM: "); Serial.println(beatAvg, 1); Serial.print("GSR: "); Serial.println(gsrLevel, 2); Serial.print("Mood: "); Serial.println(currentMood); Serial.println("========================="); delay(1000); }