320 lines
9.0 KiB
Plaintext
320 lines
9.0 KiB
Plaintext
#include <WiFi.h>
|
|
#include <Firebase_ESP_Client.h>
|
|
#include <HTTPClient.h>
|
|
#include <ESP32Servo.h>
|
|
#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);
|
|
} |