From ebca91b9a8bd43edadf7bb52524b1a3c2e9d9c95 Mon Sep 17 00:00:00 2001 From: MohammadFerryAlfansyah Date: Tue, 4 Aug 2026 00:57:05 +0700 Subject: [PATCH] Add arduino --- arduino | 320 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 320 insertions(+) create mode 100644 arduino diff --git a/arduino b/arduino new file mode 100644 index 0000000..a523e33 --- /dev/null +++ b/arduino @@ -0,0 +1,320 @@ +#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); +} \ No newline at end of file