#include #include #include #include #include "soc/soc.h" #include "soc/rtc_cntl_reg.h" #define WIFI_SSID "Hidden Network" #define WIFI_PASSWORD "tanyapakkost" #define API_KEY "AIzaSyAHroHSc9Sp2lcZMf7SrCaV9cBjbjlxcfQ" #define DATABASE_URL "https://smartinfuse-49274-default-rtdb.firebaseio.com/" #define LEGACY_TOKEN "EQUbBn1dXMb2JQUW9UbMtpNKm2wP8VmcOi3tJDLu" #define ROOM_NAME "ruang_kamboja" #define DEVICE_ID "INF-ICU-127" #define SENSOR_PIN 34 #define SERVO_PIN 21 #define BUZZER_PIN 18 #define BUTTON_PIN 19 #define LED_PIN 22 #define FB_BASE "/realtime/rooms/" ROOM_NAME "/devices/" DEVICE_ID #define FB_CTRL FB_BASE "/controlling" #define FB_MON FB_BASE "/monitoring" #define SERVO_SWEEP_DELAY 12 // ==== KONFIGURASI KALIBRASI SENSOR TETES (BARU) ==== const int jumlahSampelBaca = 20; // averaging tiap pembacaan const int jumlahSampelKalibrasi = 100; // jumlah sampel saat fase kalibrasi awal const unsigned long debounceDelayTetes = 300; // ms, jarak minimum antar tetesan float baseline = 0; float noiseMax = 0; int threshold = 0; int stateSebelumnyaTetes = LOW; unsigned long waktuTerakhirTetes = 0; // ====================================================== FirebaseData fbdo; FirebaseAuth auth; FirebaseConfig config; Servo valveServo; volatile int dropCount = 0; volatile bool newDrop = false; unsigned long lastDropTime = 0; unsigned long windowStart = 0; unsigned long lastFbCheck = 0; unsigned long lastClickTime = 0; unsigned long buttonPressTime = 0; bool buzzerActive = false; bool servoOpen = false; int servoCurrentPos = 0; int clickCount = 0; bool fbReady() { return WiFi.status() == WL_CONNECTED && Firebase.ready(); } // ==== FUNGSI KALIBRASI & PEMBACAAN SENSOR TETES (BARU) ==== int bacaRataRata() { long total = 0; for (int i = 0; i < jumlahSampelBaca; i++) { total += analogRead(SENSOR_PIN); delay(1); } return total / jumlahSampelBaca; } void kalibrasiOtomatis() { Serial.println("=== KALIBRASI OTOMATIS DIMULAI ==="); Serial.println("Pastikan TIDAK ADA TETESAN selama proses ini..."); delay(2000); long total = 0; int nilaiMin = 4095; int nilaiMax = 0; for (int i = 0; i < jumlahSampelKalibrasi; i++) { int nilai = bacaRataRata(); total += nilai; if (nilai < nilaiMin) nilaiMin = nilai; if (nilai > nilaiMax) nilaiMax = nilai; delay(50); } baseline = (float)total / jumlahSampelKalibrasi; noiseMax = nilaiMax - baseline; threshold = baseline + (noiseMax * 3) + 20; Serial.println("=== KALIBRASI SELESAI ==="); Serial.print("Baseline: "); Serial.println(baseline); Serial.print("Noise range (min-max saat kalibrasi): "); Serial.print(nilaiMin); Serial.print(" - "); Serial.println(nilaiMax); Serial.print("Threshold terpakai: "); Serial.println(threshold); Serial.println("=== MULAI MODE DETEKSI TETESAN ==="); } // Dipanggil tiap loop, menggantikan mekanisme interrupt lama. // Mengisi dropCount & newDrop persis seperti sebelumnya, supaya // bagian kode lain (Firebase, servo, dll) tidak perlu diubah. void cekTetesan() { int nilai = bacaRataRata(); int stateSekarang = (nilai > threshold) ? HIGH : LOW; if (stateSekarang == HIGH && stateSebelumnyaTetes == LOW) { if (millis() - waktuTerakhirTetes > debounceDelayTetes) { dropCount++; newDrop = true; waktuTerakhirTetes = millis(); Serial.print("[TETES] ke-"); Serial.print(dropCount); Serial.print(" | Nilai: "); Serial.println(nilai); } } stateSebelumnyaTetes = stateSekarang; } // ============================================================ void setupFirebase() { config.api_key = API_KEY; config.database_url = DATABASE_URL; config.signer.tokens.legacy_token = LEGACY_TOKEN; Firebase.begin(&config, &auth); Firebase.reconnectWiFi(true); } void activateBuzzer() { if (buzzerActive) return; digitalWrite(BUZZER_PIN, HIGH); buzzerActive = true; } void stopBuzzer() { if (!buzzerActive) return; digitalWrite(BUZZER_PIN, LOW); buzzerActive = false; } void sweepServo(int from, int to) { Serial.printf("[SWEEP] %d → %d\n", from, to); int step = (to > from) ? 1 : -1; for (int pos = from; pos != to; pos += step) { valveServo.write(pos); delay(SERVO_SWEEP_DELAY); } valveServo.write(to); servoCurrentPos = to; delay(200); } void openValve() { if (servoOpen) return; Serial.println("[SERVO] Membuka katup → 140°"); sweepServo(servoCurrentPos, 140); servoOpen = true; Serial.println("[SERVO] Katup terbuka"); } void closeValve() { if (!servoOpen) return; Serial.println("[SERVO] Menutup katup → 0°"); sweepServo(servoCurrentPos, 0); servoOpen = false; Serial.println("[SERVO] Katup tertutup"); } void kirimAPI(int dpm) { if (!fbReady()) return; HTTPClient http; http.begin("https://smart-infus-service.vercel.app/api/kirimdata"); http.addHeader("Content-Type", "application/json"); String body = "{\"device_id\":\"" DEVICE_ID "\",\"drop_per_menit\":" + String(dpm) + "}"; int httpCode = http.POST(body); Serial.printf("[API] HTTP %d\n", httpCode); http.end(); } void setup() { WRITE_PERI_REG(RTC_CNTL_BROWN_OUT_REG, 0); Serial.begin(115200); delay(500); Serial.printf("[BOOT] Reset reason: %d\n", esp_reset_reason()); pinMode(SENSOR_PIN, INPUT); pinMode(BUZZER_PIN, OUTPUT); pinMode(BUTTON_PIN, INPUT_PULLUP); pinMode(LED_PIN, OUTPUT); digitalWrite(BUZZER_PIN, LOW); // NOTE: attachInterrupt lama untuk SENSOR_PIN dihapus, // karena deteksi tetesan sekarang berbasis analogRead + threshold // hasil kalibrasi otomatis (dipanggil lewat cekTetesan() di loop()). valveServo.attach(SERVO_PIN, 500, 2400); delay(200); valveServo.write(0); servoCurrentPos = 0; delay(600); servoOpen = false; Serial.println("[SERVO] Init selesai → posisi 0°"); Serial.print("Connecting WiFi"); WiFi.begin(WIFI_SSID, WIFI_PASSWORD); while (WiFi.status() != WL_CONNECTED) { delay(500); Serial.print("."); } Serial.printf("\nIP: %s\n", WiFi.localIP().toString().c_str()); setupFirebase(); Serial.print("Waiting Firebase"); while (!Firebase.ready()) { delay(300); Serial.print("."); } Serial.println("\nFirebase ready"); Firebase.RTDB.setBool(&fbdo, FB_CTRL "/buzzer_active", false); Firebase.RTDB.setBool(&fbdo, FB_CTRL "/servo_open", false); digitalWrite(LED_PIN, HIGH); windowStart = millis(); lastDropTime = millis(); lastFbCheck = millis(); // Kalibrasi otomatis sensor tetes (BARU) - pastikan tidak ada // tetesan/aliran saat proses ini berjalan. kalibrasiOtomatis(); Serial.println("READY"); } void loop() { // Polling sensor tetes (BARU) - menggantikan interrupt lama cekTetesan(); if (newDrop) { newDrop = false; lastDropTime = millis(); } if (millis() - windowStart >= 60000UL) { portDISABLE_INTERRUPTS(); int dpm = dropCount; dropCount = 0; portENABLE_INTERRUPTS(); windowStart = millis(); Serial.printf("[MONITOR] drop/menit = %d\n", dpm); if (fbReady()) { Firebase.RTDB.setInt(&fbdo, FB_MON "/drop_rate", dpm); kirimAPI(dpm); if (dpm == 0) { Firebase.RTDB.setBool(&fbdo, FB_CTRL "/servo_open", false); Firebase.RTDB.setBool(&fbdo, FB_CTRL "/buzzer_active", true); } } } if (millis() - lastFbCheck >= 2000UL) { lastFbCheck = millis(); if (fbReady()) { if (Firebase.RTDB.getBool(&fbdo, FB_CTRL "/servo_open")) { bool fb = fbdo.boolData(); Serial.printf("[FB] servo_open = %s | state = %s\n", fb ? "true" : "false", servoOpen ? "OPEN" : "CLOSED"); if (fb && !servoOpen) openValve(); if (!fb && servoOpen) closeValve(); } if (Firebase.RTDB.getBool(&fbdo, FB_CTRL "/buzzer_active")) { bool fb = fbdo.boolData(); if (fb && !buzzerActive) activateBuzzer(); if (!fb && buzzerActive) stopBuzzer(); } } } if (digitalRead(BUTTON_PIN) == LOW) { buttonPressTime = millis(); delay(50); while (digitalRead(BUTTON_PIN) == LOW); unsigned long dur = millis() - buttonPressTime; if (dur > 2000) { Serial.println("[BTN] Hold → matiin buzzer"); if (fbReady()) Firebase.RTDB.setBool(&fbdo, FB_CTRL "/buzzer_active", false); clickCount = 0; } else { if (clickCount < 2) { clickCount++; lastClickTime = millis(); Serial.printf("[BTN] Klik ke-%d\n", clickCount); } } } if (clickCount > 0 && millis() - lastClickTime > 500UL) { if (fbReady()) { if (clickCount == 1) { Serial.println("[BTN] 1 klik → buka servo + matiin buzzer"); Firebase.RTDB.setBool(&fbdo, FB_CTRL "/servo_open", true); Firebase.RTDB.setBool(&fbdo, FB_CTRL "/buzzer_active", false); // ← PERUBAHAN } if (clickCount == 2) { Serial.println("[BTN] 2 klik → tutup servo"); Firebase.RTDB.setBool(&fbdo, FB_CTRL "/servo_open", false); } } clickCount = 0; } delay(10); }