#include "HX711.h" #include #include #include #include #include "time.h" // ====================================================== // WIFI // ====================================================== const char* ssid = "Rio"; const char* password = "Rio1234567"; // ====================================================== // FIREBASE // ====================================================== #define API_KEY "AIzaSyC-YYniuYaVJAZl0LywCbC-HKvMED0_uUY" #define DATABASE_URL "https://poultryrail-1205e-default-rtdb.firebaseio.com/" #define USER_EMAIL "esp32@gmail.com" #define USER_PASSWORD "12345678" #define FIREBASE_PROJECT_ID "poultryrail-1205e" FirebaseData fbdo; FirebaseAuth auth; FirebaseConfig config; // ====================================================== // RTC // ====================================================== RTC_DS3231 rtc; // ====================================================== // STEPPER A4988 // ====================================================== #define STEP_PIN 4 #define DIR_PIN 2 #define EN_PIN 5 long totalSteps = 3900; long posisiStepper = 0; // ====================================================== // HX711 // ====================================================== #define DT1 25 #define SCK1 26 #define DT2 32 #define SCK2 33 HX711 scale1; HX711 scale2; float calibration_factor1 = 422; float calibration_factor2 = 399; // ====================================================== // TIMER // ====================================================== unsigned long lastRTC = 0; unsigned long lastRead = 0; unsigned long lastJadwal = 0; unsigned long lastHeartbeat = 0; unsigned long lastManual = 0; void updateHeartbeat() { if (millis() - lastHeartbeat >= 5000) { lastHeartbeat = millis(); Firebase.RTDB.setInt( &fbdo, "/status/heartbeat", millis() ); } } String lastTrigger = ""; bool wifiTerhubungSebelumnya = true; void kirimNotifikasi(String pesan) { Firebase.RTDB.setString( &fbdo, "/notifikasi/pesan", pesan ); Firebase.RTDB.setString( &fbdo, "/notifikasi/status", "baru" ); } void simpanRiwayat( String waktu, String jam, String tanggal, String status ); void simpanRiwayatFirestore( String waktu, String jam, String tanggal, String status, float beratKanan, float beratKiri ); // ====================================================== // STEPPER // ====================================================== void stepMotor(long jumlahStep, int speedDelay, bool maju) { for (long i = 0; i < jumlahStep; i++) { if (i % 500 == 0) { updateHeartbeat(); } digitalWrite(STEP_PIN, HIGH); delayMicroseconds(speedDelay); digitalWrite(STEP_PIN, LOW); delayMicroseconds(speedDelay); } // simpan posisi stepper if (maju) { posisiStepper += jumlahStep; } else { posisiStepper -= jumlahStep; if (posisiStepper < 0) { posisiStepper = 0; } } } // ====================================================== // BACA BERAT // ====================================================== float bacaBerat1() { float b1 = scale1.get_units(3); if (abs(b1) < 10) b1 = 0; return round(b1 * 10) / 10.0; } float bacaBerat2() { float b2 = scale2.get_units(3); if (abs(b2) < 10) b2 = 0; return round(b2 * 10) / 10.0; } // BACA JARAK DARI FIREBASE float bacaJarakFirebase() { if (Firebase.RTDB.getFloat( &fbdo, "/sensor/jarak")) { return fbdo.floatData(); } return 999; } // ====================================================== // HENTAKAN PAKAN // ====================================================== void hentakanPakan() { // maju sedikit digitalWrite(DIR_PIN, HIGH); stepMotor(50, 800, true); delay(50); // mundur sedikit digitalWrite(DIR_PIN, LOW); stepMotor(50, 800, false); delay(50); // balik maju lagi digitalWrite(DIR_PIN, HIGH); } void prosesPemberianPakan( String key, String waktu, String jam, String tanggal ) { float jarakAwal = bacaJarakFirebase(); Serial.print("Jarak Awal: "); Serial.println(jarakAwal); // stok kurang dari 15% if (jarakAwal >= 13) { Serial.println("STOK PAKAN KURANG"); Serial.println("JADWAL DIBATALKAN"); kirimNotifikasi( "Pemberian pakan gagal karena stok habis" ); Firebase.RTDB.setString( &fbdo, "/jadwal/" + key + "/status", "Pemberian Pakan gagal dijalankan karena stok habis" ); simpanRiwayatFirestore( waktu, jam, tanggal, "Pemberian Pakan gagal dijalankan karena stok habis", bacaBerat1(), // kanan bacaBerat2() // kiri ); Firebase.RTDB.deleteNode( &fbdo, "/jadwal/" + key ); return; } float sisaKanan = bacaBerat1(); float sisaKiri = bacaBerat2(); bool isiKanan = (sisaKanan <= 30); bool isiKiri = (sisaKiri <= 30); // jika keduanya masih banyak if (!isiKanan && !isiKiri) { String pesan = "Pemberian pakan gagal karena kedua wadah masih diatas 30 gram"; Serial.println(pesan); if (key != "manual") { Firebase.RTDB.setString( &fbdo, "/jadwal/" + key + "/status", pesan ); Firebase.RTDB.deleteNode( &fbdo, "/jadwal/" + key ); } simpanRiwayatFirestore( waktu, jam, tanggal, pesan, sisaKanan, sisaKiri ); return; } Firebase.RTDB.setString( &fbdo, "/jadwal/" + key + "/status", "running" ); // ====================================================== // PAGI / SORE // ====================================================== int jumlahTitik; long langkahPerTitik; if (waktu == "Pagi") { jumlahTitik = 3; langkahPerTitik = 1250; } else { jumlahTitik = 3; langkahPerTitik = 1250; } // ====================================================== // TARGET // ====================================================== float maxKeluar1 = 5.0; float maxKeluar2 = 5.0; // ====================================================== // DRIVER ON // ====================================================== digitalWrite(EN_PIN, LOW); // ====================================================== // ARAH MAJU // ====================================================== digitalWrite(DIR_PIN, HIGH); // ====================================================== // LOOP TITIK // ====================================================== for (int titik = 1; titik <= jumlahTitik; titik++) { updateHeartbeat(); // CEK STOK SAAT BERJALAN float jarakSekarang = bacaJarakFirebase(); Serial.print("Jarak Sekarang: "); Serial.println(jarakSekarang); // stok habis // ========================================== // CEK ULANG STOK // ========================================== int deteksiHabis = 0; for (int i = 0; i < 3; i++) { float cekUlang = bacaJarakFirebase(); Serial.print("Cek Ulang "); Serial.print(i); Serial.print(" : "); Serial.println(cekUlang); if (cekUlang >= 13) { deteksiHabis++; } delay(50); } // ========================================== // JIKA 3X TERDETEK HABIS // ========================================== if (deteksiHabis >= 3) { Serial.println("PAKAN HABIS"); Serial.println("KEMBALI KE AWAL"); kirimNotifikasi( "Pakan habis saat proses pemberian berjalan" ); Firebase.RTDB.setString( &fbdo, "/servo1/action", "close" ); Firebase.RTDB.setString( &fbdo, "/servo2/action", "close" ); delay(300); digitalWrite(DIR_PIN, LOW); stepMotor(posisiStepper, 800, false); digitalWrite(EN_PIN, HIGH); Firebase.RTDB.setString( &fbdo, "/jadwal/" + key + "/status", "Pemberian pakan belum tercapai stok habis dijalan" ); simpanRiwayatFirestore( waktu, jam, tanggal, "Pemberian pakan belum tercapai stok habis dijalan", bacaBerat1(), // kanan bacaBerat2() // kiri ); Firebase.RTDB.deleteNode( &fbdo, "/jadwal/" + key ); return; } Serial.print("TITIK KE : "); Serial.println(titik); // ========================================== // STEPPER MAJU // ========================================== stepMotor(langkahPerTitik, 700, true); delay(300); // ========================================== // BERAT AWAL // ========================================== float awal1 = bacaBerat1(); float awal2 = bacaBerat2(); // ========================================== // BUKA SERVO // ========================================== bool close1 = !isiKanan; bool close2 = !isiKiri; if (isiKanan) { Firebase.RTDB.setString( &fbdo, "/servo2/action", "open" ); Serial.println("SERVO1 OPEN"); } if (isiKiri) { Firebase.RTDB.setString( &fbdo, "/servo1/action", "open" ); Serial.println("SERVO2 OPEN"); } // ========================================== // LOOP PAKAN // ========================================== while (!close1 || !close2) { updateHeartbeat(); hentakanPakan(); float sekarang1 = bacaBerat1(); float sekarang2 = bacaBerat2(); float keluar1 = sekarang1 - awal1; float keluar2 = sekarang2 - awal2; if (keluar1 < 0) keluar1 = 0; if (keluar2 < 0) keluar2 = 0; Firebase.RTDB.setFloat( &fbdo, "/sensor/keluar1", keluar1 ); Firebase.RTDB.setFloat( &fbdo, "/sensor/keluar2", keluar2 ); Serial.print("Keluar1: "); Serial.print(keluar1); Serial.print(" | Keluar2: "); Serial.println(keluar2); // ========================================== // SERVO1 CLOSE // ========================================== if (isiKanan && !close1 && keluar1 >= maxKeluar1) { Firebase.RTDB.setString( &fbdo, "/servo2/action", "close" ); close1 = true; Serial.println("SERVO2 CLOSE"); } // ========================================== // SERVO2 CLOSE // ========================================== if (isiKiri && !close2 && keluar2 >= maxKeluar2) { Firebase.RTDB.setString( &fbdo, "/servo1/action", "close" ); close2 = true; Serial.println("SERVO1 CLOSE"); } delay(10); } Serial.println("SEMUA SERVO TUTUP"); delay(300); } // ====================================================== // PAKSA TUTUP // ====================================================== Firebase.RTDB.setString( &fbdo, "/servo1/action", "close" ); Firebase.RTDB.setString( &fbdo, "/servo2/action", "close" ); delay(500); // ====================================================== // BALIK AWAL // ====================================================== digitalWrite(DIR_PIN, LOW); stepMotor(posisiStepper, 800, false); // ====================================================== // DRIVER OFF // ====================================================== digitalWrite(EN_PIN, HIGH); // ====================================================== // DONE // ====================================================== if (key != "manual") { Firebase.RTDB.setString( &fbdo, "/jadwal/" + key + "/status", "done" ); Firebase.RTDB.deleteNode( &fbdo, "/jadwal/" + key ); } simpanRiwayatFirestore( waktu, jam, tanggal, "done", bacaBerat1(), // kanan bacaBerat2() // kiri ); Firebase.RTDB.deleteNode( &fbdo, "/jadwal/" + key ); kirimNotifikasi( "Pemberian pakan selesai" ); Serial.println("SELESAI"); } // ====================================================== // CEK JADWAL // ====================================================== void cekJadwal() { if (!Firebase.ready()) return; if (!Firebase.RTDB.getJSON(&fbdo, "/jadwal")) { return; } FirebaseJson &json = fbdo.jsonObject(); size_t len = json.iteratorBegin(); if (len == 0) { json.iteratorEnd(); return; } DateTime now = rtc.now(); char jamNow[6]; sprintf(jamNow, "%02d:%02d", now.hour(), now.minute()); String tglFix = String(now.day()) + "-" + String(now.month()) + "-" + String(now.year()); // ====================================================== // LOOP JADWAL // ====================================================== for (size_t i = 0; i < len; i++) { String key, value; int type; json.iteratorGet(i, type, key, value); FirebaseJsonData data; json.get(data, key); FirebaseJson obj; obj.setJsonData(data.stringValue); FirebaseJsonData jam, tanggal, status, waktu; obj.get(jam, "jam"); obj.get(tanggal, "tanggal"); obj.get(status, "status"); obj.get(waktu, "waktu"); bool matchJam = (jam.stringValue.substring(0,5) == String(jamNow)); bool matchTanggal = (tanggal.stringValue == tglFix); bool statusIdle = (status.stringValue == "idle" || status.stringValue == ""); String id = key + String(jamNow); // ====================================================== // JALANKAN // ====================================================== if (matchJam && matchTanggal && statusIdle && id != lastTrigger) { lastTrigger = id; Serial.println("JADWAL JALAN"); Firebase.RTDB.setString( &fbdo, "/jadwal/" + key + "/status", "running" ); prosesPemberianPakan( key, waktu.stringValue, jam.stringValue, tanggal.stringValue ); } } json.iteratorEnd(); } void simpanRiwayatFirestore( String waktu, String jam, String tanggal, String status, float beratKanan, float beratKiri ) { FirebaseJson content; content.set("fields/waktu/stringValue", waktu); content.set("fields/jam/stringValue", jam); content.set("fields/tanggal/stringValue", tanggal); content.set("fields/status/stringValue", status); content.set("fields/beratKanan/doubleValue", beratKanan); content.set("fields/beratKiri/doubleValue", beratKiri); content.set( "fields/timestamp/integerValue", String(time(nullptr)) ); String documentPath = "riwayat/" + String(millis()); Firebase.Firestore.createDocument( &fbdo, FIREBASE_PROJECT_ID, "", documentPath.c_str(), content.raw()); } void cekManual() { if (Firebase.RTDB.getString(&fbdo, "/manual/action")) { String cmd = fbdo.stringData(); if (cmd == "start") { Firebase.RTDB.setString( &fbdo, "/manual/action", "running" ); DateTime now = rtc.now(); char jamManual[6]; sprintf( jamManual, "%02d:%02d", now.hour(), now.minute() ); String tanggalManual = String(now.day()) + "-" + String(now.month()) + "-" + String(now.year()); prosesPemberianPakan( "manual", "Manual", String(jamManual), tanggalManual ); Firebase.RTDB.setString( &fbdo, "/manual/action", "done" ); } } } // ====================================================== // SETUP // ====================================================== void setup() { Serial.begin(115200); // ====================================================== // STEPPER // ====================================================== pinMode(STEP_PIN, OUTPUT); pinMode(DIR_PIN, OUTPUT); pinMode(EN_PIN, OUTPUT); digitalWrite(EN_PIN, HIGH); // ====================================================== // HX711 // ====================================================== scale1.begin(DT1, SCK1); scale2.begin(DT2, SCK2); delay(1000); scale1.set_scale(calibration_factor1); scale2.set_scale(calibration_factor2); scale1.tare(); scale2.tare(); // ====================================================== // RTC // ====================================================== Wire.begin(21, 22); if (!rtc.begin()) { Serial.println("RTC ERROR"); kirimNotifikasi( "RTC DS3231 tidak terdeteksi" ); while (1); } // ====================================================== // WIFI // ====================================================== WiFi.begin(ssid, password); Serial.print("CONNECT WIFI"); while (WiFi.status() != WL_CONNECTED) { delay(500); Serial.print("."); } Serial.println(""); Serial.println("WIFI CONNECT"); // ====================================================== // WAKTU INTERNET // ====================================================== configTime(7 * 3600, 0, "pool.ntp.org", "time.nist.gov"); struct tm timeinfo; if (getLocalTime(&timeinfo)) { rtc.adjust(DateTime( timeinfo.tm_year + 1900, timeinfo.tm_mon + 1, timeinfo.tm_mday, timeinfo.tm_hour, timeinfo.tm_min, timeinfo.tm_sec )); } // ====================================================== // FIREBASE // ====================================================== 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); Serial.println("READY"); } // ====================================================== // LOOP // ====================================================== void loop() { // ======================================= // CEK WIFI // ======================================= if (WiFi.status() != WL_CONNECTED) { if (wifiTerhubungSebelumnya) { Serial.println("WIFI PUTUS"); wifiTerhubungSebelumnya = false; } WiFi.reconnect(); delay(1000); return; } else { // WiFi baru tersambung lagi if (!wifiTerhubungSebelumnya) { wifiTerhubungSebelumnya = true; kirimNotifikasi( "Koneksi WiFi kembali normal" ); } } // ======================================= // CEK FIREBASE // ======================================= if (!Firebase.ready()) { kirimNotifikasi( "Koneksi ke Firebase terputus" ); return; } // ======================================= // HEARTBEAT // ======================================= if (millis() - lastHeartbeat > 5000) { lastHeartbeat = millis(); Firebase.RTDB.setInt( &fbdo, "/status/heartbeat", millis() ); } // ====================================================== // RTC // ====================================================== if (millis() - lastRTC > 5000) { Firebase.RTDB.setInt( &fbdo, "/status/heartbeat", millis() ); lastRTC = millis(); DateTime now = rtc.now(); char waktu[9]; sprintf(waktu, "%02d:%02d:%02d", now.hour(), now.minute(), now.second()); Firebase.RTDB.setString( &fbdo, "/rtc/time", waktu ); } // ====================================================== // CEK JADWAL // ====================================================== if (millis() - lastJadwal > 5000) { lastJadwal = millis(); cekJadwal(); } if (millis() - lastManual > 1000) { lastManual = millis(); cekManual(); } // ====================================================== // BERAT REALTIME // ====================================================== if (millis() - lastRead > 1000) { lastRead = millis(); float b1 = bacaBerat1(); float b2 = bacaBerat2(); Firebase.RTDB.setFloat( &fbdo, "/sensor/berat1", b1 ); Firebase.RTDB.setFloat( &fbdo, "/sensor/berat2", b2 ); Serial.print("B1: "); Serial.print(b1); Serial.print(" | B2: "); Serial.println(b2); } }