const fetchInterval = 500; // State let chart; let activeSessionData = []; let history = []; let currentPage = 1; let fullDataPage = 1; let lastAcqStatus = 'waiting'; // DOM Elements const progressBar = document.getElementById('progressBar'); const progressContainer = document.getElementById('progressContainer'); const timerDisplay = document.getElementById('timerDisplay'); const tableBody = document.getElementById('measurementsTable'); const detailPlaceholder = document.getElementById('detailPlaceholder'); const detailContent = document.getElementById('detailContent'); const fullDataTableBody = document.getElementById('fullDataTableBody'); const historyTableBody = document.getElementById('historyTableBody'); const pageInfo = document.getElementById('pageInfo'); const prevBtn = document.getElementById('prevPage'); const nextBtn = document.getElementById('nextPage'); const fullPrevBtn = document.getElementById('fullPrevPage'); const fullNextBtn = document.getElementById('fullNextPage'); const liveDataContainer = document.getElementById('liveDataContainer'); const historyDetailContainer = document.getElementById('historyDetailContainer'); const selectedData = document.getElementById('selectedData'); const plcIpDisplay = document.getElementById('plcIpDisplay'); const connStatusDot = document.getElementById('connStatusDot'); const latencyList = document.getElementById('latencyList'); const acquisitionStatusList = document.getElementById('acquisitionStatusList'); // Initialize Chart.js function initChart() { const ctx = document.getElementById('liveChart').getContext('2d'); chart = new Chart(ctx, { type: 'line', data: { datasets: [ { label: 'Value (mm)', data: [], borderColor: '#3b82f6', backgroundColor: 'rgba(59, 130, 246, 0.1)', fill: true, pointRadius: 0, borderWidth: 2, tension: 0 }, { label: 'UCL', data: [], borderColor: '#facc15', borderWidth: 1, pointRadius: 0, fill: false, borderDash: [5, 5] }, { label: 'LCL', data: [], borderColor: '#facc15', borderWidth: 1, pointRadius: 0, fill: false, borderDash: [5, 5] }, { label: 'Target Value', data: [], borderColor: '#22c55e', borderWidth: 1.5, pointRadius: 0, fill: false, tension: 0 } ] }, options: { responsive: true, maintainAspectRatio: false, scales: { x: { type: 'linear', min: 0, max: 10000, title: { display: true, text: 'Position (mm)' } }, y: { min: -3000, max: 3000 } }, animation: false } }); } // Fetch data from Flask and update UI async function updateData() { try { const response = await fetch('/data'); if (!response.ok) throw new Error(`HTTP error! status: ${response.status}`); const data = await response.json(); if (data) { // Update Connectivity Status if (data.connected !== undefined) { connStatusDot.className = data.connected ? 'w-2 h-2 bg-green-400 rounded-full mr-2 animate-pulse' : 'w-2 h-2 bg-red-600 rounded-full mr-2'; plcIpDisplay.textContent = data.plc_ip || "Disconnected"; } // Update Latencies if (data.latencies) { const totalLatency = Object.values(data.latencies).reduce((a, b) => a + b, 0); latencyList.textContent = `Latency: ${totalLatency.toFixed(2)}ms`; } // Update acquisition status text if (data.acq_status !== undefined) { acquisitionStatusList.textContent = `• Mode: ${data.acq_status}`; if (data.acq_status === "Acquisition") { acquisitionStatusList.parentElement.classList.replace('text-gray-600', 'text-blue-400'); } else { acquisitionStatusList.parentElement.classList.replace('text-blue-400', 'text-gray-600'); } } // Handle Live Chart Update if (data.db901 && data.db901.params) { const acq = data.db901.params.acquisizione; const fine = data.db901.params.fine_acq; const pronti = data.db901.params.pronti; const limits = data.limits || { ucl: 212, lcl: 208, target: 0 }; if (acq && !fine) { // MODE: ACQUISITION // Reset chart if we just switched from waiting if (lastAcqStatus === 'Finished') { chart.data.datasets.forEach(ds => ds.data = []); lastAcqStatus = acq } if (data.db901.params.x_val !== undefined && data.db901.params.y_val !== undefined) { const x = data.db901.params.x_val; const y = data.db901.params.y_val; chart.data.datasets[0].data.push({ x, y }); chart.data.datasets[1].data.push({ x, y: limits.ucl }); chart.data.datasets[2].data.push({ x, y: limits.lcl }); chart.data.datasets[3].data.push({ x, y: limits.target }); if (chart.data.datasets[0].data.length > 100) { chart.data.datasets.forEach(ds => ds.data.shift()); } chart.update('none'); } } else if (fine && pronti) { // MODE: FINISHED lastAcqStatus = acq acquisitionStatusList.parentElement.classList.replace('text-blue-400', 'text-green-600'); if (data.db900 && data.db900.misure && data.db900.misure.length > 0) { activeSessionData = data.db900.misure.map(m => ({ x: m.x, y: m.y, ts: m.ts || new Date() })); chart.data.datasets.forEach(ds => ds.data = []); activeSessionData.forEach(p => { chart.data.datasets[0].data.push({ x: p.x, y: p.y }); chart.data.datasets[1].data.push({ x: p.x, y: limits.ucl }); chart.data.datasets[2].data.push({ x: p.x, y: limits.lcl }); chart.data.datasets[3].data.push({ x: p.x, y: limits.target }); }); chart.update(); liveDataContainer.classList.remove('hidden'); historyDetailContainer.classList.add('hidden'); detailPlaceholder.classList.add('hidden'); detailContent.classList.remove('hidden'); renderFullSessionTable(); } } else { acquisitionStatusList.textContent = `• Mode: Polling (5Hz)`; acquisitionStatusList.parentElement.classList.replace('text-blue-400', 'text-gray-600'); } } lastAcqStatus = data.acq_status; } } catch (err) { console.error("Error updating data:", err); } } // Fetch history async function fetchHistory() { try { const response = await fetch('/history'); if (response.ok) { history = await response.json(); renderHistoryTable(); } } catch (err) { console.error("Error fetching history:", err); } } function renderHistoryTable() { historyTableBody.innerHTML = ''; if (history.length === 0) { historyTableBody.innerHTML = 'No sessions recorded'; return; } const start = (currentPage - 1) * 10; const end = start + 10; const pageData = history.slice(start, end); pageData.forEach((item, i) => { const row = document.createElement('tr'); row.innerHTML = ` ${item.filename} ${item.articolo} / ${item.cod_produzione} ${new Date().toLocaleDateString()} `; row.onclick = () => loadHistoryFile(item.file_path); historyTableBody.appendChild(row); }); pageInfo.innerText = `Page ${currentPage} of ${Math.ceil(history.length / 10)}`; prevBtn.disabled = currentPage === 1; nextBtn.disabled = currentPage * 10 >= history.length; } async function loadHistoryFile(filePath) { try { const response = await fetch(`/history/${filePath}`); if (response.ok) { const data = await response.json(); activeSessionData = data.misure.map(m => ({ x: m.x, y: m.y, ts: m.ts || new Date() })); chart.data.datasets.forEach(ds => ds.data = []); activeSessionData.forEach(p => { chart.data.datasets[0].data.push({ x: p.x, y: p.y }); chart.data.datasets[1].data.push({ x: p.x, y: 212 }); chart.data.datasets[2].data.push({ x: p.x, y: 208 }); chart.data.datasets[3].data.push({ x: p.x, y: 0 }); }); chart.update(); liveDataContainer.classList.remove('hidden'); historyDetailContainer.classList.remove('hidden'); detailPlaceholder.classList.add('hidden'); detailContent.classList.remove('hidden'); renderFullSessionTable(); selectedData.innerText = `Session Loaded: ${data.articolo} | ${data.cod_produzione} | ${data.num_certificato}`; } } catch (err) { console.error("Error loading history file:", err); } } function renderFullSessionTable() { fullDataTableBody.innerHTML = ''; if (activeSessionData.length === 0) return; const start = (fullDataPage - 1) * 50; const end = start + 50; const pageData = activeSessionData.slice(start, end); pageData.forEach((p, i) => { const row = document.createElement('tr'); const diff = Math.abs(p.y - 0); let status = 'OK'; let colorClass = 'text-green-600 border-green-600'; if (diff > 3000) { status = 'ERROR'; colorClass = 'text-red-600 border-red-600'; } else if (diff > 2000) { status = 'WARNING'; colorClass = 'text-yellow-600 border-yellow-600'; } row.innerHTML = ` ${start + i + 1} ${p.ts.toLocaleTimeString()} ${p.y.toFixed(2)} ${status} `; fullDataTableBody.appendChild(row); }); fullPageInfo.innerText = `Page ${fullDataPage} of ${Math.ceil(activeSessionData.length / 50)}`; fullPrevBtn.disabled = fullDataPage === 1; fullNextBtn.disabled = fullDataPage * 50 >= activeSessionData.length; } function updateTable() { if (tableBody.innerHTML === '') { tableBody.innerHTML = 'Initializing...'; } } function toggleDetailVisibility() { if (isAcquiring || currentMode === 'history') { liveDataContainer.classList.remove('hidden'); historyDetailContainer.classList.add('hidden'); if (isAcquiring) { activeSessionData = [...dataPoints]; renderFullSessionTable(); } } else { liveDataContainer.classList.add('hidden'); historyDetailContainer.classList.add('hidden'); detailPlaceholder.classList.remove('hidden'); } } // Initialization initChart(); fetchHistory(); setInterval(updateData, fetchInterval); updateData(); // Pagination Listeners prevBtn.onclick = () => { if (currentPage > 1) { currentPage--; renderHistoryTable(); } }; nextBtn.onclick = () => { if (currentPage * 10 < history.length) { currentPage++; renderHistoryTable(); } }; fullPrevBtn.onclick = () => { if (fullDataPage > 1) { fullDataPage--; renderFullSessionTable(); } }; fullNextBtn.onclick = () => { if (fullDataPage * itemsPerPage < activeSessionData.length) { fullDataPage++; renderFullSessionTable(); } };