#include #include #include #include #include #include #include #include #include #include // āœ… Firebase #include "addons/TokenHelper.h" #include "addons/RTDBHelper.h" // Firebase #define API_KEY "AIzaSyCaLvOB31bMXPwFfIz5Gy5ecGZhDs6kW34" #define DATABASE_URL "https://sistemparkir-cc7d8-default-rtdb.firebaseio.com/" #define LEGACY_TOKEN "1uQ1lRWJqVoxeuGaSbJvzjEGX9MpRdaqXCFAxrP0" // RFID Pins #define SS_MASUK 19 #define RST_MASUK 23 #define SS_KELUAR 2 #define RST_KELUAR 15 // SPI Shared Bus #define SCK 17 #define MISO 22 #define MOSI 21 // Servo #define SERVO_MASUK_PIN 33 #define SERVO_KELUAR_PIN 25 // Buzzer #define BUZZER_PIN 13 // IR Sensor #define IR_MASUK_PIN 34 #define IR_KELUAR_PIN 35 // LED Status #define LED_PIN 12 // Tombol Reset #define RESET_BUTTON_PIN 14 const char* ssid = "Fajar"; const char* password = "fajar123"; // Firebase objects FirebaseData fbdo; FirebaseAuth auth; FirebaseConfig config; String apiKey; String databaseUrl; const char* ntpServer = "time.nist.gov"; const long gmtOffset_sec = 7 * 3600; // GMT+7 const int daylightOffset_sec = 0; // RFID & Servo MFRC522 rfidMasuk(SS_MASUK, RST_MASUK); MFRC522 rfidKeluar(SS_KELUAR, RST_KELUAR); Servo servoMasuk; Servo servoKeluar; // LCD LiquidCrystal_I2C lcd(0x27, 16, 2); // Variabel int totalSlot = 10; int jumlahMasuk = 0; int jumlahKeluar = 0; int slotTerpakai = 0; unsigned long previousMillis = 0; bool ledState = false; unsigned long buttonPressStart = 0; bool buttonPressed = false; const unsigned long resetHoldTime = 10000; // 10 detik bool emergencyActive = false; void tampilkanDefaultLCD() { int slotKosong = 0; // Mengambil data slot kosong langsung dari Firebase if (Firebase.RTDB.getInt(&fbdo, "info_parkir/slot")) { slotKosong = fbdo.intData(); // Mengambil nilai slot kosong dari Firebase } lcd.clear(); lcd.setCursor(0, 0); lcd.print(" SIPARKIR SLOT:"); lcd.setCursor(0, 1); lcd.print(" " + String(slotKosong)); // Menampilkan jumlah slot kosong dari Firebase } void tampilkanPesanLCD(String pesan) { lcd.clear(); lcd.setCursor(0, 0); lcd.print(pesan); delay(3000); tampilkanDefaultLCD(); } void tampilkanPesanLCDDuaBaris(String baris1, String baris2) { lcd.clear(); lcd.setCursor(0, 0); lcd.print(baris1); lcd.setCursor(0, 1); lcd.print(baris2); delay(3000); tampilkanDefaultLCD(); } bool bukaPintu(Servo& servo, int irPin, bool isMasuk) { // Buka pintu if (isMasuk) { servo.write(90); // buka arah masuk } else { servo.write(90); // buka arah keluar (ubah dari 0 ke 90) } Serial.println("šŸš— Pintu terbuka, menunggu kendaraan melewati sensor IR..."); unsigned long waktuMulai = millis(); const unsigned long batasWaktu = 10000; // timeout jika kendaraan tidak muncul bool terdeteksi = false; // Tunggu kendaraan muncul while (!terdeteksi && (millis() - waktuMulai < batasWaktu)) { if (digitalRead(irPin) == LOW) { terdeteksi = true; Serial.println("āœ… Kendaraan terdeteksi IR..."); } delay(100); } if (!terdeteksi) { Serial.println("āš ļø Tidak ada kendaraan yang muncul, timeout."); // Tutup pintu if (isMasuk) { servo.write(0); } else { servo.write(0); // tutup keluar (ubah dari 90 ke 0) } return false; } // Tunggu kendaraan lewat while (digitalRead(irPin) == LOW) { delay(100); } Serial.println("āœ… Kendaraan sudah melewati sensor IR."); // Tutup pintu if (isMasuk) { servo.write(0); } else { servo.write(0); // ubah dari 90 ke 0 } return true; } void updateFirebase() { int slotKosong = totalSlot - slotTerpakai; Firebase.RTDB.setInt(&fbdo, "info_parkir/masuk", jumlahMasuk); Firebase.RTDB.setInt(&fbdo, "info_parkir/keluar", jumlahKeluar); Firebase.RTDB.setInt(&fbdo, "info_parkir/slot", slotKosong); } void kirimDataKeServer(String uid, String activity) { HTTPClient http; http.begin("https://esp32-firebase-service.vercel.app/api/kirim-data"); time_t now; struct tm timeinfo; if (!getLocalTime(&timeinfo)) { Serial.println("āŒ Gagal mendapatkan waktu lokal"); return; } char timeString[30]; strftime(timeString, sizeof(timeString), "%Y-%m-%dT%H:%M:%S.000Z", &timeinfo); String payload = "{\"uid\":\"" + uid + "\",\"activity\":\"" + activity + "\",\"time\":\"" + String(timeString) + "\"}"; Serial.println("šŸ“¤ Kirim Payload: " + payload); http.begin("https://esp32-firebase-service.vercel.app/api/kirim-data"); // āœ… langsung tulis URL-nya http.addHeader("Content-Type", "application/json"); int httpResponseCode = http.POST(payload); if (httpResponseCode > 0) { Serial.printf("āœ… Kirim berhasil: %d\n", httpResponseCode); String response = http.getString(); Serial.println("Response: " + response); } else { Serial.printf("āŒ Kirim gagal: %s\n", http.errorToString(httpResponseCode).c_str()); } http.end(); } void loadDataFromFirebase() { if (Firebase.RTDB.getInt(&fbdo, "info_parkir/masuk")) { jumlahMasuk = fbdo.intData(); } if (Firebase.RTDB.getInt(&fbdo, "info_parkir/keluar")) { jumlahKeluar = fbdo.intData(); } int slotKosong = 10; if (Firebase.RTDB.getInt(&fbdo, "info_parkir/slot")) { slotKosong = fbdo.intData(); } slotTerpakai = totalSlot - slotKosong; Serial.println("āœ… Data berhasil dimuat dari Firebase"); } String getUID(MFRC522& rfid) { String uid = ""; for (byte i = 0; i < rfid.uid.size; i++) { uid += String(rfid.uid.uidByte[i], HEX); } uid.toUpperCase(); return uid; } void bunyiBuzzerTerdaftar() { digitalWrite(BUZZER_PIN, HIGH); delay(100); digitalWrite(BUZZER_PIN, LOW); } void bunyiBuzzerTidakTerdaftar() { for (int i = 0; i < 5; i++) { digitalWrite(BUZZER_PIN, HIGH); delay(100); digitalWrite(BUZZER_PIN, LOW); delay(400); } } void prosesKartu(MFRC522& rfid, bool isMasuk) { String rfidUID = getUID(rfid); Serial.println((isMasuk ? "Masuk" : "Keluar") + String(" UID: ") + rfidUID); String path = "daftar_kartu/" + rfidUID; if (Firebase.RTDB.getBool(&fbdo, path + "/value") && fbdo.boolData()) { if (Firebase.RTDB.getBool(&fbdo, path + "/status")) { bool status = fbdo.boolData(); if (isMasuk) { if (!status) { // Belum masuk if (slotTerpakai < totalSlot) { tampilkanPesanLCD("Silahkan Masuk"); bool kendaraanLewat = bukaPintu(servoMasuk, IR_MASUK_PIN, true); if (kendaraanLewat) { jumlahMasuk++; // āœ… hanya tambah kalau IR mendeteksi kendaraan slotTerpakai++; // āœ… Firebase.RTDB.setBool(&fbdo, path + "/status", true); updateFirebase(); bunyiBuzzerTerdaftar(); tampilkanDefaultLCD(); kirimDataKeServer(rfidUID, "masuk"); } else { tampilkanPesanLCD("Gagal Masuk!"); bunyiBuzzerTidakTerdaftar(); } } else { tampilkanPesanLCD("Parkiran Penuh!"); } } else { tampilkanPesanLCD("Sudah Masuk!"); } } else { // Keluar if (status) { // Sudah masuk tampilkanPesanLCD("Terima Kasih"); bool kendaraanLewat = bukaPintu(servoKeluar, IR_KELUAR_PIN, false); if (kendaraanLewat) { jumlahKeluar++; // āœ… hanya kurang kalau IR mendeteksi kendaraan slotTerpakai--; // āœ… Firebase.RTDB.setBool(&fbdo, path + "/status", false); updateFirebase(); bunyiBuzzerTerdaftar(); tampilkanDefaultLCD(); kirimDataKeServer(rfidUID, "keluar"); } else { tampilkanPesanLCD("Gagal Keluar!"); bunyiBuzzerTidakTerdaftar(); } } else { tampilkanPesanLCD("Belum Masuk!"); } } } else { tampilkanPesanLCD("Status Tidak Ada"); bunyiBuzzerTidakTerdaftar(); } } else { tampilkanPesanLCDDuaBaris("Kartu Tidak", "Terdaftar"); bunyiBuzzerTidakTerdaftar(); } rfid.PICC_HaltA(); rfid.PCD_StopCrypto1(); } void resetDataFirebase() { Serial.println("āš ļø Reset data Firebase..."); Firebase.RTDB.setInt(&fbdo, "info_parkir/masuk", 0); Firebase.RTDB.setInt(&fbdo, "info_parkir/keluar", 0); Firebase.RTDB.setInt(&fbdo, "info_parkir/slot", totalSlot); jumlahMasuk = 0; jumlahKeluar = 0; slotTerpakai = 0; // Ambil isi JSON if (Firebase.RTDB.getJSON(&fbdo, "daftar_kartu")) { FirebaseJson json = fbdo.to(); // Ambil JSON penuh String raw; json.toString(raw, true); Serial.println("šŸ“¦ Data JSON:"); Serial.println(raw); size_t len = json.iteratorBegin(); Serial.printf("šŸ” Total Key: %d\n", len); String uidKey, dummyVal; int type; for (size_t i = 0; i < len; i++) { json.iteratorGet(i, type, uidKey, dummyVal); if (uidKey.length() > 0) { String statusPath = "daftar_kartu/" + uidKey + "/status"; bool success = Firebase.RTDB.setBool(&fbdo, statusPath, false); Serial.print("Set status false untuk UID "); Serial.print(uidKey); Serial.println(success ? " āœ… BERHASIL" : " āŒ GAGAL"); } else { Serial.println("āŒ UID kosong, dilewati"); } delay(200); // Hindari limit koneksi } json.iteratorEnd(); } else { Serial.println("āŒ Gagal ambil data daftar_kartu"); } tampilkanPesanLCD("Data Direset"); } void setup() { Serial.begin(115200); Wire.begin(27, 26); lcd.init(); lcd.backlight(); tampilkanDefaultLCD(); servoMasuk.attach(SERVO_MASUK_PIN); servoKeluar.attach(SERVO_KELUAR_PIN); pinMode(BUZZER_PIN, OUTPUT); pinMode(IR_MASUK_PIN, INPUT_PULLUP); pinMode(IR_KELUAR_PIN, INPUT_PULLUP); pinMode(LED_PIN, OUTPUT); pinMode(RESET_BUTTON_PIN, INPUT_PULLUP); SPI.begin(SCK, MISO, MOSI, SS_MASUK); rfidMasuk.PCD_Init(); rfidKeluar.PCD_Init(); WiFi.mode(WIFI_STA); // Inisialisasi WiFi WiFi.begin(ssid, password); Serial.print("Menghubungkan ke WiFi..."); while (WiFi.status() != WL_CONNECTED) { delay(500); Serial.print("."); } Serial.println("\nāœ… Terhubung ke WiFi"); Serial.print("IP Address: "); Serial.println(WiFi.localIP()); configTime(gmtOffset_sec, daylightOffset_sec, ntpServer); Serial.println("Menunggu waktu NTP..."); struct tm timeinfo; while (!getLocalTime(&timeinfo)) { Serial.print("."); delay(500); } Serial.println("\nWaktu NTP berhasil didapatkan:"); Serial.println(&timeinfo, "%Y-%m-%d %H:%M:%S"); // === Firebase config === config.api_key = API_KEY; config.database_url = DATABASE_URL; config.signer.tokens.legacy_token = LEGACY_TOKEN; Firebase.begin(&config, &auth); Firebase.reconnectWiFi(true); // Load data awal dari Firebase loadDataFromFirebase(); tampilkanDefaultLCD(); Serial.println("šŸš€ Setup selesai."); } void loop() { if (WiFi.status() != WL_CONNECTED) { unsigned long currentMillis = millis(); if (currentMillis - previousMillis >= 500) { previousMillis = currentMillis; ledState = !ledState; digitalWrite(LED_PIN, ledState); } return; } else { digitalWrite(LED_PIN, HIGH); } int buttonState = digitalRead(RESET_BUTTON_PIN); if (buttonState == LOW && !buttonPressed) { buttonPressed = true; buttonPressStart = millis(); emergencyActive = true; // Bunyikan buzzer darurat digitalWrite(BUZZER_PIN, HIGH); Serial.println("🚨 Tombol emergency ditekan - buzzer aktif"); } if (buttonState == LOW && buttonPressed) { if (millis() - buttonPressStart >= resetHoldTime) { Serial.println("ā™»ļø Tombol ditekan lama - reset sistem"); // Matikan buzzer digitalWrite(BUZZER_PIN, LOW); resetDataFirebase(); // Lakukan reset (misal: reset data parkir, atau restart ESP tampilkanPesanLCDDuaBaris("Sistem di-reset", "oleh petugas"); buttonPressed = false; emergencyActive = false; } } if (buttonState == HIGH && buttonPressed) { // Tombol dilepas sebelum 10 detik digitalWrite(BUZZER_PIN, LOW); buttonPressed = false; emergencyActive = false; Serial.println("šŸ”• Tombol emergency dilepas"); } if (rfidMasuk.PICC_IsNewCardPresent() && rfidMasuk.PICC_ReadCardSerial()) { prosesKartu(rfidMasuk, true); } if (rfidKeluar.PICC_IsNewCardPresent() && rfidKeluar.PICC_ReadCardSerial()) { prosesKartu(rfidKeluar, false); } }