#include #include #include #include #include #include // === WIFI & FIREBASE === #define WIFI_SSID "KOPI" #define WIFI_PASSWORD "digoreng123" #define API_KEY "AIzaSyD3gpQON7sS6gR1CTf3S0le5WmjJN-KQ8g" #define DATABASE_URL "https://beramalbaik-90b62-default-rtdb.firebaseio.com/" #define LEGACY_TOKEN "Kz12MOC1xz5UVZih8BU4g3mopL2fNdh3CMyynnc3" // === PIN SENSOR === #define S0 14 #define S1 27 #define S2 26 #define S3 25 #define sensorOut 33 #define IR_KOIN_1000_1 32 #define IR_KOIN_1000_2 18 #define IR_KOIN_500_1 19 #define IR_KOIN_500_2 23 #define RELAY_PIN 2 LiquidCrystal_I2C lcd(0x27, 16, 2); FirebaseData fbdo; FirebaseAuth auth; FirebaseConfig config; Adafruit_Fingerprint finger = Adafruit_Fingerprint(&Serial2); int totalUang = 0; bool tampilSementara = false; unsigned long waktuTampil = 0; unsigned long waktuRefresh = 0; String lastLine1 = "", lastLine2 = ""; bool bukaKunci = false; volatile bool ir1000_1_triggered = false; volatile bool ir1000_2_triggered = false; volatile bool ir500_1_triggered = false; volatile bool ir500_2_triggered = false; unsigned long lastKoin1000 = 0; unsigned long lastKoin500 = 0; unsigned long debounceDelay = 30; bool fingerprintGagalBaru = false; volatile unsigned long lastInterrupt1000_1 = 0; void IRAM_ATTR handleIR1000_1() { unsigned long now = millis(); if (now - lastInterrupt1000_1 > debounceDelay) { ir1000_1_triggered = true; lastInterrupt1000_1 = now; } } volatile unsigned long lastInterrupt1000_2 = 0; void IRAM_ATTR handleIR1000_2() { unsigned long now = millis(); if (now - lastInterrupt1000_2 > debounceDelay) { ir1000_2_triggered = true; lastInterrupt1000_2 = now; } } volatile unsigned long lastInterrupt500_1 = 0; void IRAM_ATTR handleIR500_1() { unsigned long now = millis(); if (now - lastInterrupt500_1 > debounceDelay) { ir500_1_triggered = true; lastInterrupt500_1 = now; } } volatile unsigned long lastInterrupt500_2 = 0; void IRAM_ATTR handleIR500_2() { unsigned long now = millis(); if (now - lastInterrupt500_2 > debounceDelay) { ir500_2_triggered = true; lastInterrupt500_2 = now; } } void connectWiFi() { WiFi.begin(WIFI_SSID, WIFI_PASSWORD); Serial.print("Menghubungkan WiFi"); while (WiFi.status() != WL_CONNECTED) { Serial.print("."); delay(500); } Serial.println(" Terhubung!"); } 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 setupTCS3200() { pinMode(S0, OUTPUT); pinMode(S1, OUTPUT); pinMode(S2, OUTPUT); pinMode(S3, OUTPUT); pinMode(sensorOut, INPUT); digitalWrite(S0, HIGH); digitalWrite(S1, LOW); } int averageColorFrequency(int s2_val, int s3_val) { long total = 0; for (int i = 0; i < 3; i++) { digitalWrite(S2, s2_val); digitalWrite(S3, s3_val); delayMicroseconds(300); total += pulseIn(sensorOut, LOW); } return total / 3; } void tampilkanLCD(String line1, String line2) { if (line1 != lastLine1 || line2 != lastLine2) { lcd.clear(); lcd.setCursor(0, 0); lcd.print(line1); lcd.setCursor(0, 1); lcd.print(line2); lastLine1 = line1; lastLine2 = line2; } } void tampilkanTotalUang() { if (Firebase.RTDB.getInt(&fbdo, "/kotak_amal/total_uang")) { totalUang = fbdo.intData(); tampilkanLCD("Total Uang:", "Rp " + String(totalUang)); } } void simpanKeHistory(int nominal) { if (WiFi.status() == WL_CONNECTED) { HTTPClient http; http.begin("https://kotakamal-api.vercel.app/api/kirimdata"); http.addHeader("Content-Type", "application/json"); String payload = "{\"nominal_uang\":" + String(nominal) + "}"; int httpResponseCode = http.POST(payload); if (httpResponseCode > 0) { Serial.println("✅ History saved: " + http.getString()); } else { Serial.println("❌ Gagal kirim history: " + http.errorToString(httpResponseCode)); } http.end(); } else { Serial.println("❌ WiFi belum terhubung"); } } String getISOTime() { time_t now; struct tm timeinfo; if (!getLocalTime(&timeinfo)) { Serial.println("Gagal mendapatkan waktu"); return ""; } char isoTime[30]; strftime(isoTime, sizeof(isoTime), "%Y-%m-%dT%H:%M:%SZ", &timeinfo); return String(isoTime); } void updateFirebase(int jumlah) { int currentTotal = 0; if (Firebase.RTDB.getInt(&fbdo, "/kotak_amal/total_uang")) { currentTotal = fbdo.intData(); } int updatedTotal = currentTotal + jumlah; if (Firebase.RTDB.setInt(&fbdo, "/kotak_amal/total_uang", updatedTotal)) { Serial.println("Firebase updated: Rp " + String(updatedTotal)); } else { Serial.println("Gagal update Firebase: " + fbdo.errorReason()); } totalUang = updatedTotal; simpanKeHistory(jumlah); } bool cekFingerprint() { if (finger.getImage() != FINGERPRINT_OK) return false; if (finger.image2Tz() != FINGERPRINT_OK) return false; if (finger.fingerSearch() != FINGERPRINT_OK) return false; Serial.println("ID fingerprint terdeteksi: " + String(finger.fingerID)); return true; } bool daftarFingerprint(int id) { Serial.println("Letakkan jari Anda untuk didaftarkan..."); while (finger.getImage() != FINGERPRINT_OK); if (finger.image2Tz(1) != FINGERPRINT_OK) return false; Serial.println("Angkat jari, lalu letakkan kembali..."); tampilkanLCD("Angkat jari", "Letakkan kembali"); delay(2000); while (finger.getImage() != FINGERPRINT_OK); if (finger.image2Tz(2) != FINGERPRINT_OK) return false; if (finger.createModel() != FINGERPRINT_OK) return false; if (finger.storeModel(id) != FINGERPRINT_OK) return false; return true; } void tampilkanSementara(String line1, String line2, int nominal) { tampilkanLCD(line1, line2); updateFirebase(nominal); tampilSementara = true; waktuTampil = millis(); } int stringToHashId(String input) { unsigned int hash = 0; for (int i = 0; i < input.length(); i++) { hash = 31 * hash + input.charAt(i); } return abs((int)(hash % 127) + 1); // pastikan dalam range 1–127 } bool konfirmasiDeteksi(int pin1, int pin2) { delay(50); // debounce sensor return digitalRead(pin1) == LOW || digitalRead(pin2) == LOW; } void cekUangKertas() { static unsigned long lastDetectedTime = 0; static int lastDetectedValue = 0; static unsigned long lastCheck = 0; if (millis() - lastCheck < 200) return; lastCheck = millis(); int red = averageColorFrequency(LOW, LOW); int green = averageColorFrequency(HIGH, HIGH); int blue = averageColorFrequency(LOW, HIGH); Serial.printf("RGB: R=%d G=%d B=%d\n", red, green, blue); int detectedValue = 0; if (red >= 28 && red <= 32 && green >= 40 && green <= 46 && blue >= 35 && blue <= 39) detectedValue = 100000; else if (red >= 30 && red <= 33 && green >= 26 && green <= 30 && blue >= 22 && blue <= 26) detectedValue = 50000; else if (red >= 42 && red <= 46 && green >= 42 && green <= 46 && blue >= 44 && blue <= 48) detectedValue = 20000; else if (red >= 39 && red <= 43 && green >= 40 && green <= 44 && blue >= 33 && blue <= 37) detectedValue = 10000; else if (red >= 22 && red <= 25 && green >= 33 && green <= 37 && blue >= 35 && blue <= 39) detectedValue = 5000; else if (red >= 36 && red <= 40 && green >= 36 && green <= 40 && blue >= 31 && blue <= 35) detectedValue = 2000; else if (red >= 25 && red <= 28 && green >= 28 && green <= 32 && blue >= 32 && blue <= 36) detectedValue = 1000; if (detectedValue > 0 && (millis() - lastDetectedTime > 1500 || detectedValue != lastDetectedValue)) { tampilkanSementara("Uang Kertas:", "Rp " + String(detectedValue), detectedValue); lastDetectedTime = millis(); lastDetectedValue = detectedValue; } } void cekKoin1000(unsigned long now) { if ((ir1000_1_triggered || ir1000_2_triggered) && (now - lastKoin1000 > debounceDelay)) { noInterrupts(); ir1000_1_triggered = false; ir1000_2_triggered = false; interrupts(); lastKoin1000 = now; tampilkanSementara("Koin Masuk:", "Rp 1000", 1000); Serial.println("✅ Koin Rp 1000 Terdeteksi"); } } void cekKoin500(unsigned long now) { if ((ir500_1_triggered || ir500_2_triggered) && (now - lastKoin500 > debounceDelay)) { noInterrupts(); ir500_1_triggered = false; ir500_2_triggered = false; interrupts(); lastKoin500 = now; tampilkanSementara("Koin Masuk:", "Rp 500", 500); Serial.println("✅ Koin Rp 500 Terdeteksi"); } } void setup() { Serial.begin(115200); lcd.init(); lcd.backlight(); setupTCS3200(); connectWiFi(); setupFirebase(); configTime(7 * 3600, 0, "pool.ntp.org", "time.nist.gov"); pinMode(IR_KOIN_1000_1, INPUT_PULLUP); pinMode(IR_KOIN_1000_2, INPUT_PULLUP); pinMode(IR_KOIN_500_1, INPUT_PULLUP); pinMode(IR_KOIN_500_2, INPUT_PULLUP); attachInterrupt(digitalPinToInterrupt(IR_KOIN_1000_1), handleIR1000_1, FALLING); attachInterrupt(digitalPinToInterrupt(IR_KOIN_1000_2), handleIR1000_2, FALLING); attachInterrupt(digitalPinToInterrupt(IR_KOIN_500_1), handleIR500_1, FALLING); attachInterrupt(digitalPinToInterrupt(IR_KOIN_500_2), handleIR500_2, FALLING); pinMode(RELAY_PIN, OUTPUT); digitalWrite(RELAY_PIN, HIGH); Serial2.begin(57600, SERIAL_8N1, 16, 17); finger.begin(57600); if (finger.verifyPassword()) { Serial.println("Sensor fingerprint terdeteksi."); } else { Serial.println("Sensor fingerprint TIDAK terdeteksi!"); tampilkanLCD("Sensor Fingerprint", "TIDAK TERDETEKSI"); delay(3000); // tampilkan sebentar tampilkanTotalUang(); // kembali ke tampilan normal } tampilkanLCD("Kotak Amal", "Siap Digunakan"); delay(2000); tampilkanTotalUang(); // Reset trigger flags ir1000_1_triggered = false; ir1000_2_triggered = false; ir500_1_triggered = false; ir500_2_triggered = false; delay(1000); // Stabilkan sensor setelah boot } void loop() { cekUangKertas(); unsigned long now = millis(); cekKoin1000(now); cekKoin500(now); // === Tampilan Otomatis === if (tampilSementara && millis() - waktuTampil >= 2000) { tampilSementara = false; tampilkanTotalUang(); } if (!tampilSementara && millis() - waktuRefresh >= 3000) { tampilkanTotalUang(); waktuRefresh = millis(); } // ==== MODE DAFTAR FINGERPRINT (HANYA ID 1) ==== if (Firebase.RTDB.getBool(&fbdo, "/status_daftar") && fbdo.boolData() == true) { Serial.println("🔄 Masuk Mode Daftar Fingerprint"); finger.deleteModel(1); // Hapus dulu ID 1 delay(100); tampilkanLCD("Tempelkan jari", "untuk daftar"); delay(1000); if (daftarFingerprint(1)) { Serial.println("✅ Berhasil daftar sidik jari di ID 1"); tampilkanLCD("Pendaftaran", "berhasil"); } else { Serial.println("❌ Gagal daftar sidik jari"); tampilkanLCD("Pendaftaran", "gagal"); } delay(2000); tampilkanTotalUang(); // Reset status daftar Firebase.RTDB.setBool(&fbdo, "/status_daftar", false); } // ==== CEK FINGERPRINT UNTUK AKSES ==== if (!tampilSementara) { if (cekFingerprint()) { fingerprintGagalBaru = false; Serial.println("Fingerprint cocok, membuka pintu..."); tampilkanLCD("Fingerprint cocok", "Membuka kunci..."); digitalWrite(RELAY_PIN, LOW); delay(3000); digitalWrite(RELAY_PIN, HIGH); tampilkanTotalUang(); } else if (!fingerprintGagalBaru) { fingerprintGagalBaru = true; tampilkanLCD("Fingerprint", "tidak cocok!"); delay(2000); tampilkanTotalUang(); } } // ==== HAPUS SEMUA FINGERPRINT ==== if (Firebase.RTDB.getBool(&fbdo, "/hapus_fingerprint_data") && fbdo.boolData() == true) { tampilkanLCD("Menghapus semua", "sidik jari..."); Serial.println("Menghapus semua data sidik jari..."); for (int i = 1; i < 128; i++) { if (finger.deleteModel(i) == FINGERPRINT_OK) { Serial.print("Hapus ID "); Serial.println(i); } } Firebase.RTDB.setBool(&fbdo, "/hapus_fingerprint_data", false); tampilkanLCD("Semua sidik jari", "telah dihapus"); delay(2000); tampilkanTotalUang(); }   delay(5); }