#include #include #include #include #include // WiFi credentials const char* ssid = "KOPI"; const char* password = "digoreng123"; // Firebase configuration const String firebaseHost = "https://my-fish-7db48-default-rtdb.firebaseio.com/"; const String apiKey = "AIzaSyCGMJfu0R3LmJp6IIxoTyspgJpC-0slcCE"; // Pakan sensor & actuator #define TRIG_PAKAN 12 #define ECHO_PAKAN 13 #define SERVO_PIN 15 #define BUTTON_PIN 4 #define LED_PIN 2 #define POMPA_RELAY 19 #define PEMBERSIH_RELAY 18 Servo servoPakan; // Flags bool kontrolPakan = false; bool kontrolPembersih = false; bool statusPembersihManual = false; bool pompaOverride = false; // Timing unsigned long lastSensorSend = 0; unsigned long intervalSensor = 60000; unsigned long lastCleaner = 0; unsigned long intervalPembersih = 60 * 60000; unsigned long lastJadwalUpdate = 0; unsigned long lastBlink = 0; bool ledState = false; unsigned long waktuPembersihBerikutnya = 0; int intervalSetelahPakan = 3 * 60 * 1000; // Settings const float tinggiPakanMax = 7.0; // cm // Jadwal pakan #define MAX_JADWAL 10 struct JadwalPakan { int jam; int menit; int takar; }; JadwalPakan jadwalPakan[MAX_JADWAL]; int jumlahJadwal = 0; int lastFeedingDay[MAX_JADWAL]; void setup() { Serial.begin(115200); pinMode(TRIG_PAKAN, OUTPUT); pinMode(ECHO_PAKAN, INPUT); pinMode(BUTTON_PIN, INPUT_PULLUP); pinMode(LED_PIN, OUTPUT); pinMode(POMPA_RELAY, OUTPUT); pinMode(PEMBERSIH_RELAY, OUTPUT); digitalWrite(POMPA_RELAY, HIGH); digitalWrite(PEMBERSIH_RELAY, HIGH); servoPakan.attach(SERVO_PIN); connectToWiFi(); setupNTP(); ambilJadwalPakanFirebase(); ambilSettingFirebase(); } void loop() { if (WiFi.status() != WL_CONNECTED) { if (millis() - lastBlink >= 500) { ledState = !ledState; digitalWrite(LED_PIN, ledState); lastBlink = millis(); } } else { digitalWrite(LED_PIN, HIGH); } ambilSettingFirebase(); ambilKontrolFirebase(); cekDanEksekusiJadwal(); if (millis() - lastSensorSend >= intervalSensor) { float jarak = bacaUltrasonik(TRIG_PAKAN, ECHO_PAKAN); if (jarak != -1) { float tinggiPakan = hitungTinggiPakan(jarak); Serial.printf("Tinggi Pakan: %.2f cm\n", tinggiPakan); kirimKeFirebase(tinggiPakan); } else { Serial.println("Gagal membaca sensor ultrasonik."); } lastSensorSend = millis(); } if (millis() - lastCleaner >= intervalPembersih) { Serial.println("Pembersih otomatis aktif..."); pompaOverride = true; digitalWrite(PEMBERSIH_RELAY, LOW); digitalWrite(POMPA_RELAY, LOW); delay(15000); digitalWrite(PEMBERSIH_RELAY, HIGH); digitalWrite(POMPA_RELAY, HIGH); pompaOverride = false; lastCleaner = millis(); } if (millis() >= waktuPembersihBerikutnya && waktuPembersihBerikutnya > 0) { Serial.println("Pembersih otomatis setelah pakan aktif..."); pompaOverride = true; digitalWrite(PEMBERSIH_RELAY, LOW); digitalWrite(POMPA_RELAY, LOW); delay(15000); digitalWrite(PEMBERSIH_RELAY, HIGH); digitalWrite(POMPA_RELAY, HIGH); pompaOverride = false; waktuPembersihBerikutnya = 0; } static bool lastFirebasePembersih = false; if (kontrolPembersih != lastFirebasePembersih) { lastFirebasePembersih = kontrolPembersih; digitalWrite(PEMBERSIH_RELAY, kontrolPembersih ? LOW : HIGH); digitalWrite(POMPA_RELAY, kontrolPembersih ? LOW : HIGH); pompaOverride = kontrolPembersih; Serial.println(kontrolPembersih ? "Pembersih manual ON" : "OFF"); } if (kontrolPakan) { tuangPakan(5); delay(1000); } static bool lastButtonState = HIGH; bool currentButtonState = digitalRead(BUTTON_PIN); if (lastButtonState == HIGH && currentButtonState == LOW) { statusPembersihManual = !statusPembersihManual; digitalWrite(PEMBERSIH_RELAY, statusPembersihManual ? LOW : HIGH); digitalWrite(POMPA_RELAY, statusPembersihManual ? LOW : HIGH); pompaOverride = statusPembersihManual; Serial.println(statusPembersihManual ? "Tombol Pembersih: ON" : "OFF"); } lastButtonState = currentButtonState; if (millis() - lastJadwalUpdate > 100000) { ambilJadwalPakanFirebase(); lastJadwalUpdate = millis(); } delay(100); } // ======================== FUNGSI ============================= void connectToWiFi() { WiFi.begin(ssid, password); while (WiFi.status() != WL_CONNECTED) { delay(500); Serial.print("."); } Serial.println("\nWiFi Terhubung"); } void setupNTP() { configTime(7 * 3600, 0, "pool.ntp.org", "time.nist.gov"); } float bacaUltrasonik(int trigPin, int echoPin) { digitalWrite(trigPin, LOW); delayMicroseconds(2); digitalWrite(trigPin, HIGH); delayMicroseconds(10); digitalWrite(trigPin, LOW); long duration = pulseIn(echoPin, HIGH, 30000); if (duration == 0) return -1; // Tidak terbaca return duration * 0.034 / 2; } float hitungTinggiPakan(float jarak) { if (jarak <= 0.0 || jarak > tinggiPakanMax) return 0.0; return tinggiPakanMax - jarak; } void kirimKeFirebase(float tinggiPakan) { if (WiFi.status() == WL_CONNECTED) { HTTPClient http; String url = firebaseHost + "sensor_data.json?auth=" + apiKey; String payload = "{\"tinggi_pakan\":" + String(tinggiPakan, 2) + "}"; http.begin(url); http.addHeader("Content-Type", "application/json"); http.PUT(payload); http.end(); } } void ambilKontrolFirebase() { if (WiFi.status() == WL_CONNECTED) { HTTPClient http; String url = firebaseHost + "kontrol_alat.json?auth=" + apiKey; http.begin(url); int httpCode = http.GET(); if (httpCode == HTTP_CODE_OK) { String response = http.getString(); DynamicJsonDocument doc(512); deserializeJson(doc, response); kontrolPakan = doc["pakan"]; kontrolPembersih = doc["pembersih"]; } http.end(); } } void ambilSettingFirebase() { if (WiFi.status() == WL_CONNECTED) { HTTPClient http; String url = firebaseHost + "setting.json?auth=" + apiKey; http.begin(url); int httpCode = http.GET(); if (httpCode == HTTP_CODE_OK) { String response = http.getString(); DynamicJsonDocument doc(512); deserializeJson(doc, response); intervalSensor = doc["interval_data"] | 60000; intervalPembersih = (doc["interval_pembersih"] | 60) * 60000; } http.end(); } } void ambilJadwalPakanFirebase() { if (WiFi.status() == WL_CONNECTED) { HTTPClient http; String url = firebaseHost + "jadwal_pakan.json?auth=" + apiKey; http.begin(url); int httpCode = http.GET(); if (httpCode == HTTP_CODE_OK) { String response = http.getString(); DynamicJsonDocument doc(2048); if (!deserializeJson(doc, response)) { jumlahJadwal = 0; struct tm timeinfo; getLocalTime(&timeinfo); int nowJam = timeinfo.tm_hour; int nowMenit = timeinfo.tm_min; int today = timeinfo.tm_yday; for (JsonPair kv : doc.as()) { if (jumlahJadwal < MAX_JADWAL) { String jamStr = kv.value()["jam"].as(); int takar = kv.value()["takar"].as(); int jam = 0, menit = 0; sscanf(jamStr.c_str(), "%d:%d", &jam, &menit); jadwalPakan[jumlahJadwal] = {jam, menit, takar}; lastFeedingDay[jumlahJadwal] = (jam < nowJam || (jam == nowJam && menit < nowMenit)) ? today : -1; jumlahJadwal++; } } for (JsonPair kv : doc.as()) { if (kv.value().containsKey("interval_set")) { intervalSetelahPakan = kv.value()["interval_set"].as() * 60 * 1000; break; } } } } http.end(); } } void cekDanEksekusiJadwal() { struct tm timeinfo; if (!getLocalTime(&timeinfo)) return; int nowJam = timeinfo.tm_hour; int nowMenit = timeinfo.tm_min; int today = timeinfo.tm_yday; for (int i = 0; i < jumlahJadwal; i++) { if (jadwalPakan[i].jam == nowJam && jadwalPakan[i].menit == nowMenit) { if (lastFeedingDay[i] != today) { Serial.printf("Tuang pakan (jadwal ke-%d): %02d:%02d, takar %d\n", i + 1, nowJam, nowMenit, jadwalPakan[i].takar); tuangPakan(jadwalPakan[i].takar); lastFeedingDay[i] = today; waktuPembersihBerikutnya = millis() + intervalSetelahPakan; delay(1000); } } } } void tuangPakan(int takar) { for (int i = 0; i < takar; i++) { servoPakan.write(0); delay(500); servoPakan.write(90); delay(500); } kontrolPakan = false; }