// 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 = '
'; return; } const historyArray = Object.values(data).sort((a, b) => new Date(b.date) - new Date(a.date)); let historyHTML = '