document.addEventListener('DOMContentLoaded', () => { // --- CONFIGURATION --- const SOCKET_URL = "http://192.168.1.12:5000"; const API_BASE = "http://192.168.1.12:5000/api"; // Added for API calls // --- DOM ELEMENT REFERENCES --- const grid = document.getElementById('chambersGrid'); const chamberTmpl = document.getElementById('chamberTemplate'); const logTextArea = document.getElementById('instance-log'); const connChip = document.getElementById('connection-status-chip'); const stationNameEl = document.getElementById('station-name'); const stationLocationEl = document.getElementById('station-location'); const deviceIdEl = document.getElementById('device-id'); const productIdEl = document.getElementById('product-id'); const lastUpdateEl = document.getElementById('last-update-status'); const stationDiagCodeEl = document.getElementById('station-diag-code'); const backupPowerChip = document.getElementById('backup-power-chip'); const diagFlagsGrid = document.getElementById('diag-flags-grid'); const audioSelect = document.getElementById('audio-command-select'); // Header Buttons const refreshBtn = document.getElementById('refreshBtn'); const downloadBtn = document.getElementById('downloadBtn'); const logoutBtn = document.getElementById('logout-btn'); // --- STATE --- let selectedStation = null; let socket; let statusPollingInterval; // To hold our interval timer let chamberData = Array(9).fill({ batteryPresent: false }); // The list of errors from your Python code const DIAGNOSTIC_ERRORS = [ "Lock Power Cut", "Main Power Cut", "Relayboard CAN", "DB CAN Recv", "MB Can Recv", "Smoke Alarm", "Water Alarm", "Phase Failure", "Earth Leakage" ]; // --- NEW: SWAP PROCESS ELEMENTS & LOGIC --- const swapIdleText = document.getElementById('swap-idle-text'); const swapPairsList = document.getElementById('swap-pairs-list'); const startSwapBtn = document.getElementById('start-swap-btn'); const abortSwapBtn = document.getElementById('abort-swap-btn'); const clearSwapBtn = document.getElementById('clear-swap-btn'); const resetBtn = document.getElementById('station-reset-btn'); let currentPair = [], swapPairs = []; // --- SWAP UI LOGIC --- function updateSwapUI() { const isBuilding = currentPair.length > 0 || swapPairs.length > 0; if (swapIdleText) swapIdleText.style.display = isBuilding ? 'none' : 'block'; if (startSwapBtn) startSwapBtn.disabled = swapPairs.length === 0; const pairedOut = swapPairs.map(p => p[0]), pairedIn = swapPairs.map(p => p[1]); document.querySelectorAll('.chamber-card').forEach(card => { const n = parseInt(card.dataset.chamberId, 10); card.classList.remove('paired', 'pending'); if (pairedOut.includes(n) || pairedIn.includes(n)) card.classList.add('paired'); else if (currentPair.includes(n)) card.classList.add('pending'); }); if (swapPairsList) { swapPairsList.innerHTML = ''; if (isBuilding) { swapPairs.forEach(p => { const e = document.createElement('div'); e.className = 'text-sm font-semibold flex items-center justify-center gap-2 bg-black/20 rounded p-1.5'; e.innerHTML = `${p[0]}→${p[1]}`; swapPairsList.appendChild(e); }); if (currentPair.length > 0) { const e = document.createElement('div'); e.className = 'text-sm font-semibold flex items-center justify-center gap-2 bg-black/20 rounded p-1.5 ring-2 ring-sky-500'; e.innerHTML = `${currentPair[0]}→?`; swapPairsList.appendChild(e); } } } } function handleChamberClick(num) { // Deselection logic if (currentPair.length === 1 && currentPair[0] === num) { currentPair = []; updateSwapUI(); return; } const isAlreadyPaired = swapPairs.flat().includes(num); if (isAlreadyPaired) { swapPairs = swapPairs.filter(pair => !pair.includes(num)); updateSwapUI(); return; } // Selection logic if (swapPairs.length >= 4) return alert('Maximum of 4 swap pairs reached.'); // Note: Live validation would go here. Removed for testing. currentPair.push(num); if (currentPair.length === 2) { swapPairs.push([...currentPair]); currentPair = []; } updateSwapUI(); } function clearSelection() { currentPair = []; swapPairs = []; updateSwapUI(); } // --- HELPER FUNCTIONS (Your original code is unchanged) --- const applyButtonFeedback = (button) => { if (!button) return; button.classList.add('btn-feedback'); setTimeout(() => { button.classList.remove('btn-feedback'); }, 150); }; // And ensure your sendCommand function does NOT have the feedback logic const sendCommand = (command, data = null) => { if (!selectedStation || !socket || !socket.connected) { logToInstance(`Cannot send command '${command}', not connected.`, "error"); return; } const payload = { station_id: selectedStation.id, command: command, data: data }; socket.emit('rpc_request', payload); logToInstance(`Sent command: ${command}`, 'cmd'); }; const setChipStyle = (element, text, style) => { if (!element) return; const baseClass = element.className.split(' ')[0]; element.textContent = text; element.className = `${baseClass} chip-${style}`; }; const logToInstance = (message, type = 'info') => { if (!logTextArea) return; const timestamp = new Date().toLocaleTimeString(); const typeIndicator = type === 'error' ? '[ERROR]' : type === 'cmd' ? '[CMD]' : '[INFO]'; const newLog = `[${timestamp}] ${typeIndicator} ${message}\n`; logTextArea.value = newLog + logTextArea.value; }; const updateChamberUI = (card, slot) => { if (!card || !slot) return; const filledState = card.querySelector('.filled-state'); const emptyState = card.querySelector('.empty-state'); const doorPill = card.querySelector('.door-pill'); // Always update door status doorPill.textContent = slot.doorStatus ? 'OPEN' : 'CLOSED'; doorPill.className = slot.doorStatus ? 'door-pill door-open' : 'door-pill door-close'; const slotTempText = `${((slot.slotTemperature || 0) / 10).toFixed(1)} °C`; if (slot.batteryPresent) { filledState.style.display = 'flex'; emptyState.style.display = 'none'; // Update the detailed view card.querySelector('.slot-temp').textContent = slotTempText; card.querySelector('.bat-id-big').textContent = slot.batteryIdentification || '—'; card.querySelector('.soc').textContent = `${slot.soc || 0}%`; // --- Populate the detailed view --- card.querySelector('.bat-id-big').textContent = slot.batteryIdentification || '—'; card.querySelector('.soc').textContent = `${slot.soc || 0}%`; card.querySelector('.voltage').textContent = `${((slot.voltage || 0) / 1000).toFixed(1)} V`; card.querySelector('.bat-temp').textContent = `${((slot.batteryMaxTemp || 0) / 10).toFixed(1)} °C`; card.querySelector('.bat-fault').textContent = slot.batteryFaultCode || '—'; card.querySelector('.current').textContent = `${((slot.current || 0) / 1000).toFixed(1)} A`; card.querySelector('.slot-temp').textContent = `${((slot.slotTemperature || 0) / 10).toFixed(1)} °C`; card.querySelector('.chg-fault').textContent = slot.chargerFaultCode || '—'; const batPill = card.querySelector('.battery-status-pill'); batPill.innerHTML = ` Present`; batPill.className = 'battery-status-pill chip chip-emerald'; const chgPill = card.querySelector('.charger-status-pill'); if (slot.chargerMode === 1) { chgPill.innerHTML = ` Charging`; chgPill.className = 'charger-status-pill chip chip-sky'; } else { chgPill.innerHTML = ` Idle`; chgPill.className = 'charger-status-pill chip chip-slate'; } } else { // Show the empty view filledState.style.display = 'none'; emptyState.style.display = 'flex'; // --- DEBUGGING LOGIC --- const tempElement = card.querySelector('.slot-temp-empty'); if (tempElement) { tempElement.textContent = slotTempText; // console.log(`Chamber ${slot.chamberNo}: Found .slot-temp-empty, setting text to: ${slotTempText}`); } else { // console.error(`Chamber ${slot.chamberNo}: Element .slot-temp-empty NOT FOUND! Check your HTML template.`); } } // Check if the icon library is loaded and then render the icons if (typeof lucide !== 'undefined') { lucide.createIcons(); } else { console.error('Lucide icon script is not loaded. Please check dashboard.html'); } }; // --- NEW: Function to decode the SDC and update the UI --- const updateDiagnosticsUI = (sdcCode) => { if (!diagFlagsGrid) return; diagFlagsGrid.innerHTML = ''; // Clear previous statuses DIAGNOSTIC_ERRORS.forEach((errorText, index) => { // Use bitwise AND to check if the bit at this index is set const isActive = (sdcCode & (1 << index)) !== 0; const div = document.createElement('div'); div.textContent = errorText; // Apply different styles based on whether the alarm is active if (isActive) { div.className = 'text-rose-300 text-center font-semibold'; } else { div.className = 'text-slate-500 text-center'; } diagFlagsGrid.appendChild(div); }); }; const resetDashboardUI = () => { grid.querySelectorAll('.chamber-card').forEach(card => { card.querySelector('.bat-id-big').textContent = 'Waiting...'; card.querySelector('.soc').textContent = '—'; card.querySelector('.voltage').textContent = '—'; card.querySelector('.bat-temp').textContent = '—'; card.querySelector('.bat-fault').textContent = '—'; card.querySelector('.current').textContent = '—'; card.querySelector('.slot-temp').textContent = '—'; card.querySelector('.chg-fault').textContent = '—'; // Show the "empty" view by default when resetting card.querySelector('.filled-state').style.display = 'none'; card.querySelector('.empty-state').style.display = 'flex'; }); logToInstance("Station is offline. Clearing stale data.", "error"); }; // --- NEW: This function polls the API for the true station status --- const checkStationStatus = async () => { if (!selectedStation) return; try { const response = await fetch(`${API_BASE}/stations`); if (!response.ok) return; const stations = await response.json(); const thisStation = stations.find(s => s.id === selectedStation.id); if (thisStation && connChip) { stationNameEl.textContent = thisStation.name; stationLocationEl.textContent = thisStation.location; if (thisStation.status === 'Online') { connChip.innerHTML = ` Online`; connChip.className = 'cham_chip cham_chip-emerald'; } else { connChip.innerHTML = ` Offline`; connChip.className = 'cham_chip cham_chip-rose'; lastUpdateEl.textContent = "Waiting for data..."; resetDashboardUI(); } } } catch (error) { console.error("Failed to fetch station status:", error); } }; // --- DOWNLOAD MODAL LOGIC --- const showDownloadModal = () => { const modalOverlay = document.createElement('div'); modalOverlay.className = "fixed inset-0 bg-black/70 backdrop-blur-sm flex items-center justify-center z-50"; modalOverlay.innerHTML = `