// FIREBASE KONFIGURASI import { initializeApp } from "https://www.gstatic.com/firebasejs/9.6.1/firebase-app.js"; import { getDatabase, ref, set, onValue, push } from "https://www.gstatic.com/firebasejs/9.6.1/firebase-database.js"; // Firebase Configuration const firebaseConfig = { apiKey: "AIzaSyAS1qxaUCFRZ1Fe25qGK27JtwCWEPuaYys", authDomain: "parkingslot-c5fba.firebaseapp.com", databaseURL: "https://parkingslot-c5fba-default-rtdb.firebaseio.com", projectId: "parkingslot-c5fba", storageBucket: "parkingslot-c5fba.firebasestorage.app", messagingSenderId: "675813222621", appId: "1:675813222621:web:2986b33a1e5fd10c5b9334" }; // Initialize Firebase const app = initializeApp(firebaseConfig); const db = getDatabase(app); console.log("Firebase berhasil terhubung:", db); // Global Variables let isAdminMode = false; let previousSlotStatus = { slot_1: null, // null berarti belum pernah di-set slot_2: null, slot_3: null }; let slotStatus = { slot_1: "Tersedia", slot_2: "Tersedia", slot_3: "Tersedia" }; let isSystemInitialized = false; // Set isAdminMode true jika di halaman admin.html if (window.location.pathname.includes('admin.html')) { isAdminMode = true; console.log("Mode Admin aktif"); } else { console.log("Mode User aktif"); } // Initialize previous status dari Firebase saat startup function initializePreviousStatus() { console.log("πŸ”„ Menginisialisasi status sebelumnya dari Firebase..."); return new Promise((resolve, reject) => { let loadedSlots = 0; const totalSlots = 3; // Load status sebelumnya dari Firebase untuk setiap slot for (let i = 1; i <= 3; i++) { const slotKey = `slot_${i}`; onValue(ref(db, `parkir/slot_${i}`), function(snapshot) { const data = snapshot.val(); if (data && data.status) { previousSlotStatus[slotKey] = data.status; console.log(`βœ… Status awal slot ${i}: ${data.status}`); } else { // Jika tidak ada data, set default previousSlotStatus[slotKey] = "Tersedia"; console.log(`⚠️ Slot ${i} tidak ada data, set default: Tersedia`); } loadedSlots++; if (loadedSlots === totalSlots) { isSystemInitialized = true; console.log("βœ… Semua slot berhasil diinisialisasi"); console.log("Previous Status:", previousSlotStatus); resolve(); } }, { onlyOnce: true }); } // Timeout fallback jika Firebase lambat setTimeout(() => { if (!isSystemInitialized) { console.log("⏰ Timeout inisialisasi, menggunakan default values"); previousSlotStatus = { slot_1: "Tersedia", slot_2: "Tersedia", slot_3: "Tersedia" }; isSystemInitialized = true; resolve(); } }, 5000); }); } // Setup MQTT const client = mqtt.connect('ws://broker.hivemq.com:8000/mqtt'); client.on('connect', function () { console.log("πŸ”— Terhubung ke Broker MQTT"); client.subscribe('parkir/slot_1'); client.subscribe('parkir/slot_2'); client.subscribe('parkir/slot_3'); client.subscribe('parkir/status'); // Initialize previous status setelah MQTT connect setTimeout(async () => { await initializePreviousStatus(); console.log("πŸš€ Sistem siap menerima data MQTT"); }, 1000); }); client.on('message', function (topic, message) { // Jangan proses jika sistem belum diinisialisasi if (!isSystemInitialized) { console.log("⏳ Sistem belum siap, mengabaikan pesan MQTT"); return; } console.log(`πŸ“¨ Pesan MQTT diterima: ${message.toString()} di topik: ${topic}`); const distance = parseInt(message.toString()); if (isNaN(distance)) { console.error('❌ Data tidak valid diterima:', message.toString()); return; } // Process each slot if (topic === 'parkir/slot_1') { processSlotUpdate(1, distance, 'mqtt'); } if (topic === 'parkir/slot_2') { processSlotUpdate(2, distance, 'mqtt'); } if (topic === 'parkir/slot_3') { processSlotUpdate(3, distance, 'mqtt'); } checkParkingStatus(); }); // Fungsi utama untuk memproses update slot - DIPERBAIKI function processSlotUpdate(slotNumber, distance, source = 'unknown') { const newStatus = distance > 5 ? "Tersedia" : "Terisi"; const slotKey = `slot_${slotNumber}`; console.log(`πŸ”„ Processing slot ${slotNumber}: distance=${distance}, newStatus=${newStatus}, source=${source}`); console.log(`πŸ“Š Previous status slot ${slotNumber}: ${previousSlotStatus[slotKey]}`); // Update status slot di UI updateSlotStatus(slotNumber, distance); // Cek perubahan status - LOGIKA DIPERBAIKI const prevStatus = previousSlotStatus[slotKey]; // Jika ini adalah data pertama (previous status null), langsung log if (prevStatus === null) { console.log(`πŸ†• DATA PERTAMA - Slot ${slotNumber}: null -> ${newStatus}`); logParkingHistory(slotNumber, newStatus, isAdminMode, source); previousSlotStatus[slotKey] = newStatus; } // Jika ada perubahan status dari status sebelumnya else if (prevStatus !== newStatus) { console.log(`πŸ”₯ PERUBAHAN TERDETEKSI - Slot ${slotNumber}: ${prevStatus} -> ${newStatus}`); console.log(`πŸ“ Mode saat ini: ${isAdminMode ? 'Admin' : 'User'}`); // SELALU log parking history baik di user maupun admin mode logParkingHistory(slotNumber, newStatus, isAdminMode, source); // Update previous status SETELAH logging previousSlotStatus[slotKey] = newStatus; console.log(`βœ… Previous status slot ${slotNumber} diupdate ke: ${newStatus}`); } else { console.log(`➑️ Tidak ada perubahan status slot ${slotNumber}: ${newStatus}`); } // Update current status slotStatus[slotKey] = newStatus; } // Fungsi untuk update indikator pada denah function updateMapIndicator(slot, status) { const indicator = document.getElementById('indicator_' + slot); if (indicator) { indicator.classList.remove('available', 'occupied'); if (status === "Tersedia") { indicator.classList.add('available'); indicator.style.backgroundColor = '#4CAF50'; } else { indicator.classList.add('occupied'); indicator.style.backgroundColor = '#f44336'; } indicator.title = `Slot ${slot} - ${status}`; console.log(`πŸ—ΊοΈ Indikator denah slot ${slot} diupdate: ${status}`); } } function updateSlotStatus(slot, distance) { const statusElement = document.getElementById('status_' + slot); const slotElement = document.getElementById('sensor_' + slot); let status; if (distance > 5) { status = "Tersedia"; if (statusElement) { statusElement.textContent = "Status: Tersedia"; statusElement.className = "status empty"; } if (slotElement) { slotElement.style.backgroundColor = "lightgreen"; } } else { status = "Terisi"; if (statusElement) { statusElement.textContent = "Status: Terisi"; statusElement.className = "status occupied"; } if (slotElement) { slotElement.style.backgroundColor = "lightcoral"; } } updateMapIndicator(slot, status); // Simpan ke Firebase tanpa memanggil processSlotUpdate lagi untuk menghindari loop updateFirebaseStatusOnly(slot, distance); } function checkParkingStatus() { const allFull = slotStatus.slot_1 === "Terisi" && slotStatus.slot_2 === "Terisi" && slotStatus.slot_3 === "Terisi"; if (allFull) { console.log("🚫 Parkir Penuh - Palang tetap tertutup."); client.publish('palang/status', 'Tutup'); } else { console.log("βœ… Ada slot tersedia - Palang dibuka."); client.publish('palang/status', 'Buka'); } } // Fungsi khusus untuk update Firebase tanpa trigger logging function updateFirebaseStatusOnly(slot, distance) { const status = distance > 5 ? "Tersedia" : "Terisi"; console.log(`πŸ’Ύ Menyimpan ke Firebase - Slot ${slot}: distance=${distance}, status=${status}`); set(ref(db, 'parkir/slot_' + slot), { distance: distance, status: status, lastUpdate: new Date().toISOString(), updatedBy: isAdminMode ? 'admin' : 'user' }) .then(() => { console.log(`βœ… Status slot ${slot} berhasil disimpan ke Firebase`); }) .catch((error) => { console.error("❌ Gagal menyimpan status ke Firebase:", error); }); } // Fungsi log parking history - DIPERBAIKI DENGAN LOGGING DETAIL function logParkingHistory(slot, status, isAdmin, source = 'unknown') { const timestamp = new Date().toLocaleString('id-ID'); const historyData = { slot: slot, status: status, timestamp: timestamp, date: new Date().toISOString(), source: isAdmin ? 'admin' : 'user', dataSource: source, // mqtt, firebase, manual sessionId: generateSessionId() }; console.log(`πŸ“Š MENYIMPAN RIWAYAT PARKIR:`); console.log(` - Slot: ${slot}`); console.log(` - Status: ${status}`); console.log(` - Mode: ${historyData.source}`); console.log(` - Data Source: ${source}`); console.log(` - Timestamp: ${timestamp}`); push(ref(db, 'parking_history'), historyData) .then(() => { console.log(`βœ… SUKSES! Riwayat parkir slot ${slot} tersimpan (${historyData.source})`); // Hanya update tampilan jika di mode admin if (isAdmin && typeof updateHistoryDisplay === 'function') { console.log(`πŸ”„ Mengupdate tampilan riwayat admin...`); updateHistoryDisplay(); } }) .catch((error) => { console.error(`❌ GAGAL! Menyimpan riwayat parkir slot ${slot}:`, error); }); } // Generate session ID untuk tracking function generateSessionId() { return Date.now() + '_' + Math.random().toString(36).substr(2, 9); } // Fungsi untuk update tampilan riwayat (hanya di admin mode) function updateHistoryDisplay() { // Hanya jalankan jika di admin mode if (!isAdminMode) { console.log("updateHistoryDisplay diabaikan - bukan mode admin"); return; } onValue(ref(db, 'parking_history'), function(snapshot) { const historyContainer = document.getElementById('history-container'); if (!historyContainer) { console.log("Element history-container tidak ditemukan"); return; } const data = snapshot.val(); if (!data) { historyContainer.innerHTML = '
πŸ“ Belum ada riwayat parkir
'; return; } const historyArray = Object.values(data).sort((a, b) => new Date(b.date) - new Date(a.date)); let historyHTML = '
'; historyArray.forEach(record => { const statusClass = record.status === 'Terisi' ? 'entry' : 'exit'; const statusText = record.status === 'Terisi' ? 'Masuk' : 'Keluar'; const sourceIcon = record.source === 'admin' ? 'πŸ”§' : 'πŸ‘€'; const dataSourceIcon = record.dataSource === 'mqtt' ? 'πŸ“‘' : 'πŸ’Ύ'; historyHTML += `
Slot ${record.slot} - ${statusText} ${sourceIcon} ${record.source} ${dataSourceIcon} ${record.dataSource || 'unknown'}
${record.timestamp}
`; }); historyHTML += '
'; historyContainer.innerHTML = historyHTML; console.log(`πŸ“‹ Tampilan riwayat diupdate dengan ${historyArray.length} record`); }); } // Listener realtime Firebase untuk semua slot - DIPERBAIKI function setupRealtimeListeners() { console.log("πŸ”₯ Setting up Firebase realtime listeners..."); // Listener untuk slot 1 onValue(ref(db, 'parkir/slot_1'), function(snapshot) { const data = snapshot.val(); if (data) { console.log(`πŸ”₯ Firebase realtime update slot 1: distance=${data.distance}, status=${data.status}`); // Hanya update UI, jangan trigger processSlotUpdate untuk menghindari double logging const statusElement = document.getElementById('status_1'); const slotElement = document.getElementById('sensor_1'); if (data.distance > 5) { if (statusElement) { statusElement.textContent = "Status: Tersedia"; statusElement.className = "status empty"; } if (slotElement) { slotElement.style.backgroundColor = "lightgreen"; } } else { if (statusElement) { statusElement.textContent = "Status: Terisi"; statusElement.className = "status occupied"; } if (slotElement) { slotElement.style.backgroundColor = "lightcoral"; } } updateMapIndicator(1, data.status); slotStatus.slot_1 = data.status; } }); // Listener untuk slot 2 onValue(ref(db, 'parkir/slot_2'), function(snapshot) { const data = snapshot.val(); if (data) { console.log(`πŸ”₯ Firebase realtime update slot 2: distance=${data.distance}, status=${data.status}`); const statusElement = document.getElementById('status_2'); const slotElement = document.getElementById('sensor_2'); if (data.distance > 5) { if (statusElement) { statusElement.textContent = "Status: Tersedia"; statusElement.className = "status empty"; } if (slotElement) { slotElement.style.backgroundColor = "lightgreen"; } } else { if (statusElement) { statusElement.textContent = "Status: Terisi"; statusElement.className = "status occupied"; } if (slotElement) { slotElement.style.backgroundColor = "lightcoral"; } } updateMapIndicator(2, data.status); slotStatus.slot_2 = data.status; } }); // Listener untuk slot 3 onValue(ref(db, 'parkir/slot_3'), function(snapshot) { const data = snapshot.val(); if (data) { console.log(`πŸ”₯ Firebase realtime update slot 3: distance=${data.distance}, status=${data.status}`); const statusElement = document.getElementById('status_3'); const slotElement = document.getElementById('sensor_3'); if (data.distance > 5) { if (statusElement) { statusElement.textContent = "Status: Tersedia"; statusElement.className = "status empty"; } if (slotElement) { slotElement.style.backgroundColor = "lightcoral"; } } else { if (statusElement) { statusElement.textContent = "Status: Terisi"; statusElement.className = "status occupied"; } if (slotElement) { slotElement.style.backgroundColor = "lightcoral"; } } updateMapIndicator(3, data.status); slotStatus.slot_3 = data.status; } }); console.log("βœ… Realtime listeners untuk Firebase telah diatur"); } // Mode switching functions (Admin login modal etc.) window.showAdminLogin = function () { const modal = document.getElementById('admin-modal'); if (modal) modal.style.display = 'flex'; } window.closeAdminModal = function () { const modal = document.getElementById('admin-modal'); const input = document.getElementById('admin-password'); if (modal) modal.style.display = 'none'; if (input) input.value = ''; } window.loginAdmin = function () { const input = document.getElementById('admin-password'); if (input && input.value === 'admin123') { window.location.href = 'admin.html'; } else { alert('Password salah!'); } } window.handlePasswordKeypress = function (event) { if (event.key === 'Enter') { loginAdmin(); } } // Update admin status display function updateAdminStatus() { if (isAdminMode) { for (let i = 1; i <= 3; i++) { const userStatus = document.getElementById('status_' + i)?.textContent; const adminElement = document.getElementById('admin-status-' + i); if (adminElement && userStatus) { adminElement.textContent = userStatus.replace('Status: ', ''); adminElement.style.color = userStatus.includes('Tersedia') ? '#4CAF50' : '#f44336'; adminElement.style.fontWeight = 'bold'; } } } } // FUNGSI DEBUG YANG DITINGKATKAN window.debugParkingSystem = function() { console.log("=== πŸ” DEBUG PARKING SYSTEM ==="); console.log("Mode:", isAdminMode ? "Admin" : "User"); console.log("System Initialized:", isSystemInitialized); console.log("Current Slot Status:", slotStatus); console.log("Previous Slot Status:", previousSlotStatus); console.log("Firebase DB Connected:", !!db); console.log("MQTT Client Connected:", client.connected); console.log("==============================="); } // Fungsi untuk testing manual - DIPERBAIKI window.testHistoryLogging = function(slot, status) { console.log(`πŸ§ͺ MANUAL TEST - Logging history for slot ${slot} with status ${status}`); // Simulasi perubahan status const slotKey = `slot_${slot}`; const currentPrev = previousSlotStatus[slotKey]; console.log(`πŸ“Š Before test - Previous: ${currentPrev}, New: ${status}`); // Force log tanpa cek perubahan logParkingHistory(slot, status, isAdminMode, 'manual-test'); // Update previous status previousSlotStatus[slotKey] = status; console.log(`βœ… Test completed - Previous status updated to: ${status}`); } // FUNGSI KHUSUS UNTUK FORCE LOGGING (Testing) window.forceLogHistory = function(slot, status) { console.log(`🚨 FORCE LOGGING - Slot ${slot}, Status: ${status}`); logParkingHistory(slot, status, isAdminMode, 'force-test'); } // Inisialisasi saat DOM ready window.addEventListener('DOMContentLoaded', function() { console.log(`πŸš€ Sistem parkir dimulai - Mode: ${isAdminMode ? 'πŸ”§ Admin' : 'πŸ‘€ User'}`); // Inisialisasi indikator denah for (let i = 1; i <= 3; i++) { updateMapIndicator(i, "Tersedia"); } console.log("πŸ—ΊοΈ Indikator denah telah diinisialisasi"); // Setup realtime listeners setupRealtimeListeners(); // Jika admin mode, setup tampilan riwayat dan update berkala if (isAdminMode) { console.log("πŸ”§ Mengaktifkan fitur admin..."); updateHistoryDisplay(); // Update admin status setiap detik setInterval(updateAdminStatus, 1000); console.log("βœ… Admin mode: Pemantauan riwayat realtime aktif"); } else { console.log("πŸ‘€ User mode: Siap mengirim data riwayat ke Firebase"); // Test functions untuk user mode window.testUserMode = function() { console.log("πŸ§ͺ Testing user mode logging..."); console.log("πŸ” Current state:", { initialized: isSystemInitialized, previousStatus: previousSlotStatus, currentStatus: slotStatus }); // Test dengan force logging forceLogHistory(1, "Terisi"); setTimeout(() => forceLogHistory(1, "Tersedia"), 3000); }; // Fungsi untuk simulasi sensor window.simulateSensor = function(slot, distance) { console.log(`🎯 Simulasi sensor slot ${slot} dengan jarak ${distance}cm`); processSlotUpdate(slot, distance, 'simulation'); }; } console.log("βœ… Inisialisasi sistem parkir selesai"); console.log("πŸ’‘ Fungsi debugging:"); console.log(" - debugParkingSystem() : Info sistem"); console.log(" - forceLogHistory(slot, status) : Force log history"); if (!isAdminMode) { console.log(" - testUserMode() : Test user mode logging"); console.log(" - simulateSensor(slot, distance) : Simulasi sensor"); } });