577 lines
21 KiB
JavaScript
577 lines
21 KiB
JavaScript
// 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 = '<div class="loading-message">📝 Belum ada riwayat parkir</div>';
|
|
return;
|
|
}
|
|
|
|
const historyArray = Object.values(data).sort((a, b) => new Date(b.date) - new Date(a.date));
|
|
let historyHTML = '<div class="history-list">';
|
|
|
|
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 += `
|
|
<div class="history-item ${statusClass}">
|
|
<div class="history-info">
|
|
<strong>Slot ${record.slot}</strong> - ${statusText}
|
|
<small>${sourceIcon} ${record.source} ${dataSourceIcon} ${record.dataSource || 'unknown'}</small>
|
|
</div>
|
|
<div class="history-time">${record.timestamp}</div>
|
|
</div>
|
|
`;
|
|
});
|
|
|
|
historyHTML += '</div>';
|
|
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");
|
|
}
|
|
}); |