#include #include #include #include #include #include #include "soc/soc.h" #include "soc/rtc_cntl_reg.h" // ================= FIREBASE ================= #define API_KEY "AIzaSyDOt0tippHwz6JaPG9Zo147ATJ9USV5bKs" #define DATABASE_URL "https://tabungankuy-30a9d-default-rtdb.firebaseio.com/" #define LEGACY_TOKEN "FTqzKI4HW32paLnR9pQXB72YoUHN1NXEjZMfjO4f" // ================= TCS3200 ================= #define S0 14 #define S1 27 #define S2 26 #define S3 25 #define SENSOR_OUT 33 // ================= HARDWARE ================= #define SERVO_MASUK_PIN 4 #define SERVO_TURUN_PIN 5 #define SERVO_BUKA_PIN 15 // ================= OBJECT ================= LiquidCrystal_I2C lcd(0x27, 16, 2); FirebaseData fbdo; FirebaseAuth auth; FirebaseConfig config; Servo servoMasuk; Servo servoTurun; Servo servoBuka; // ================= STATE ================= int lastDetectedVal = 0; bool lastKonfirmasi = false; bool lastBukaTabungan = false; bool lastServoMasuk = false; bool servoAttached = false; // ===================================================== // LCD // ===================================================== void lcdDefault() { lcd.clear(); lcd.setCursor(0, 0); lcd.print(" TABUNGANKU "); lcd.setCursor(0, 1); lcd.print(" SIAP DIGUNAKAN "); } void lcdUangTerdeteksi(int nilai) { lcd.clear(); lcd.setCursor(0, 0); lcd.print("UANG TERDETEKSI"); lcd.setCursor(0, 1); String text = "Rp " + String(nilai); while (text.length() < 16) text += " "; lcd.print(text); } void lcdUangMasuk(int nilai) { lcd.clear(); lcd.setCursor(0, 0); lcd.print(" UANG MASUK "); lcd.setCursor(0, 1); String text = "Rp " + String(nilai); while (text.length() < 16) text += " "; lcd.print(text); } void lcdTabunganTerbuka() { lcd.clear(); lcd.setCursor(0, 0); lcd.print(" TABUNGAN "); lcd.setCursor(0, 1); lcd.print(" TERBUKA "); } void updateLCD() { if (lastBukaTabungan) { lcdTabunganTerbuka(); } else if (lastKonfirmasi && lastDetectedVal > 0) { lcdUangMasuk(lastDetectedVal); } else if (lastDetectedVal > 0) { lcdUangTerdeteksi(lastDetectedVal); } else { lcdDefault(); } } // ===================================================== // WIFI // ===================================================== void connectWiFi() { WiFi.mode(WIFI_STA); WiFi.setSleep(false); WiFiManager wm; wm.setTimeout(180); // 3 menit untuk setup WiFi bool res = wm.autoConnect("TABUNGANKU_SETUP"); if (!res) { Serial.println("Gagal konek WiFi"); ESP.restart(); } Serial.println("WiFi Connected!"); Serial.print("IP : "); Serial.println(WiFi.localIP()); } // ===================================================== // FIREBASE // ===================================================== 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); Serial.println("Firebase Connected"); } // ===================================================== // TCS3200 // ===================================================== void setupTCS3200() { pinMode(S0, OUTPUT); pinMode(S1, OUTPUT); pinMode(S2, OUTPUT); pinMode(S3, OUTPUT); pinMode(SENSOR_OUT, INPUT); digitalWrite(S0, HIGH); digitalWrite(S1, LOW); Serial.println("Sensor TCS3200 Ready"); } int avgFreq(int s2Val, int s3Val) { long total = 0; for (int i = 0; i < 3; i++) { digitalWrite(S2, s2Val); digitalWrite(S3, s3Val); delayMicroseconds(300); total += pulseIn(SENSOR_OUT, LOW); } return total / 3; } // ===================================================== // DETEKSI UANG // ===================================================== void cekUangKertas() { static unsigned long lastCheck = 0; static unsigned long lastDetectedTime = 0; static int lastDetectedValue = 0; if (millis() - lastCheck < 300) return; lastCheck = millis(); if (lastKonfirmasi) return; int r = avgFreq(LOW, LOW); int g = avgFreq(HIGH, HIGH); int b = avgFreq(LOW, HIGH); Serial.print("R: "); Serial.print(r); Serial.print(" | G: "); Serial.print(g); Serial.print(" | B: "); Serial.println(b); int val = 0; if (r>=110&&r<=120 && g>=140&&g<=150 && b>=124&&b<=134) val = 100000; else if (r>=175&&r<=185 && g>=160&&g<=170 && b>=150&&b<=160) val = 50000; else if (r>=138&&r<=148 && g>=125&&g<=135 && b>=105&&b<=115) val = 20000; else if (r>=145&&r<=155 && g>=180&&g<=190 && b>=150&&b<=160) val = 10000; else if (r>=115&&r<=125 && g>=169&&g<=179 && b>=140&&b<=150) val = 5000; else if (r>=145&&r<=155 && g>=145&&g<=155 && b>=120&&b<=130) val = 2000; else if (r>=129&&r<=139 && g>=125&&g<=135 && b>=120&&b<=130) val = 1000; if (val > 0 && (millis() - lastDetectedTime > 1500 || val != lastDetectedValue)) { Serial.println("===================="); Serial.print("Uang : Rp "); Serial.println(val); Serial.println("===================="); Firebase.RTDB.setInt( &fbdo, "/tabungan/detected_value", val ); lastDetectedTime = millis(); lastDetectedValue = val; lastDetectedVal = val; updateLCD(); } } // ===================================================== // SERVO HELPER // ===================================================== void attachServo() { if (!servoAttached) { servoMasuk.attach(SERVO_MASUK_PIN); servoTurun.attach(SERVO_TURUN_PIN); servoBuka.attach(SERVO_BUKA_PIN); servoAttached = true; delay(300); servoMasuk.write(0); servoTurun.write(0); servoBuka.write(0); } } // ===================================================== // FIREBASE LISTENER // ===================================================== void cekKonfirmasiFlutter() { if (!Firebase.RTDB.getBool( &fbdo, "/tabungan/konfirmasi_masuk")) return; bool konfirmasi = fbdo.boolData(); if (konfirmasi == lastKonfirmasi) return; lastKonfirmasi = konfirmasi; Serial.print("Konfirmasi: "); Serial.println(konfirmasi); attachServo(); if (konfirmasi) { servoTurun.write(90); Serial.println("Servo Turun Aktif"); } else { servoTurun.write(0); Serial.println("Servo Turun Mati"); // reset nominal ketika tombol batalkan ditekan lastDetectedVal = 0; Firebase.RTDB.setInt( &fbdo, "/tabungan/detected_value", 0 ); Serial.println("Detected Reset"); // kembali ke tampilan awal lcdDefault(); return; } updateLCD(); } void cekServoMasuk() { if (!Firebase.RTDB.getBool( &fbdo, "/tabungan/masukkan_uang")) return; bool buka = fbdo.boolData(); if (buka == lastServoMasuk) return; lastServoMasuk = buka; attachServo(); servoMasuk.write(buka ? 150 : 0); Serial.print("Servo Masuk: "); Serial.println( buka ? "BUKA" : "TUTUP" ); } void cekServoBukaTabungan() { static bool sedangBuka = false; static unsigned long waktuBuka = 0; static int lastSisa = -1; if (!Firebase.RTDB.getBool( &fbdo, "/tabungan/buka_tabungan")) return; bool buka = fbdo.boolData(); // buka servo sekali saat menerima TRUE if (buka && !sedangBuka) { attachServo(); servoBuka.write(150); waktuBuka = millis(); sedangBuka = true; lastBukaTabungan = true; Serial.println("Servo Tabungan: TERBUKA"); } // countdown 25 detik if (sedangBuka) { int sisa = 25 - ((millis() - waktuBuka) / 1000); if (sisa < 0) sisa = 0; if (sisa != lastSisa) { lastSisa = sisa; lcd.clear(); lcd.setCursor(0, 0); lcd.print("Pintu Terbuka"); lcd.setCursor(0, 1); lcd.print("Sisa:"); lcd.print(sisa); lcd.print(" dtk"); } // tutup otomatis setelah 25 detik if (millis() - waktuBuka >= 25000) { servoBuka.write(0); sedangBuka = false; lastBukaTabungan = false; Firebase.RTDB.setBool( &fbdo, "/tabungan/buka_tabungan", false ); lcdDefault(); Serial.println("Servo Tabungan: TERTUTUP"); } } } void cekDetectedValueFirebase() { static unsigned long lastSync = 0; if (millis() - lastSync < 1000) return; lastSync = millis(); if (!Firebase.RTDB.getInt( &fbdo, "/tabungan/detected_value")) return; int val = fbdo.intData(); // jika nilai direset dari Flutter if (val == 0) { lastDetectedVal = 0; lcdDefault(); return; } if (lastKonfirmasi) return; if (val == lastDetectedVal) return; lastDetectedVal = val; Serial.print("Sync Firebase: "); Serial.println(val); updateLCD(); } // ===================================================== // SETUP // ===================================================== void setup() { // disable brownout sementara WRITE_PERI_REG( RTC_CNTL_BROWN_OUT_REG, 0 ); Serial.begin(115200); Serial.println(); Serial.println("System Booting..."); Wire.begin(); lcd.init(); lcd.backlight(); lcd.clear(); lcd.setCursor(0, 0); lcd.print(" TABUNGANKU "); lcd.setCursor(0, 1); lcd.print(" BOOTING... "); setupTCS3200(); // JANGAN attach servo di awal // supaya tidak brownout connectWiFi(); setupFirebase(); // reset firebase Firebase.RTDB.setInt( &fbdo, "/tabungan/detected_value", 0 ); Firebase.RTDB.setBool( &fbdo, "/tabungan/konfirmasi_masuk", false ); Firebase.RTDB.setBool( &fbdo, "/tabungan/masukkan_uang", false ); Firebase.RTDB.setBool( &fbdo, "/tabungan/buka_tabungan", false ); Serial.println("Firebase Reset"); lcdDefault(); Serial.println("System Ready"); } // ===================================================== // LOOP // ===================================================== void loop() { static unsigned long lastWiFiCheck = 0; if (millis() - lastWiFiCheck > 10000) { lastWiFiCheck = millis(); if (WiFi.status() != WL_CONNECTED) { connectWiFi(); } } cekUangKertas(); cekKonfirmasiFlutter(); cekServoMasuk(); cekServoBukaTabungan(); cekDetectedValueFirebase(); delay(200); }