#include #include #include #include #include #include #include // === KREDENSIAL FIREBASE === // === KREDENSIAL FIREBASE === #define API_KEY "AIzaSyBRT9jVU0jbLLri5ZWFgELiP-MwdWqJqGY" #define DATABASE_URL "https://palang-pintu-1ee1b-default-rtdb.asia-southeast1.firebasedatabase.app/" #define LEGACY_TOKEN "ii4jFWAjUalW2zkYddwhzk8313hD5TLsewohcvdR" // === PIN SENSOR DAN KOMPONEN === #define TRIG1 14 #define ECHO1 27 #define TRIG2 25 #define ECHO2 26 #define SERVO_PIN 18 #define BUZZER_PIN 12 #define LED_AMAN 23 #define LED_BAHAYA 32 #define PIEZO_PIN1 34 #define RXD2 16 #define TXD2 17 // === OBJEK & VARIABEL GLOBAL === FirebaseData fbdo; FirebaseAuth auth; FirebaseConfig config; Servo servoJalur1; LiquidCrystal_I2C lcd(0x27, 16, 2); unsigned long lastUpdate = 0; unsigned long lastSerialPrint = 0; const unsigned long interval = 5000; unsigned long intervalJeda = 5000; bool isOfflineMode = false; bool keretaTerdeteksi = false; bool keretaLewat = false; int upClickCount = 0; int downClickCount = 0; bool lastButtonUpState = HIGH; bool lastButtonDownState = HIGH; // Flag to track if the waiting message for Jalur 2 has been sent bool waitingMessageSent = false; // Flag to track if the "Kereta telah lewat di Jalur 1!" message has been sent bool keretaLewatMessageSent = false; int tresholdUltrasonik = 50; // Default 50 cm int tresholdPiezo = 300; // Default 300 String phoneNumber = "6282221448622"; // Nomor telepon yang akan dihubungi String serviceURL = "http://192.168.37.201:3000/send-message"; // Default service URL // Fungsi untuk mengambil status_kereta dari Firebase dan mengontrol palang void fetchStatusKereta() { // Coba ambil status kereta dari Firebase if (Firebase.RTDB.getBool(&fbdo, "/status_kereta")) { if (fbdo.dataType() == "boolean") { // Cek apakah tipe data adalah boolean bool statusKereta = fbdo.boolData(); // Ambil nilai boolean dari Firebase // Hanya tampilkan status jika ada pembaruan static bool lastStatusKereta = false; // Variabel untuk menyimpan status terakhir if (statusKereta != lastStatusKereta) { // Cek apakah status berubah Serial.print("[INFO] Status kereta dari Firebase: "); Serial.println(statusKereta ? "DITUTUP" : "DIBUKA"); lastStatusKereta = statusKereta; // Update status terakhir } // Lakukan aksi berdasarkan status kereta if (statusKereta) { // Status kereta true, tutup palang servoJalur1.write(90); // Tutup palang digitalWrite(LED_BAHAYA, HIGH); // LED merah menyala digitalWrite(LED_AMAN, LOW); digitalWrite(BUZZER_PIN, HIGH); // Bunyi buzzer lcd.clear(); lcd.setCursor(0, 0); lcd.print("PALANG DITUTUP"); } else { // Status kereta false, buka palang servoJalur1.write(0); // Buka palang digitalWrite(LED_BAHAYA, LOW); digitalWrite(LED_AMAN, HIGH); digitalWrite(BUZZER_PIN, LOW); // Matikan buzzer lcd.clear(); lcd.setCursor(0, 0); lcd.print("PALANG DIBUKA"); } } else { // Jika tipe data bukan boolean Serial.print("[ERROR] Data bukan boolean: "); Serial.println(fbdo.dataType()); // Tampilkan tipe data yang salah } } else { // Jika gagal mendapatkan data dari Firebase Serial.print("[ERROR] Gagal mendapatkan status kereta: "); Serial.println(fbdo.errorReason()); // Menampilkan alasan error } } // === AMBIL DATA THRESHOLD DARI FIREBASE === void updateThresholdFromFirebase() { if (Firebase.RTDB.getInt(&fbdo, "/treshold/ultrasonik") && fbdo.dataType() == "int") { tresholdUltrasonik = fbdo.intData(); Serial.print("Treshold Ultrasonik diperbarui: "); Serial.println(tresholdUltrasonik); } if (Firebase.RTDB.getInt(&fbdo, "/treshold/piezo") && fbdo.dataType() == "int") { tresholdPiezo = fbdo.intData(); Serial.print("Treshold Piezo diperbarui: "); Serial.println(tresholdPiezo); } if (Firebase.RTDB.getInt(&fbdo, "/treshold/interval_jeda") && fbdo.dataType() == "int") { intervalJeda = fbdo.intData() * 1000; Serial.print("Interval Jeda diperbarui: "); Serial.println(intervalJeda); } } void sendWAAlert(String message) { HTTPClient http; http.begin(serviceURL); // Gunakan serviceURL yang telah disesuaikan String payload = "{\"phone\": \"" + phoneNumber + "\", \"message\": \"" + message + "\"}"; // Gunakan phoneNumber langsung http.addHeader("Content-Type", "application/json"); // Kirim permintaan POST int httpResponseCode = http.POST(payload); if (httpResponseCode > 0) { // Menyediakan feedback sukses Serial.println("Pesan WhatsApp terkirim ke " + phoneNumber + ": " + String(httpResponseCode)); } else { // Menyediakan feedback gagal Serial.println("Gagal mengirim pesan WhatsApp ke " + phoneNumber + ": " + String(httpResponseCode)); Serial.println("Error: " + http.errorToString(httpResponseCode)); } http.end(); // Tutup permintaan HTTP } // === SETUP === void setup() { Serial.begin(115200); Serial2.begin(9600, SERIAL_8N1, RXD2, TXD2); // komunikasi ke ESP1 servoJalur1.setPeriodHertz(50); servoJalur1.attach(SERVO_PIN); Wire.begin(22, 21); lcd.init(); lcd.backlight(); pinMode(TRIG1, OUTPUT); pinMode(ECHO1, INPUT); pinMode(TRIG2, OUTPUT); pinMode(ECHO2, INPUT); pinMode(PIEZO_PIN1, INPUT); pinMode(BUZZER_PIN, OUTPUT); pinMode(LED_AMAN, OUTPUT); pinMode(LED_BAHAYA, OUTPUT); digitalWrite(LED_AMAN, HIGH); digitalWrite(LED_BAHAYA, LOW); lcd.setCursor(0, 0); lcd.print("Menghubungkan..."); Serial.println("Menghubungkan ke WiFi..."); // WiFiManager untuk konfigurasi URL WiFiManager wm; WiFiManagerParameter custom_service_url("service_url", "Service URL", serviceURL.c_str(), 40); // Ukuran maksimal 40 karakter wm.addParameter(&custom_service_url); // Menambahkan parameter wm.setTimeout(60); if (!wm.autoConnect("PALANG 2-Setup")) { Serial.println("Gagal konek WiFi. Mode offline."); lcd.clear(); lcd.setCursor(0, 0); lcd.print("Mode Offline"); isOfflineMode = true; // Set mode offline delay(2000); } else { Serial.println("WiFi Terhubung!"); lcd.clear(); lcd.setCursor(0, 0); lcd.print("WiFi Terhubung"); delay(2000); // Ambil URL yang dimasukkan pengguna serviceURL = custom_service_url.getValue(); // Ambil URL yang dikonfigurasi Serial.println("Service URL: " + serviceURL); // Debug URL yang diambil // Konfigurasi Firebase jika diperlukan config.api_key = API_KEY; config.database_url = DATABASE_URL; config.signer.tokens.legacy_token = LEGACY_TOKEN; Firebase.begin(&config, &auth); Firebase.reconnectWiFi(true); updateThresholdFromFirebase(); // Update threshold from Firebase } lcd.clear(); lcd.setCursor(0, 0); lcd.print("PALANG PINTAR"); Serial.println("Sistem Siap: PALANG PINTAR"); } // Fungsi utama yang berjalan di loop void loop() { // Baca sensor Ultrasonik 1 untuk mendeteksi kereta digitalWrite(TRIG1, LOW); delayMicroseconds(2); digitalWrite(TRIG1, HIGH); delayMicroseconds(10); digitalWrite(TRIG1, LOW); long duration1 = pulseIn(ECHO1, HIGH); float distance1 = duration1 * 0.034 / 2; // Menghitung jarak // Baca sensor Ultrasonik 2 digitalWrite(TRIG2, LOW); delayMicroseconds(2); digitalWrite(TRIG2, HIGH); delayMicroseconds(10); digitalWrite(TRIG2, LOW); long duration2 = pulseIn(ECHO2, HIGH); float distance2 = duration2 * 0.034 / 2; // Menghitung jarak // Baca sensor Piezo int piezo1Val = analogRead(PIEZO_PIN1); // Menampilkan status sensor ke Serial Monitor setiap interval tertentu if (millis() - lastSerialPrint >= interval) { lastSerialPrint = millis(); Serial.println("===== STATUS SENSOR ====="); Serial.print("Ultrasonik 1: "); Serial.println(distance1); Serial.print("Ultrasonik 2: "); Serial.println(distance2); Serial.print("Piezo1: "); Serial.println(piezo1Val); // Jika nilai piezo 0, tampilkan status palang terbuka atau tidak ada kereta if (piezo1Val == 0) { Serial.println("[INFO] Palang terbuka - Tidak ada kereta"); } } // === Ambil Status Kereta secara Berkala dari Firebase === fetchStatusKereta(); // Fungsi yang memeriksa status_kereta setiap interval dan mengontrol palang // === Deteksi Kereta Masuk (Jalur 2) === if (distance1 < tresholdUltrasonik && piezo1Val > tresholdPiezo && !keretaTerdeteksi) { // Periksa status_kereta di Firebase if (Firebase.RTDB.getBool(&fbdo, "/status_kereta")) { bool statusKereta = fbdo.boolData(); // status_kereta (perintah umum) if (!statusKereta) { // Hanya tutup palang jika status_kereta adalah false digitalWrite(LED_BAHAYA, HIGH); digitalWrite(LED_AMAN, LOW); digitalWrite(BUZZER_PIN, HIGH); // Bunyi buzzer servoJalur1.write(90); // Tutup palang Jalur 2 lcd.clear(); lcd.setCursor(0, 0); lcd.print("KERETA DATANG"); sendWAAlert("Peringatan: Kereta datang di Jalur 2!"); // Kirim pesan WA Serial.println("[INFO] Kereta terdeteksi di Jalur 2 - Palang DITUTUP"); // Update status_kereta di Firebase Firebase.RTDB.setBool(&fbdo, "/status_kereta", true); // Update status kereta menjadi true // Kirim perintah ke ESP Jalur 1 untuk tutup palang jika dalam mode offline if (isOfflineMode) { Serial2.write('0'); // Perintah tutup ke ESP Jalur 1 } // Update status_kereta_jalur2 di Firebase Firebase.RTDB.setBool(&fbdo, "/status_kereta_jalur2", true); // Update status Jalur 2 keretaTerdeteksi = true; keretaLewat = false; } } } // === Cek Kereta Lewat (Jalur 2) === if (keretaTerdeteksi && distance2 < tresholdUltrasonik) { keretaLewat = true; } if (keretaTerdeteksi && keretaLewat && distance2 > tresholdUltrasonik) { // Update status_kereta_jalur2 menjadi false setelah kereta lewat Firebase.RTDB.setBool(&fbdo, "/status_kereta_jalur2", false); // Status Jalur 2 menjadi false // Kirim pesan "Kereta telah lewat di Jalur 2!" hanya sekali if (!keretaLewatMessageSent) { sendWAAlert("Peringatan: Kereta telah lewat di Jalur 2!"); // Kirim pesan WA keretaLewatMessageSent = true; // Set flag to prevent sending multiple messages Serial.println("[INFO] Kereta sudah lewat - Pesan telah dikirim."); } // Periksa status_kereta_jalur1 if (Firebase.RTDB.getBool(&fbdo, "/status_kereta_jalur1")) { bool statusKeretaJalur1 = fbdo.boolData(); // Cek status_kereta_jalur1 if (statusKeretaJalur1 && !waitingMessageSent) { // Jika status_kereta_jalur1 adalah true, kirim pesan menunggu sendWAAlert("Peringatan: Menunggu kereta 1 lewat!"); // Kirim pesan WA Serial.println("[INFO] Menunggu kereta 1 lewat..."); waitingMessageSent = true; // Set flag to true to prevent multiple messages } // Cek jika status_kereta adalah false dan status_kereta_jalur1 juga false if (!statusKeretaJalur1) { bool statusKereta = fbdo.boolData(); // Cek status_kereta global if (!statusKereta) { // Hanya buka palang jika status_kereta adalah false servoJalur1.write(0); // Buka palang Jalur 2 digitalWrite(LED_BAHAYA, LOW); digitalWrite(LED_AMAN, HIGH); digitalWrite(BUZZER_PIN, LOW); // Matikan buzzer lcd.clear(); lcd.setCursor(0, 0); lcd.print("KERETA LEWAT"); Serial.println("[INFO] Kereta sudah lewat - Palang DIBUKA"); // Update status_kereta di Firebase Firebase.RTDB.setBool(&fbdo, "/status_kereta", false); // Update status kereta menjadi false // Kirim perintah ke ESP Jalur 1 untuk membuka palang jika dalam mode offline if (isOfflineMode) { Serial2.write('1'); // Perintah buka ke ESP Jalur 1 } keretaTerdeteksi = false; keretaLewat = false; } } } } // === Terima sinyal dari ESP Jalur 1 === (Mode Offline) if (isOfflineMode && Serial2.available() > 0) { char incomingByte = Serial2.read(); if (incomingByte == '0') { // Perintah tutup dari ESP Jalur 1 servoJalur1.write(90); // Tutup palang digitalWrite(LED_BAHAYA, HIGH); digitalWrite(LED_AMAN, LOW); digitalWrite(BUZZER_PIN, HIGH); // Bunyi buzzer Serial.println("[ESP1] Sinyal diterima - Palang DITUTUP oleh ESP1"); lcd.clear(); lcd.setCursor(0, 0); lcd.print("ESP2: PALANG TUTUP"); } if (incomingByte == '1') { // Perintah buka dari ESP Jalur 1 servoJalur1.write(0); // Buka palang digitalWrite(LED_BAHAYA, LOW); digitalWrite(LED_AMAN, HIGH); digitalWrite(BUZZER_PIN, LOW); Serial.println("[ESP1] Sinyal diterima - Palang DIBUKA oleh ESP1"); lcd.clear(); lcd.setCursor(0, 0); lcd.print("ESP2: PALANG BUKA"); } } delay(500); // jeda agar loop tidak terlalu cepat }