#include #include #include #include #include #include #include // === WiFi === #define WIFI_SSID "iPhone pasa" #define WIFI_PASSWORD "caca2004" // === Firebase === #define API_KEY "AIzaSyAkcK-nMUY8AcQRpOyrb0xACIgq9Er31Fk" #define DATABASE_URL "https://petfeeder-e9908-default-rtdb.asia-southeast1.firebasedatabase.app" #define USER_EMAIL "tkk@gmail.com" #define USER_PASSWORD "123456" FirebaseData fbdo; FirebaseAuth auth; FirebaseConfig config; // === Servo === Servo myservo; const int servoPin = 18; // === RTC === RTC_DS3231 rtc; // === NTP === WiFiUDP ntpUDP; NTPClient timeClient(ntpUDP, "pool.ntp.org", 25200, 60000); // UTC+7 // === Manual Feed State === bool manualFeedingInProgress = false; unsigned long lastScheduleCheck = 0; const unsigned long scheduleInterval = 5000; // === Fungsi WiFi === void connectWiFi() { WiFi.begin(WIFI_SSID, WIFI_PASSWORD); Serial.print("WiFi connecting"); while (WiFi.status() != WL_CONNECTED) { delay(500); Serial.print("."); } Serial.println("\nāœ… WiFi connected"); } // === Fungsi log riwayat === void logFeedingHistory(String type, int duration) { DateTime now = rtc.now(); String timeString = String(now.day()) + "-" + String(now.month()) + "-" + String(now.year()) + " " + String(now.hour()) + ":" + String(now.minute()); FirebaseJson log; log.set("type", type); log.set("duration", duration); log.set("timestamp", now.unixtime()); log.set("time", timeString); String path = "/feed_history/" + String(now.unixtime()); if (Firebase.RTDB.setJSON(&fbdo, path.c_str(), &log)) { Serial.println("šŸ“ Riwayat pakan disimpan."); } else { Serial.printf("āŒ Gagal simpan riwayat: %s\n", fbdo.errorReason().c_str()); } } // === Fungsi feedNow === void feedNow(int duration, String type = "manual") { Serial.printf("šŸ– Servo ON selama %d detik (%s)\n", duration, type.c_str()); myservo.write(180); delay(duration * 1000); myservo.write(0); Serial.println("šŸ– Servo OFF"); logFeedingHistory(type, duration); } // === Cek manual feed === void checkManualFeed() { if (manualFeedingInProgress) return; if (Firebase.RTDB.getJSON(&fbdo, "/manual_feeder")) { FirebaseJson &json = fbdo.jsonObject(); FirebaseJsonData result; int duration = 0; bool active = false; if (json.get(result, "duration") && result.type == "int") { duration = result.intValue; } if (json.get(result, "active") && result.type == "boolean") { active = result.boolValue; } if (active && duration > 0) { manualFeedingInProgress = true; feedNow(duration, "manual"); FirebaseJson update; update.set("active", false); Firebase.RTDB.updateNode(&fbdo, "/manual_feeder", &update); manualFeedingInProgress = false; } } } // === Cek jadwal === void checkScheduleFeed() { if (millis() - lastScheduleCheck < scheduleInterval) return; lastScheduleCheck = millis(); DateTime now = rtc.now(); int currentHour = now.hour(); int currentMinute = now.minute(); if (Firebase.RTDB.getJSON(&fbdo, "/schedules")) { FirebaseJson &schedules = fbdo.jsonObject(); FirebaseJsonData result; size_t count = schedules.iteratorBegin(); static String lastFeedKey = ""; static int lastFeedMinute = -1; for (size_t i = 0; i < count; i++) { String key, value; int type; schedules.iteratorGet(i, type, key, value); FirebaseJson child; schedules.get(result, key); child.setJsonData(result.stringValue); int hour = -1, minute = -1, duration = 0; bool enabled = false; if (child.get(result, "hour") && result.type == "int") hour = result.intValue; if (child.get(result, "minute") && result.type == "int") minute = result.intValue; if (child.get(result, "duration") && result.type == "int") duration = result.intValue; if (child.get(result, "enabled") && result.type == "boolean") enabled = result.boolValue; if (enabled && hour == currentHour && minute == currentMinute) { if (key != lastFeedKey || currentMinute != lastFeedMinute) { feedNow(duration, "schedule"); lastFeedKey = key; lastFeedMinute = currentMinute; } } } schedules.iteratorEnd(); } } // === Setup === void setup() { Serial.begin(115200); Wire.begin(); connectWiFi(); myservo.attach(servoPin); myservo.write(0); if (!rtc.begin()) { Serial.println("RTC tidak ditemukan!"); while (1); } // Sync waktu dari NTP ke RTC timeClient.begin(); timeClient.update(); rtc.adjust(DateTime(timeClient.getEpochTime())); config.api_key = API_KEY; config.database_url = DATABASE_URL; auth.user.email = USER_EMAIL; auth.user.password = USER_PASSWORD; Firebase.begin(&config, &auth); Firebase.reconnectWiFi(true); } // === Loop utama === void loop() { checkManualFeed(); checkScheduleFeed(); }